Moving AttributeParsers to XMLElement, where they belong.

This is a pretty major refactoring, and it's not done yet. Management of the
AttributeParser and the BundleAttributeParsers is now in dedicated classes.
XMLElement has access to them, and uses them for Boolean and Double values. All
other parsing is still going through old calls through UiBinderWriter to the new
managers, for now.

Of particular note: there are now many fewer direct dependencies on
UiBinderWriter, and much more DI style injection of logging and other services.
Also, dummy loggers are introduced to eliminate console noise from unit tests.

Next steps are making ComputedAttributeParser use the new game (and in the
process tidy its fix for currently broken {{ escaping), and making the new
LayoutPanelParsers get their enum values in the new style. Then on to strings.

Review by: hermes@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6589 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 0e91b94..edd5f16 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -350,7 +350,7 @@
    * method, in milliseconds, measured from when the <i>last</i> client connects
    * (and thus starts the test). default of 5 minutes.
    */
-  private static final long TEST_METHOD_TIMEOUT_MILLIS = 300000;
+  private static final long TEST_METHOD_TIMEOUT_MILLIS = 50 * 60 * 1000;
 
   /**
    * Singleton object for hosting unit tests. All test case instances executed
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
index a39a779..c4d383a 100644
--- a/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,7 +16,7 @@
 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.MortalLogger;
 
 /**
  * Attribute parsers are classes that parse xml attribute values, turning them
@@ -26,12 +26,12 @@
 
   /**
    * Parse the given attribute value.
-   *
+   * 
    * @param value the attribute value to be parsed
-   * @param writer the writer
+   * @param logger the logger
    * @return a valid Java expression
-   * @throws UnableToCompleteException
+   * @throws UnableToCompleteException on parse error
    */
-  String parse(String value, UiBinderWriter writer)
-    throws UnableToCompleteException;
+  String parse(String value, MortalLogger logger)
+      throws UnableToCompleteException;
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
index ebb4fce..cf6708d 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
@@ -20,7 +20,7 @@
 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.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLAttribute;
 import com.google.gwt.uibinder.rebind.XMLElement;
@@ -47,12 +47,11 @@
   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> localizedValues = fetchLocalizedAttributeValues(
+        elem, writer);
 
     final Map<String, String> requiredValues = new HashMap<String, String>();
-    final Map<String, JParameter> unfilledRequiredParams =
-        new HashMap<String, JParameter>();
+    final Map<String, JType> unfilledRequiredParams = new HashMap<String, JType>();
 
     final OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type,
         writer.getLogger());
@@ -66,7 +65,7 @@
 
     if (creator != null) {
       for (JParameter param : creator.getParameters()) {
-        unfilledRequiredParams.put(param.getName(), param);
+        unfilledRequiredParams.put(param.getName(), param.getType());
       }
     }
 
@@ -78,22 +77,22 @@
       String key = property.getKey();
       String value = property.getValue();
 
-      JParameter param = unfilledRequiredParams.get(key);
-      if (param != null) {
-        if (!isString(writer, param)) {
+      JType paramType = unfilledRequiredParams.get(key);
+      if (paramType != null) {
+        if (!isString(writer, paramType)) {
           writer.die("In %s, cannot appply message attribute to non-string "
               + "constructor argument %s %s.", elem,
-              param.getType().getSimpleSourceName(), key);
+              paramType.getSimpleSourceName(), key);
         }
 
         requiredValues.put(key, value);
         unfilledRequiredParams.remove(key);
-     } else {
+      } else {
         JMethod setter = ownerFieldClass.getSetter(key);
         JParameter[] params = setter == null ? null : setter.getParameters();
 
         if (setter == null || !(params.length == 1)
-            || !isString(writer, params[0])) {
+            || !isString(writer, params[0].getType())) {
           writer.die("In %s, no method found to apply message attribute %s",
               elem, key);
         } else {
@@ -122,16 +121,14 @@
       }
 
       if (unfilledRequiredParams.keySet().contains(propertyName)) {
-        JParameter parameter = unfilledRequiredParams.get(propertyName);
-        AttributeParser parser =
-            writer.getAttributeParser(attribute, parameter);
+        JType paramType = unfilledRequiredParams.get(propertyName);
+        AttributeParser parser = writer.getAttributeParser(attribute, paramType);
         if (parser == null) {
           writer.die("In %s, unable to parse %s as constructor argument "
-              + "of type %s", elem, attribute,
-              parameter.getType().getSimpleSourceName());
+              + "of type %s", elem, attribute, paramType.getSimpleSourceName());
         }
-        requiredValues.put(propertyName, parser.parse(attribute.consumeValue(),
-            writer));
+        requiredValues.put(propertyName, parser.parse(
+            attribute.consumeRawValue(), writer.getLogger()));
         unfilledRequiredParams.remove(propertyName);
       } else {
         JMethod setter = ownerFieldClass.getSetter(propertyName);
@@ -140,22 +137,20 @@
               elem.getLocalName(), initialCap(propertyName));
         }
 
-        JParameter[] params = setter.getParameters();
-
-        AttributeParser parser = writer.getAttributeParser(attribute, params);
+        AttributeParser parser = writer.getAttributeParser(attribute,
+            getParamTypes(setter));
 
         if (parser == null) {
           writer.die("In %s, unable to parse %s.", elem, attribute);
         }
-        setterValues.put(propertyName, parser.parse(attribute.consumeValue(),
-            writer));
+        setterValues.put(propertyName, parser.parse(
+            attribute.consumeRawValue(), writer.getLogger()));
       }
     }
 
     if (!unfilledRequiredParams.isEmpty()) {
-      StringBuilder b =
-          new StringBuilder(String.format("%s missing required arguments:",
-              elem));
+      StringBuilder b = new StringBuilder(String.format(
+          "%s missing required arguments:", elem));
       for (String name : unfilledRequiredParams.keySet()) {
         b.append(" ").append(name);
       }
@@ -174,8 +169,8 @@
     }
 
     for (String propertyName : setterValues.keySet()) {
-      writer.addStatement("%s.set%s(%s);", fieldName,
-          initialCap(propertyName), setterValues.get(propertyName));
+      writer.addStatement("%s.set%s(%s);", fieldName, initialCap(propertyName),
+          setterValues.get(propertyName));
     }
   }
 
@@ -199,19 +194,23 @@
     return localizedValues;
   }
 
+  private JType[] getParamTypes(JMethod setter) {
+    JParameter[] params = setter.getParameters();
+    JType[] types = new JType[params.length];
+    for (int i = 0; i < params.length; i++) {
+      types[i] = params[i].getType();
+    }
+    return types;
+  }
+
   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 boolean isString(UiBinderWriter writer, JType paramType) {
+    JType stringType = writer.getOracle().findType(String.class.getName());
+    return stringType.equals(paramType);
   }
 
   private String[] makeArgsList(final Map<String, String> valueMap,
diff --git a/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
index 6179dfa..22db059 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
@@ -16,16 +16,19 @@
 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.MortalLogger;
 
 /**
  * Parses a single boolean attribute.
  */
-public class BooleanAttributeParser extends SimpleAttributeParser {
+public class BooleanAttributeParser extends StrictAttributeParser {
 
-  public String parse(String value, UiBinderWriter writer)
+  public String parse(String value, MortalLogger logger)
       throws UnableToCompleteException {
-    // TODO(jgw): check validity.
-    return super.parse(value, writer);
+    if (value.equals("true") || value.equals("false")) {
+      return value;
+    } 
+    
+    return super.parse(value, logger);
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
index 37a2d92..811fd85 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
@@ -16,7 +16,7 @@
 package com.google.gwt.uibinder.parsers;
 
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 /**
  * Interprets an attribute's contents as a method call on a resource class (one
@@ -53,7 +53,7 @@
     return isBundleStatic;
   }
 
-  public String parse(String attribute, UiBinderWriter writer) {
+  public String parse(String attribute, MortalLogger ignored) {
     StringBuilder b = new StringBuilder();
     String[] values = attribute.split(" ");
     boolean first = true;
diff --git a/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
index 4bdede2..404aada 100644
--- a/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
@@ -49,30 +49,30 @@
 
     // Parse horizontal and vertical alignment attributes.
     if (cellElem.hasAttribute(HALIGN_ATTR)) {
-      String value = halignParser.parse(cellElem.consumeAttribute(HALIGN_ATTR),
-          writer);
+      String value = halignParser.parse(cellElem.consumeRawAttribute(HALIGN_ATTR),
+          writer.getLogger());
       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);
+      String value = valignParser.parse(cellElem.consumeRawAttribute(VALIGN_ATTR),
+          writer.getLogger());
       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);
+      String value = stringParser.parse(cellElem.consumeRawAttribute(WIDTH_ATTR),
+          writer.getLogger());
       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);
+      String value = stringParser.parse(cellElem.consumeRawAttribute(HEIGHT_ATTR),
+          writer.getLogger());
       writer.addStatement("%1$s.setCellHeight(%2$s, %3$s);", fieldName,
           childFieldName, value);
     }
diff --git a/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
index 3ce52fa..b483249 100644
--- a/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
@@ -37,6 +37,7 @@
     this.writer = writer;
   }
 
+  @SuppressWarnings("deprecation")
   public String interpretElement(XMLElement elem)
       throws UnableToCompleteException {
     Map<String, String> attNameToToken = new HashMap<String, String>();
@@ -44,8 +45,19 @@
     for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
       XMLAttribute att = elem.getAttribute(i);
       AttributeParser parser = writer.getBundleAttributeParser(att);
-      
-      if (parser == null && att.hasComputedValue()) {
+
+      if (parser != null) {
+        // Legacy res:style='style.pretty'
+        String parsedValue = parser.parse(att.consumeRawValue(),
+            writer.getLogger());
+        String attToken = writer.tokenForExpression(parsedValue);
+
+        // Use local name here, replacing res:style with plain old style
+        attNameToToken.put(att.getLocalName(), attToken);
+        continue;
+      }
+
+      if (att.hasComputedValue()) {
         /*
          * It's okay to simply create a string parser by hand, rather than
          * asking the writer to magically guess what kind of parser we need,
@@ -53,12 +65,18 @@
          * markup. Bean parser takes care of the strongly typed cases.
          */
         parser = new StringAttributeParser();
-      }
-
-      if (parser != null) {
-        String parsedValue = parser.parse(att.consumeValue(), writer);
+        String parsedValue = parser.parse(att.consumeRawValue(),
+            writer.getLogger());
         String attToken = writer.tokenForExpression(parsedValue);
-        attNameToToken.put(att.getLocalName(), attToken);
+        attNameToToken.put(att.getName(), attToken);
+      } else {
+        /*
+         * No computed value, but make sure that any {{ madness gets escaped.
+         * TODO(rjrjr) Move this to XMLElement RSN
+         */
+        String n = att.getName();
+        String v = att.consumeRawValue().replace("{{", "{");
+        elem.setAttribute(n, v);
       }
     }
 
diff --git a/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java b/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
index 70c428b..e5ef60e 100644
--- a/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
@@ -57,29 +57,31 @@
       }
 
       // Look for innerHTML first.
-      HtmlInterpreter interpreter =
-          HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+      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);
+        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);
+        String html = child.consumeRawAttribute("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);
+        String text = child.consumeRawAttribute("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);
+        String image = child.consumeRawAttribute("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
index 690c927..9cf5019 100644
--- a/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java
@@ -143,9 +143,9 @@
       UiBinderWriter writer) throws UnableToCompleteException {
     // TODO(rjrjr) parser should come from XMLElement
 
-    String value = headerElem.consumeAttribute(attribute, null);
+    String value = headerElem.consumeRawAttribute(attribute, null);
     if (value != null) {
-      value = new StrictAttributeParser().parse(value, writer);
+      value = new StrictAttributeParser().parse(value, writer.getLogger());
     }
     return value;
   }
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
index face22a..2ce28a5 100644
--- a/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
@@ -89,7 +89,7 @@
       String childFieldName = writer.parseElementToField(widget);
 
       if (requiresSize(child)) {
-        double size = child.consumeDoubleAttribute("size");
+        String size = child.consumeDoubleAttribute("size");
         writer.addStatement("%s.%s(%s, %f);", fieldName, addMethodName(child),
             childFieldName, size);
       } else {
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
index 45b7dea..c67f073 100644
--- a/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
@@ -63,7 +63,7 @@
       if (!child.hasAttribute("direction")) {
         writer.die("Dock must specify the 'direction' attribute");
       }
-      String value = child.consumeAttribute("direction");
+      String value = child.consumeRawAttribute("direction");
       String translated = values.get(value);
       if (translated == null) {
         writer.die("Invalid value: dockDirection='" + value + "'");
@@ -77,13 +77,13 @@
       // 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"));
+            fieldName, childFieldName, child.consumeRawAttribute("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"));
+            fieldName, childFieldName, child.consumeRawAttribute("height"));
       }
 
       // Parse the CellPanel-specific attributes on the Dock element.
diff --git a/user/src/com/google/gwt/uibinder/parsers/DoubleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/DoubleAttributeParser.java
new file mode 100644
index 0000000..056bfd2
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DoubleAttributeParser.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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.MortalLogger;
+
+/**
+ * Parses a single double attribute.
+ */
+public class DoubleAttributeParser extends StrictAttributeParser {
+
+  public String parse(String value, MortalLogger logger)
+      throws UnableToCompleteException {
+    try {
+      Double.parseDouble(value);
+      // Happy double
+      return value;
+    } catch (NumberFormatException e) {
+      // Not a double, maybe super sees a field ref
+    }
+    return super.parse(value, logger);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
index 20c5133..3f0143e 100644
--- a/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
@@ -42,7 +42,7 @@
       if (elem.hasAttribute("id")) {
         writer.die(String.format(
             "Cannot declare id=\"%s\" and %s=\"%s\" on the same element",
-            elem.consumeAttribute("id"), writer.getUiFieldAttributeName(),
+            elem.consumeRawAttribute("id"), writer.getUiFieldAttributeName(),
             fieldName));
       }
 
diff --git a/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
index 6aba4e7..ce63d2f 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -30,25 +30,25 @@
 
   public void parse(XMLElement elem, String fieldName, JClassType type,
       final UiBinderWriter writer) throws UnableToCompleteException {
-    String customTag =
-      UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute("tag"));
+    String customTag = UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeRawAttribute("tag"));
 
     /*
-     * Gathers up elements that indicate nested widgets (but only those
-     * that are not inside msg elements).
+     * Gathers up elements that indicate nested widgets (but only those that are
+     * not inside msg elements).
      */
-    WidgetInterpreter widgetInterpreter = new WidgetInterpreter(fieldName, writer);
+    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.
+     * attributes. There may be widgets inside a msg, which is why the
+     * construction in makeHtmlInterpreter is so complicated.
      */
     HtmlInterpreter htmlInterpreter = makeHtmlInterpreter(fieldName, writer);
 
     writer.beginAttachedSection(fieldName + ".getElement()");
-    String html = elem.consumeInnerHtml(InterpreterPipe.newPipe(widgetInterpreter,
-        htmlInterpreter));
+    String html = elem.consumeInnerHtml(InterpreterPipe.newPipe(
+        widgetInterpreter, htmlInterpreter));
     writer.endAttachedSection();
 
     /*
@@ -58,10 +58,11 @@
      * signature (by passing in type).
      */
     if ("".equals(customTag)) {
-      writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + html + "\"");
+      writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + html
+          + "\"");
     } else {
-      writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + customTag + "\"",
-          "\"" + html + "\"");
+      writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + customTag
+          + "\"", "\"" + html + "\"");
     }
   }
 
@@ -73,17 +74,16 @@
       final UiBinderWriter uiWriter) {
     final String ancestorExpression = fieldName + ".getElement()";
 
-    PlaceholderInterpreterProvider placeholderInterpreterProvider =
-        new PlaceholderInterpreterProvider() {
-          public PlaceholderInterpreter get(MessageWriter message) {
-            return new WidgetPlaceholderInterpreter(fieldName, uiWriter,
-                message, ancestorExpression);
-          }
-        };
+    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));
+    HtmlInterpreter htmlInterpreter = new HtmlInterpreter(uiWriter,
+        ancestorExpression, new HtmlMessageInterpreter(uiWriter,
+            placeholderInterpreterProvider));
 
     return htmlInterpreter;
   }
diff --git a/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
index 6352e9d..b70b717 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
@@ -16,7 +16,7 @@
 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.MortalLogger;
 
 import java.util.HashMap;
 
@@ -38,11 +38,11 @@
       "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_CENTER");
   }
 
-  public String parse(String value, UiBinderWriter writer)
+  public String parse(String value, MortalLogger logger)
       throws UnableToCompleteException {
     String translated = values.get(value);
     if (translated == null) {
-      writer.die("Invalid value: horizontalAlignment='" + value + "'");
+      logger.die("Invalid value: horizontalAlignment='" + value + "'");
     }
     return translated;
   }
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntParser.java b/user/src/com/google/gwt/uibinder/parsers/IntAttributeParser.java
similarity index 65%
rename from user/src/com/google/gwt/uibinder/parsers/IntParser.java
rename to user/src/com/google/gwt/uibinder/parsers/IntAttributeParser.java
index 924b1da..dbb11bc 100644
--- a/user/src/com/google/gwt/uibinder/parsers/IntParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/IntAttributeParser.java
@@ -16,16 +16,22 @@
 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.MortalLogger;
 
 /**
  * Parses an integer value.
  */
-public class IntParser extends SimpleAttributeParser {
+public class IntAttributeParser extends StrictAttributeParser {
 
-  public String parse(String value, UiBinderWriter writer)
+  public String parse(String value, MortalLogger logger)
       throws UnableToCompleteException {
-    // TODO(jgw): validate
-    return super.parse(value, writer);
+    try {
+      Integer.parseInt(value);
+      // Yup, it's an int, use it as such.
+      return value;
+    } catch (NumberFormatException e) {
+      // Not an int, let super see if it's a field ref
+    }
+    return super.parse(value, logger);
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java b/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
index 070718c..beb078f 100644
--- a/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
@@ -15,14 +15,14 @@
  */
 package com.google.gwt.uibinder.parsers;
 
-import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 /**
  * Parses a pair of integer values.
  */
 public class IntPairParser implements AttributeParser {
 
-  public String parse(String value, UiBinderWriter writer) {
+  public String parse(String value, MortalLogger ignored) {
     // TODO(jgw): parse & validate
     return value;
   }
diff --git a/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java b/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
index 2d1cedc..448b9ee 100644
--- a/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
@@ -31,11 +31,9 @@
       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");
-      }
+      String vertical = elem.consumeBooleanAttribute("vertical");
+      writer.setFieldInitializerAsConstructor(fieldName,
+          writer.getOracle().findType(MenuBar.class.getName()), vertical);
     }
 
     // Parse children.
diff --git a/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java b/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
index 0c8d6d6..5ca789f 100644
--- a/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
@@ -34,7 +34,7 @@
 
     if (type.equals(writer.getOracle().findType(RadioButton.class.getName()))) {
       writer.setFieldInitializerAsConstructor(fieldName, type,
-          '"' + elem.consumeAttribute("name") + '"');
+          '"' + elem.consumeRawAttribute("name") + '"');
     }
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/SimpleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/SimpleAttributeParser.java
deleted file mode 100644
index 4ba503e..0000000
--- a/user/src/com/google/gwt/uibinder/parsers/SimpleAttributeParser.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.uibinder.parsers.FieldReferenceConverter.Delegate;
-import com.google.gwt.uibinder.parsers.FieldReferenceConverter.IllegalFieldReferenceException;
-import com.google.gwt.uibinder.rebind.UiBinderWriter;
-
-/**
- * Basic attribute parser, handles simple field references. Intended for
- * subclassing.
- */
-public class SimpleAttributeParser implements AttributeParser {
-
-  /** 
-   * Package protected for testing.
-   */
-  static class FieldReferenceDelegate implements Delegate {
-    boolean sawFragment = false;
-    boolean sawReference = false;
-    
-    public String handleFragment(String fragment)
-        throws IllegalFieldReferenceException {
-      if (fragment.length() > 0) {
-        assertOnly();
-        sawFragment = true;
-      }
-      return fragment;
-    }
-
-    public String handleReference(String reference)
-        throws IllegalFieldReferenceException {
-      assertOnly();
-      sawReference = true;
-      return reference;
-    }
-
-    private void assertOnly() {
-      if (sawFragment || sawReference) {
-        throw new IllegalFieldReferenceException();
-      }
-    }
-  }
-
-  /**
-   * If the value holds a single field reference "{like.this}", converts it to a
-   * java expression. If none is found, the value is returned unchanged in the
-   * expectation that subclass will deal with it as a literal.
-   * <p>
-   * In any other case (e.g. more than one field reference), an
-   * UnableToCompleteException is thrown.
-   */
-  public String parse(String value, UiBinderWriter writer)
-      throws UnableToCompleteException {
-
-    try {
-      return new FieldReferenceConverter(new FieldReferenceDelegate()).convert(value);
-    } catch (IllegalFieldReferenceException e) {
-      writer.die("Bad field reference: \"%s\"", value);
-      return null; // Unreachable
-    }
-  }
-}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
index 58feb85..85e1261 100644
--- a/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
@@ -63,7 +63,7 @@
         widgetElem = stackChild;
       }
 
-      double size = headerElem.consumeDoubleAttribute("size");
+      String size = headerElem.consumeDoubleAttribute("size");
       XMLElement headerWidgetElem = headerElem.consumeSingleChildElement();
       String headerFieldName = writer.parseElementToField(headerWidgetElem);
       String childFieldName = writer.parseElementToField(widgetElem);
diff --git a/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
index 2897280..3d38704 100644
--- a/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
@@ -39,7 +39,7 @@
       String stackItemLabel = null;
       String variableAttributeName = elem.getPrefix() + ":" + ATTRIBUTE_TEXT;
       if (child.hasAttribute(variableAttributeName)) {
-        stackItemLabel = child.consumeAttribute(variableAttributeName);
+        stackItemLabel = child.consumeRawAttribute(variableAttributeName);
       }
 
       String childFieldName = writer.parseElementToField(child);
diff --git a/user/src/com/google/gwt/uibinder/parsers/StrictAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/StrictAttributeParser.java
index 4285c8a..18acbc7 100644
--- a/user/src/com/google/gwt/uibinder/parsers/StrictAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/StrictAttributeParser.java
@@ -18,7 +18,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.uibinder.parsers.FieldReferenceConverter.Delegate;
 import com.google.gwt.uibinder.parsers.FieldReferenceConverter.IllegalFieldReferenceException;
-import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 /**
  * Fall through attribute parser. Accepts a field reference or nothing.
@@ -60,13 +60,13 @@
    * In any other case (e.g. more than one field reference), an
    * UnableToCompleteException is thrown.
    */
-  public String parse(String value, UiBinderWriter writer)
+  public String parse(String value, MortalLogger logger)
       throws UnableToCompleteException {
 
     try {
       return new FieldReferenceConverter(new FieldReferenceDelegate()).convert(value);
     } catch (IllegalFieldReferenceException e) {
-      writer.die("Bad field reference: \"%s\"", value);
+      logger.die("Bad field reference: \"%s\"", value);
       return null; // Unreachable
     }
   }
diff --git a/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
index 4261ec6..8e4de2b 100644
--- a/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.uibinder.parsers;
 
+import com.google.gwt.uibinder.rebind.MortalLogger;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 
 /**
@@ -36,7 +37,7 @@
   final FieldReferenceConverter braceReplacor = new FieldReferenceConverter(
       new FieldReferenceDelegate());
 
-  public String parse(String value, UiBinderWriter writer) {
+  public String parse(String value, MortalLogger ignored) {
     return braceReplacor.convert(value);
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/TabLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/TabLayoutPanelParser.java
index 18dc393..3d0e42c 100644
--- a/user/src/com/google/gwt/uibinder/parsers/TabLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/TabLayoutPanelParser.java
@@ -40,14 +40,14 @@
   public void parse(XMLElement panelElem, String fieldName, JClassType type,
       UiBinderWriter writer) throws UnableToCompleteException {
     // TabLayoutPanel requires tabBar size and unit ctor args.
-    double size = panelElem.consumeDoubleAttribute("barHeight");
+    String size = panelElem.consumeDoubleAttribute("barHeight");
     Unit unit = panelElem.consumeEnumAttribute("barUnit", Unit.class);
 
     String enumName = DockLayoutPanelParser.getFullyQualifiedEnumName(unit);
     JClassType tlpType = writer.getOracle().findType(
         TabLayoutPanel.class.getName());
     writer.setFieldInitializerAsConstructor(fieldName, tlpType,
-        Double.toString(size), enumName);
+        size, enumName);
 
     // Parse children.
     for (XMLElement tabElem : panelElem.consumeChildElements()) {
diff --git a/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
index 728d184..ae17c34 100644
--- a/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
@@ -49,7 +49,7 @@
       // Get the caption, if any.
       String tabCaption = "";
       if (child.hasAttribute("text")) {
-        tabCaption = child.consumeAttribute("text");
+        tabCaption = child.consumeRawAttribute("text");
       }
 
       // Get the single required child widget.
diff --git a/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java b/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
index 4ce0858..d2a0d372 100644
--- a/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
@@ -64,7 +64,7 @@
       throws UnableToCompleteException {
     String attributeValue = null;
     if (elem.hasAttribute(attribute)) {
-      attributeValue = elem.consumeAttribute(attribute);
+      attributeValue = elem.consumeRawAttribute(attribute);
 
       if ("".equals(attributeValue)) {
         writer.die("In %s, value for attribute %s cannot be empty", elem, attribute);
diff --git a/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
index 70ba1ca..6a7fcad 100644
--- a/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
@@ -15,7 +15,8 @@
  */
 package com.google.gwt.uibinder.parsers;
 
-import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.MortalLogger;
 
 import java.util.HashMap;
 
@@ -37,11 +38,11 @@
       "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_BOTTOM");
   }
 
-  public String parse(String value, UiBinderWriter writer) {
+  public String parse(String value, MortalLogger logger)
+      throws UnableToCompleteException {
     String translated = values.get(value);
     if (translated == null) {
-      throw new RuntimeException("Invalid value: vorizontalAlignment='"
-        + value + "'");
+      logger.die("Invalid value: vorizontalAlignment='%s'", value);
     }
     return translated;
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java b/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java
new file mode 100644
index 0000000..b5c65d8
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java
@@ -0,0 +1,117 @@
+/*
+ * 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.JType;
+import com.google.gwt.uibinder.parsers.AttributeParser;
+import com.google.gwt.uibinder.parsers.StrictAttributeParser;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class AttributeParsers {
+  private static final String DOUBLE = "double";
+  private static final String BOOLEAN = "boolean";
+
+  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);
+    }
+  }
+
+  /**
+   * Class names of parsers for values of attributes with no namespace prefix,
+   * keyed by method parameter signatures.
+   */
+  private final Map<String, String> parsers = new HashMap<String, String>();
+
+  public AttributeParsers() {
+    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.IntAttributeParser");
+
+    addAttributeParser(DOUBLE,
+        "com.google.gwt.uibinder.parsers.DoubleAttributeParser");
+
+    addAttributeParser("int,int",
+        "com.google.gwt.uibinder.parsers.IntPairParser");
+
+    addAttributeParser("com.google.gwt.user.client.ui.HasHorizontalAlignment."
+        + "HorizontalAlignmentConstant",
+        "com.google.gwt.uibinder.parsers.HorizontalAlignmentConstantParser");
+  }
+
+  public AttributeParser get(JType... types) {
+    AttributeParser rtn = getForKey(getParametersKey(types));
+    if (rtn == null && types.length == 1) {
+      rtn = new StrictAttributeParser();
+    }
+
+    return rtn;
+  }
+
+  public AttributeParser getBooleanParser() {
+    return getForKey(BOOLEAN);
+  }
+
+  public AttributeParser getDoubleParser() {
+    return getForKey(DOUBLE);
+  }
+
+  private void addAttributeParser(String signature, String className) {
+    parsers.put(signature, className);
+  }
+
+  private AttributeParser getForKey(String key) {
+    String parserClassName = parsers.get(key);
+    if (parserClassName != null) {
+      return getAttributeParserByClassName(parserClassName);
+    }
+
+    return null;
+  }
+
+  /**
+   * Given a types array, return a key for the attributeParsers table.
+   */
+  private String getParametersKey(JType[] types) {
+    StringBuffer b = new StringBuffer();
+    for (JType t : types) {
+      if (b.length() > 0) {
+        b.append(',');
+      }
+      b.append(t.getParameterizedQualifiedSourceName());
+    }
+    return b.toString();
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/BundleAttributeParsers.java b/user/src/com/google/gwt/uibinder/rebind/BundleAttributeParsers.java
new file mode 100644
index 0000000..ad8fcbc
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/BundleAttributeParsers.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.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.uibinder.parsers.BundleAttributeParser;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+import com.google.gwt.uibinder.rebind.model.OwnerField;
+import com.google.gwt.uibinder.rebind.model.OwnerFieldClass;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Deprecated
+class BundleAttributeParsers {
+  private static final String BUNDLE_URI_SCHEME = "urn:with:";
+
+  private final TypeOracle oracle;
+  private final String gwtPrefix;
+  private final MortalLogger logger;
+  private final OwnerClass ownerClass;
+  private final String templatePath;
+  private final JClassType uiOwnerType;
+
+  /**
+   * Map of bundle parsers, keyed by bundle class name.
+   */
+  private final Map<String, BundleAttributeParser> parsers = new LinkedHashMap<String, BundleAttributeParser>();
+
+  public BundleAttributeParsers(TypeOracle oracle, String gwtPrefix,
+      MortalLogger logger, OwnerClass ownerClass, String templatePath,
+      JClassType uiOwnerType) {
+    this.oracle = oracle;
+    this.gwtPrefix = gwtPrefix;
+    this.logger = logger;
+    this.ownerClass = ownerClass;
+    this.templatePath = templatePath;
+    this.uiOwnerType = uiOwnerType;
+  }
+
+  public BundleAttributeParser get(OwnerFieldClass type) {
+    return parsers.get(type.getRawType().getQualifiedSourceName());
+  }
+
+  public BundleAttributeParser get(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 = parsers.get(bundleClassName);
+    if (parser == null) {
+      JClassType bundleClassType = getOracle().findType(bundleClassName);
+      if (bundleClassType == null) {
+        die("No such resource class: " + bundleClassName);
+      }
+      parser = createBundleParser(bundleClassType, attribute);
+      parsers.put(bundleClassName, parser);
+    }
+
+    return parser;
+  }
+
+  public Map<String, BundleAttributeParser> getMap() {
+    return Collections.unmodifiableMap(parsers);
+  }
+
+  /**
+   * Creates a parser for the given bundle class. This method will die soon.
+   */
+  private BundleAttributeParser createBundleParser(JClassType bundleClass,
+      XMLAttribute attribute) throws UnableToCompleteException {
+
+    final String templateResourceName = attribute.getName().split(":")[0];
+    warn("The %1$s mechanism is deprecated. Instead, declare the following "
+        + "%2$s:with element as a child of your %2$s:UiBinder element: "
+        + "<%2$s:with field='%3$s' type='%4$s.%5$s' />", BUNDLE_URI_SCHEME,
+        gwtPrefix, templateResourceName, bundleClass.getPackage().getName(),
+        bundleClass.getName());
+
+    // Try to find any bundle instance created with UiField.
+    OwnerField field = getOwnerClass().getUiFieldForType(bundleClass);
+    if (field != null) {
+      if (!templateResourceName.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);
+      }
+    }
+
+    // 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);
+    }
+
+    return new BundleAttributeParser(bundleClass, "my"
+        + bundleClass.getName().replace('.', '_') + "Instance", true);
+  }
+
+  private void die(String string, Object... params)
+      throws UnableToCompleteException {
+    logger.die(string, params);
+  }
+
+  private TypeOracle getOracle() {
+    return oracle;
+  }
+
+  private OwnerClass getOwnerClass() {
+    return ownerClass;
+  }
+
+  private void warn(String string, Object... params) {
+    logger.warn(string, params);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetEscapedInnerTextVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetEscapedInnerTextVisitor.java
index d3de434..24cf27c 100644
--- a/user/src/com/google/gwt/uibinder/rebind/GetEscapedInnerTextVisitor.java
+++ b/user/src/com/google/gwt/uibinder/rebind/GetEscapedInnerTextVisitor.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -31,21 +31,21 @@
    * and uses the writer to report errors.
    */
   public static void getEscapedInnerText(Element elem, StringBuffer buffer,
-      Interpreter<String> interpreter, UiBinderWriter writer)
+      Interpreter<String> interpreter, XMLElementProvider writer)
       throws UnableToCompleteException {
-    new ChildWalker().accept(elem, new GetEscapedInnerTextVisitor(buffer, interpreter,
-        writer));
+    new ChildWalker().accept(elem, new GetEscapedInnerTextVisitor(buffer,
+        interpreter, writer));
   }
 
   protected final StringBuffer buffer;
   protected final Interpreter<String> interpreter;
-  protected final UiBinderWriter writer;
+  protected final XMLElementProvider elementProvider;
 
   protected GetEscapedInnerTextVisitor(StringBuffer buffer,
-      Interpreter<String> interpreter, UiBinderWriter writer) {
+      Interpreter<String> interpreter, XMLElementProvider elementProvider) {
     this.buffer = buffer;
     this.interpreter = interpreter;
-    this.writer = writer;
+    this.elementProvider = elementProvider;
   }
 
   public void visitCData(CDATASection d) {
@@ -53,8 +53,7 @@
   }
 
   public void visitElement(Element e) throws UnableToCompleteException {
-    String replacement =
-        interpreter.interpretElement(new XMLElement(e, writer));
+    String replacement = interpreter.interpretElement(elementProvider.get(e));
 
     if (replacement != null) {
       buffer.append(replacement);
@@ -62,17 +61,17 @@
   }
 
   public void visitText(Text t) {
-    String escaped =
-        UiBinderWriter.escapeText(t.getTextContent(), preserveWhiteSpace(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;
+        ? (Element) t.getParentNode() : null;
 
-    boolean preserveWhitespace =
-        parent != null && "pre".equals(parent.getTagName());
+    boolean preserveWhitespace = parent != null
+        && "pre".equals(parent.getTagName());
     // TODO(rjrjr) What about script blocks?
     return preserveWhitespace;
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
index 79ab82a..4eae5dd 100644
--- a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
@@ -28,20 +28,20 @@
    * each descendant, and uses the writer to report errors.
    */
   public static void getEscapedInnerHtml(Element elem, StringBuffer buffer,
-      Interpreter<String> interpreter, UiBinderWriter writer)
+      Interpreter<String> interpreter, XMLElementProvider writer)
       throws UnableToCompleteException {
     new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter,
         writer));
   }
 
   private GetInnerHtmlVisitor(StringBuffer buffer,
-      Interpreter<String> interpreter, UiBinderWriter writer) {
+      Interpreter<String> interpreter, XMLElementProvider writer) {
     super(buffer, interpreter, writer);
   }
 
   @Override
   public void visitElement(Element elem) throws UnableToCompleteException {
-    XMLElement xmlElement = new XMLElement(elem, writer);
+    XMLElement xmlElement = elementProvider.get(elem);
     String replacement = interpreter.interpretElement(xmlElement);
     if (replacement != null) {
       buffer.append(replacement);
@@ -50,7 +50,7 @@
 
     // TODO(jgw): Ditch the closing tag when there are no children.
     buffer.append(xmlElement.consumeOpeningTag());
-    getEscapedInnerHtml(elem, buffer, interpreter, writer);
+    getEscapedInnerHtml(elem, buffer, interpreter, elementProvider);
     buffer.append(xmlElement.getClosingTag());
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index e746a44..689665e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -126,7 +126,7 @@
 
   private JClassType consumeCssResourceType(XMLElement elem)
       throws UnableToCompleteException {
-    String typeName = elem.consumeAttribute("type", null);
+    String typeName = elem.consumeRawAttribute("type", null);
     if (typeName == null) {
       return cssResourceType;
     }
@@ -166,16 +166,13 @@
   private void createImage(XMLElement elem) throws UnableToCompleteException {
     String name = elem.consumeRequiredAttribute(FIELD_ATTRIBUTE);
     // @source is optional on ImageResource
-    String source = elem.consumeAttribute(SOURCE_ATTRIBUTE, null);
+    String source = elem.consumeRawAttribute(SOURCE_ATTRIBUTE, null);
 
-    Boolean flipRtl = null;
-    if (elem.hasAttribute(FLIP_RTL_ATTRIBUTE)) {
-      flipRtl = elem.consumeBooleanAttribute(FLIP_RTL_ATTRIBUTE);
-    }
+    Boolean flipRtl = elem.consumeBooleanConstantAttribute(FLIP_RTL_ATTRIBUTE);
 
     RepeatStyle repeatStyle = null;
     if (elem.hasAttribute(REPEAT_STYLE_ATTRIBUTE)) {
-      String value = elem.consumeAttribute(REPEAT_STYLE_ATTRIBUTE);
+      String value = elem.consumeRawAttribute(REPEAT_STYLE_ATTRIBUTE);
       try {
         repeatStyle = RepeatStyle.valueOf(value);
       } catch (IllegalArgumentException e) {
@@ -237,16 +234,16 @@
 
   private void createStyle(XMLElement elem) throws UnableToCompleteException {
     String body = elem.consumeUnescapedInnerText();
-    String source = elem.consumeAttribute(SOURCE_ATTRIBUTE);
+    String source = elem.consumeRawAttribute(SOURCE_ATTRIBUTE);
     
     if (0 == body.length() && 0 == source.length()) {
       writer.die("%s must have either a src attribute or body text", elem);
     }
     
-    String name = elem.consumeAttribute(FIELD_ATTRIBUTE, "style");
+    String name = elem.consumeRawAttribute(FIELD_ATTRIBUTE, "style");
     JClassType publicType = consumeCssResourceType(elem);
 
-    String importTypeNames = elem.consumeAttribute(IMPORT_ATTRIBUTE, null);
+    String importTypeNames = elem.consumeRawAttribute(IMPORT_ATTRIBUTE, null);
     LinkedHashSet<JClassType> importTypes = new LinkedHashSet<JClassType>();
     if (importTypeNames != null) {
       String[] typeNames = importTypeNames.split("\\s+");
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 7879c23..e094e7e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -17,11 +17,9 @@
 
 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.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.dom.client.TagName;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.parsers.AttributeMessageParser;
@@ -29,7 +27,6 @@
 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.parsers.StrictAttributeParser;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
 import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
 import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
@@ -65,12 +62,11 @@
  * TODO(rdamazio): Refactor this, extract model classes, improve ordering
  * guarantees, etc.
  * 
- * TODO(rjrjr): Improve error messages
+ * TODO(rjrjr): Line numbers in error messages.
  */
 @SuppressWarnings("deprecation")
 public class UiBinderWriter {
   static final String BINDER_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:";
 
   // TODO(rjrjr) Another place that we need a general anonymous field
@@ -138,24 +134,6 @@
     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.
@@ -174,7 +152,8 @@
       JClassType curType = q.removeFirst();
       list.add(curType);
 
-      // Add implemented interfaces to the back of the queue (breadth first, remember?)
+      // Add implemented interfaces to the back of the queue (breadth first,
+      // remember?)
       for (JClassType intf : curType.getImplementedInterfaces()) {
         q.add(intf);
       }
@@ -191,24 +170,11 @@
   private final MortalLogger logger;
 
   /**
-   * 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;
@@ -259,6 +225,8 @@
    * section.
    */
   private final LinkedList<List<String>> detachStatementsStack = new LinkedList<List<String>>();
+  private final AttributeParsers attributeParsers;
+  private final BundleAttributeParsers bundleParsers;
 
   UiBinderWriter(JClassType baseClass, String implClassName,
       String templatePath, TypeOracle oracle, MortalLogger logger)
@@ -302,25 +270,10 @@
         this.implClassName, CLIENT_BUNDLE_FIELD, logger);
     handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
     fieldManager = new FieldManager(logger);
-  }
 
-  /** 
-   * Hack for testing. Die method works, nothing else does 
-   */
-  UiBinderWriter() {
-    this.baseClass = null;
-    this.implClassName = null;
-    this.oracle = null;
-    this.logger = new MortalLogger(new PrintWriterTreeLogger());
-    this.templatePath = null;
-    this.messages = null;
-    uiRootType = null;
-    uiOwnerType = null;
-
-    ownerClass = null;
-    bundleClass = null;
-    handlerEvaluator = null;
-    fieldManager = null;
+    attributeParsers = new AttributeParsers();
+    bundleParsers = new BundleAttributeParsers(oracle, gwtPrefix, logger,
+        getOwnerClass(), templatePath, uiOwnerType);
   }
 
   /**
@@ -563,22 +516,11 @@
   }
 
   /**
-   * Find and return an appropriate attribute parser for a set of parameters, or
+   * Find and return an appropriate attribute parser for a set of types, or
    * return null.
    */
-  public AttributeParser getAttributeParser(JParameter... params) {
-    String paramTypeNames = getParametersKey(params);
-    String parserClassName = attributeParsers.get(paramTypeNames);
-
-    if (parserClassName != null) {
-      return getAttributeParserByClassName(parserClassName);
-    }
-
-    if (params.length == 1) {
-      return new StrictAttributeParser();
-    }
-
-    return null;
+  public AttributeParser getAttributeParser(JType... types) {
+    return attributeParsers.get(types);
   }
 
   /**
@@ -589,10 +531,10 @@
    * returned.
    */
   public AttributeParser getAttributeParser(XMLAttribute attribute,
-      JParameter... params) throws UnableToCompleteException {
+      JType... types) throws UnableToCompleteException {
     AttributeParser parser = getBundleAttributeParser(attribute);
     if (parser == null) {
-      parser = getAttributeParser(params);
+      parser = getAttributeParser(types);
     }
     return parser;
   }
@@ -608,27 +550,7 @@
   @Deprecated
   public BundleAttributeParser getBundleAttributeParser(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 bundleClassType = getOracle().findType(bundleClassName);
-      if (bundleClassType == null) {
-        die("No such resource class: " + bundleClassName);
-      }
-      parser = createBundleParser(bundleClassType, attribute);
-      bundleParsers.put(bundleClassName, parser);
-    }
-
-    return parser;
+    return bundleParsers.get(attribute);
   }
 
   public ImplicitClientBundle getBundleClass() {
@@ -664,7 +586,7 @@
   public String getUiFieldAttributeName() {
     return gwtPrefix + ":field";
   }
-  
+
   public boolean isBinderElement(XMLElement elem) {
     String uri = elem.getNamespaceUri();
     return uri != null && BINDER_URI.equals(uri);
@@ -781,15 +703,12 @@
     Element documentElement = doc.getDocumentElement();
     gwtPrefix = documentElement.lookupPrefix(BINDER_URI);
 
-    XMLElement elem = new XMLElement(documentElement, this);
+    XMLElement elem = new XMLElementProviderImpl(attributeParsers,
+        bundleParsers, logger).get(documentElement);
     this.rendered = tokenator.detokenate(parseDocumentElement(elem));
     printWriter.print(rendered);
   }
 
-  private void addAttributeParser(String signature, String className) {
-    attributeParsers.put(signature, className);
-  }
-
   private void addElementParser(String gwtClass, String parser) {
     elementParsers.put(gwtClass, parser);
   }
@@ -801,51 +720,6 @@
   }
 
   /**
-   * Creates a parser for the given bundle class. This method will die soon.
-   */
-  private BundleAttributeParser createBundleParser(JClassType bundleClass,
-      XMLAttribute attribute) throws UnableToCompleteException {
-
-    final String[] templateResourceNames = attribute.getName().split(":");
-    if (templateResourceNames.length == 0) {
-      throw new RuntimeException("No template resource");
-    }
-    final String templateResourceName = templateResourceNames[0];
-    warn("The %1$s mechanism is deprecated. Instead, declare the following "
-        + "%2$s:with element as a child of your %2$s:UiBinder element: "
-        + "<%2$s:with field='%3$s' type='%4$s.%5$s' />", BUNDLE_URI_SCHEME,
-        gwtPrefix, templateResourceName, bundleClass.getPackage().getName(),
-        bundleClass.getName());
-
-    // Try to find any bundle instance created with UiField.
-    OwnerField field = getOwnerClass().getUiFieldForType(bundleClass);
-    if (field != null) {
-      if (!templateResourceName.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);
-      }
-    }
-
-    // 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);
-    }
-
-    return new BundleAttributeParser(bundleClass, "my"
-        + bundleClass.getName().replace('.', '_') + "Instance", true);
-  }
-
-  /**
    * Outputs a bundle resource for a given bundle attribute parser.
    */
   private String declareStaticField(BundleAttributeParser parser) {
@@ -922,7 +796,7 @@
     if (elem.hasAttribute("id") && isWidgetElement(elem)) {
       hasOldSchoolId = true;
       // If an id is specified on the element, use that.
-      fieldName = elem.consumeAttribute("id");
+      fieldName = elem.consumeRawAttribute("id");
       warn("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);
@@ -931,25 +805,11 @@
       if (hasOldSchoolId) {
         die("Cannot declare both id and field on the same element: " + elem);
       }
-      fieldName = elem.consumeAttribute(getUiFieldAttributeName());
+      fieldName = elem.consumeRawAttribute(getUiFieldAttributeName());
     }
     return fieldName;
   }
 
-  /**
-   * 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();
@@ -1141,21 +1001,6 @@
     addWidgetParser("DockLayoutPanel");
     addWidgetParser("StackLayoutPanel");
     addWidgetParser("TabLayoutPanel");
-
-    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");
   }
 
   /**
@@ -1215,9 +1060,8 @@
   }
 
   private void writeClassOpen(IndentedWriter w) {
-    w.write("public class %s implements UiBinder<%s, %s>, %s {",
-        implClassName, uiRootType.getName(), uiOwnerType.getName(),
-        baseClass.getName());
+    w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
+        uiRootType.getName(), uiOwnerType.getName(), baseClass.getName());
     w.indent();
   }
 
@@ -1291,7 +1135,7 @@
       String fieldName = ownerField.getName();
       FieldWriter fieldWriter = fieldManager.lookup(fieldName);
 
-      BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType().getRawType().getQualifiedSourceName());
+      BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType());
 
       if (bundleParser != null) {
         // ownerField is a bundle resource.
@@ -1337,8 +1181,10 @@
   private void writeStaticBundleInstances(IndentedWriter niceWriter) {
     // TODO(rjrjr) It seems bad that this method has special
     // knowledge of BundleAttributeParser, but that'll die soon so...
-    for (String key : bundleParsers.keySet()) {
-      String declaration = declareStaticField(bundleParsers.get(key));
+
+    Map<String, BundleAttributeParser> bpMap = bundleParsers.getMap();
+    for (String key : bpMap.keySet()) {
+      String declaration = declareStaticField(bpMap.get(key));
       if (declaration != null) {
         niceWriter.write(declaration);
       }
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
index c0a3818..811a91f 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
@@ -24,17 +24,16 @@
  * parser writers out of trouble.
  */
 public class XMLAttribute {
-
   private XMLElement xmlElem;
   private Attr w3cAttr;
 
-  public XMLAttribute(XMLElement element, Attr attr) {
+  XMLAttribute(XMLElement element, Attr attr) {
     this.xmlElem = element;
     this.w3cAttr = attr;
   }
 
-  public String consumeValue() {
-    return xmlElem.consumeAttribute(w3cAttr.getName());
+  public String consumeRawValue() {
+    return xmlElem.consumeRawAttribute(w3cAttr.getName());
   }
   
   public String getLocalName() {
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index 9ef7461..6e9babc 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -40,6 +40,8 @@
  * will run first, and if they consume a value, less-specific parsers will not
  * see it.
  */
+@SuppressWarnings("deprecation")
+// BundleAttributeParser not quite ready to die
 public class XMLElement {
   /**
    * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and
@@ -83,11 +85,13 @@
     }
   }
 
-  private final UiBinderWriter writer;
-
   private final Element elem;
+  final AttributeParsers attributeParsers;
+  final BundleAttributeParsers bundleParsers;
+  private final MortalLogger logger;
 
   private final String debugString;
+  private final XMLElementProvider provider;
 
   {
     // from com/google/gxp/compiler/schema/html.xml
@@ -107,57 +111,56 @@
     NO_END_TAG.add("wbr");
   }
 
-  public XMLElement(Element elem, UiBinderWriter writer) {
+  XMLElement(Element elem, AttributeParsers attributeParsers,
+      BundleAttributeParsers bundleParsers, MortalLogger logger,
+      XMLElementProvider provider) {
     this.elem = elem;
-    this.writer = writer;
+    this.attributeParsers = attributeParsers;
+    this.bundleParsers = bundleParsers;
+    this.logger = logger;
+    this.provider = provider;
+
     this.debugString = getOpeningTag();
   }
 
   /**
-   * Consumes the given attribute and returns its trimmed value, or null if it
-   * was unset. The returned string is not escaped.
+   * Consumes the named attribute as a boolean expression: a literal, or a field
+   * reference. At the moment field references are validated for syntax
+   * only--there is no check that there is any such field or methods, nor
+   * that they have the correct return type.
    * 
-   * @param name the attribute's full name (including prefix)
-   * @return the attribute's value, or ""
-   */
-  public String consumeAttribute(String name) {
-    String value = elem.getAttribute(name);
-    elem.removeAttribute(name);
-    return value.trim();
-  }
-
-  /**
-   * Consumes the given attribute and returns its trimmed value, or the given
-   * default value if it was unset. The returned string is not escaped.
+   * @return "true", "false", an expression that will evaluate to a boolean
+   *         value in the generated code, or "" if there is no such attribute
    * 
-   * @param name the attribute's full name (including prefix)
-   * @param defaultValue the value to return if the attribute was unset
-   * @return the attribute's value, or defaultValue
+   * @throws UnableToCompleteException on unparseable value
    */
-  public String consumeAttribute(String name, String defaultValue) {
-    String value = consumeAttribute(name);
-    if ("".equals(value)) {
-      return defaultValue;
-    }
-    return value;
-  }
-
-  /**
-   * Consumes the given attribute as a boolean value.
-   * 
-   * @throws UnableToCompleteException
-   */
-  public boolean consumeBooleanAttribute(String attr)
+  public String consumeBooleanAttribute(String name)
       throws UnableToCompleteException {
-    String value = consumeAttribute(attr);
-    if (value.equals("true")) {
-      return true;
-    } else if (value.equals("false")) {
-      return false;
+    String value = consumeRawAttribute(name);
+    return attributeParsers.getBooleanParser().parse(value, logger);
+  }
+
+  /**
+   * Consumes the named attribute as a boolean expression. This will not accept
+   * {field.reference} expressions. It is intended for values that must be
+   * resolved at compile time, such as generated annotation values.
+   * 
+   * @return {@link Boolean#TRUE}, {@link Boolean#FALSE}, or null if no such
+   *         attribute
+   * 
+   * @throws UnableToCompleteException on unparseable value
+   */
+  public Boolean consumeBooleanConstantAttribute(String name)
+      throws UnableToCompleteException {
+    String value = consumeRawAttribute(name);
+    if ("".equals(value)) {
+      return null;
     }
-    writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
-        + "as a boolean value", attr, this));
-    return false; // unreachable line for happy compiler
+    if (value.equals("true") || value.equals("false")) {
+      return Boolean.valueOf(value);
+    }
+    logger.die("In %s, %s must be \"true\" or \"false\"", this, name);
+    return null; // unreachable
   }
 
   /**
@@ -192,7 +195,7 @@
     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);
+        XMLElement childElement = provider.get((Element) childNode);
         if (interpreter.interpretElement(childElement)) {
           elements.add(childElement);
           doomed.add(childNode);
@@ -207,21 +210,20 @@
   }
 
   /**
-   * Consumes the given attribute as a double value.
+   * Consumes the given attribute as a double expression: a literal, or a field
+   * reference. At the moment field references are validated for syntax
+   * only--there is no check that there is any such field or methods, nor
+   * that they have the correct return type.
    * 
-   * @param attr the attribute's full name (including prefix)
-   * @return the attribute's value as a double
-   * @throws UnableToCompleteException
+   * @return a double literal, an expression that will evaluate to a double
+   *         value in the generated code, or "" if there is no such attribute
+   * 
+   * @throws UnableToCompleteException on unparseable value
    */
-  public double consumeDoubleAttribute(String attr)
+  public String consumeDoubleAttribute(String name)
       throws UnableToCompleteException {
-    try {
-      return Double.parseDouble(consumeAttribute(attr));
-    } catch (NumberFormatException e) {
-      writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
-          + "as a double value", attr, this));
-      return 0; // unreachable line for happy compiler
-    }
+    String value = consumeRawAttribute(name);
+    return attributeParsers.getDoubleParser().parse(value, logger);
   }
 
   /**
@@ -234,7 +236,7 @@
    */
   public <T extends Enum<T>> T consumeEnumAttribute(String attr, Class<T> type)
       throws UnableToCompleteException {
-    String strValue = consumeAttribute(attr);
+    String strValue = consumeRawAttribute(attr);
 
     // Get the enum value. Enum.valueOf() throws IAE if the specified string is
     // not valid.
@@ -248,7 +250,7 @@
     }
 
     if (value == null) {
-      writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+      logger.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
           + "as a %s enum", attr, this, type.getSimpleName()));
     }
     return value;
@@ -276,7 +278,7 @@
       throw new NullPointerException("interpreter must not be null");
     }
     StringBuffer buf = new StringBuffer();
-    GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, writer);
+    GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider);
 
     clearChildren(elem);
     return buf.toString().trim();
@@ -323,14 +325,14 @@
     StringBuffer buf = new StringBuffer();
 
     GetEscapedInnerTextVisitor.getEscapedInnerText(elem, buf, interpreter,
-        writer);
+        provider);
 
     // 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);
+        logger.die("Text value of \"%s\" has illegal child \"%s\"", this, child);
       }
     }
 
@@ -339,23 +341,6 @@
   }
 
   /**
-   * Consumes the given attribute as an int value.
-   * 
-   * @param attr the attribute's full name (including prefix)
-   * @return the attribute's value as an int
-   * @throws UnableToCompleteException
-   */
-  public int consumeIntAttribute(String attr) throws UnableToCompleteException {
-    try {
-      return Integer.parseInt(consumeAttribute(attr));
-    } catch (NumberFormatException e) {
-      writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
-          + "as an int value", attr, this));
-      return 0; // unreachable line for happy compiler
-    }
-  }
-
-  /**
    * Consumes all attributes, and returns a string representing the entire
    * opening tag. E.g., "<div able='baker'>"
    */
@@ -363,19 +348,48 @@
     String rtn = getOpeningTag();
 
     for (int i = getAttributeCount() - 1; i >= 0; i--) {
-      getAttribute(i).consumeValue();
+      getAttribute(i).consumeRawValue();
     }
     return rtn;
   }
 
   /**
+   * Consumes the given attribute and returns its trimmed value, or null if it
+   * was unset. The returned string is not escaped.
+   * 
+   * @param name the attribute's full name (including prefix)
+   * @return the attribute's value, or ""
+   */
+  public String consumeRawAttribute(String name) {
+    String value = elem.getAttribute(name);
+    elem.removeAttribute(name);
+    return value.trim();
+  }
+
+  /**
+   * Consumes the given attribute and returns its trimmed value, or the given
+   * default value if it was unset. The returned string is not escaped.
+   * 
+   * @param name the attribute's full name (including prefix)
+   * @param defaultValue the value to return if the attribute was unset
+   * @return the attribute's value, or defaultValue
+   */
+  public String consumeRawAttribute(String name, String defaultValue) {
+    String value = consumeRawAttribute(name);
+    if ("".equals(value)) {
+      return defaultValue;
+    }
+    return value;
+  }
+
+  /**
    * Consumes the named attribute, or dies if it is missing.
    */
   public String consumeRequiredAttribute(String name)
       throws UnableToCompleteException {
-    String value = consumeAttribute(name);
+    String value = consumeRawAttribute(name);
     if ("".equals(value)) {
-      writer.die("In %s, missing required attribute name \"%s\"", this, name);
+      logger.die("In %s, missing required attribute name \"%s\"", this, name);
     }
     return value;
   }
@@ -391,15 +405,15 @@
     XMLElement ret = null;
     for (XMLElement child : consumeChildElements()) {
       if (ret != null) {
-        writer.die("%s may only contain a single child element, but found "
+        logger.die("%s may only contain a single child element, but found "
             + "%s and %s.", this, ret, child);
       }
 
       ret = child;
     }
-    
+
     if (ret == null) {
-      writer.die("%s must have a single child element", this);
+      logger.die("%s must have a single child element", this);
     }
 
     return ret;
@@ -420,8 +434,9 @@
     if (children.getLength() < 1) {
       return "";
     }
-    if (children.getLength() > 1 || Node.TEXT_NODE != children.item(0).getNodeType()) {
-      writer.die("%s must contain only text", this);
+    if (children.getLength() > 1
+        || Node.TEXT_NODE != children.item(0).getNodeType()) {
+      logger.die("%s must contain only text", this);
     }
     Text t = (Text) children.item(0);
     return t.getTextContent();
@@ -478,7 +493,7 @@
     if (parent == null || Node.ELEMENT_NODE != parent.getNodeType()) {
       return null;
     }
-    return new XMLElement((Element) parent, writer);
+    return provider.get((Element) parent);
   }
 
   public String getPrefix() {
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java b/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java
new file mode 100644
index 0000000..7e47451
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java
@@ -0,0 +1,22 @@
+/*
+ * 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.w3c.dom.Element;
+
+interface XMLElementProvider {
+  XMLElement get(Element e);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElementProviderImpl.java b/user/src/com/google/gwt/uibinder/rebind/XMLElementProviderImpl.java
new file mode 100644
index 0000000..a3576f6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElementProviderImpl.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.rebind;
+
+import org.w3c.dom.Element;
+
+@SuppressWarnings("deprecation")
+//BundleAttributeParser not quite ready to die
+class XMLElementProviderImpl implements XMLElementProvider {
+  private final AttributeParsers attributeParsers;
+  private final BundleAttributeParsers bundleParsers; 
+  private final MortalLogger logger;
+  
+  public XMLElementProviderImpl(AttributeParsers attributeParsers,
+    BundleAttributeParsers bundleParsers, MortalLogger logger) {
+    this.attributeParsers = attributeParsers;
+    this.bundleParsers = bundleParsers;
+    this.logger = logger;
+  }
+  
+  public XMLElement get(Element e) {
+    return new XMLElement(e, attributeParsers, bundleParsers, logger, this);
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
index d99ff50..f9f4248 100644
--- a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
@@ -114,10 +114,8 @@
         logger.die("%s has no attribute matching %s", elem, child);
       }
 
-      String defaultMessage =
-          MessageWriter.escapeMessageFormat(elem.consumeAttribute(attributeName));
-      defaultMessage =
-          UiBinderWriter.escapeTextForJavaStringLiteral(defaultMessage);
+      String defaultMessage = MessageWriter.escapeMessageFormat(elem.consumeRawAttribute(attributeName));
+      defaultMessage = UiBinderWriter.escapeTextForJavaStringLiteral(defaultMessage);
       attributeMessages.add(new AttributeMessage(attributeName, declareMessage(
           child, defaultMessage)));
     }
@@ -130,7 +128,7 @@
    */
   public String consumeMessageAttribute(String attName, XMLElement elem) {
     String fullAttName = getMessagesPrefix() + ":" + attName;
-    return elem.consumeAttribute(fullAttName);
+    return elem.consumeRawAttribute(fullAttName);
   }
 
   /**
@@ -262,12 +260,12 @@
    */
   String consumeMessageElementAttribute(String attName, XMLElement elem) {
     if (elem.hasAttribute(attName)) {
-      return UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute(attName));
+      return UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeRawAttribute(attName));
     }
 
     String fullAttName = getMessagesPrefix() + ":" + attName;
     if (elem.hasAttribute(fullAttName)) {
-      String value = elem.consumeAttribute(fullAttName);
+      String value = elem.consumeRawAttribute(fullAttName);
       logger.warn(
           "In %s, deprecated prefix \"%s:\" on \"%s\". Use \"%s\" instead.",
           elem, getMessagesPrefix(), fullAttName, attName);
diff --git a/user/test/com/google/gwt/uibinder/All.java b/user/test/com/google/gwt/uibinder/All.java
new file mode 100644
index 0000000..8ad61c4
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/All.java
@@ -0,0 +1,23 @@
+package com.google.gwt.uibinder;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Convenience suite for running both UiBinder suites from an IDE. Its name
+ * does not end in Suite to keep ant from running it redundantly.
+ */
+public class All {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("All UiBinder tests");
+
+    suite.addTest(UiBinderJreSuite.suite());
+    suite.addTest(UiBinderGwtSuite.suite());
+
+    return suite;
+  }
+
+  private All() {
+  }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
index 1d0fff2..6bd28ef 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
@@ -25,13 +25,14 @@
  */
 public class UiBinderGwtSuite {
   public static Test suite() {
-    GWTTestSuite suite = new GWTTestSuite("Test suite for UiBinder GWTTestCases");
-    
+    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/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index 428590b..a59d7e2 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -16,7 +16,7 @@
 package com.google.gwt.uibinder;
 
 import com.google.gwt.uibinder.parsers.FieldReferenceConverterTest;
-import com.google.gwt.uibinder.parsers.SimpleAttributeParserTest;
+import com.google.gwt.uibinder.parsers.IntAttributeParserTest;
 import com.google.gwt.uibinder.parsers.StrictAttributeParserTest;
 import com.google.gwt.uibinder.parsers.StringAttributeParserTest;
 import com.google.gwt.uibinder.rebind.GwtResourceEntityResolverTest;
@@ -50,7 +50,7 @@
 
     // parsers
     suite.addTestSuite(FieldReferenceConverterTest.class);
-    suite.addTestSuite(SimpleAttributeParserTest.class);
+    suite.addTestSuite(IntAttributeParserTest.class);
     suite.addTestSuite(StrictAttributeParserTest.class);
     suite.addTestSuite(StringAttributeParserTest.class);
 
diff --git a/user/test/com/google/gwt/uibinder/parsers/IntAttributeParserTest.java b/user/test/com/google/gwt/uibinder/parsers/IntAttributeParserTest.java
new file mode 100644
index 0000000..f4f8d5c
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/parsers/IntAttributeParserTest.java
@@ -0,0 +1,36 @@
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.DummyMortalLogger;
+
+import junit.framework.TestCase;
+
+public class IntAttributeParserTest extends TestCase {
+  private IntAttributeParser parser;
+  private DummyMortalLogger logger;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    parser = new IntAttributeParser();
+    logger = new DummyMortalLogger();
+  }
+
+  public void testGood() throws UnableToCompleteException {
+    assertEquals("1234", parser.parse("1234", logger));
+    assertEquals("-4321", parser.parse("-4321", logger));
+  }
+
+  public void testBad() {
+    try {
+      parser.parse("fnord", logger);
+      fail("Expected UnableToCompleteException");
+    } catch (UnableToCompleteException e) {
+      /* pass */
+    }
+  }
+
+  public void testFieldRef() throws UnableToCompleteException {
+    assertEquals("foo.bar().baz()", parser.parse("{foo.bar.baz}", logger));
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/parsers/SimpleAttributeParserTest.java b/user/test/com/google/gwt/uibinder/parsers/SimpleAttributeParserTest.java
deleted file mode 100644
index acd91e1..0000000
--- a/user/test/com/google/gwt/uibinder/parsers/SimpleAttributeParserTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.uibinder.parsers.FieldReferenceConverter.IllegalFieldReferenceException;
-import com.google.gwt.uibinder.parsers.SimpleAttributeParser.FieldReferenceDelegate;
-
-import junit.framework.TestCase;
-
-/**
- * Tests SimpleAttributeParser. Actually, tests its static inner class which
- * does all of the actual work, so that we don't have to struggle to mock
- * UiBinderWriter.
- */
-public class SimpleAttributeParserTest extends TestCase {
-  FieldReferenceConverter bp = new FieldReferenceConverter(new FieldReferenceDelegate());
-
-  public void testSimple() {
-    String before = "{able.baker.charlie.prawns}";
-    String expected = "able.baker().charlie().prawns()";
-    assertEquals(expected, bp.convert(before));
-  }
-  
-  public void testNone() {
-    String before = "able.baker.charlie.prawns";
-    assertEquals(before, bp.convert(before));
-  }
-
-  public void testTooManyShouldFail() {
-    String before = "{able.baker.charlie} {prawns.are.yummy}";
-    try {
-      bp.convert(before);
-      fail();
-    } catch (IllegalFieldReferenceException e) {
-      /* pass */
-    }
-  }
-  
-  public void testMixedShouldFail() {
-    String before = "{able.baker.charlie} prawns are still yummy}";
-    try {
-      bp.convert(before);
-      fail();
-    } catch (IllegalFieldReferenceException e) {
-      /* pass */
-    }
-  }
-}
diff --git a/user/test/com/google/gwt/uibinder/rebind/DummyMortalLogger.java b/user/test/com/google/gwt/uibinder/rebind/DummyMortalLogger.java
new file mode 100644
index 0000000..65a130f
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/DummyMortalLogger.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+public class DummyMortalLogger extends MortalLogger {
+  public DummyMortalLogger() {
+    super(new DummyTreeLogger());
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/DummyTreeLogger.java b/user/test/com/google/gwt/uibinder/rebind/DummyTreeLogger.java
new file mode 100644
index 0000000..061d635
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/DummyTreeLogger.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.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+class DummyTreeLogger extends TreeLogger {
+  
+  @Override
+  public DummyTreeLogger branch(Type type, String msg, Throwable caught,
+      HelpInfo helpInfo) {
+    return new DummyTreeLogger();
+  }
+
+  @Override
+  public boolean isLoggable(Type type) {
+    return false;
+  }
+
+  @Override
+  public void log(Type type, String msg, Throwable caught, HelpInfo helpInfo) {
+  }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
index bdaaaa1..28f20c9 100644
--- a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
@@ -39,31 +39,85 @@
   private Document doc;
   private Element item;
   private XMLElement elm;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-    init("<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>");
-  }
+  private XMLElementProvider elemProvider;
 
   public void testConsumeAttribute() {
-    assertEquals("attr1Value", elm.consumeAttribute("attr1"));
-    assertEquals("", elm.consumeAttribute("attr1"));
+    assertEquals("attr1Value", elm.consumeRawAttribute("attr1"));
+    assertEquals("", elm.consumeRawAttribute("attr1"));
   }
 
   public void testConsumeAttributeWithDefault() {
-    assertEquals("attr1Value", elm.consumeAttribute("attr1", "default"));
-    assertEquals("default", elm.consumeAttribute("attr1", "default"));
-    assertEquals("otherDefault", elm.consumeAttribute("unsetthing",
+    assertEquals("attr1Value", elm.consumeRawAttribute("attr1", "default"));
+    assertEquals("default", elm.consumeRawAttribute("attr1", "default"));
+    assertEquals("otherDefault", elm.consumeRawAttribute("unsetthing",
         "otherDefault"));
   }
 
-  public void testConsumeRequired() throws UnableToCompleteException {
-    assertEquals("attr1Value", elm.consumeRequiredAttribute("attr1"));
+  public void testConsumeBooleanConstant() throws ParserConfigurationException,
+      SAXException, IOException, UnableToCompleteException {
+    init("<doc><elm yes='true' no='false' "
+        + "fnord='fnord' ref='{foo.bar.baz}'/></doc>");
+
+    assertNull(elm.consumeBooleanConstantAttribute("foo"));
+
+    assertTrue(elm.consumeBooleanConstantAttribute("yes"));
+    assertNull(elm.consumeBooleanConstantAttribute("yes"));
+
+    assertFalse(elm.consumeBooleanConstantAttribute("no"));
+    assertNull(elm.consumeBooleanConstantAttribute("no"));
+
     try {
-      elm.consumeRequiredAttribute("unsetthing");
-      fail("Should have thrown UnableToCompleteException");
-    } catch (UnableToCompleteException e) {
+      elm.consumeBooleanConstantAttribute("ref");
+      fail("Should throw UnableToCompleteException on field ref");
+    } catch (UnableToCompleteException c) {
+      /* pass */
+    }
+
+    try {
+      elm.consumeBooleanConstantAttribute("fnord");
+      fail("Should throw UnableToCompleteException on misparse");
+    } catch (UnableToCompleteException c) {
+      /* pass */
+    }
+  }
+
+  public void testConsumeBoolean() throws ParserConfigurationException,
+      SAXException, IOException, UnableToCompleteException {
+    init("<doc><elm yes='true' no='false' "
+        + "fnord='fnord' ref='{foo.bar.baz}'/></doc>");
+
+    assertEquals("", elm.consumeBooleanAttribute("foo"));
+
+    assertEquals("true", elm.consumeBooleanAttribute("yes"));
+    assertEquals("", elm.consumeBooleanAttribute("yes"));
+
+    assertEquals("false", elm.consumeBooleanAttribute("no"));
+    assertEquals("", elm.consumeBooleanAttribute("no"));
+
+    assertEquals("foo.bar().baz()", elm.consumeBooleanAttribute("ref"));
+
+    try {
+      elm.consumeBooleanAttribute("fnord");
+      fail("Should throw UnableToCompleteException on misparse");
+    } catch (UnableToCompleteException c) {
+      /* pass */
+    }
+  }
+
+  public void testConsumeDouble() throws UnableToCompleteException,
+      ParserConfigurationException, SAXException, IOException {
+    init("<doc><elm minus='-123.45' plus='123.45' minus-one='-1' "
+        + "plus-one='1' fnord='fnord' ref='{foo.bar.baz}'/></doc>");
+    assertEquals("1", elm.consumeDoubleAttribute("plus-one"));
+    assertEquals("-1", elm.consumeDoubleAttribute("minus-one"));
+    assertEquals("123.45", elm.consumeDoubleAttribute("plus"));
+    assertEquals("-123.45", elm.consumeDoubleAttribute("minus"));
+    assertEquals("foo.bar().baz()", elm.consumeBooleanAttribute("ref"));
+
+    try {
+      elm.consumeBooleanAttribute("fnord");
+      fail("Should throw UnableToCompleteException on misparse");
+    } catch (UnableToCompleteException c) {
       /* pass */
     }
   }
@@ -83,6 +137,16 @@
         elm.consumeInnerTextEscapedAsHtmlStringLiteral(new NullInterpreter<String>()));
   }
 
+  public void testConsumeRequired() throws UnableToCompleteException {
+    assertEquals("attr1Value", elm.consumeRequiredAttribute("attr1"));
+    try {
+      elm.consumeRequiredAttribute("unsetthing");
+      fail("Should have thrown UnableToCompleteException");
+    } catch (UnableToCompleteException e) {
+      /* pass */
+    }
+  }
+
   public void testConsumeSingleChildElementEmpty()
       throws ParserConfigurationException, SAXException, IOException,
       UnableToCompleteException {
@@ -96,7 +160,7 @@
     init("<doc><elm><child>Hi.</child></elm></doc>");
     assertEquals("Hi.",
         elm.consumeSingleChildElement().consumeUnescapedInnerText());
-    
+
     init("<doc><elm id='elm'><child>Hi.</child><child>Ho.</child></elm></doc>");
     try {
       elm.consumeSingleChildElement();
@@ -104,19 +168,6 @@
     } catch (UnableToCompleteException e) {
       /* pass */
     }
- }
-
-  private void init(final String domString)
-      throws ParserConfigurationException, SAXException, IOException {
-    doc = DocumentTestHelp.documentForString(domString);
-    item = (Element) doc.getDocumentElement().getElementsByTagName("elm").item(
-        0);
-    elm = new XMLElement(item, new UiBinderWriter());
-  }
-
-  private void appendText(final String text) {
-    Text t = doc.createTextNode(text);
-    item.appendChild(t);
   }
 
   public void testConsumeUnescapedInnerText() throws UnableToCompleteException {
@@ -130,7 +181,7 @@
   }
 
   public void testEmptyStringOnMissingAttribute() {
-    assertEquals("", elm.consumeAttribute("fnord"));
+    assertEquals("", elm.consumeRawAttribute("fnord"));
   }
 
   public void testIterator() {
@@ -141,7 +192,7 @@
       String expected = expecteds[i];
       assertEquals(expected, attr.getLocalName());
       assertFalse(attr.isConsumed());
-      assertEquals(expected + "Value", attr.consumeValue());
+      assertEquals(expected + "Value", attr.consumeRawValue());
       assertTrue(attr.isConsumed());
       seen.add(expected);
     }
@@ -153,8 +204,30 @@
 
     Element item = (Element) doc.getDocumentElement().getElementsByTagName("br").item(
         0);
-    XMLElement elm = new XMLElement(item, null);
+    XMLElement elm = elemProvider.get(item);
     assertEquals("br", item.getTagName());
     assertEquals("", elm.getClosingTag());
   }
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    init("<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>");
+  }
+
+  private void appendText(final String text) {
+    Text t = doc.createTextNode(text);
+    item.appendChild(t);
+  }
+
+  private void init(final String domString)
+      throws ParserConfigurationException, SAXException, IOException {
+    doc = DocumentTestHelp.documentForString(domString);
+    item = (Element) doc.getDocumentElement().getElementsByTagName("elm").item(
+        0);
+
+    elemProvider = new XMLElementProviderImpl(new AttributeParsers(), null,
+        new DummyMortalLogger());
+    elm = elemProvider.get(item);
+  }
 }
diff --git a/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java b/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java
index fd4e161..68756b6 100644
--- a/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/model/OwnerClassTest.java
@@ -19,12 +19,12 @@
 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.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.MouseOverEvent;
 import com.google.gwt.uibinder.client.UiFactory;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.rebind.DummyMortalLogger;
 import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
 import com.google.gwt.uibinder.rebind.MortalLogger;
 import com.google.gwt.user.client.ui.Button;
@@ -49,7 +49,7 @@
   protected void setUp() throws Exception {
     super.setUp();
 
-    logger = new MortalLogger(new PrintWriterTreeLogger());
+    logger = new DummyMortalLogger();
     gwtTypeAdapter = new JClassTypeAdapter();
   }
 
@@ -76,6 +76,7 @@
   /**
    * Uibinder class for testing of {@link UiFactory}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class UiFactoryClass {
     @UiFactory
     Label createLabel() {
@@ -102,6 +103,7 @@
   /**
    * Uibinder class for testing bad usage of {@link UiFactory}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class BadUiFactoryClass {
     @UiFactory
     int thisShouldntWork() {
@@ -124,6 +126,7 @@
   /**
    * Uibinder class for testing bad usage of {@link UiFactory}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class DuplicateUiFactoryClass {
     @UiFactory
     Label labelFactory1() {
@@ -151,6 +154,7 @@
   /**
    * Uibinder class for testing of {@link UiField}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class UiFieldsClass {
     @UiField
     Label label1;
@@ -197,6 +201,7 @@
   /**
    * Uibinder class for testing bad usage of {@link UiField}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class BadUiFieldsClass {
     @UiField
     int thisShouldntWork;
@@ -217,6 +222,7 @@
   /**
    * Uibinder class for testing of {@link UiHandler}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class UiHandlersClass {
     @UiHandler("myField")
     void onMyFieldClicked(ClickEvent ev) {
@@ -280,6 +286,7 @@
   /**
    * Parent class for testing inheritance of owner classes.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class ParentUiBinderClass {
     @UiField
     Label label1;
@@ -298,6 +305,7 @@
   /**
    * Child class for testing inheritance of owner classes.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class ChildUiBinderClass extends ParentUiBinderClass {
     @UiField(provided = true)
     Button button1;
diff --git a/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java
index 5477c02..7746a02 100644
--- a/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java
@@ -22,8 +22,8 @@
 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.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.uibinder.rebind.DummyMortalLogger;
 import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
 import com.google.gwt.uibinder.rebind.MortalLogger;
 import com.google.gwt.user.client.ui.Label;
@@ -36,13 +36,12 @@
 public class OwnerFieldClassTest extends TestCase {
 
   private JClassTypeAdapter gwtTypeAdapter;
-  private MortalLogger logger;
+  private MortalLogger logger = new DummyMortalLogger();
 
   @Override
   protected void setUp() throws Exception {
     super.setUp();
 
-    logger = new MortalLogger(new PrintWriterTreeLogger());
     gwtTypeAdapter = new JClassTypeAdapter();
   }
 
@@ -69,6 +68,7 @@
   /**
    * Class with lots of setters for testing.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class SettersTestClass {
     // No ambiguity in these setters
     public void setBlaBla(int x) {
@@ -177,6 +177,7 @@
   /**
    * Class with overridden setters for testing.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class OverriddenSettersTestClass extends SettersTestClass {
     // Simple override of parent method
     @Override
@@ -278,6 +279,7 @@
   /**
    * Class with a {@link UiConstructor}-annotated constructor.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class UiConstructorClass {
     @UiConstructor
     public UiConstructorClass(boolean visible) {
@@ -307,6 +309,7 @@
    * Class with (disallowed) multiple constructors annotated with
    * {@link UiConstructor}.
    */
+  @SuppressWarnings("unused") // We know these methods are unused
   private static class MultiUiConstructorsClass {
     @UiConstructor
     public MultiUiConstructorsClass(boolean visible) {
diff --git a/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java
index 37a77e4..c34347f 100644
--- a/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldTest.java
@@ -18,8 +18,8 @@
 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.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.rebind.DummyMortalLogger;
 import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
 import com.google.gwt.uibinder.rebind.MortalLogger;
 import com.google.gwt.user.client.ui.Button;
@@ -40,7 +40,7 @@
   protected void setUp() throws Exception {
     super.setUp();
 
-    logger = new MortalLogger(new PrintWriterTreeLogger());
+    logger = new DummyMortalLogger();
     gwtTypeAdapter = new JClassTypeAdapter();
     ownerType = gwtTypeAdapter.adaptJavaClass(this.getClass());
   }
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
index c58c289..465ff75 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
@@ -80,6 +80,11 @@
     assertEquals("HTML", elm.getInnerHTML());
   }
 
+  public void testBraceEscaping() {
+    assertEquals("blah di blah {foo: \"bar\"} di blah", 
+        widgetUi.bracedParagraph.getAttribute("fnord"));
+  }
+  
   public void testBundle() {
     assertEquals(getCenter(), widgetUi.bundledLabel.getParent());
     assertEquals(new FakeBundle().helloText(), widgetUi.bundledLabel.getText());
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
index c819f6b..39500f2 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
@@ -122,6 +122,7 @@
   @UiField ParagraphElement simpleSpriteParagraph;
   @UiField DataResource heartCursorResource;
   @UiField CssImportScopeSample cssImportScopeSample;
+  @UiField ParagraphElement bracedParagraph;
 
   public WidgetBasedUi() {
     this.bundledLabel = new Label();
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
index ada70ec..9c3ed83 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
@@ -160,7 +160,7 @@
   </gwt:Dock>
   <gwt:Dock direction='CENTER'>
     <gwt:HTMLPanel>
-      <p><ui:msg>This is a demonstration and test bed of GWT's shiny UiBinder
+      <p ui:field='bracedParagraph' fnord='blah di blah {{foo: "bar"} di blah'><ui:msg>This is a demonstration and test bed of GWT's shiny 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">