Revision: 8708
Author: son...@google.com
Date: Thu Sep  2 13:27:55 2010
Log: Add @UiChild annotation to specify how UiBinder should add a child element.
Patch by: sonnyf
Reviw by: bobv, rjrjr
Review at http://gwt-code-reviews.appspot.com/794801

http://code.google.com/p/google-web-toolkit/source/detail?r=8708

Added:
 /trunk/user/src/com/google/gwt/uibinder/client/UiChild.java
 /trunk/user/src/com/google/gwt/uibinder/elementparsers/UiChildParser.java
/trunk/user/test/com/google/gwt/uibinder/elementparsers/UiChildParserTest.java
Modified:
 /trunk/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
 /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
 /trunk/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
/trunk/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java

=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/uibinder/client/UiChild.java Thu Sep 2 13:27:55 2010
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a method as the appropriate way to add a child widget to the parent
+ * class.
+ *
+ * <p>
+ * The limit attribute specifies the number of times the function can be safely + * called. If no limit is specified, it is assumed to be unlimited. Only one + * child is permitted under each custom tag specified so the limit represents
+ * the number of times the tag can be present in any object.
+ *
+ * <p>
+ * The tagname attribute indicates the name of the tag this method will handle + * in the {...@link UiBinder} template. If none is specified, the method name must
+ * begin with "add", and the tag is assumed to be the remaining characters
+ * (after the "add" prefix") entirely in lowercase.
+ *
+ * <p>
+ * For example, <code>
+ *
+ * @UiChild MyWidget#addCustomChild(Widget w) </code> and
+ *
+ *          <pre>
+ *          <p:MyWidget>
+ *          <p:customchild>
+ *          <g:SomeWidget />
+ *          </p:customchild>
+ *          </p:MyWidget>
+ * </pre> would invoke the <code>addCustomChild</code> function to add
+ *          an instance of SomeWidget.
+ */
+...@documented
+...@retention(RetentionPolicy.RUNTIME)
+...@target(ElementType.METHOD)
+public @interface UiChild {
+
+  int limit() default -1;
+
+  String tagname() default "";
+}
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/uibinder/elementparsers/UiChildParser.java Thu Sep 2 13:27:55 2010
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2010 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.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.dev.util.Pair;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
+import com.google.gwt.uibinder.rebind.model.OwnerFieldClass;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Parses any children of widgets that use the {...@link UiChild} annotation.
+ */
+public class UiChildParser implements ElementParser {
+
+  private String fieldName;
+
+  /**
+   * Mapping of child tag to the number of times it has been called
+   */
+ private Map<String, Integer> numCallsToChildMethod = new HashMap<String, Integer>();
+  private Map<String, Pair<JMethod, Integer>> uiChildMethods;
+  private UiBinderWriter writer;
+
+ public void parse(final XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    this.fieldName = fieldName;
+    this.writer = writer;
+
+    OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type,
+        writer.getLogger());
+
+    uiChildMethods = ownerFieldClass.getUiChildMethods();
+
+    // Parse children.
+    elem.consumeChildElements(new Interpreter<Boolean>() {
+      public Boolean interpretElement(XMLElement child)
+          throws UnableToCompleteException {
+        if (isValidChildElement(elem, child)) {
+          handleChild(child);
+          return true;
+        }
+        return false;
+      }
+    });
+  }
+
+  /**
+ * Checks if this call will go over the limit for the number of valid calls.
+   * If it won't, it will increment the number of calls made.
+   *
+   * @throws UnableToCompleteException
+   */
+  private void checkLimit(int limit, String tag, XMLElement toAdd)
+      throws UnableToCompleteException {
+    Integer priorCalls = numCallsToChildMethod.get(tag);
+    if (priorCalls == null) {
+      priorCalls = 0;
+    }
+    if (limit > 0 && priorCalls > 0 && priorCalls + 1 > limit) {
+ writer.die(toAdd, "Can only use the @UiChild tag " + tag + " " + limit
+          + " time(s).");
+    }
+    numCallsToChildMethod.put(tag, priorCalls + 1);
+  }
+
+  /**
+ * Process a child element that should be added using a {...@link UiChild} method
+   */
+ private void handleChild(XMLElement child) throws UnableToCompleteException {
+    String tag = child.getLocalName();
+    Pair<JMethod, Integer> methodPair = uiChildMethods.get(tag);
+    JMethod method = methodPair.left;
+    int limit = methodPair.right;
+ Iterator<XMLElement> children = child.consumeChildElements().iterator();
+
+    // If the UiChild tag has no children just return.
+    if (!children.hasNext()) {
+      return;
+    }
+    XMLElement toAdd = children.next();
+
+    if (!writer.isWidgetElement(toAdd)) {
+      writer.die(child, "Expecting only widgets in %s", child);
+    }
+
+    // Make sure that there is only one element per tag.
+    if (children.hasNext()) {
+ writer.die(toAdd, "Can only have one element per @UiChild parser tag.");
+    }
+
+    // Check that this element won't put us over the limit.
+    checkLimit(limit, tag, toAdd);
+
+    // Add the child using the @UiChild function
+    String[] parameters = makeArgsList(child, method, toAdd);
+
+    writer.addStatement("%1$s.%2$s(%3$s);", fieldName, method.getName(),
+        UiBinderWriter.asCommaSeparatedList(parameters));
+  }
+
+ private boolean isValidChildElement(XMLElement parent, XMLElement child) {
+    if (child != null && child.getNamespaceUri() != null
+        && child.getNamespaceUri().equals(parent.getNamespaceUri())
+        && uiChildMethods.containsKey(child.getLocalName())) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+ * Go through all of the given method's required attributes and consume them + * from the given element. Any attributes not required by the method are left + * untouched. If a parameter is not present in the element, it will be passed
+   * null.
+   *
+   * @param element The element to find the necessary attributes for the
+   *          parameters to the method.
+   * @param method The method to gather parameters for.
+   * @return The list of parameters to send to the function.
+   * @throws UnableToCompleteException
+   */
+  private String[] makeArgsList(XMLElement element, JAbstractMethod method,
+      XMLElement toAdd) throws UnableToCompleteException {
+    JParameter[] params = method.getParameters();
+    String[] args = new String[params.length];
+    args[0] = writer.parseElementToField(toAdd);
+
+    // First parameter is the child widget
+    for (int index = 1; index < params.length; index++) {
+      JParameter param = params[index];
+      String defaultValue = null;
+
+      if (param.getType().isPrimitive() != null) {
+ defaultValue = param.getType().isPrimitive().getUninitializedFieldExpression();
+      }
+      String value = element.consumeAttributeWithDefault(param.getName(),
+          defaultValue, param.getType());
+      args[index] = value;
+    }
+    return args;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/uibinder/elementparsers/UiChildParserTest.java Thu Sep 2 13:27:55 2010
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2010 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.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.uibinder.rebind.FieldWriter;
+
+import junit.framework.TestCase;
+
+import org.xml.sax.SAXParseException;
+
+/**
+ * Tests {...@link UiChildParser}.
+ */
+public class UiChildParserTest extends TestCase {
+  private static final String CHILDREN = "#CHILDREN#";
+ private static final String PARSED_TYPE = "com.google.gwt.user.client.ui.HasUiChildren";
+  private static final String UIBINDER = "<g:HasUiChildren>" + CHILDREN
+      + "</g:HasUiChildren>";
+  private ElementParserTester tester;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    MockJavaResource itemSubclass = new MockJavaResource(PARSED_TYPE) {
+      @Override
+      protected CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package com.google.gwt.user.client.ui;\n");
+        code.append("import com.google.gwt.uibinder.client.UiChild;\n");
+        code.append("public class HasUiChildren {\n");
+        code.append("  public HasUiChildren() {\n");
+        code.append("  }\n\n");
+        code.append("  @UiChild\n");
+        code.append("  void addChild(Object child) {\n");
+        code.append("  }\n\n");
+        code.append("  @UiChild(tagname=\"achild\")\n");
+        code.append("  void addNamedChild(Object child) {\n");
+        code.append("  }\n\n");
+        code.append("  @UiChild(tagname=\"alimitedchild\", limit=1)\n");
+ code.append(" void addLimitedChild(Object child, int param1) {\n");
+        code.append("  }\n\n");
+        code.append("}\n");
+        return code;
+      }
+    };
+    tester = new ElementParserTester(PARSED_TYPE, new UiChildParser(),
+        itemSubclass);
+  }
+
+  public void testAddChildWithParameters() throws SAXParseException,
+      UnableToCompleteException {
+    String b = UIBINDER.replaceAll(CHILDREN,
+        "<g:alimitedchild param1=\"1\"> <g:Label/> </g:alimitedchild>");
+
+    FieldWriter w = tester.parse(b.toString());
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("Only call was: " + tester.writer.statements.get(0),
+        "fieldName.addLimitedChild(<g:Label>, 1);",
+        tester.writer.statements.get(0));
+ assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testAddChildWithParametersMissing() throws SAXParseException,
+      UnableToCompleteException {
+    String b = UIBINDER.replaceAll(CHILDREN,
+        "<g:alimitedchild> <g:Label/> </g:alimitedchild>");
+    FieldWriter w = tester.parse(b);
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("Only call was: " + tester.writer.statements.get(0),
+        "fieldName.addLimitedChild(<g:Label>, 0);",
+        tester.writer.statements.get(0));
+ assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testAddLimitedChildInvalid() throws SAXParseException {
+    String b = UIBINDER.replaceAll(CHILDREN,
+        "  <g:alimitedchild param1=\"1\"> <g:Label/> </g:alimitedchild>"
+ + " <g:alimitedchild param1=\"2\"> <g:Label/> </g:alimitedchild>");
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException exception) {
+      assertEquals(1, tester.writer.statements.size());
+      assertEquals("Only call was: " + tester.writer.statements.get(0),
+          "fieldName.addLimitedChild(<g:Label>, 1);",
+          tester.writer.statements.get(0));
+    }
+  }
+
+  public void testAddNamedChild() throws SAXParseException,
+      UnableToCompleteException {
+    String b = UIBINDER.replaceAll(CHILDREN,
+        "<g:achild > <g:Label/> </g:achild>");
+
+    FieldWriter w = tester.parse(b.toString());
+    assertEquals(1, tester.writer.statements.size());
+    assertEquals("fieldName.addNamedChild(<g:Label>);",
+        tester.writer.statements.get(0));
+ assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testBadChild() throws SAXParseException,
+      UnableToCompleteException {
+    String b = UIBINDER.replaceAll(CHILDREN,
+        "<g:badtag> <g:Label/> </g:badtag>");
+
+    FieldWriter w = tester.parse(b);
+    assertEquals("No children should have been consumed.", 0,
+        tester.writer.statements.size());
+ assertNull("Parser should never set an initializer.", w.getInitializer());
+
+    // children tags should be lowercase only
+    b = UIBINDER.replaceAll(CHILDREN, "<g:Child />");
+    w = tester.parse(b);
+    assertEquals("No children should have been consumed.", 0,
+        tester.writer.statements.size());
+ assertNull("Parser should never set an initializer.", w.getInitializer());
+  }
+
+  public void testIncompatibleChild() throws SAXParseException,
+      UnableToCompleteException {
+ String b = UIBINDER.replaceAll(CHILDREN, "<g:child> <div/> </g:child>");
+
+    try {
+      FieldWriter w = tester.parse(b);
+      fail("Incompatible type should have thrown an error.");
+    } catch (UnableToCompleteException exception) {
+      assertEquals(0, tester.writer.statements.size());
+    }
+  }
+
+  public void testMultipleChildrenInvalid() throws SAXParseException,
+      UnableToCompleteException {
+    String b = UIBINDER.replaceAll(CHILDREN,
+        "<g:child ><g:Label/><g:Label/></g:child>");
+
+    try {
+      FieldWriter w = tester.parse(b.toString());
+      fail("Cannot have multiple children under an @UiChild tag.");
+    } catch (UnableToCompleteException exception) {
+      assertEquals(0, tester.writer.statements.size());
+    }
+  }
+
+  public void testNoParameterChild() throws SAXParseException,
+      UnableToCompleteException {
+ String b = UIBINDER.replaceAll(CHILDREN, "<g:child> <g:Label/> </g:child>");
+
+    FieldWriter w = tester.parse(b);
+    assertEquals(1, tester.writer.statements.size());
+ assertNull("Parser should never set an initializer.", w.getInitializer());
+    assertEquals("fieldName.addChild(<g:Label>);",
+        tester.writer.statements.get(0));
+  }
+}
=======================================
--- /trunk/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java Mon Nov 9 20:50:50 2009 +++ /trunk/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java Thu Sep 2 13:27:55 2010
@@ -15,7 +15,15 @@
  */
 package com.google.gwt.core.ext.typeinfo;

-import static com.google.gwt.core.ext.typeinfo.JniConstants.*;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_BOOLEAN;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_BYTE;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_CHAR;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_DOUBLE;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_FLOAT;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_INT;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_LONG;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_SHORT;
+import static com.google.gwt.core.ext.typeinfo.JniConstants.DESC_VOID;

 import java.util.HashMap;
 import java.util.Map;
@@ -25,19 +33,23 @@
  */
 public class JPrimitiveType extends JType {
   public static final JPrimitiveType BOOLEAN = create("boolean", "Boolean",
-      DESC_BOOLEAN);
- public static final JPrimitiveType BYTE = create("byte", "Byte", DESC_BYTE);
+      DESC_BOOLEAN, "false");
+ public static final JPrimitiveType BYTE = create("byte", "Byte", DESC_BYTE,
+      "0");
   public static final JPrimitiveType CHAR = create("char", "Character",
-      DESC_CHAR);
+      DESC_CHAR, "0");
   public static final JPrimitiveType DOUBLE = create("double", "Double",
-      DESC_DOUBLE);
+      DESC_DOUBLE, "0d");
   public static final JPrimitiveType FLOAT = create("float", "Float",
-      DESC_FLOAT);
- public static final JPrimitiveType INT = create("int", "Integer", DESC_INT); - public static final JPrimitiveType LONG = create("long", "Long", DESC_LONG);
+      DESC_FLOAT, "0f");
+ public static final JPrimitiveType INT = create("int", "Integer", DESC_INT,
+      "0");
+ public static final JPrimitiveType LONG = create("long", "Long", DESC_LONG,
+      "0L");
   public static final JPrimitiveType SHORT = create("short", "Short",
-      DESC_SHORT);
- public static final JPrimitiveType VOID = create("void", "Void", DESC_VOID);
+      DESC_SHORT, "0");
+ public static final JPrimitiveType VOID = create("void", "Void", DESC_VOID,
+      "null");

   private static Map<String, JPrimitiveType> map;

@@ -45,9 +57,10 @@
     return getMap().get(typeName);
   }

- private static JPrimitiveType create(String name, String boxedName, char jni) { + private static JPrimitiveType create(String name, String boxedName, char jni,
+      String defaultValue) {
     JPrimitiveType type = new JPrimitiveType(name, boxedName,
-        String.valueOf(jni));
+        String.valueOf(jni), defaultValue);
     Object existing = getMap().put(name, type);
     assert (existing == null);
     return type;
@@ -62,14 +75,18 @@

   private final String boxedName;

+  private final String defaultValue;
+
   private final String jni;

   private final String name;

-  private JPrimitiveType(String name, String boxedName, String jni) {
+  private JPrimitiveType(String name, String boxedName, String jni,
+      String defaultValue) {
     this.name = name;
     this.boxedName = boxedName;
     this.jni = jni;
+    this.defaultValue = defaultValue;
   }

   @Override
@@ -100,6 +117,10 @@
   public String getSimpleSourceName() {
     return name;
   }
+
+  public String getUninitializedFieldExpression() {
+    return defaultValue;
+  }

   @Override
   public JArrayType isArray() {
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java Tue Aug 31 07:18:57 2010 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java Thu Sep 2 13:27:55 2010
@@ -30,6 +30,7 @@
 import com.google.gwt.uibinder.elementparsers.BeanParser;
 import com.google.gwt.uibinder.elementparsers.ElementParser;
 import com.google.gwt.uibinder.elementparsers.IsEmptyParser;
+import com.google.gwt.uibinder.elementparsers.UiChildParser;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
 import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
 import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
@@ -646,10 +647,8 @@
    */
   public void setFieldInitializerAsConstructor(String fieldName,
       JClassType type, String... args) {
-    setFieldInitializer(
-        fieldName,
-        formatCode("new %s(%s)", type.getQualifiedSourceName(),
-            asCommaSeparatedList(args)));
+    setFieldInitializer(fieldName, formatCode("new %s(%s)",
+        type.getQualifiedSourceName(), asCommaSeparatedList(args)));
   }

   /**
@@ -846,6 +845,7 @@
      * something?
      */
     parsers.add(new AttributeMessageParser());
+    parsers.add(new UiChildParser());

     for (JClassType curType : getClassHierarchyBreadthFirst(type)) {
       try {
@@ -884,7 +884,8 @@
        * the @UiField annotated field in the owning class
        */
       if (!templateClass.isAssignableTo(fieldType)) {
- die("In @UiField %s, template field and owner field types don't match: %s is not assignable to %s",
+        die(
+ "In @UiField %s, template field and owner field types don't match: %s is not assignable to %s",
             ownerField.getName(), templateClass.getQualifiedSourceName(),
             fieldType.getQualifiedSourceName());
       }
@@ -899,8 +900,9 @@
        * direction of the assignability check and do no init.
        */
       if (!fieldType.isAssignableTo(templateClass)) {
- die("In UiField(provided = true) %s, template field and field types don't match: "
-            + "@UiField(provided=true)%s is not assignable to %s",
+        die(
+ "In UiField(provided = true) %s, template field and field types don't match: "
+                + "@UiField(provided=true)%s is not assignable to %s",
             ownerField.getName(), fieldType.getQualifiedSourceName(),
             templateClass.getQualifiedSourceName());
       }
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java Tue Dec 15 19:43:09 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java Thu Sep 2 13:27:55 2010
@@ -22,6 +22,8 @@
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.dev.util.Pair;
+import com.google.gwt.uibinder.client.UiChild;
 import com.google.gwt.uibinder.client.UiConstructor;
 import com.google.gwt.uibinder.rebind.MortalLogger;

@@ -63,11 +65,17 @@
     return clazz;
   }

+  private Set<String> ambiguousSetters;
+  private final MortalLogger logger;
   private final JClassType rawType;
private final Map<String, JMethod> setters = new HashMap<String, JMethod>();
-  private Set<String> ambiguousSetters;
+  /**
+ * Mapping from all of the @UiChild tags to their corresponding methods and
+   * limits on being called.
+   */
+ private final Map<String, Pair<JMethod, Integer>> uiChildren = new HashMap<String, Pair<JMethod, Integer>>();
+
   private JConstructor uiConstructor;
-  private final MortalLogger logger;

   /**
    * Default constructor. This is package-visible for testing only.
@@ -83,6 +91,7 @@

     findUiConstructor(forType);
     findSetters(forType);
+    findUiChildren(forType);
   }

   /**
@@ -111,6 +120,15 @@

     return setters.get(propertyName);
   }
+
+  /**
+   * Returns a list of methods annotated with @UiChild.
+   *
+   * @return a list of all add child methods
+   */
+  public Map<String, Pair<JMethod, Integer>> getUiChildMethods() {
+    return uiChildren;
+  }

   /**
    * Returns the constructor annotated with @UiConstructor, or null if none
@@ -234,6 +252,38 @@
       setters.put(propertyName, setter);
     }
   }
+
+  /**
+   * Scans the class to find all methods annotated with @UiChild
+   *
+   * @param ownerType the type of the owner class
+   * @throws UnableToCompleteException
+   */
+  private void findUiChildren(JClassType ownerType)
+      throws UnableToCompleteException {
+    JMethod[] methods = ownerType.getMethods();
+    while (ownerType != null) {
+      for (JMethod method : methods) {
+        UiChild annotation = method.getAnnotation(UiChild.class);
+        if (annotation != null) {
+          String tag = annotation.tagname();
+          int limit = annotation.limit();
+          if (tag.equals("")) {
+            String name = method.getName();
+            if (name.startsWith("add")) {
+              tag = name.substring(3).toLowerCase();
+            } else {
+              logger.die(method.getName()
+                  + " must either specify a UiChild tagname or begin "
+                  + "with \"add\".");
+            }
+          }
+          uiChildren.put(tag, Pair.create(method, limit));
+        }
+      }
+      ownerType = ownerType.getSuperclass();
+    }
+  }

   /**
* Finds the constructor annotated with @UiConcontructor if there is one, and
@@ -264,8 +314,7 @@
   private boolean isSetterMethod(JMethod method) {
     // All setter methods should be public void setSomething(...)
     return method.isPublic() && !method.isStatic()
-        && method.getName().startsWith("set")
-        && method.getName().length() > 3
+ && method.getName().startsWith("set") && method.getName().length()
3
         && method.getReturnType() == JPrimitiveType.VOID;
   }

=======================================
--- /trunk/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java Tue Dec 15 19:43:09 2009 +++ /trunk/user/test/com/google/gwt/uibinder/rebind/model/OwnerFieldClassTest.java Thu Sep 2 13:27:55 2010
@@ -22,6 +22,8 @@
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.dev.util.Pair;
+import com.google.gwt.uibinder.client.UiChild;
 import com.google.gwt.uibinder.client.UiConstructor;
 import com.google.gwt.uibinder.rebind.JClassTypeAdapter;
 import com.google.gwt.uibinder.rebind.MortalLogger;
@@ -29,6 +31,8 @@

 import junit.framework.TestCase;

+import java.util.Map;
+
 /**
  * Tests for descriptors of potential owner field classes.
  */
@@ -68,7 +72,8 @@
   /**
    * Class with lots of setters for testing.
    */
-  @SuppressWarnings("unused") // We know these methods are unused
+  @SuppressWarnings("unused")
+  // We know these methods are unused
   private static class SettersTestClass {
     // No ambiguity in these setters
     public void setBlaBla(int x) {
@@ -118,7 +123,7 @@
     void setNothing(int x) {
       throw new UnsupportedOperationException("Should never get called");
     }
-
+
     public void set() {
     }

@@ -136,8 +141,7 @@
   }

   public void testOwnerFieldClass_setters() throws Exception {
-    JClassType settersType =
-        gwtTypeAdapter.adaptJavaClass(SettersTestClass.class);
+ JClassType settersType = gwtTypeAdapter.adaptJavaClass(SettersTestClass.class);
     JClassType stringType = gwtTypeAdapter.adaptJavaClass(String.class);
OwnerFieldClass settersClass = OwnerFieldClass.getFieldClass(settersType,
         MortalLogger.NULL);
@@ -159,8 +163,7 @@
   }

   public void testOwnerFieldClass_ambiguousSetters() throws Exception {
-    JClassType settersType =
-        gwtTypeAdapter.adaptJavaClass(SettersTestClass.class);
+ JClassType settersType = gwtTypeAdapter.adaptJavaClass(SettersTestClass.class);
     JClassType stringType = gwtTypeAdapter.adaptJavaClass(String.class);
OwnerFieldClass settersClass = OwnerFieldClass.getFieldClass(settersType,
         MortalLogger.NULL);
@@ -182,7 +185,8 @@
   /**
    * Class with overridden setters for testing.
    */
-  @SuppressWarnings("unused") // We know these methods are unused
+  @SuppressWarnings("unused")
+  // We know these methods are unused
private static class OverriddenSettersTestClass extends SettersTestClass {
     // Simple override of parent method
     @Override
@@ -225,8 +229,7 @@
   }

   public void testOwnerFieldClass_overriddenSetters() throws Exception {
-    JClassType settersType =
-        gwtTypeAdapter.adaptJavaClass(OverriddenSettersTestClass.class);
+ JClassType settersType = gwtTypeAdapter.adaptJavaClass(OverriddenSettersTestClass.class);
     JClassType stringType = gwtTypeAdapter.adaptJavaClass(String.class);
OwnerFieldClass settersClass = OwnerFieldClass.getFieldClass(settersType,
         MortalLogger.NULL);
@@ -281,11 +284,94 @@

     gwtTypeAdapter.verifyAll();
   }
+
+  /**
+   * Class with a {...@link UiChild}-annotated methods.
+   */
+  @SuppressWarnings("unused")
+  // We know these methods are unused
+  private static class UiChildClass {
+    public UiChildClass() {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    @UiChild
+    void addChild(Object child) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    @UiChild(tagname = "second", limit = 4)
+    void doesNotStartWithAdd(Object child) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+  }
+
+  public void testOwnerFieldClass_withUiChildren() throws Exception {
+ JClassType parentType = gwtTypeAdapter.adaptJavaClass(UiChildClass.class);
+    OwnerFieldClass parentClass = OwnerFieldClass.getFieldClass(parentType,
+        MortalLogger.NULL);
+    assertEquals(parentType, parentClass.getRawType());
+
+ Map<String, Pair<JMethod, Integer>> childMethods = parentClass.getUiChildMethods();
+    assertNotNull(childMethods);
+    assertEquals(2, childMethods.size());
+
+    Pair<JMethod, Integer> childPair = childMethods.get("child");
+    assertEquals("addChild", childPair.left.getName());
+    assertEquals(Integer.valueOf(-1), childPair.right);
+
+    Pair<JMethod, Integer> secondPair = childMethods.get("second");
+    assertEquals("doesNotStartWithAdd", secondPair.left.getName());
+    assertEquals(Integer.valueOf(4), secondPair.right);
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  public void testOwnerFieldClass_withNoUiChildren() throws Exception {
+    JClassType parentType = gwtTypeAdapter.adaptJavaClass(Object.class);
+    OwnerFieldClass parentClass = OwnerFieldClass.getFieldClass(parentType,
+        MortalLogger.NULL);
+    assertEquals(parentType, parentClass.getRawType());
+
+ Map<String, Pair<JMethod, Integer>> childMethods = parentClass.getUiChildMethods();
+    assertNotNull(childMethods);
+    assertEquals(0, childMethods.size());
+
+    gwtTypeAdapter.verifyAll();
+  }
+
+  /**
+   * Class with a {...@link UiChild}-annotated methods.
+   */
+  @SuppressWarnings("unused")
+  // We know these methods are unused
+  private static class UiChildWithPoorMethodNames {
+    public UiChildWithPoorMethodNames() {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+
+    @UiChild
+    void poorlyNamedMethodWithoutTag(Object child) {
+      throw new UnsupportedOperationException("Should never get called");
+    }
+  }
+
+  public void testOwnerFieldClass_withBadlyNamedMethod() {
+ JClassType parentType = gwtTypeAdapter.adaptJavaClass(UiChildWithPoorMethodNames.class);
+    try {
+ OwnerFieldClass parentClass = OwnerFieldClass.getFieldClass(parentType,
+          MortalLogger.NULL);
+ fail("Class should error because @UiChild method has invalid name (and no tag specified).");
+    } catch (UnableToCompleteException expected) {
+      gwtTypeAdapter.verifyAll();
+    }
+  }

   /**
    * Class with a {...@link UiConstructor}-annotated constructor.
    */
-  @SuppressWarnings("unused") // We know these methods are unused
+  @SuppressWarnings("unused")
+  // We know these methods are unused
   private static class UiConstructorClass {
     @UiConstructor
     public UiConstructorClass(boolean visible) {
@@ -294,10 +380,9 @@
   }

   public void testOwnerFieldClass_withUiConstructor() throws Exception {
-    JClassType constructorsType =
-        gwtTypeAdapter.adaptJavaClass(UiConstructorClass.class);
-    OwnerFieldClass constructorsClass =
-        OwnerFieldClass.getFieldClass(constructorsType, MortalLogger.NULL);
+ JClassType constructorsType = gwtTypeAdapter.adaptJavaClass(UiConstructorClass.class);
+    OwnerFieldClass constructorsClass = OwnerFieldClass.getFieldClass(
+        constructorsType, MortalLogger.NULL);
     assertEquals(constructorsType, constructorsClass.getRawType());

     JConstructor constructor = constructorsClass.getUiConstructor();
@@ -315,7 +400,8 @@
    * Class with (disallowed) multiple constructors annotated with
    * {...@link UiConstructor}.
    */
-  @SuppressWarnings("unused") // We know these methods are unused
+  @SuppressWarnings("unused")
+  // We know these methods are unused
   private static class MultiUiConstructorsClass {
     @UiConstructor
     public MultiUiConstructorsClass(boolean visible) {
@@ -328,10 +414,8 @@
     }
   }

-  public void testOwnerFieldClass_withMultipleUiConstructors()
-      throws Exception {
-    JClassType constructorsType =
-        gwtTypeAdapter.adaptJavaClass(MultiUiConstructorsClass.class);
+ public void testOwnerFieldClass_withMultipleUiConstructors() throws Exception { + JClassType constructorsType = gwtTypeAdapter.adaptJavaClass(MultiUiConstructorsClass.class);

     try {
       OwnerFieldClass.getFieldClass(constructorsType, MortalLogger.NULL);
@@ -343,7 +427,7 @@

   /**
    * Asserts that the given method has the proper name and parameters.
-   *
+   *
    * @param method the actual method
    * @param methodName the expected method name
    * @param parameterTypes the expected parameter types

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to