Fixes bad code gen on non-widget children in DockLayoutPanelParser, and allows
<g:center> to appear anywhere.

To allow real unit testing, refactored UiBinderWriter to be a bit more DI. This
included moving w3c parsing knowledge outside of the writer. In the process made
the w3c dom test helper into the actual production code--it was nearly identical
anyway, and now test and prod won't drift.

This took a little while. But the next one won't.

Review by jgw, scottb
http://gwt-code-reviews.appspot.com/93806

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6714 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
index 0bf38f5..0ec22f9 100644
--- a/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
@@ -39,6 +39,16 @@
  */
 public class DockLayoutPanelParser implements ElementParser {
 
+  private static class CenterChild {
+    final String widgetName;
+    final XMLElement child;
+
+    public CenterChild(XMLElement child, String widgetName) {
+      this.widgetName = widgetName;
+      this.child = child;
+    }
+  }
+
   private static final Map<String, String> DOCK_NAMES = new HashMap<String, String>();
 
   static {
@@ -61,30 +71,43 @@
           writer.getOracle().findType(DockLayoutPanel.class.getName()), unit);
     }
 
+    CenterChild center = null;
+
     // Parse children.
     for (XMLElement child : elem.consumeChildElements()) {
       // Make sure the element is one of the fixed set of valid directions.
       if (!isValidChildElement(elem, child)) {
         writer.die(
-            "In %1$s, child must be one of " +
-            "<%2$s:north>, <%2$s:south>, <%2$s:east>, <%2$s:west> or <%2$s:center>, " +
-            "but found %3$s",
-            elem, elem.getPrefix(), child);
+            "In %1$s, child must be one of "
+                + "<%2$s:north>, <%2$s:south>, <%2$s:east>, <%2$s:west> or <%2$s:center>, "
+                + "but found %3$s", elem, elem.getPrefix(), child);
       }
 
       // Consume the single widget element.
       XMLElement widget = child.consumeSingleChildElement();
-      String childFieldName = writer.parseElementToField(widget);
+      if (!writer.isWidgetElement(widget)) {
+        writer.die("In %s, %s must contain a widget, but found %s", elem, child,
+            widget);
+      }
+      String widgetName = writer.parseElementToField(widget);
 
-      if (requiresSize(child)) {
+      if (child.getLocalName().equals("center")) {
+        if (center != null) {
+          writer.die("In %s, only one <%s:center> is allowed", elem,
+              elem.getPrefix());
+        }
+        center = new CenterChild(child, widgetName);
+      } else {
         String size = child.consumeDoubleAttribute("size");
         writer.addStatement("%s.%s(%s, %s);", fieldName, addMethodName(child),
-            childFieldName, size);
-      } else {
-        writer.addStatement("%s.%s(%s);", fieldName, addMethodName(child),
-            childFieldName);
+            widgetName, size);
       }
     }
+
+    if (center != null) {
+      writer.addStatement("%s.%s(%s);", fieldName, addMethodName(center.child),
+          center.widgetName);
+    }
   }
 
   private String addMethodName(XMLElement elem) {
@@ -102,8 +125,8 @@
   private boolean isValidChildElement(XMLElement parent, XMLElement child) {
     String childNsUri = child.getNamespaceUri();
     if (childNsUri == null) {
-        return false;
-    } 
+      return false;
+    }
     if (!childNsUri.equals(parent.getNamespaceUri())) {
       return false;
     }
@@ -113,8 +136,4 @@
     // Made it through the gauntlet.
     return true;
   }
-
-  private boolean requiresSize(XMLElement elem) {
-    return !elem.getLocalName().equals("center");
-  }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
index 24052c5..72b9044 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -45,6 +45,10 @@
     this.name = name;
     this.logger = logger;
   }
+  
+  public String getInitializer() {
+    return initializer;
+  }
 
   public void needs(FieldWriter f) {
     needs.add(f);
diff --git a/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java b/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java
index 0e5a47c..46d2288 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AttributeParsers.java
@@ -24,7 +24,10 @@
 import java.util.HashMap;
 import java.util.Map;
 
-class AttributeParsers {
+/**
+ * Managers access to all implementations of {@link AttributeParser}
+ */
+public class AttributeParsers {
   private static final String DOUBLE = "double";
   private static final String BOOLEAN = "boolean";
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
index 67418ea..9b7a225 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
@@ -26,7 +26,7 @@
  * This class handles all {@link FieldWriter} instances created for the current
  * template.
  */
-class FieldManager {
+public class FieldManager {
 
   private static final String DUPLICATE_FIELD_ERROR = "Duplicate declaration of field %1$s.";
 
@@ -46,8 +46,8 @@
   /**
    * Basic constructor just injects an oracle instance.
    */
-  public FieldManager(MortalLogger logger2) {
-    this.logger = logger2;
+  public FieldManager(MortalLogger logger) {
+    this.logger = logger;
   }
 
   /**
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
index a4c2beb..cfab907 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -36,6 +36,8 @@
 
   String getQualifiedSourceName();
 
+  String getInitializer();
+  
   /**
    * @return the type of this field, or null if this field is of a type that has
    *         not yet been generated
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
index 5bd718a..7c6ebc7 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -26,7 +26,11 @@
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
 import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
 
+import org.w3c.dom.Document;
+import org.xml.sax.SAXParseException;
+
 import java.io.PrintWriter;
+import java.net.URL;
 
 /**
  * Generator for implementations of
@@ -36,6 +40,8 @@
 
   private static final String TEMPLATE_SUFFIX = ".ui.xml";
 
+  static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder";
+
   /**
    * Given a UiBinder interface, return the path to its ui.xml file, suitable
    * for any classloader to find it as a resource.
@@ -103,18 +109,22 @@
   }
 
   private void generateOnce(JClassType interfaceType, String implName,
-      PrintWriter binderPrintWrier, TreeLogger treeLogger, TypeOracle oracle,
+      PrintWriter binderPrintWriter, TreeLogger treeLogger, TypeOracle oracle,
       PrintWriterManager writerManager)
-      throws UnableToCompleteException {
+ throws UnableToCompleteException {
 
     MortalLogger logger = new MortalLogger(treeLogger);
     String templatePath = deduceTemplateFile(logger, interfaceType);
+    MessagesWriter messages = new MessagesWriter(BINDER_URI, logger,
+        templatePath, interfaceType.getPackage().getName(), implName);
 
     UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType, implName,
-        templatePath, oracle, logger);
-    uiBinderWriter.parseDocument(binderPrintWrier);
+        templatePath, oracle, logger, new FieldManager(logger), messages);
 
-    MessagesWriter messages = uiBinderWriter.getMessages();
+    Document doc = getW3cDoc(logger, templatePath);
+
+    uiBinderWriter.parseDocument(doc, binderPrintWriter);
+
     if (messages.hasMessages()) {
       messages.write(writerManager.makePrintWriterFor(messages.getMessagesClassName()));
     }
@@ -124,4 +134,21 @@
 
     writerManager.commit();
   }
+
+  private Document getW3cDoc(MortalLogger logger, String templatePath)
+      throws UnableToCompleteException {
+    URL url = UiBinderGenerator.class.getClassLoader().getResource(templatePath);
+    if (null == url) {
+      logger.die("Unable to find resource: " + templatePath);
+    }
+    
+    Document doc = null;
+    try {
+      doc = new W3cDomHelper().documentFor(url);
+    } catch (SAXParseException e) {
+      logger.die("Error parsing XML (line " + e.getLineNumber() + "): "
+          + e.getMessage(), e);
+    }
+    return doc;
+  }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index b3391f1..fc67fe6 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -110,7 +110,7 @@
       writer.die("Bad prefix on <%s:%s>? The root element must be in "
           + "xml namespace \"%s\" (usually with prefix \"ui:\"), "
           + "but this has prefix \"%s\"", elem.getPrefix(),
-          elem.getLocalName(), UiBinderWriter.BINDER_URI, elem.getPrefix());
+          elem.getLocalName(), UiBinderGenerator.BINDER_URI, elem.getPrefix());
     }
 
     if (!TAG.equals(elem.getLocalName())) {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 5e281fe..9a6e5ae 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -33,15 +33,9 @@
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
 
-import java.io.IOException;
-import java.io.InputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -50,10 +44,6 @@
 import java.util.Locale;
 import java.util.Map;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
 /**
  * Writer for UiBinder generated classes.
  * 
@@ -64,7 +54,6 @@
  */
 @SuppressWarnings("deprecation")
 public class UiBinderWriter {
-  static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder";
   private static final String PACKAGE_URI_SCHEME = "urn:import:";
 
   // TODO(rjrjr) Another place that we need a general anonymous field
@@ -226,17 +215,17 @@
   private final AttributeParsers attributeParsers;
   private final BundleAttributeParsers bundleParsers;
 
-  UiBinderWriter(JClassType baseClass, String implClassName,
-      String templatePath, TypeOracle oracle, MortalLogger logger)
+  public UiBinderWriter(JClassType baseClass, String implClassName,
+      String templatePath, TypeOracle oracle, MortalLogger logger,
+      FieldManager fieldManager, MessagesWriter messagesWriter)
       throws UnableToCompleteException {
     this.baseClass = baseClass;
     this.implClassName = implClassName;
     this.oracle = oracle;
     this.logger = logger;
     this.templatePath = templatePath;
-
-    this.messages = new MessagesWriter(BINDER_URI, logger, templatePath,
-        baseClass.getPackage().getName(), this.implClassName);
+    this.fieldManager = fieldManager;
+    this.messages = messagesWriter;
 
     // Check for possible misuse 'GWT.create(UiBinder.class)'
     JClassType uibinderItself =
@@ -267,7 +256,6 @@
     bundleClass = new ImplicitClientBundle(baseClass.getPackage().getName(),
         this.implClassName, CLIENT_BUNDLE_FIELD, logger);
     handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
-    fieldManager = new FieldManager(logger);
 
     attributeParsers = new AttributeParsers();
     bundleParsers = new BundleAttributeParsers(oracle, gwtPrefix, logger,
@@ -563,7 +551,7 @@
 
   public boolean isBinderElement(XMLElement elem) {
     String uri = elem.getNamespaceUri();
-    return uri != null && BINDER_URI.equals(uri);
+    return uri != null && UiBinderGenerator.BINDER_URI.equals(uri);
   }
 
   public boolean isWidgetElement(XMLElement elem) {
@@ -659,23 +647,16 @@
    * Entry point for the code generation logic. It generates the
    * implementation's superstructure, and parses the root widget (leading to all
    * of its children being parsed as well).
+   * @param doc TODO
    */
-  void parseDocument(PrintWriter printWriter) throws UnableToCompleteException {
-    Document doc = null;
-    try {
-      doc = parseXmlResource(templatePath);
-    } catch (SAXParseException e) {
-      die("Error parsing XML (line " + e.getLineNumber() + "): "
-          + e.getMessage(), e);
-    }
-
+  void parseDocument(Document doc, PrintWriter printWriter) throws UnableToCompleteException {
     JClassType uiBinderClass = getOracle().findType(UiBinder.class.getName());
     if (!baseClass.isAssignableTo(uiBinderClass)) {
       die(baseClass.getName() + " must implement UiBinder");
     }
 
     Element documentElement = doc.getDocumentElement();
-    gwtPrefix = documentElement.lookupPrefix(BINDER_URI);
+    gwtPrefix = documentElement.lookupPrefix(UiBinderGenerator.BINDER_URI);
 
     XMLElement elem = new XMLElementProviderImpl(attributeParsers,
         bundleParsers, oracle, logger).get(documentElement);
@@ -911,45 +892,6 @@
     return null;
   }
 
-  private Document parseXmlResource(final String resourcePath)
-      throws SAXParseException, UnableToCompleteException {
-    // Get the document builder. We need namespaces, and automatic expanding
-    // of entity references (the latter of which makes life somewhat easier
-    // for XMLElement).
-    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-    factory.setNamespaceAware(true);
-    factory.setExpandEntityReferences(true);
-    DocumentBuilder builder;
-    try {
-      builder = factory.newDocumentBuilder();
-    } catch (ParserConfigurationException e) {
-      throw new RuntimeException(e);
-    }
-
-    try {
-      ClassLoader classLoader = UiBinderGenerator.class.getClassLoader();
-      URL url = classLoader.getResource(resourcePath);
-      if (null == url) {
-        die("Unable to find resource: " + resourcePath);
-      }
-
-      InputStream stream = url.openStream();
-      InputSource input = new InputSource(stream);
-      input.setSystemId(url.toExternalForm());
-
-      builder.setEntityResolver(new GwtResourceEntityResolver());
-
-      return builder.parse(input);
-    } catch (SAXParseException e) {
-      // Let SAXParseExceptions through.
-      throw e;
-    } catch (SAXException e) {
-      throw new RuntimeException(e);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
   private void registerParsers() {
     // TODO(rjrjr): Allow third-party parsers to register themselves
     // automagically, according to http://b/issue?id=1867118
diff --git a/user/src/com/google/gwt/uibinder/rebind/W3cDomHelper.java b/user/src/com/google/gwt/uibinder/rebind/W3cDomHelper.java
new file mode 100644
index 0000000..b275f16
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/W3cDomHelper.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Simplifies instantiation of the w3c XML parser, in just the style
+ * that UiBinder likes it. Used by both prod and test.
+ */
+public class W3cDomHelper {
+  private final DocumentBuilderFactory factory;
+  private final DocumentBuilder builder;
+
+  public W3cDomHelper() {
+    factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true);
+    factory.setExpandEntityReferences(true);
+    try {
+      builder = factory.newDocumentBuilder();
+    } catch (ParserConfigurationException e) {
+      throw new RuntimeException(e);
+    }
+    builder.setEntityResolver(new GwtResourceEntityResolver());
+  }
+
+  /**
+   * Creates an XML document model with the given contents. Nice for testing.
+   * 
+   * @param string the document contents
+   */
+  public Document documentFor(String string) throws SAXException,
+      IOException {
+    return builder.parse(new ByteArrayInputStream(string.getBytes()));
+  }
+
+  public Document documentFor(URL url) throws SAXParseException {
+    try {
+      InputStream stream = url.openStream();
+      InputSource input = new InputSource(stream);
+      input.setSystemId(url.toExternalForm());
+
+      return builder.parse(input);
+    } catch (SAXParseException e) {
+      // Let SAXParseExceptions through.
+      throw e;
+    } catch (SAXException e) {
+      throw new RuntimeException(e);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java b/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java
index 7e47451..0c6b5e0 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElementProvider.java
@@ -17,6 +17,9 @@
 
 import org.w3c.dom.Element;
 
-interface XMLElementProvider {
+/**
+ * Implemented by objects that instantiate XMLElement.
+ */
+public 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
index 2a6c713..e9c98cf 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElementProviderImpl.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElementProviderImpl.java
@@ -19,7 +19,10 @@
 
 import org.w3c.dom.Element;
 
-class XMLElementProviderImpl implements XMLElementProvider {
+/**
+ * The default implemenatation of {@link XMLElementProvider}.
+ */
+public class XMLElementProviderImpl implements XMLElementProvider {
   private final AttributeParsers attributeParsers;
   @SuppressWarnings("deprecation")
   // bundleParsers for legacy templates
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index a59d7e2..b56ba29 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.uibinder;
 
+import com.google.gwt.uibinder.parsers.DockLayoutPanelParserTest;
 import com.google.gwt.uibinder.parsers.FieldReferenceConverterTest;
 import com.google.gwt.uibinder.parsers.IntAttributeParserTest;
 import com.google.gwt.uibinder.parsers.StrictAttributeParserTest;
@@ -49,6 +50,7 @@
     suite.addTestSuite(OwnerFieldTest.class);
 
     // parsers
+    suite.addTestSuite(DockLayoutPanelParserTest.class);
     suite.addTestSuite(FieldReferenceConverterTest.class);
     suite.addTestSuite(IntAttributeParserTest.class);
     suite.addTestSuite(StrictAttributeParserTest.class);
diff --git a/user/test/com/google/gwt/uibinder/parsers/DockLayoutPanelParserTest.java b/user/test/com/google/gwt/uibinder/parsers/DockLayoutPanelParserTest.java
new file mode 100644
index 0000000..027dd80
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/parsers/DockLayoutPanelParserTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.javac.impl.MockResourceOracle;
+import com.google.gwt.dev.util.collect.Lists;
+import com.google.gwt.uibinder.rebind.AttributeParsers;
+import com.google.gwt.uibinder.rebind.FieldManager;
+import com.google.gwt.uibinder.rebind.FieldWriter;
+import com.google.gwt.uibinder.rebind.MortalLogger;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.W3cDomHelper;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElementProvider;
+import com.google.gwt.uibinder.rebind.XMLElementProviderImpl;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.test.UiJavaResources;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A unit test. Guess what of. 
+ */
+public class DockLayoutPanelParserTest extends TestCase {
+  private static class MyUiBinderWriter extends UiBinderWriter {
+    final List<String> statements = new ArrayList<String>();
+    String died;
+
+    public MyUiBinderWriter(JClassType baseClass, String implClassName,
+        String templatePath, TypeOracle oracle, MortalLogger logger,
+        FieldManager fieldManager, MessagesWriter messagesWriter)
+        throws UnableToCompleteException {
+      super(baseClass, implClassName, templatePath, oracle, logger,
+          fieldManager, messagesWriter);
+    }
+
+    @Override
+    public void addStatement(String format, Object... args) {
+      statements.add(String.format(format, args));
+    }
+
+    @Override
+    public String parseElementToField(XMLElement elem)
+        throws UnableToCompleteException {
+      return elem.consumeOpeningTag();
+    }
+
+    @Override
+    public void die(String message, Object... params)
+        throws UnableToCompleteException {
+      noteDeath(String.format(message, params));
+      super.die(message, params);
+    }
+
+    @Override
+    public void die(String message) throws UnableToCompleteException {
+      noteDeath(message);
+      super.die(message);
+    }
+    
+    /** Handy place to set a break point and inspect suicide notes. */
+    void noteDeath(String s) {
+      died = s;
+    }
+  }
+
+  private static final W3cDomHelper docHelper = new W3cDomHelper();
+  private static final String BINDER_URI = "binderUri";
+
+  private static final String fieldName = "fieldName";
+
+  // TODO(rjrjr) Move this to JavaResourceBase. Have to do it atomically for
+  // all other tests that define their own Enum.
+  private static final MockJavaResource ENUM = new MockJavaResource(
+  "java.lang.Enum") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public abstract class Enum<E extends Enum<E>> {\n");
+      code.append("  protected Enum(String name, int ordinal) {}\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  private static final MockJavaResource MY_UI_JAVA = new MockJavaResource(
+      "my.Ui") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package my;\n");
+      code.append("import com.google.gwt.user.client.ui.Widget;\n");
+      code.append("public class Ui {\n");
+      code.append("  public interface BaseClass extends "
+          + "com.google.gwt.uibinder.client.UiBinder<Widget, BaseClass> {}\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  private TypeOracle types;
+  private XMLElementProvider elemProvider;
+  private Document doc;
+  private MyUiBinderWriter writer;
+  private DockLayoutPanelParser parser;
+  private JClassType dockLayoutPanelType;
+  private FieldManager fieldManager;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+
+    MockResourceOracle resources = new MockResourceOracle(getUiResources());
+    CompilationState state = new CompilationState(TreeLogger.NULL, resources);
+    types = state.getTypeOracle();
+    dockLayoutPanelType = types.findType("com.google.gwt.user.client.ui.DockLayoutPanel");
+
+    elemProvider = new XMLElementProviderImpl(new AttributeParsers(), null,
+        types, MortalLogger.NULL);
+
+    fieldManager = new FieldManager(MortalLogger.NULL);
+    fieldManager.registerField(
+        types.findType(DockLayoutPanel.class.getCanonicalName()), fieldName);
+
+    String templatePath = "TemplatePath.ui.xml";
+    String implName = "ImplClass";
+    JClassType baseType = types.findType("my.Ui.BaseClass");
+    MessagesWriter messages = new MessagesWriter(BINDER_URI, MortalLogger.NULL,
+        templatePath, baseType.getPackage().getName(), implName);
+
+    writer = new MyUiBinderWriter(baseType, implName, templatePath, types,
+        MortalLogger.NULL, fieldManager, messages);
+    parser = new DockLayoutPanelParser();
+  }
+
+  public void testHappy() throws UnableToCompleteException, SAXException,
+      IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<ui:UiBinder xmlns:ui='" + BINDER_URI + "'");
+    b.append("    xmlns:g='urn:import:com.google.gwt.user.client.ui'>");
+    b.append("  <g:DockLayoutPanel unit='EM'>");
+    b.append("    <g:north size='5'>");
+    b.append("      <g:Label id='north'>north</g:Label>");
+    b.append("    </g:north>");
+    b.append("    <g:center>");
+    b.append("      <g:Label id='center'>center</g:Label>");
+    b.append("    </g:center>");
+    b.append("  </g:DockLayoutPanel>");
+    b.append("</ui:UiBinder>");
+
+    String[] expected = {
+        "fieldName.addNorth(<g:Label id='north'>, 5);",
+        "fieldName.add(<g:Label id='center'>);",};
+
+    parser.parse(getElem(b.toString()), fieldName, dockLayoutPanelType, writer);
+    FieldWriter w = fieldManager.lookup(fieldName);
+    assertEquals(
+        "new com.google.gwt.user.client.ui.DockLayoutPanel(com.google.gwt.dom.client.Style.Unit.EM)",
+        w.getInitializer());
+
+    Iterator<String> i = writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+    assertNull(writer.died);
+  }
+
+  public void testNiceCenter() throws UnableToCompleteException, SAXException,
+      IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<ui:UiBinder xmlns:ui='" + BINDER_URI + "'");
+    b.append("    xmlns:g='urn:import:com.google.gwt.user.client.ui'>");
+    b.append("  <g:DockLayoutPanel unit='EM'>");
+    b.append("    <g:center>");
+    b.append("      <g:Label id='center'>center</g:Label>");
+    b.append("    </g:center>");
+    b.append("    <g:north size='5'>");
+    b.append("      <g:Label id='north'>north</g:Label>");
+    b.append("    </g:north>");
+    b.append("  </g:DockLayoutPanel>");
+    b.append("</ui:UiBinder>");
+
+    String[] expected = {
+        "fieldName.addNorth(<g:Label id='north'>, 5);",
+        "fieldName.add(<g:Label id='center'>);",};
+
+    parser.parse(getElem(b.toString()), fieldName, dockLayoutPanelType, writer);
+    FieldWriter w = fieldManager.lookup(fieldName);
+    assertEquals(
+        "new com.google.gwt.user.client.ui.DockLayoutPanel(com.google.gwt.dom.client.Style.Unit.EM)",
+        w.getInitializer());
+
+    Iterator<String> i = writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+  }
+
+  public void testTooManyCenters() throws SAXException, IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<ui:UiBinder xmlns:ui='" + BINDER_URI + "'");
+    b.append("    xmlns:g='urn:import:com.google.gwt.user.client.ui'>");
+    b.append("  <g:DockLayoutPanel unit='EM'>");
+    b.append("    <g:center>");
+    b.append("      <g:Label id='center'>center</g:Label>");
+    b.append("    </g:center>");
+    b.append("    <g:center>");
+    b.append("      <g:Label id='centerAlso'>centaur</g:Label>");
+    b.append("    </g:center>");
+    b.append("  </g:DockLayoutPanel>");
+    b.append("</ui:UiBinder>");
+
+    try {
+      parser.parse(getElem(b.toString()), fieldName, dockLayoutPanelType,
+          writer);
+      fail();
+    } catch (UnableToCompleteException e) {
+      assertNotNull(writer.died);
+    }
+  }
+
+  public void testBadChild() throws SAXException, IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<ui:UiBinder xmlns:ui='" + BINDER_URI + "'");
+    b.append("    xmlns:g='urn:import:com.google.gwt.user.client.ui'>");
+    b.append("  <g:DockLayoutPanel unit='EM'>");
+    b.append("    <g:west><foo/></g:west>");
+    b.append("  </g:DockLayoutPanel>");
+    b.append("</ui:UiBinder>");
+
+    try {
+      parser.parse(getElem(b.toString()), fieldName, dockLayoutPanelType,
+          writer);
+      fail();
+    } catch (UnableToCompleteException e) {
+      assertNotNull(writer.died);
+    }
+  }
+
+  MockJavaResource[] getUiResources() {
+    List<MockJavaResource> rtn = Lists.create(UiJavaResources.getUiResources());
+    rtn.add(MY_UI_JAVA);
+    rtn.add(ENUM);
+    return rtn.toArray(new MockJavaResource[rtn.size()]);
+  }
+
+  private XMLElement getElem(String string) throws SAXException, IOException {
+    doc = docHelper.documentFor(string);
+    Element w3cElem = (Element) doc.getDocumentElement().getElementsByTagName(
+        "g:DockLayoutPanel").item(0);
+    XMLElement elem = elemProvider.get(w3cElem);
+    return elem;
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/DocumentTestHelp.java b/user/test/com/google/gwt/uibinder/rebind/DocumentTestHelp.java
deleted file mode 100644
index f82bfac..0000000
--- a/user/test/com/google/gwt/uibinder/rebind/DocumentTestHelp.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.uibinder.rebind;
-
-import org.w3c.dom.Document;
-import org.xml.sax.SAXException;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-/**
- * Support methods for testing uibinder.
- */
-class DocumentTestHelp {
-  /**
-   * Creates an XML document model with the given contents.
-   *
-   * @param string the document contents
-   */
-  public static Document documentForString(String string)
-      throws ParserConfigurationException, SAXException, IOException {
-    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-    factory.setNamespaceAware(true);
-    factory.setExpandEntityReferences(true);
-    DocumentBuilder builder = factory.newDocumentBuilder();
-    Document doc = builder.parse(new ByteArrayInputStream(string.getBytes()));
-    return doc;
-  }
-
-  /**
-   * Not instantiable.
-   */
-  private DocumentTestHelp() {
-  }
-}
diff --git a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
index f47e96e..e448c81 100644
--- a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
@@ -34,35 +34,35 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import javax.xml.parsers.ParserConfigurationException;
-
 /**
  * Tests XMLElement.
  */
 public class XMLElementTest extends TestCase {
   private static final String STRING_WITH_DOUBLEQUOTE = "I have a \" quote in me";
+  private static final W3cDomHelper docHelper = new W3cDomHelper();
   private Document doc;
   private Element item;
   private XMLElement elm;
   private XMLElementProvider elemProvider;
-  
-  TypeOracle oracle;
-  
+  private TypeOracle oracle;
+
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    init("<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>");
-
     MockResourceOracle resourceOracle = new MockResourceOracle(
         JavaResourceBase.getStandardResources());
-
     CompilationState state = new CompilationState(TreeLogger.NULL,
         resourceOracle);
     oracle = state.getTypeOracle();
+
+    elemProvider = new XMLElementProviderImpl(new AttributeParsers(), null,
+        oracle, MortalLogger.NULL);
+
+    init("<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>");
   }
 
-  public void testConsumeBoolean() throws ParserConfigurationException,
-      SAXException, IOException, UnableToCompleteException {
+  public void testConsumeBoolean() throws SAXException, IOException,
+      UnableToCompleteException {
     init("<doc><elm yes='true' no='false' "
         + "fnord='fnord' ref='{foo.bar.baz}'/></doc>");
 
@@ -84,8 +84,8 @@
     }
   }
 
-  public void testConsumeBooleanConstant() throws ParserConfigurationException,
-      SAXException, IOException, UnableToCompleteException {
+  public void testConsumeBooleanConstant() throws SAXException, IOException,
+      UnableToCompleteException {
     init("<doc><elm yes='true' no='false' "
         + "fnord='fnord' ref='{foo.bar.baz}'/></doc>");
 
@@ -113,7 +113,7 @@
   }
 
   public void testConsumeDouble() throws UnableToCompleteException,
-      ParserConfigurationException, SAXException, IOException {
+      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"));
@@ -167,9 +167,8 @@
     }
   }
 
-  public void testConsumeSingleChildElementEmpty()
-      throws ParserConfigurationException, SAXException, IOException,
-      UnableToCompleteException {
+  public void testConsumeSingleChildElementEmpty() throws SAXException,
+      IOException, UnableToCompleteException {
     try {
       elm.consumeSingleChildElement();
       fail("Should throw on single child element");
@@ -220,10 +219,9 @@
   }
 
   public void testNoEndTags() throws Exception {
-    Document doc = DocumentTestHelp.documentForString("<doc><br/></doc>");
-
-    Element item = (Element) doc.getDocumentElement().getElementsByTagName("br").item(
-        0);
+    doc = docHelper.documentFor("<doc><br/></doc>");
+    Element documentElement = doc.getDocumentElement();
+    Element item = (Element) documentElement.getElementsByTagName("br").item(0);
     XMLElement elm = elemProvider.get(item);
     assertEquals("br", item.getTagName());
     assertEquals("", elm.getClosingTag());
@@ -234,14 +232,10 @@
     item.appendChild(t);
   }
 
-  private void init(final String domString)
-      throws ParserConfigurationException, SAXException, IOException {
-    doc = DocumentTestHelp.documentForString(domString);
+  private void init(final String domString) throws SAXException, IOException {
+    doc = docHelper.documentFor(domString);
     item = (Element) doc.getDocumentElement().getElementsByTagName("elm").item(
         0);
-
-    elemProvider = new XMLElementProviderImpl(new AttributeParsers(), null,
-        oracle, MortalLogger.NULL);
     elm = elemProvider.get(item);
   }
 }
diff --git a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
new file mode 100644
index 0000000..53e847e
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
@@ -0,0 +1,112 @@
+/*
+ * 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.test;
+
+import com.google.gwt.dev.javac.impl.JavaResourceBase;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.util.collect.Lists;
+
+import java.util.List;
+
+/**
+ * A paired down set of GWT widget Java source files for code generator testing.
+ */
+public class UiJavaResources {
+
+  public static final MockJavaResource WIDGET = new MockJavaResource(
+      "com.google.gwt.user.client.ui.Widget") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class Widget {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource DOCK_LAYOUT_PANEL = new MockJavaResource(
+      "com.google.gwt.user.client.ui.DockLayoutPanel") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class DockLayoutPanel extends Widget {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource SPLIT_LAYOUT_PANEL = new MockJavaResource(
+      "com.google.gwt.user.client.ui.SplitLayoutPanel") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class SplitLayoutPanel extends DockLayoutPanel {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource LABEL = new MockJavaResource(
+      "com.google.gwt.user.client.ui.Label") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class Label extends Widget {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource UI_BINDER = new MockJavaResource(
+      "com.google.gwt.uibinder.client.UiBinder") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.uibinder.client;\n");
+      code.append("public interface UiBinder<U, O> {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource STYLE = new MockJavaResource(
+      "com.google.gwt.dom.client.Style") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.dom.client;\n");
+      code.append("public class Style  {\n");
+      code.append("  public enum Unit { PX, PT, EM };\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  /**
+   * @return a pale reflection of com.google.gwt.user.ui, plus
+   *         {@link JavaResourceBase#getStandardResources}
+   */
+  public static MockJavaResource[] getUiResources() {
+    MockJavaResource[] base = JavaResourceBase.getStandardResources();
+    List<MockJavaResource> rtn = Lists.create(base);
+    rtn.add(WIDGET);
+    rtn.add(DOCK_LAYOUT_PANEL);
+    rtn.add(SPLIT_LAYOUT_PANEL);
+    rtn.add(LABEL);
+    rtn.add(UI_BINDER);
+    rtn.add(STYLE);
+    return rtn.toArray(new MockJavaResource[rtn.size()]);
+  }
+}
\ No newline at end of file