Stricter XMLElement#consumeSingleChild, fixes NPE in DockLayoutPanelParser.
Also improves testing of XMLElement

Reviewed by jgw

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6356 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
index 0490af6..67d8ca2 100644
--- a/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
@@ -87,9 +87,6 @@
       if (ns.equals(elem.getNamespaceUri()) && tagName.equals(CELL_TAG)) {
         // It's a cell element, so parse its single child as a widget.
         XMLElement widget = child.consumeSingleChildElement();
-        if (widget == null) {
-          writer.die("Cell must contain a single child widget");
-        }
         String childFieldName = writer.parseElementToField(widget);
         writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
 
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
index 3493cd9e..49d30d5 100644
--- a/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
@@ -68,9 +68,6 @@
 
       // And they can only have a single child widget.
       XMLElement widget = child.consumeSingleChildElement();
-      if (widget == null) {
-        writer.die("Dock must contain a single child widget.");
-      }
       String childFieldName = writer.parseElementToField(widget);
       writer.addStatement("%1$s.add(%2$s, %3$s);", fieldName, childFieldName, translated);
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 9726c2b..7d8acb6 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
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.typeinfo.JPackage;
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.shell.log.TreeItemLogger;
 import com.google.gwt.dom.client.TagName;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.parsers.AttributeMessageParser;
@@ -60,10 +61,10 @@
 
 /**
  * Writer for UiBinder generated classes.
- *
+ * 
  * TODO(rdamazio): Refactor this, extract model classes, improve ordering
  * guarantees, etc.
- *
+ * 
  * TODO(rjrjr): Improve error messages
  */
 @SuppressWarnings("deprecation")
@@ -160,7 +161,7 @@
   /**
    * 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
    */
@@ -193,7 +194,7 @@
   /**
    * 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.
    */
@@ -282,11 +283,30 @@
     fieldManager = new FieldManager(logger);
   }
 
+  /** 
+   * Hack for testing. Die method works, nothing else does 
+   */
+  UiBinderWriter() {
+    this.baseClass = null;
+    this.implClassName = null;
+    this.oracle = null;
+    this.logger = new MortalLogger(new TreeItemLogger());
+    this.templatePath = null;
+    this.messages = null;
+    uiRootType = null;
+    uiOwnerType = null;
+
+    ownerClass = null;
+    bundleClass = null;
+    handlerEvaluator = null;
+    fieldManager = null;
+  }
+
   /**
    * Statements to be excuted right after the current attached element is
    * detached. This is useful for doing things that might be expensive while the
    * element is attached to the DOM.
-   *
+   * 
    * @param format
    * @param args
    */
@@ -313,7 +333,7 @@
   /**
    * Begin a section where a new attachable element is being parsed. Note that
    * attachment is only done when actually needed.
-   *
+   * 
    * @param element to be attached for this section
    */
   public void beginAttachedSection(String element) {
@@ -330,7 +350,7 @@
    * 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
@@ -351,7 +371,7 @@
   /**
    * 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 {
@@ -391,7 +411,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)
@@ -449,7 +469,7 @@
 
   /**
    * Ensure that the specified element is attached to the DOM.
-   *
+   * 
    * @param element variable name of element to be attached
    */
   public void ensureAttached(String element) {
@@ -466,7 +486,7 @@
 
   /**
    * Ensure that the specified field is attached to the DOM.
-   *
+   * 
    * @param field variable name of the field to be attached
    */
   public void ensureFieldAttached(String field) {
@@ -476,7 +496,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
    */
@@ -561,7 +581,7 @@
   /**
    * 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.
@@ -641,7 +661,7 @@
    * name of the field (possibly private) that will hold it. The element is
    * likely to make recursive calls back to this method to have its children
    * parsed.
-   *
+   * 
    * @param elem the xml element to be parsed
    * @return the name of the field containing the parsed widget
    */
@@ -675,7 +695,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) {
@@ -699,7 +719,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) {
@@ -823,7 +843,7 @@
   /**
    * Ensures that all of the internal data structures are cleaned up correctly
    * at the end of parsing the document.
-   *
+   * 
    * @throws UnableToCompleteException
    */
   private void ensureAttachmentCleanedUp() throws UnableToCompleteException {
@@ -859,8 +879,8 @@
   }
 
   /**
-   * Use this method to format code. It forces the use of the en-US locale,
-   * so that things like decimal format don't get mangled.
+   * Use this method to format code. It forces the use of the en-US locale, so
+   * that things like decimal format don't get mangled.
    */
   private String formatCode(String format, Object... params) {
     String r = String.format(Locale.US, format, params);
@@ -870,7 +890,7 @@
   /**
    * 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 {
@@ -927,7 +947,7 @@
 
   /**
    * 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) {
@@ -936,7 +956,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?
      */
@@ -1015,7 +1035,7 @@
 
   /**
    * Parses a package uri (i.e. package://com.google...).
-   *
+   * 
    * @throws UnableToCompleteException on bad package name
    */
   private JPackage parseNamespacePackage(String ns)
@@ -1190,7 +1210,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)
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index 20d08a2..5c5595e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -382,9 +382,9 @@
 
   /**
    * Consumes a single child element, ignoring any text nodes and throwing an
-   * exception if more than one child element is found.
+   * exception if no child is found, or more than one child element is found.
    * 
-   * @throws UnableToCompleteException
+   * @throws UnableToCompleteException on no children, or too many
    */
   public XMLElement consumeSingleChildElement()
       throws UnableToCompleteException {
@@ -397,6 +397,10 @@
 
       ret = child;
     }
+    
+    if (ret == null) {
+      writer.die("%s must have a single child element", this);
+    }
 
     return ret;
   }
diff --git a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
index bde2493..92b76e9 100644
--- a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
@@ -36,74 +36,100 @@
  * Tests XMLElement.
  */
 public class XMLElementTest extends TestCase {
-  /**
-   *
-   */
   private static final String STRING_WITH_DOUBLEQUOTE = "I have a \" quote in me";
-  private static final String dom =
-    "<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>";
   private Document doc;
   private Element item;
   private XMLElement elm;
-  
+
   @Override
   protected void setUp() throws Exception {
     super.setUp();
-    doc = UiBinderTesting.documentForString(dom);
-    item = (Element) doc.getDocumentElement().getElementsByTagName(
-        "elm").item(0);
-    elm = new XMLElement(item, null);
-    
+    init("<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>");
   }
 
   public void testConsumeAttribute() {
     assertEquals("attr1Value", elm.consumeAttribute("attr1"));
     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"));
+    assertEquals("otherDefault", elm.consumeAttribute("unsetthing",
+        "otherDefault"));
   }
-  
+
   public void testConsumeRequired() throws UnableToCompleteException {
     assertEquals("attr1Value", elm.consumeRequiredAttribute("attr1"));
+    try {
+      elm.consumeRequiredAttribute("unsetthing");
+      fail("Should have thrown UnableToCompleteException");
+    } catch (UnableToCompleteException e) {
+      /* pass */
+    }
+  }
 
-    // TODO(rjrjr) Can't test this until UiBinderWriter can be mocked
-//    try {
-//      elm.consumeRequiredAttribute("unsetthing");
-//      fail("Should have thrown UnableToCompleteException");
-//    } catch (UnableToCompleteException e) {
-//       /* pass */
-//    }    
-  }
-  
-  public void testConsumeInnerTextEscapedAsHtmlStringLiteral() throws UnableToCompleteException {
+  public void testConsumeInnerTextEscapedAsHtmlStringLiteral()
+      throws UnableToCompleteException {
     appendText(STRING_WITH_DOUBLEQUOTE);
-    assertEquals(UiBinderWriter.escapeTextForJavaStringLiteral(STRING_WITH_DOUBLEQUOTE),
+    assertEquals(
+        UiBinderWriter.escapeTextForJavaStringLiteral(STRING_WITH_DOUBLEQUOTE),
         elm.consumeInnerTextEscapedAsHtmlStringLiteral(new NullInterpreter<String>()));
   }
-  
-  public void testConsumeInnerTextEscapedAsHtmlStringLiteralEmpty() throws UnableToCompleteException {
-    assertEquals("",
+
+  public void testConsumeInnerTextEscapedAsHtmlStringLiteralEmpty()
+      throws UnableToCompleteException {
+    assertEquals(
+        "",
         elm.consumeInnerTextEscapedAsHtmlStringLiteral(new NullInterpreter<String>()));
   }
-  
+
+  public void testConsumeSingleChildElementEmpty()
+      throws ParserConfigurationException, SAXException, IOException,
+      UnableToCompleteException {
+    try {
+      elm.consumeSingleChildElement();
+      fail("Should throw on single child element");
+    } catch (UnableToCompleteException e) {
+      /* pass */
+    }
+
+    init("<doc><elm><child>Hi.</child></elm></doc>");
+    assertEquals("Hi.",
+        elm.consumeSingleChildElement().consumeUnescapedInnerText());
+    
+    init("<doc><elm id='elm'><child>Hi.</child><child>Ho.</child></elm></doc>");
+    try {
+      elm.consumeSingleChildElement();
+      fail("Should throw on too many children");
+    } catch (UnableToCompleteException e) {
+      /* pass */
+    }
+ }
+
+  private void init(final String domString)
+      throws ParserConfigurationException, SAXException, IOException {
+    doc = UiBinderTesting.documentForString(domString);
+    item = (Element) doc.getDocumentElement().getElementsByTagName("elm").item(
+        0);
+    elm = new XMLElement(item, new UiBinderWriter());
+  }
+
   private void appendText(final String text) {
     Text t = doc.createTextNode(STRING_WITH_DOUBLEQUOTE);
     item.appendChild(t);
   }
-  
+
   public void testConsumeUnescapedInnerText() throws UnableToCompleteException {
     appendText(STRING_WITH_DOUBLEQUOTE);
     assertEquals(STRING_WITH_DOUBLEQUOTE, elm.consumeUnescapedInnerText());
   }
-  
-  public void testConsumeUnescapedInnerTextEmpty() throws UnableToCompleteException {
+
+  public void testConsumeUnescapedInnerTextEmpty()
+      throws UnableToCompleteException {
     assertEquals("", elm.consumeUnescapedInnerText());
   }
-  
+
   public void testEmptyStringOnMissingAttribute()
       throws ParserConfigurationException, SAXException, IOException {
     assertEquals("", elm.consumeAttribute("fnord"));