Introduces {field.references} to UiBinder, and deprecates xmlns
uri:with hackery

Reviewed by: jgw


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6059 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
index 4b034b5..241a9c2 100644
--- a/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
@@ -22,16 +22,16 @@
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
 
 /**
- * Examines each element for child <m:attr/> elements, and replaces the
+ * Examines each element for child <ui:attr/> elements, and replaces the
  * corresponding attributes of the examinee with references to the translatable
  * messages created.
  * <p>
  * That is, when examining element foo in
  * <pre>
  *   &lt;foo bar="baz"&gt;
- *     &lt;m:attr name="baz"&gt;
+ *     &lt;ui:attr name="baz"&gt;
  *   &lt;/foo&gt;</pre>
- * cosume the m:attr element, and declare a method on the Messages interface
+ * cosume the ui:attr element, and declare a method on the Messages interface
  * with {@literal @}Default("baz")
  */
  class AttributeMessageInterpreter implements XMLElement.Interpreter<String> {
@@ -52,7 +52,7 @@
 
     /*
      * Return null because we don't want to replace the dom element with any
-     * particular string (though we may have consumed its id or gwt:field)
+     * particular string (though we may have consumed its id or ui:field)
      */
     return null;
   }
diff --git a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
index dec5021..826edcc 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
@@ -20,7 +20,6 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JParameter;
-import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 import com.google.gwt.uibinder.rebind.XMLAttribute;
@@ -28,10 +27,8 @@
 import com.google.gwt.uibinder.rebind.messages.AttributeMessage;
 import com.google.gwt.uibinder.rebind.model.OwnerFieldClass;
 
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -43,7 +40,7 @@
 
   /**
    * Generates code to initialize all bean attributes on the given element.
-   * Includes support for &lt;m:attribute /&gt; children that will apply
+   * Includes support for &lt;ui:attribute /&gt; children that will apply
    * to setters
    * @throws UnableToCompleteException
    */
@@ -144,17 +141,13 @@
 
         JParameter[] params = setter.getParameters();
 
-        AttributeParser parser =
-            writer.getAttributeParser(attribute, params);
+        AttributeParser parser = writer.getAttributeParser(attribute, params);
+        
         if (parser == null) {
-          if (params.length == 1 && JPrimitiveType.INT == params[0].getType()) {
-            writer.die("In %s, %s cannot be parsed for %s.",
-                    elem, attribute, asString(setter));
-          }
-        } else {
-          setterValues.put(propertyName, parser.parse(attribute.consumeValue(),
-              writer));
+          writer.die("In %s, unable to parse %s.", elem, attribute);
         }
+        setterValues.put(propertyName, parser.parse(attribute.consumeValue(),
+            writer));
       }
     }
 
@@ -185,12 +178,6 @@
     }
   }
 
-  private String asString(JMethod setter) {
-    String sig = String.format("%s#%s(%s)", setter.getEnclosingType().getName(),
-        setter.getName(), getParamTypes(setter));
-    return sig;
-  }
-
   /**
    * Fetch the localized attributes that were stored by the
    * AttributeMessageParser.
@@ -211,18 +198,6 @@
     return localizedValues;
   }
 
-  private StringBuilder getParamTypes(JMethod setter) {
-    StringBuilder args = new StringBuilder();
-    for (Iterator<JParameter> i =
-        Arrays.asList(setter.getParameters()).iterator(); i.hasNext();) {
-      args.append(i.next().getType().getSimpleSourceName());
-      if (i.hasNext()) {
-        args.append(", ");
-      }
-    }
-    return args;
-  }
-
   private String initialCap(String propertyName) {
     return propertyName.substring(0, 1).toUpperCase() +
     propertyName.substring(1);
diff --git a/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
index 3c48775..6179dfa 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.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
@@ -15,15 +15,17 @@
  */
 package com.google.gwt.uibinder.parsers;
 
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 
 /**
  * Parses a single boolean attribute.
  */
-public class BooleanAttributeParser implements AttributeParser {
+public class BooleanAttributeParser extends SimpleAttributeParser {
 
-  public String parse(String value, UiBinderWriter writer) {
+  public String parse(String value, UiBinderWriter writer)
+      throws UnableToCompleteException {
     // TODO(jgw): check validity.
-    return value;
+    return super.parse(value, writer);
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
index 2dce510..37a2d92 100644
--- a/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
@@ -21,7 +21,9 @@
 /**
  * Interprets an attribute's contents as a method call on a resource class (one
  * tied to an xmnls prefix via a "with://" url).
+ * @deprecated soon to die, replaced by brace expressions
  */
+@Deprecated
 public class BundleAttributeParser implements AttributeParser {
 
   private final JClassType bundleClass;
diff --git a/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
index f9ebf80..3ce52fa 100644
--- a/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.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
@@ -24,11 +24,12 @@
 import java.util.Map;
 
 /**
- * Assigns computed values to element attributes, e.g.
- * res:styleName="style.pretty" which will become something like
+ * Assigns computed values to element attributes, e.g. nasty old
+ * resources:styleName="style.pretty" and nice new
+ * styleName={resources.style.pretty}, which will become something like
  * myWidget.setStyleName(resources.style().pretty()) in the generated code.
  */
- class ComputedAttributeInterpreter implements XMLElement.Interpreter<String> {
+class ComputedAttributeInterpreter implements XMLElement.Interpreter<String> {
 
   private final UiBinderWriter writer;
 
@@ -42,8 +43,18 @@
 
     for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
       XMLAttribute att = elem.getAttribute(i);
+      AttributeParser parser = writer.getBundleAttributeParser(att);
+      
+      if (parser == null && 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,
+         * because we are only used in string contexts, for attributes in html
+         * markup. Bean parser takes care of the strongly typed cases.
+         */
+        parser = new StringAttributeParser();
+      }
 
-      AttributeParser parser = writer.getAttributeParser(att);
       if (parser != null) {
         String parsedValue = parser.parse(att.consumeValue(), writer);
         String attToken = writer.tokenForExpression(parsedValue);
diff --git a/user/src/com/google/gwt/uibinder/parsers/FieldReferenceConverter.java b/user/src/com/google/gwt/uibinder/parsers/FieldReferenceConverter.java
new file mode 100644
index 0000000..90dce2b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/FieldReferenceConverter.java
@@ -0,0 +1,165 @@
+/*
+ * 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 java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Deals with field references, e.g. the bits in braces here: <code>&lt;div
+ * class="{style.enabled} fancy {style.impressive}" /></code>, by converting
+ * them to java expressions (with the help of a {@link #Delegate}).
+ * <p>
+ * A field reference is one or more segments separated by dots. The first
+ * segment is considered to be a reference to a ui field, and succeeding
+ * segments are method calls. So, <code>"{able.baker.charlie}"</code> becomes
+ * <code>"able.baker().charlie()"</code>.
+ * <p>
+ * A field reference starts with '{' and is followed immediately by a character
+ * that can legally start a java identifier&mdash;that is a letter, $, or
+ * underscore. Braces not followed by such a character are left in place.
+ * <p>
+ * Opening braces may be escape by doubling them. That is, "{{foo}" will
+ * converted to "{foo}", with no field reference detected.
+ */
+public class FieldReferenceConverter {
+  
+  /**
+   * May be thrown by the {@link #Delegate} for badly formatted input.
+   */
+  @SuppressWarnings("serial")
+  public static class IllegalFieldReferenceException extends RuntimeException {    
+  }
+  
+  /**
+   * Responsible for the bits around and between the field references.
+   * May throw IllegalFieldReferenceException as it sees fit.
+   */
+  public interface Delegate {
+    /**
+     * Called for fragment around and between field references.
+     * <p>
+     * Note that it will be called with empty strings if these surrounding bits
+     * are empty. E.g., "{style.enabled} fancy {style.impressive}" would call
+     * this method three times, with "", " fancy ", and "".
+     * <p>
+     * A string with no field references is treated as a single fragment, and
+     * causes a single call to this method.
+     */
+    String handleFragment(String fragment)
+        throws IllegalFieldReferenceException;
+
+    /**
+     * Called for each expanded field reference, to allow it to be stitched
+     * together with surrounding fragments.
+     */
+    String handleReference(String reference)
+        throws IllegalFieldReferenceException;
+  }
+
+  /**
+   * Used by {@link #hasFieldReferences}. Passthrough implementation that notes
+   * when handleReference has been called.
+   */
+  private static final class Telltale implements
+      FieldReferenceConverter.Delegate {
+    boolean hasComputed = false;
+
+    public String handleFragment(String fragment) {
+      return fragment;
+    }
+
+    public String handleReference(String reference) {
+      hasComputed = true;
+      return reference;
+    }
+
+    public boolean hasComputed() {
+      return hasComputed;
+    }
+  }
+
+  private static final Pattern BRACES = Pattern.compile("[{]([^}]*)[}]");
+
+  private static final Pattern LEGAL_FIRST_CHAR = Pattern.compile("^[$_a-zA-Z].*");
+
+  /**
+   * @return true if the given string holds one or more field references
+   */
+  public static boolean hasFieldReferences(String string) {
+    Telltale telltale = new Telltale();
+    new FieldReferenceConverter(telltale).convert(string);
+    return telltale.hasComputed();
+  }
+
+  private final Delegate delegate;
+
+  public FieldReferenceConverter(Delegate delegate) {
+    this.delegate = delegate;
+  }
+
+  /**
+   * @throws IllegalFieldReferenceException if the delegate does
+   */
+  public String convert(String in) {
+    StringBuilder b = new StringBuilder();
+    int nextFindStart = 0;
+    int lastMatchEnd = 0;
+
+    Matcher m = BRACES.matcher(in);
+    while (m.find(nextFindStart)) {
+      String fieldReference = m.group(1);
+      if (!legalFirstCharacter(fieldReference)) {
+        nextFindStart = m.start() + 2;
+        continue;
+      }
+
+      String precedingFragment = in.substring(lastMatchEnd, m.start());
+      precedingFragment = handleFragment(precedingFragment);
+      b.append(precedingFragment);
+
+      fieldReference = expandDots(fieldReference);
+      b.append(delegate.handleReference(fieldReference));
+      nextFindStart = lastMatchEnd = m.end();
+    }
+
+    b.append(handleFragment(in.substring(lastMatchEnd)));
+    return b.toString();
+  }
+
+  private String expandDots(String value) {
+    StringBuilder b = new StringBuilder();
+    String[] segments = value.split("[.]");
+
+    for (String segment : segments) {
+      if (b.length() == 0) {
+        b.append(segment); // field name
+      } else {
+        b.append(".").append(segment).append("()");
+      }
+    }
+    return b.toString();
+  }
+
+  private String handleFragment(String fragment) {
+    fragment = fragment.replace("{{", "{");
+    return delegate.handleFragment(fragment);
+  }
+
+  private boolean legalFirstCharacter(String fieldReference) {
+    return LEGAL_FIRST_CHAR.matcher(fieldReference).matches();
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
index 230abfc..6352e9d 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
@@ -39,7 +39,7 @@
   }
 
   public String parse(String value, UiBinderWriter writer)
-  throws UnableToCompleteException {
+      throws UnableToCompleteException {
     String translated = values.get(value);
     if (translated == null) {
       writer.die("Invalid value: horizontalAlignment='" + value + "'");
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java
index 18e1d6c..dc709b8 100644
--- a/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.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
@@ -25,10 +25,10 @@
  * by a custom parser when calling {@link XMLElement#consumeInnerHtml}
  * <ul>
  * <li>Assigns computed values to element attributes (e.g.
- * res:class="style.pretty")
+ * class="{style.pretty}")
  * <li>Generates fields to hold named dom elements (e.g. &lt;div
  * gwt:field="importantDiv"&gt;)
- * <li>Turns &lt;m:msg&gt; and &lt;m:attr&gt; elements into methods on a
+ * <li>Turns &lt;ui:msg&gt; and &lt;ui:attr&gt; elements into methods on a
  * generated Messages interface
  * </ul>
  */
@@ -40,7 +40,7 @@
    * {@link com.google.gwt.user.client.ui.UIObject} (or really, any object that
    * responds to <code>getElement()</code>). Uses an instance of
    * {@link HtmlMessageInterpreter} to process message elements.
-   *
+   * 
    * @param uiExpression An expression that can be evaluated at runtime to find
    *          an object whose getElement() method can be called to get an
    *          ancestor of all Elements generated from the interpreted HTML.
@@ -57,13 +57,13 @@
   /**
    * Rather than using this constructor, you probably want to use the
    * {@link #newInterpreterForUiObject} factory method.
-   *
+   * 
    * @param ancestorExpression An expression that can be evaluated at runtime to
    *          find an Element that will be an ancestor of all Elements generated
    *          from the interpreted HTML.
-   * @param messageInterpreter an interpreter to handle msg and ph elements, typically an instance
-   *          of {@link HtmlMessageInterpreter}. This interpreter gets last
-   *          crack
+   * @param messageInterpreter an interpreter to handle msg and ph elements,
+   *          typically an instance of {@link HtmlMessageInterpreter}. This
+   *          interpreter gets last crack
    */
   public HtmlInterpreter(UiBinderWriter writer, String ancestorExpression,
       Interpreter<String> messageInterpreter) {
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntParser.java b/user/src/com/google/gwt/uibinder/parsers/IntParser.java
index 11a85ee..924b1da 100644
--- a/user/src/com/google/gwt/uibinder/parsers/IntParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/IntParser.java
@@ -15,15 +15,17 @@
  */
 package com.google.gwt.uibinder.parsers;
 
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.uibinder.rebind.UiBinderWriter;
 
 /**
  * Parses an integer value.
  */
-public class IntParser implements AttributeParser {
+public class IntParser extends SimpleAttributeParser {
 
-  public String parse(String value, UiBinderWriter writer) {
+  public String parse(String value, UiBinderWriter writer)
+      throws UnableToCompleteException {
     // TODO(jgw): validate
-    return value;
+    return super.parse(value, writer);
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/parsers/SimpleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/SimpleAttributeParser.java
new file mode 100644
index 0000000..4ba503e
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/SimpleAttributeParser.java
@@ -0,0 +1,77 @@
+/*
+ * 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/StrictAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/StrictAttributeParser.java
new file mode 100644
index 0000000..4285c8a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StrictAttributeParser.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * Fall through attribute parser. Accepts a field reference or nothing.
+ */
+public class StrictAttributeParser implements AttributeParser {
+
+  /** 
+   * Package protected for testing
+   */
+  static class FieldReferenceDelegate implements Delegate {
+    boolean sawReference = false;
+    
+    public String handleFragment(String fragment)
+        throws IllegalFieldReferenceException {
+      if (fragment.length() > 0) {
+        throw new IllegalFieldReferenceException();
+      }
+      return fragment;
+    }
+
+    public String handleReference(String reference)
+        throws IllegalFieldReferenceException {
+      assertOnly();
+      sawReference = true;
+      return reference;
+    }
+
+    private void assertOnly() {
+      if (sawReference) {
+        throw new IllegalFieldReferenceException();
+      }
+    }
+  }
+
+  /**
+   * If the value holds a single field reference "{like.this}", converts it to a
+   * Java Expression.
+   * <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/StringAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
index e289991..4261ec6 100644
--- a/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.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
@@ -21,8 +21,22 @@
  * Parses a string attribute.
  */
 public class StringAttributeParser implements AttributeParser {
+  /* package private for testing */
+  static class FieldReferenceDelegate implements FieldReferenceConverter.Delegate {
+    public String handleFragment(String literal) {
+      return "\"" + UiBinderWriter.escapeTextForJavaStringLiteral(literal)
+          + "\"";
+    }
+
+    public String handleReference(String reference) {
+      return String.format(" + %s + ", reference);
+    }
+  }
+
+  final FieldReferenceConverter braceReplacor = new FieldReferenceConverter(
+      new FieldReferenceDelegate());
 
   public String parse(String value, UiBinderWriter writer) {
-    return "\"" + UiBinderWriter.escapeTextForJavaStringLiteral(value) + "\"";
+    return braceReplacor.convert(value);
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
index 28172e9..d467c4e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -28,8 +28,8 @@
  * <p>
  * A field can have a custom initialization statement, set via
  * {@link #setInitializer}. Without one it will be initialized via a
- * {@link GWT#create} call. (In the rare case that you need a field not to be
- * initialized, initialize it to "null".)
+ * {@link com.google.gwt.core.client.GWT#create} call. (In the rare case that
+ * you need a field not to be initialized, initialize it to "null".)
  * <p>
  * Dependencies can be declared between fields via {@link #needs}, to ensure
  * that one can be initialized via reference to another. Circular references are
@@ -77,11 +77,14 @@
 
   /**
    * Used to provide an initializer string to use instead of a
-   * {@link com.google.gwt.core.client.GWT#create()} call.
-   * Note that this is an RHS expression. Don't include the leading '=', and
-   * don't end it with ';'.
+   * {@link com.google.gwt.core.client.GWT#create()} call. Note that this is an
+   * RHS expression. Don't include the leading '=', and don't end it with ';'.
+   * 
+   * @throws IllegalStateException on second attempt to set the initializer
    */
   public void setInitializer(String initializer) {
+    // TODO(rjrjr) Should be able to make this a constructor argument
+    // when BundleAttributeParser dies
     if (this.initializer != null) {
       throw new IllegalStateException(String.format(
           "Second attempt to set initializer for field \"%s\", "
@@ -91,8 +94,25 @@
   }
 
   /**
+   * @deprecated needed only by
+   *             {@link com.google.gwt.uibinder.parsers.BundleAttributeParser},
+   *             which will die soon
+   * @throws IllegalStateException if initializer in a later call doesn't match
+   *           earlier call
+   */
+  @Deprecated
+  public void setInitializerMaybe(String initializer) {
+    if (this.initializer != null && !this.initializer.equals(initializer)) {
+      throw new IllegalStateException(String.format(
+          "Attempt to change initializer for field \"%s\", "
+              + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
+    }
+    this.initializer = initializer;
+  }
+
+  /**
    * Write the field delcaration.
-   *
+   * 
    * @return false if unable to write for lack of a default constructor
    */
   // TODO(rjrjr) This return code thing is silly. We should
@@ -113,11 +133,12 @@
     }
 
     if (initializer == null) {
-      if (type.findConstructor(new JType[0]) == null) {
+      if ((type.isInterface() == null)
+          && (type.findConstructor(new JType[0]) == null)) {
         return false;
       }
-      initializer =
-          String.format("(%1$s) GWT.create(%1$s.class)", getFullTypeName());
+      initializer = String.format("(%1$s) GWT.create(%1$s.class)",
+          getFullTypeName());
     }
 
     w.write("%s %s = %s;", getFullTypeName(), name, initializer);
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 723db3e..d53a53e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.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
@@ -29,6 +29,7 @@
 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.OwnerClass;
 import com.google.gwt.uibinder.rebind.model.OwnerField;
@@ -47,10 +48,9 @@
 
 /**
  * Writer for UiBinder generated classes.
- *
+ * 
  * TODO(rdamazio): Refactor this, extract model classes, improve ordering
- *                 guarantees, etc.
- * TODO(rjrjr): Improve error messages
+ * guarantees, etc. TODO(rjrjr): Improve error messages
  */
 public class UiBinderWriter {
   private static final String GWT_URI = "urn:ui:com.google.gwt.uibinder";
@@ -104,6 +104,7 @@
 
     return escapeTextForJavaStringLiteral(text);
   }
+
   /**
    * Escape characters that would mess up interpretation of this string as a
    * string literal in generated code (that is, protect \n and " ).
@@ -118,11 +119,12 @@
   private static String capitalizePropName(String propName) {
     return propName.substring(0, 1).toUpperCase() + propName.substring(1);
   }
+
   private static AttributeParser getAttributeParserByClassName(
       String parserClassName) {
     try {
-      Class<? extends AttributeParser> parserClass =
-          Class.forName(parserClassName).asSubclass(AttributeParser.class);
+      Class<? extends AttributeParser> parserClass = Class.forName(
+          parserClassName).asSubclass(AttributeParser.class);
       return parserClass.newInstance();
     } catch (ClassNotFoundException e) {
       throw new RuntimeException("Unable to instantiate parser", e);
@@ -135,10 +137,11 @@
       throw new RuntimeException("Unable to instantiate parser", e);
     }
   }
+
   /**
    * Returns a list of the given type and all its superclasses and implemented
    * interfaces in a breadth-first traversal.
-   *
+   * 
    * @param type the base type
    * @return a breadth-first collection of its type hierarchy
    */
@@ -174,27 +177,25 @@
     b.append(type.getName());
     return b.toString();
   }
+
   /**
    * Class names of parsers for values of attributes with no namespace prefix,
    * keyed by method parameter signatures.
-   *
+   * 
    * TODO(rjrjr) Seems like the attribute parsers belong in BeanParser, which is
    * the only thing that uses them.
    */
-  private final Map<String, String> attributeParsers =
-      new HashMap<String, String>();
+  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>();
+  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 Map<String, BundleAttributeParser> bundleParsers = new HashMap<String, BundleAttributeParser>();
 
   private final List<String> initStatements = new ArrayList<String>();
 
@@ -257,9 +258,6 @@
     initStatements.add(String.format(format, params));
   }
 
-  // TODO(rjrjr) These declare* methods should be returning FieldWriter
-  // instances, not strings.
-
   /**
    * Adds a statement to the block run after fields are declared.
    */
@@ -284,14 +282,14 @@
    * generate a unique dom id at runtime. Further code will be generated to be
    * run after widgets are instantiated, to use that dom id in a getElementById
    * call and assign the Element instance to its field.
-   *
+   * 
    * @param fieldName The name of the field being declared
    * @param parentElementExpression an expression to be evaluated at runtime,
    *          which will return an Element that is an ancestor of this one
    *          (needed by the getElementById call mentioned above).
    */
-  public String declareDomField(String fieldName,
-      String parentElementExpression) throws UnableToCompleteException {
+  public String declareDomField(String fieldName, String parentElementExpression)
+      throws UnableToCompleteException {
     String name = declareDomIdHolder();
     setFieldInitializer(fieldName, "null");
     addInitStatement(
@@ -304,15 +302,14 @@
   /**
    * Declare a variable that will be filled at runtime with a unique id, safe
    * for use as a dom element's id attribute.
-   *
+   * 
    * @return that variable's name.
    */
   public String declareDomIdHolder() throws UnableToCompleteException {
     String domHolderName = "domId" + domId++;
-    FieldWriter domField = fieldManager.registerField(
-        "java.lang.String", domHolderName);
-    domField.setInitializer(
-        "com.google.gwt.dom.client.Document.get().createUniqueId()");
+    FieldWriter domField = fieldManager.registerField("java.lang.String",
+        domHolderName);
+    domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
     return domHolderName;
   }
 
@@ -335,7 +332,7 @@
    * If this element has a gwt:field attribute, create a field for it of the
    * appropriate type, and return the field name. If no gwt:field attribute is
    * found, do nothing and return null
-   *
+   * 
    * @return The new field name, or null if no field is created
    */
   public String declareFieldIfNeeded(XMLElement elem)
@@ -381,7 +378,7 @@
   /**
    * Finds the JClassType that corresponds to this XMLElement, which must be a
    * Widget or an Element.
-   *
+   * 
    * @throws UnableToCompleteException If no such widget class exists
    * @throws RuntimeException if asked to handle a non-widget, non-DOM element
    */
@@ -404,7 +401,7 @@
     if (pkg != null) {
       rtn = pkg.findType(tagName);
       if (rtn == null) {
-        die("No class matching \"%s\" in %s",tagName, ns);
+        die("No class matching \"%s\" in %s", tagName, ns);
       }
     }
 
@@ -450,13 +447,38 @@
       return getAttributeParserByClassName(parserClassName);
     }
 
+    if (params.length == 1) {
+      return new StrictAttributeParser();
+    }
+    
     return null;
   }
 
   /**
-   * Finds an attribute parser for the given xml attribute.
+   * Find and return an appropriate attribute parser for an attribute and set of
+   * parameters, or return null.
+   * <p>
+   * If params is of size one, a parser of some kind is guaranteed to be returned.
    */
-  public AttributeParser getAttributeParser(XMLAttribute attribute)
+  public AttributeParser getAttributeParser(XMLAttribute attribute,
+      JParameter... params) throws UnableToCompleteException {
+    AttributeParser parser = getBundleAttributeParser(attribute);
+    if (parser == null) {
+      parser = getAttributeParser(params);
+    }
+    return parser;
+  }
+
+  /**
+   * Finds an attribute {@link BundleAttributeParser} for the given xml
+   * attribute, if any, based on its namespace uri.
+   * 
+   * @return the parser or null
+   * @deprecated exists only to support {@link BundleAttributeParser}, which
+   *             will be leaving us soon.
+   */
+  @Deprecated
+  public BundleAttributeParser getBundleAttributeParser(XMLAttribute attribute)
       throws UnableToCompleteException {
     if (attribute.getNamespaceUri() == null) {
       return null;
@@ -467,8 +489,7 @@
       return null;
     }
 
-    String bundleClassName =
-        attributePrefixUri.substring(BUNDLE_URI_SCHEME.length());
+    String bundleClassName = attributePrefixUri.substring(BUNDLE_URI_SCHEME.length());
     BundleAttributeParser parser = bundleParsers.get(bundleClassName);
     if (parser == null) {
       JClassType bundleClass = getOracle().findType(bundleClassName);
@@ -476,26 +497,12 @@
         die("No such bundle class: " + bundleClassName);
       }
       parser = createBundleParser(bundleClass, attribute);
-
       bundleParsers.put(bundleClassName, parser);
     }
 
     return parser;
   }
 
-  /**
-   * Find and return an appropriate attribute parser for an attribute and set of
-   * parameters, or return null.
-   */
-  public AttributeParser getAttributeParser(XMLAttribute attribute,
-      JParameter... params) throws UnableToCompleteException {
-    AttributeParser parser = getAttributeParser(attribute);
-    if (parser == null) {
-      parser = getAttributeParser(params);
-    }
-    return parser;
-  }
-
   public String getGwtFieldAttributeName() {
     return gwtPrefix + ":field";
   }
@@ -527,7 +534,7 @@
    * Parses the widget associated with the specified element, and returns the
    * name of the field (possibly private) that will hold the object it
    * describes.
-   *
+   * 
    * @param elem the xml element to be parsed
    * @return the name of the field containing the parsed widget
    */
@@ -560,7 +567,7 @@
   /**
    * Gives the writer the initializer to use for this field instead of the
    * default GWT.create call.
-   *
+   * 
    * @throws IllegalStateException if an initializer has already been set
    */
   public void setFieldInitializer(String fieldName, String factoryMethod) {
@@ -568,14 +575,13 @@
   }
 
   /**
-   * Instructs the writer to initialize the field with a specific
-   * contructor invocaction, instead of the default GWT.create call.
+   * Instructs the writer to initialize the field with a specific contructor
+   * invocaction, instead of the default GWT.create call.
    */
   public void setFieldInitializerAsConstructor(String fieldName,
       JClassType type, String... args) {
-    setFieldInitializer(fieldName,
-        String.format("new %s(%s)", getFullTypeName(type),
-            asCommaSeparatedList(args)));
+    setFieldInitializer(fieldName, String.format("new %s(%s)",
+        getFullTypeName(type), asCommaSeparatedList(args)));
   }
 
   /**
@@ -585,7 +591,7 @@
    * token, surrounded by plus signs. This is useful in strings to be handed to
    * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
    * other runtime expression in the string.
-   *
+   * 
    * @param expression
    */
   public String tokenForExpression(String expression) {
@@ -635,11 +641,36 @@
   }
 
   /**
-   * Creates a parser for the given bundle class.
+   * 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];
+    logger.log(TreeLogger.WARN, String.format(
+        "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 name='%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) {
@@ -647,27 +678,6 @@
           + "()", false);
     }
 
-    // Try to find any bundle instance created with UiField.
-    OwnerField field = getOwnerClass().getUiFieldForType(bundleClass);
-    if (field != null) {
-      String[] tmp = attribute.getName().split(":");
-      String templateName = tmp[0];
-      if (!templateName.equals(field.getName())) {
-         die("Template %s has no \"xmlns:%s='urn:with:%s'\" for %s.%s#%s",
-            templatePath,
-            field.getName(),
-            bundleClass.getQualifiedSourceName(),
-            uiOwnerType.getPackage().getName(),
-            uiOwnerType.getName(),
-            field.getName());
-      }
-
-      if (field.isProvided()) {
-        return new BundleAttributeParser(bundleClass,
-            "owner." + field.getName(), false);
-      }
-    }
-
     return new BundleAttributeParser(bundleClass, "my"
         + bundleClass.getName().replace('.', '_') + "Instance", true);
   }
@@ -679,6 +689,54 @@
   }
 
   /**
+   * Interprets <ui:with> elements.
+   */
+  private void createResource(XMLElement elem) throws UnableToCompleteException {
+    String resourceName = elem.consumeRequiredAttribute("name");
+    String resourceTypeName = elem.consumeRequiredAttribute("type");
+
+    JClassType resourceType = oracle.findType(resourceTypeName);
+    if (resourceType == null) {
+      die("In %s, no such type %s", elem, resourceTypeName);
+    }
+
+    if (elem.getAttributeCount() > 0) {
+      die("In %s, should only find attributes \"name\" and \"type\"");
+    }
+
+    // TODO(rjrjr) This is redundant with BeanParser, I think. Probably
+    // should refactor from here and there to FieldWriter
+
+    FieldWriter fieldWriter = fieldManager.registerField(resourceTypeName,
+        resourceName);
+    OwnerField ownerField = getOwnerClass().getUiField(resourceName);
+
+    // Perhaps it is provided via @UiField
+
+    if (ownerField != null) {
+      if (!resourceType.equals(ownerField.getType().getRawType())) {
+        die("In %s, type must match %s", ownerField);
+      }
+
+      if (ownerField.isProvided()) {
+        fieldWriter.setInitializer("owner." + ownerField.getName());
+        return;
+      }
+    }
+
+    // Nope. Maybe a @UiFactory will make it
+
+    JMethod factoryMethod = getOwnerClass().getUiFactoryMethod(resourceType);
+    if (factoryMethod != null) {
+      fieldWriter.setInitializer(String.format("owner.%s()",
+          factoryMethod.getName()));
+    }
+
+    // If neither of the above, the FieldWriter's default GWT.create call will
+    // do just fine.
+  }
+
+  /**
    * Outputs a bundle resource for a given bundle attribute parser.
    */
   private String declareStaticField(BundleAttributeParser parser) {
@@ -700,8 +758,7 @@
    * Given a DOM tag name, return the corresponding Element subclass.
    */
   private JClassType findElementTypeForTag(String tag) {
-    JClassType elementClass =
-        oracle.findType("com.google.gwt.dom.client.Element");
+    JClassType elementClass = oracle.findType("com.google.gwt.dom.client.Element");
     JClassType[] types = elementClass.getSubtypes();
     for (JClassType type : types) {
       TagName annotation = type.getAnnotation(TagName.class);
@@ -717,14 +774,29 @@
     return elementClass;
   }
 
+  private void findResources(XMLElement binderElement)
+      throws UnableToCompleteException {
+    binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
+      public Boolean interpretElement(XMLElement elem)
+          throws UnableToCompleteException {
+        if (!(GWT_URI.equals(elem.getNamespaceUri()) && "with".equals(elem.getLocalName()))) {
+          return false; // Not of interest, do not consume
+        }
+
+        createResource(elem);
+
+        return true; // Yum
+      }
+    });
+  }
+
   /**
    * Inspects this element for a gwt:field attribute. If one is found, the
    * attribute is consumed and its value returned.
-   *
+   * 
    * @return The field name declared by an element, or null if none is declared
    */
-  private String getFieldName(XMLElement elem)
-      throws UnableToCompleteException {
+  private String getFieldName(XMLElement elem) throws UnableToCompleteException {
     String fieldName = null;
     boolean hasOldSchoolId = false;
     if (elem.hasAttribute("id") && isWidget(elem)) {
@@ -755,8 +827,7 @@
   private String getParametersKey(JParameter[] params) {
     String paramTypeNames = "";
     for (int i = 0; i < params.length; ++i) {
-      paramTypeNames +=
-          params[i].getType().getParameterizedQualifiedSourceName();
+      paramTypeNames += params[i].getType().getParameterizedQualifiedSourceName();
       if (i != params.length - 1) {
         paramTypeNames += ",";
       }
@@ -778,14 +849,13 @@
     } catch (ClassNotFoundException e) {
       throw new RuntimeException("Unable to instantiate parser", e);
     } catch (ClassCastException e) {
-      throw new RuntimeException(
-          parserClassName + " must extend ElementParser");
+      throw new RuntimeException(parserClassName + " must extend ElementParser");
     }
   }
 
   /**
    * Find a set of element parsers for the given ui type.
-   *
+   * 
    * The list of parsers will be returned in order from most- to least-specific.
    */
   private Iterable<ElementParser> getParsersForClass(JClassType type) {
@@ -794,7 +864,7 @@
     /*
      * Let this non-widget parser go first (it finds <m:attribute/> elements).
      * Any other such should land here too.
-     *
+     * 
      * TODO(rjrjr) Need a scheme to associate these with a namespace uri or
      * something?
      */
@@ -827,8 +897,8 @@
   }
 
   /**
-   * Writes a field setter if the field is not provided and the field class
-   * is compatible with its respective template field.
+   * Writes a field setter if the field is not provided and the field class is
+   * compatible with its respective template field.
    */
   private void maybeWriteFieldSetter(IndentedWriter niceWriter,
       OwnerField ownerField, JClassType templateClass, String templateField)
@@ -842,8 +912,8 @@
     }
 
     if (!ownerField.isProvided()) {
-      niceWriter.write("owner.%1$s = %2$s;",
-          ownerField.getName(), templateField);
+      niceWriter.write("owner.%1$s = %2$s;", ownerField.getName(),
+          templateField);
     }
   }
 
@@ -857,18 +927,19 @@
     }
 
     XMLElement elem = new XMLElement(documentElement, this);
+    findResources(elem);
     messages.findMessagesConfig(elem);
     elem = elem.consumeSingleChildElement();
     String rootField = parseWidget(elem);
 
     StringWriter stringWriter = new StringWriter();
-    IndentedWriter niceWriter =
-        new IndentedWriter(new PrintWriter(stringWriter));
+    IndentedWriter niceWriter = new IndentedWriter(
+        new PrintWriter(stringWriter));
 
     if (rootDeclaration != null) {
-       writeBifurcatedBinder(niceWriter);
+      writeBifurcatedBinder(niceWriter);
     } else {
-       writeEagerBinder(niceWriter, rootField);
+      writeEagerBinder(niceWriter, rootField);
     }
 
     return stringWriter.toString();
@@ -876,7 +947,7 @@
 
   /**
    * Parses a package uri (i.e. package://com.google...).
-   *
+   * 
    * @throws UnableToCompleteException on bad package name
    */
   private JPackage parseNamespacePackage(String ns)
@@ -897,7 +968,8 @@
 
   private void registerParsers() {
     // TODO(rjrjr): Allow third-party parsers to register themselves
-    //              automagically, according to http://b/issue?id=1867118
+    // automagically, according to http://b/issue?id=1867118
+
     // Parses the root element in UiBinder, for non-widget templates
     addParser("com.google.gwt.dom.client.Element",
         "com.google.gwt.uibinder.parsers.DomElementParser");
@@ -929,9 +1001,8 @@
     addAttributeParser("int,int",
         "com.google.gwt.uibinder.parsers.IntPairParser");
 
-    addAttributeParser(
-        "com.google.gwt.user.client.ui.HasHorizontalAlignment."
-            + "HorizontalAlignmentConstant",
+    addAttributeParser("com.google.gwt.user.client.ui.HasHorizontalAlignment."
+        + "HorizontalAlignmentConstant",
         "com.google.gwt.uibinder.parsers.HorizontalAlignmentConstantParser");
   }
 
@@ -994,9 +1065,8 @@
 
   private void writeClassOpen(IndentedWriter w) {
     w.write("public class %s extends AbstractUiBinder<%s, %s> \n"
-        + "implements %s {",
-        implClassName, uiRootType.getName(), uiOwnerType.getName(),
-        baseClass.getName());
+        + "implements %s {", implClassName, uiRootType.getName(),
+        uiOwnerType.getName(), baseClass.getName());
     w.indent();
   }
 
@@ -1008,11 +1078,11 @@
    * creates an object with references to the generated ui's fields, and stores
    * it in a hash map keyed by the root UI object. This binding object is
    * removed from the map during the call to bindUi and used to fill the
-   * client's <code>{@literal @}UiField</code>s. I would prefer to find a
-   * way to do without the map, but am stumped.
+   * client's <code>{@literal @}UiField</code>s. I would prefer to find a way to do
+   * without the map, but am stumped.
    * <p>
    * See a sample in {@link "http://go/gwt-eager-binder"}
-   *
+   * 
    * @throws UnableToCompleteException
    */
   private void writeEagerBinder(IndentedWriter w, String rootField)
@@ -1072,7 +1142,7 @@
     w.newline();
 
     // use instance binder to make the ui, and store the binder away
-    // to wait for the call to  bindUi
+    // to wait for the call to bindUi
     w.write("%s root = instanceBinder.makeUi();", uiRootType.getName());
     w.write("rootToBindCommand.put(root, instanceBinder);");
     w.write("return root;");
@@ -1091,8 +1161,8 @@
     w.write("%s instanceBinder = rootToBindCommand.remove(root);",
         instanceBinderType());
     w.write("assert instanceBinder != null : "
-          + "\"Attempted to bind an unknown UI, or to bind the same one twice\""
-          + ";");
+        + "\"Attempted to bind an unknown UI, or to bind the same one twice\""
+        + ";");
     w.write("instanceBinder.doBind(owner);");
 
     // close bindUiMethod
@@ -1115,8 +1185,7 @@
     for (OwnerField ownerField : ownerFields) {
       FieldWriter fieldWriter = fieldManager.lookup(ownerField.getName());
 
-      BundleAttributeParser bundleParser = bundleParsers.get(
-          ownerField.getType().getRawType().getQualifiedSourceName());
+      BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType().getRawType().getQualifiedSourceName());
 
       if (bundleParser != null) {
         // ownerField is a bundle resource.
@@ -1125,17 +1194,14 @@
 
       } else if (fieldWriter != null) {
         // ownerField is a widget.
-        maybeWriteFieldSetter(niceWriter, ownerField,
-            fieldWriter.getType(), ownerField.getName());
+        maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getType(),
+            ownerField.getName());
 
       } else {
         // ownerField was not found as bundle resource or widget, must die.
-        die("Template %s has no %s attribute for %s.%s#%s",
-            templatePath,
-            getGwtFieldAttributeName(),
-            uiOwnerType.getPackage().getName(),
-            uiOwnerType.getName(),
-            ownerField.getName());
+        die("Template %s has no %s attribute for %s.%s#%s", templatePath,
+            getGwtFieldAttributeName(), uiOwnerType.getPackage().getName(),
+            uiOwnerType.getName(), ownerField.getName());
       }
     }
   }
@@ -1145,7 +1211,7 @@
    * gwt:field in the template. For those that have not had constructor
    * generation suppressed, emit GWT.create() calls instantiating them (or die
    * if they have no default constructor).
-   *
+   * 
    * @throws UnableToCompleteException on constructor problem
    */
   private void writeGwtFields(IndentedWriter niceWriter)
@@ -1161,7 +1227,7 @@
         // is fixed properly, a null fieldWriter will be an error
         // (would that be a user error or a runtime error? Not sure)
         if (fieldWriter != null) {
-          setFieldInitializer(fieldName,
+          fieldManager.lookup(fieldName).setInitializerMaybe(
               String.format("owner.%1$s", fieldName));
         }
       }
@@ -1171,8 +1237,7 @@
     fieldManager.writeGwtFieldsDeclaration(niceWriter, uiOwnerType.getName());
   }
 
-  private void writeHandlers(IndentedWriter w)
-      throws UnableToCompleteException {
+  private void writeHandlers(IndentedWriter w) throws UnableToCompleteException {
     handlerEvaluator.run(w, fieldManager, "owner");
   }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
index e14e5d9..c0a3818 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.uibinder.rebind;
 
+import com.google.gwt.uibinder.parsers.FieldReferenceConverter;
+
 import org.w3c.dom.Attr;
 
 /**
@@ -34,7 +36,7 @@
   public String consumeValue() {
     return xmlElem.consumeAttribute(w3cAttr.getName());
   }
-
+  
   public String getLocalName() {
     return w3cAttr.getLocalName();
   }
@@ -47,6 +49,10 @@
     return w3cAttr.getNamespaceURI();
   }
 
+  public boolean hasComputedValue() {
+    return FieldReferenceConverter.hasFieldReferences(w3cAttr.getValue());
+  }
+
   public boolean hasToken() {
     return Tokenator.hasToken(w3cAttr.getValue());
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index 07e4002..e1a16ab 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -40,8 +40,6 @@
  * see it.
  */
 public class XMLElement {
-  private static final Set<String> NO_END_TAG = new HashSet<String>();
-
   /**
    * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and
    * {@link #consumeChildElements(Interpreter)}.
@@ -75,6 +73,8 @@
     }
   }
 
+  private static final Set<String> NO_END_TAG = new HashSet<String>();
+
   private static void clearChildren(Element elem) {
     Node child;
     while ((child = elem.getFirstChild()) != null) {
@@ -113,9 +113,9 @@
   }
 
   /**
-   * Consumes the given attribute and returns its trimmed value, or null
-   * if it was undeclared. The returned string is not escaped.
-   *
+   * 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 null
    */
@@ -126,6 +126,22 @@
   }
 
   /**
+   * 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 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
@@ -271,13 +287,13 @@
     return interpreter.postProcess(text);
   }
 
- /**
-   * Consumes all attributes, and returns a string representing the
-   * entire opening tag. E.g., "<div able='baker'>"
+  /**
+     * Consumes all attributes, and returns a string representing the
+     * entire opening tag. E.g., "<div able='baker'>"
    */
   public String consumeOpeningTag() {
     String rtn = getOpeningTag();
-
+  
     for (int i = getAttributeCount() - 1; i >= 0; i--) {
       getAttribute(i).consumeValue();
     }
@@ -285,6 +301,18 @@
   }
 
   /**
+   * Consumes the named attribute, or dies if it is missing.
+   */
+  public String consumeRequiredAttribute(String name)
+      throws UnableToCompleteException {
+    String value = consumeAttribute(name);
+    if ("".equals(value)) {
+      writer.die("In %s, missing required attribute name\"%s\"", this);
+    }
+    return value;
+  }
+
+  /**
    * Consumes a single child element, ignoring any text nodes and throwing an
    * exception if more than one child element is found.
    */
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 31b5fee..20d4e9e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
@@ -97,7 +97,7 @@
    *
    * <pre>
    * &lt;img src="blueSky.jpg" alt="A blue sky">
-   *   &lt;m:attribute m:name="alt" description="blue sky image alt text"/>
+   *   &lt;ui:attribute name="alt" description="blue sky image alt text"/>
    * &lt;/img>
    * </pre>
    *
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
index 00381df..5250c8c 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
@@ -83,16 +83,6 @@
   }
 
   /**
-   * Returns the method annotated with @UiFactory which returns the given type.
-   *
-   * @param forType the type to look for a factory of
-   * @return the factory method, or null if none exists
-   */
-  public JMethod getUiFactoryMethod(OwnerFieldClass forType) {
-    return getUiFactoryMethod(forType.getRawType());
-  }
-
-  /**
    * Gets a field with the given name.
    * It's important to notice that a field may not exist on the owner class even
    * if it has a name in the XML and even has handlers attached to it -  such a
@@ -113,7 +103,9 @@
    *
    * @param type the type of the field
    * @return the field descriptor
+   * @deprecated This will die with {@link com.google.gwt.uibinder.parsers.BundleAttributeParser}
    */
+  @Deprecated 
   public OwnerField getUiFieldForType(JClassType type) {
     return uiFieldTypes.get(type);
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
index 73e7edd..239eb93 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
@@ -22,7 +22,7 @@
 
 /**
  * Descriptor for a field of the owner class.
- *
+ * <p>
  * Please notice that some fields defined in the XML and in the generated binder
  * class may not be present in the owner class - for instance, they may not be
  * relevant to the code of the owner class.
@@ -52,8 +52,6 @@
       throw new UnableToCompleteException();
     }
 
-    // TODO(rdamazio): For non-widget classes (resources), this will be useless
-    //                 since there are no setters, no uiconstructor, etc.
     this.fieldType = OwnerFieldClass.getFieldClass(fieldClassType);
 
     // Get the UiField annotation and process it
@@ -89,4 +87,10 @@
   public boolean isProvided() {
     return isProvided;
   }
+  
+  @Override
+  public String toString() {
+    return String.format("%s.%s#%s", fieldType.getRawType().getPackage(),
+        fieldType.getRawType().getName(), name);
+  }
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/sample/client/Foo.java b/user/src/com/google/gwt/uibinder/sample/client/ArbitraryPojo.java
similarity index 91%
rename from user/src/com/google/gwt/uibinder/sample/client/Foo.java
rename to user/src/com/google/gwt/uibinder/sample/client/ArbitraryPojo.java
index c858727..5795837 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/Foo.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/ArbitraryPojo.java
@@ -18,9 +18,9 @@
 /**
  * Used when testing non-primitive property setting in templates.
  */
-public class Foo {
+public class ArbitraryPojo {
   @Override
   public String toString() {
-    return "I am a foo!";
+    return "I am an arbitrary pojo.";
   }
 }
diff --git a/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java b/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
index 1b64c58..e96234b 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
@@ -19,11 +19,11 @@
  * Faux bundle used by test.
  */
 public class FakeBundle {
-  public Foo foo() {
-    return  new Foo();
-  }
-
   public String helloText() {
     return "hello";
   }
+
+  public ArbitraryPojo pojo() {
+    return  new ArbitraryPojo();
+  }
 }
diff --git a/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java b/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
index b859688..0978271 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
@@ -37,9 +37,9 @@
     return getLabel().getText();
   }
 
-  public void setFoo(Foo foo) {
+  public void setPojo(ArbitraryPojo pojo) {
     getLabel().setText("This widget has non primitive properties: "
-        + foo.toString());
+        + pojo.toString());
   }
 
   private Label getLabel() {
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
index 6fd1345..9282b5c 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -13,7 +13,7 @@
   limitations under the License.
 -->
 
-<!DOCTYPE gwt:UiBinder 
+<!DOCTYPE ui:UiBinder 
   SYSTEM "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent"
   [
     <!ENTITY % MyEntities SYSTEM "MyEntities.ent">
@@ -39,22 +39,21 @@
   and add your custom entity definitions to it, and use this 
   for your DOCTYPE:
   
-  <!DOCTYPE gwt:UiBinder SYSTEM "my-xhtml.ent">
+  <!DOCTYPE ui:UiBinder SYSTEM "my-xhtml.ent">
 -->
 
-<gwt:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
   xmlns:gwt='urn:import:com.google.gwt.user.client.ui'
   xmlns:demo='urn:import:com.google.gwt.uibinder.sample.client'
 
-  xmlns:foo='urn:with:com.google.gwt.uibinder.sample.client.FakeBundle'
-  xmlns:res='urn:with:com.google.gwt.uibinder.sample.client.WidgetBasedUiResources'
-
   ui:defaultLocale="en_us"
   ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
   ui:generateFormat="com.google.gwt.i18n.rebind.format.PropertiesFormat"
   ui:generateFilename="myapp_translate_source"
   ui:generateLocales="default"
 >
+<ui:with name='res' type='com.google.gwt.uibinder.sample.client.WidgetBasedUiResources' />
+<ui:with name='values' type='com.google.gwt.uibinder.sample.client.FakeBundle' />
 <gwt:DockPanel ui:field="root" width="100%">
   <gwt:Dock direction='NORTH'>
     <gwt:HTML>
@@ -82,7 +81,7 @@
   </gwt:Dock>
   <gwt:Dock direction='CENTER'>
     <gwt:HTMLPanel>
-      <p><ui:msg>This is a demonstration and test bed of GWT's nascent UiBinder
+      <p><ui:msg>This is a demonstration and test bed of GWT's 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">
@@ -113,7 +112,7 @@
           Of course, it could just as easily be a Tree under a MenuBar...
         </ui:msg>
       </p>
-      <div res:class="style.menuBar">
+      <div class="{res.style.menuBar}">
         <gwt:MenuBar ui:field="topMenuBar" vertical="false">
           <gwt:MenuItem>
             <div id="higgledy">
@@ -121,7 +120,7 @@
                 Higgeldy
                </ui:msg>
             </div>
-            <gwt:MenuBar vertical="true" res:styleName="style.menuBar">
+            <gwt:MenuBar vertical="true" styleName="{res.style.menuBar}">
               <gwt:MenuItem>able</gwt:MenuItem>
               <gwt:MenuItem>baker</gwt:MenuItem>
               <gwt:MenuItem>charlie</gwt:MenuItem>
@@ -133,7 +132,7 @@
                 Piggledy
               </ui:msg>
               </div>
-            <gwt:MenuBar vertical="true" res:styleName="style.menuBar">
+            <gwt:MenuBar vertical="true" styleName="{res.style.menuBar}">
               <gwt:MenuItem>delta</gwt:MenuItem>
               <gwt:MenuItem>echo</gwt:MenuItem>
               <gwt:MenuItem>foxtrot</gwt:MenuItem>
@@ -144,7 +143,7 @@
               <ui:msg description="Last 7 Day Period">Pop</ui:msg>
             </div>
             <gwt:MenuBar vertical="true" ui:field="dropdownMenuBar" 
-              res:styleName="style.menuBar">
+              styleName="{res.style.menuBar}">
               <gwt:MenuItem ui:field='menuItemCustomDateRange'>
                 <ui:msg description="Custom date range">
                   the Dog
@@ -240,8 +239,8 @@
        Templates work with what is now called ImmutableResourceBundle, but
        which you will soon come to know and love as ClientBundle. For example,
        this label gets its text from somplace tricky and its style from a resource:</p>
-      <gwt:Label ui:field='bundledLabel' foo:text="helloText" res:styleName="style.prettyText"/>
-      <p res:class="style.prettyText" id='prettyPara'>
+      <gwt:Label ui:field='bundledLabel' text="{values.helloText}" styleName="{res.style.prettyText}"/>
+      <p class="{res.style.prettyText}" id='prettyPara'>
         This stylish paragraph also gets its good looks from a
         resource.
       </p>
@@ -289,18 +288,18 @@
       <h2>Placeholders in localizables</h2>
       <p><ui:msg>When you mark up your text for translation, there will be bits
       that you don't want the translators to mess with. You can protect
-      these with <span id="placeholdersSpan" style="font-weight:bold" res:class="style.prettyText"
+      these with <span id="placeholdersSpan" style="font-weight:bold" class="{res.style.prettyText}"
         ui:ph="boldSpan">placeholders</span><ui:ph name="tm"><sup ui:field="tmElement"
-        res:class="style.tmText">TM</sup></ui:ph>.</ui:msg></p>
+        class="{res.style.tmText}">TM</sup></ui:ph>.</ui:msg></p>
 
       <p><ui:msg>You may also need to have <span ui:field="spanInMsg"
-      style="font-weight:bold" res:class="style.prettyText">named
-      portions</span> of <span res:class="style.prettyText">translatable text</span></ui:msg></p>
+      style="font-weight:bold" class="{res.style.prettyText}">named
+      portions</span> of <span class="{res.style.prettyText}">translatable text</span></ui:msg></p>
 
       <p><ui:msg>Of course you'll want to be able to do this kind of thing
       with widgets <demo:MyDatePicker/> as well, whether they <gwt:Label>HasText<ui:ph name="tm">*TM*</ui:ph></gwt:Label>
       or <gwt:HTML>HasHTML<ui:ph name="TM2"><sup ui:field="tmElementJr"
-        res:class="style.tmText">TM</sup></ui:ph></gwt:HTML></ui:msg></p> 
+        class="{res.style.tmText}">TM</sup></ui:ph></gwt:HTML></ui:msg></p> 
 
       <h2>Any Widget You Like</h2>
       <p><demo:AnnotatedStrictLabel text="This widget is not default instantiable." ui:field="strictLabel"/></p>
@@ -313,7 +312,7 @@
       </demo:StrictLabel></p>
       <p><demo:NeedlesslyAnnotatedLabel ui:field="needlessLabel"
         text="Sometimes people do things they don't need to do. Don't punish them."/></p>
-      <demo:FooLabel ui:field='theFoo' foo:foo="foo"/>
+      <demo:FooLabel ui:field='theFoo' pojo="{values.pojo}"/>
 
     <h2>How to use the debugId, addStyleNames, addDependentStyleNames attributes</h2>
     <p>You can use the <strong>debugId</strong>, <strong>addStyleNames</strong>, and <strong>addDependentStyleNames</strong>
@@ -329,7 +328,7 @@
 
     <b>Template:</b>
     <pre style="border: 1px dashed #666; padding: 5px 0;">
-  &lt;gwt:UiBinder
+  &lt;ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:gwt='urn:import:com.google.gwt.user.client.ui'&gt;
 
@@ -340,7 +339,7 @@
       &lt;gwt:Button ui:field="btnGo" debugId="myButton" addStyleNames="buttonStyle" text="a button with extra attributes" /&gt;
     &lt;/gwt:FlowPanel>
 
-  &lt;/gwt:UiBinder&gt;</pre>
+  &lt;/ui:UiBinder&gt;</pre>
 
     <b>HTML:</b>
     <pre style="border: 1px dashed #666; padding: 5px 0;">
@@ -365,7 +364,7 @@
 
     <b>Template:</b>
     <pre style="border: 1px dashed #666; padding: 5px 0;">
-  &lt;gwt:UiBinder
+  &lt;ui:UiBinder
     xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:gwt='urn:import:com.google.gwt.user.client.ui'&gt;
 
@@ -375,7 +374,7 @@
       &lt;gwt:TextBox ui:field="textbox" /&gt;
     &lt;/gwt:FlowPanel>
 
-  &lt;/gwt:UiBinder&gt;</pre>
+  &lt;/ui:UiBinder&gt;</pre>
 
   <b>The code:</b>
   <pre style="border: 1px dashed #666; padding: 5px 0;">
@@ -453,4 +452,4 @@
     </gwt:HTMLPanel>
   </gwt:Dock>
 </gwt:DockPanel>
-</gwt:UiBinder>
+</ui:UiBinder>
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
index 5e28452..95e5b5d 100644
--- a/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
@@ -24,10 +24,11 @@
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.uibinder.sample.client.ArbitraryPojo;
 import com.google.gwt.uibinder.sample.client.ClickyLink;
 import com.google.gwt.uibinder.sample.client.DomBasedUi;
 import com.google.gwt.uibinder.sample.client.FakeBundle;
-import com.google.gwt.uibinder.sample.client.Foo;
+import com.google.gwt.uibinder.sample.client.FooLabel;
 import com.google.gwt.uibinder.sample.client.WidgetBasedUi;
 import com.google.gwt.uibinder.sample.client.WidgetBasedUiResources;
 import com.google.gwt.user.client.DOM;
@@ -96,9 +97,10 @@
     Element pretty = DOM.getElementById("prettyPara");
     assertEquals(resources.style().prettyText(), pretty.getClassName());
 
-    Foo f = new Foo();
-    assertTrue("Expect " + f,
-        widgetUi.getTheFoo().getText().contains(f.toString()));
+    ArbitraryPojo pojo = new ArbitraryPojo();
+    FooLabel foo = new FooLabel();
+    foo.setPojo(pojo);
+    assertEquals(foo.getText(), widgetUi.getTheFoo().getText());
   }
 
   public void testCenter() {
diff --git a/user/test/com/google/gwt/uibinder/parsers/FieldReferenceConverterTest.java b/user/test/com/google/gwt/uibinder/parsers/FieldReferenceConverterTest.java
new file mode 100644
index 0000000..4dfdbdf
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/parsers/FieldReferenceConverterTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 junit.framework.TestCase;
+
+public class FieldReferenceConverterTest extends TestCase {
+
+  FieldReferenceConverter.Delegate provider = new FieldReferenceConverter.Delegate() {
+    public String handleFragment(String path) {
+      return "*" + path + "*";
+    }
+
+    public String handleReference(String reference) {
+      return String.format(" & %s & ", reference);
+    }
+  };
+  FieldReferenceConverter converter = new FieldReferenceConverter(provider);
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+  }
+
+  public void testNone() {
+    String before = "able.baker.charlie";
+    String expected = "*able.baker.charlie*";
+
+    assertEquals(expected, converter.convert(before));
+  }
+
+  public void testReplaceSimple() {
+    String before = "able {baker} charlie";
+    String expected = "*able * & baker & * charlie*";
+
+    assertEquals(expected, converter.convert(before));
+  }
+
+  public void testReplaceSeveral() {
+    String before = "{foo.bar.baz} baker {bang.zoom} delta {zap}";
+    String expected = "** & foo.bar().baz() & * baker * & bang.zoom() & * delta * & zap & **";
+
+    assertEquals(expected, converter.convert(before));
+  }
+
+  public void testEscaping() {
+    String before = "Well {{Hi mom}!";
+    String expected = "*Well {Hi mom}!*";
+
+    assertEquals(expected, converter.convert(before));
+  }
+
+  public void testIgnoreEmpty() {
+    String before = "Hi {} mom";
+    String expected = "*Hi {} mom*";
+
+    assertEquals(expected, converter.convert(before));
+  }
+
+  public void testIgnoreNonIdentifierFirstChar() {
+    String before = "Hi { } mom, how { are } {1you}?";
+    String expected = "*Hi { } mom, how { are } {1you}?*";
+
+    assertEquals(expected, converter.convert(before));
+  }
+  
+  public void testHasFieldReferences() {
+    assertTrue(FieldReferenceConverter.hasFieldReferences("{able} {baker}"));
+    assertFalse(FieldReferenceConverter.hasFieldReferences("{{able} baker { Charlie }"));
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/parsers/SimpleAttributeParserTest.java b/user/test/com/google/gwt/uibinder/parsers/SimpleAttributeParserTest.java
new file mode 100644
index 0000000..acd91e1
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/parsers/SimpleAttributeParserTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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/parsers/StrictAttributeParserTest.java b/user/test/com/google/gwt/uibinder/parsers/StrictAttributeParserTest.java
new file mode 100644
index 0000000..3e109a2
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/parsers/StrictAttributeParserTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.StrictAttributeParser.FieldReferenceDelegate;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests StrictAttributeParser. 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 StrictAttributeParserTest 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 testNoneShouldFail() {
+    String before = "able.baker.charlie.prawns";
+    try {
+      bp.convert(before);
+      fail();
+    } catch (IllegalFieldReferenceException e) {
+      /* pass */
+    }
+  }
+
+  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/parsers/StringAttributeParserTest.java b/user/test/com/google/gwt/uibinder/parsers/StringAttributeParserTest.java
new file mode 100644
index 0000000..bbab8ef
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/parsers/StringAttributeParserTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.StringAttributeParser.FieldReferenceDelegate;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests StringAttributeParser. 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 StringAttributeParserTest 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 testEscaping() {
+    String before = "{able.baker.charlie} \"Howdy\nfriend\" {prawns.are.yummy}";
+    String expected = "\"\" + able.baker().charlie() + \" \\\"Howdy\\nfriend\\\" \" + prawns.are().yummy() + \"\"";
+    String after = bp.convert(before);
+    assertEquals(expected, after);
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
index 7ea8294..5096590 100644
--- a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.uibinder.rebind;
 
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.uibinder.testing.UiBinderTesting;
 
 import junit.framework.TestCase;
@@ -53,6 +54,24 @@
     assertEquals("", elm.consumeAttribute("attr1"));
   }
   
+  public void testConsumeAttributeWithDefault() {
+    assertEquals("attr1Value", elm.consumeAttribute("attr1", "default"));
+    assertEquals("default", elm.consumeAttribute("attr1", "default"));
+    assertEquals("otherDefault", elm.consumeAttribute("unsetthing", "otherDefault"));
+  }
+  
+  public void testConsumeRequired() throws UnableToCompleteException {
+    assertEquals("attr1Value", elm.consumeRequiredAttribute("attr1"));
+
+    // TODO(rjrjr) Can't test this until die() is factored out of UiBinderWriter
+//    try {
+//      elm.consumeRequiredAttribute("unsetthing");
+//      fail("Should have thrown UnableToCompleteException");
+//    } catch (UnableToCompleteException e) {
+//       /* pass */
+//    }    
+  }
+  
   public void testEmptyStringOnMissingAttribute()
       throws ParserConfigurationException, SAXException, IOException {
     assertEquals("", elm.consumeAttribute("fnord"));