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