Allows owner class to provide a parameterized type.

Fixes incorrect direction of isAssignable check when using @UiField(provided = true)

Also deletes some unused code: declareDomField no longer uses its element param, because ensureCurrentFieldAttached no longer uses its arg, etc.

Review at http://gwt-code-reviews.appspot.com/359801

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7940 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
index 8c2f3c1..4e7f502 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
@@ -37,7 +37,7 @@
       throws UnableToCompleteException {
     String fieldName = writer.declareFieldIfNeeded(elem);
     if (fieldName != null) {
-      String token = writer.declareDomField(fieldName, element);
+      String token = writer.declareDomField(fieldName);
 
       if (elem.hasAttribute("id")) {
         writer.die(String.format(
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
index 686cb2a..225133b 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
@@ -75,7 +75,7 @@
       // we'll reuse ids for any template rendered more than once.
       String idHolder = uiWriter.declareDomIdHolder();
       String childField = uiWriter.parseElementToField(elem);
-      uiWriter.ensureFieldAttached(fieldName);
+      uiWriter.ensureCurrentFieldAttached();
       
       String elementPointer = idHolder + "Element";
       uiWriter.addInitStatement(
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
index 912c5b7..c61f284 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -71,21 +71,6 @@
   }
 
   public void setInitializer(String initializer) {
-    if (this.initializer != null) {
-      throw new IllegalStateException(String.format(
-          "Second attempt to set initializer for field \"%s\", "
-              + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
-    }
-    setInitializerMaybe(initializer);
-  }
-
-  @Deprecated
-  public void setInitializerMaybe(String initializer) {
-    if (this.initializer != null && !this.initializer.equals(initializer)) {
-      throw new IllegalStateException(String.format(
-          "Attempt to change initializer for field \"%s\", "
-              + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
-    }
     this.initializer = initializer;
   }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
index 95e81aa..77456d5 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -74,9 +74,6 @@
    * Used to provide an initializer string to use instead of a
    * {@link com.google.gwt.core.client.GWT#create()} call. Note that this is an
    * RHS expression. Don't include the leading '=', and don't end it with ';'.
-   * <p>
-   * TODO(rjrjr) Should be able to make this a constructor argument when
-   * BundleAttributeParser dies.
    * 
    * @throws UnableToCompleteException
    * @throws IllegalStateException on second attempt to set the initializer
@@ -84,16 +81,6 @@
   void setInitializer(String initializer);
 
   /**
-   * @deprecated needed only by
-   *             {@link com.google.gwt.uibinder.attributeparsers.BundleAttributeParser}
-   *             , which will die soon
-   * @throws IllegalStateException if initializer in a later call doesn't match
-   *           earlier call
-   */
-  @Deprecated
-  void setInitializerMaybe(String initializer);
-
-  /**
    * Write the field delcaration.
    */
   void write(IndentedWriter w) throws UnableToCompleteException;
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
index bf2b7f0..b7aa445 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
@@ -16,6 +16,7 @@
 package com.google.gwt.uibinder.rebind;
 
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
 
 /**
  * Implementation of FieldWriter for fields whose type already exists (that is,
@@ -31,6 +32,12 @@
     if (type == null) {
       throw new IllegalArgumentException("type cannot be null");
     }
+
+    JGenericType genericType = type.isGenericType();
+    if (genericType != null) {
+      type = genericType.getRawType();
+    }
+
     this.type = type;
   }
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index a013d27..53e7466 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -129,8 +129,7 @@
    * @param type the base type
    * @return a breadth-first collection of its type hierarchy
    */
-  static Iterable<JClassType> getClassHierarchyBreadthFirst(
-      JClassType type) {
+  static Iterable<JClassType> getClassHierarchyBreadthFirst(JClassType type) {
     LinkedList<JClassType> list = new LinkedList<JClassType>();
     LinkedList<JClassType> q = new LinkedList<JClassType>();
 
@@ -324,13 +323,10 @@
    * call and assign the Element instance to its field.
    * 
    * @param fieldName The name of the field being declared
-   * @param parentElementExpression an expression to be evaluated at runtime,
-   *          which will return an Element that is an ancestor of this one
-   *          (needed by the getElementById call mentioned above).
    */
-  public String declareDomField(String fieldName, String parentElementExpression)
+  public String declareDomField(String fieldName)
       throws UnableToCompleteException {
-    ensureAttached(parentElementExpression);
+    ensureAttached();
     String name = declareDomIdHolder();
     setFieldInitializer(fieldName, "null");
     addInitStatement(
@@ -444,10 +440,9 @@
   /**
    * Ensure that the specified element is attached to the DOM.
    * 
-   * @param element variable name of element to be attached
    * @see #beginAttachedSection(String)
    */
-  public void ensureAttached(String element) {
+  public void ensureAttached() {
     String attachSectionElement = attachSectionElements.getFirst();
     if (!attachedVars.containsKey(attachSectionElement)) {
       String attachedVar = "attachRecord" + nextAttachVar;
@@ -464,11 +459,10 @@
    * an object that responds to Element getElement(). Convenience wrapper for
    * {@link ensureAttached}<code>(field + ".getElement()")</code>.
    * 
-   * @param field variable name of the field to be attached
    * @see #beginAttachedSection(String)
    */
-  public void ensureFieldAttached(String field) {
-    ensureAttached(field + ".getElement()");
+  public void ensureCurrentFieldAttached() {
+    ensureAttached();
   }
 
   /**
@@ -723,7 +717,7 @@
    * 
    * @throws UnableToCompleteException
    */
-  private void ensureAttachmentCleanedUp() throws UnableToCompleteException {
+  private void ensureAttachmentCleanedUp() {
     if (!attachSectionElements.isEmpty()) {
       throw new IllegalStateException("Attachments not cleaned up: "
           + attachSectionElements);
@@ -856,15 +850,34 @@
       throws UnableToCompleteException {
     JClassType fieldType = ownerField.getType().getRawType();
 
-    if (!templateClass.isAssignableTo(fieldType)) {
-      die("Template field and owner field types don't match: %s != %s",
-          templateClass.getQualifiedSourceName(),
-          fieldType.getQualifiedSourceName());
-    }
-
     if (!ownerField.isProvided()) {
+      /*
+       * Normally check that the type the template created can be slammed into
+       * 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",
+            ownerField.getName(), templateClass.getQualifiedSourceName(),
+            fieldType.getQualifiedSourceName());
+      }
+      /*
+       * And initialize the field
+       */
       niceWriter.write("owner.%1$s = %2$s;", ownerField.getName(),
           templateField);
+    } else {
+      /*
+       * But with @UiField(provided=true) the user builds it, so reverse the
+       * 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",
+            ownerField.getName(), fieldType.getQualifiedSourceName(),
+            templateClass.getQualifiedSourceName());
+      }
     }
   }
 
@@ -979,7 +992,8 @@
 
     // createAndBindUi method
     w.write("public %s createAndBindUi(final %s owner) {",
-        uiRootType.getParameterizedQualifiedSourceName(), uiOwnerType.getParameterizedQualifiedSourceName());
+        uiRootType.getParameterizedQualifiedSourceName(),
+        uiOwnerType.getParameterizedQualifiedSourceName());
     w.indent();
     w.newline();
 
@@ -1010,7 +1024,9 @@
 
   private void writeClassOpen(IndentedWriter w) {
     w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
-        uiRootType.getParameterizedQualifiedSourceName(), uiOwnerType.getParameterizedQualifiedSourceName(), baseClass.getParameterizedQualifiedSourceName());
+        uiRootType.getParameterizedQualifiedSourceName(),
+        uiOwnerType.getParameterizedQualifiedSourceName(),
+        baseClass.getParameterizedQualifiedSourceName());
     w.indent();
   }
 
@@ -1039,11 +1055,9 @@
         String fieldName = ownerField.getName();
         FieldWriter fieldWriter = fieldManager.lookup(fieldName);
 
-        // TODO(hermes) This can be null due to http://b/1836504. If that
-        // is fixed properly, a null fieldWriter will be an error
-        // (would that be a user error or a runtime error? Not sure)
+        // TODO why can this be null?
         if (fieldWriter != null) {
-          fieldManager.lookup(fieldName).setInitializerMaybe(
+          fieldManager.lookup(fieldName).setInitializer(
               formatCode("owner.%1$s", fieldName));
         }
       }
diff --git a/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java
index d31db80..1a6213c 100644
--- a/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java
+++ b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.java
@@ -31,16 +31,16 @@
 
   static final Binder binder = GWT.create(Binder.class);
 
-  @UiField
-  Abstract<?> a;
-
+  @UiField Abstract<?> fromFactory;
+  @UiField(provided = true) Specific<T> provided = new Specific<T>();
+  
   ParameterizedWidget() {
     initWidget(binder.createAndBindUi(this));
   }
   
   @UiFactory
-  Abstract<?> createA() {
-    return new Abstract<String>() {
+  Abstract<T> createA() {
+    return new Abstract<T>() {
     };
   }
 }
diff --git a/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.ui.xml b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.ui.xml
index 5a707f8..0c6fe45 100644
--- a/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/ParameterizedWidget.ui.xml
@@ -1,4 +1,8 @@
 <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
-    xmlns:t="urn:import:com.google.gwt.uibinder.test.client">
- <t:Abstract ui:field='a'></t:Abstract>
-</ui:UiBinder>
\ No newline at end of file
+  xmlns:g="urn:import:com.google.gwt.user.client.ui"
+  xmlns:t="urn:import:com.google.gwt.uibinder.test.client">
+  <g:FlowPanel>
+    <t:Abstract ui:field='fromFactory' />
+    <t:Abstract ui:field='provided' />
+  </g:FlowPanel>
+</ui:UiBinder>
diff --git a/user/test/com/google/gwt/uibinder/test/client/Specific.java b/user/test/com/google/gwt/uibinder/test/client/Specific.java
new file mode 100644
index 0000000..dbeb021
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/Specific.java
@@ -0,0 +1,26 @@
+/*
+ * 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.test.client;
+
+/**
+ * A specific implementation of {@link Abstract}, used to test generics in
+ * UiBinder. See {@link ParameterizedWidget}.
+ * 
+ * @param <T> a param type
+ */
+public class Specific<T> extends Abstract<T> {
+
+}
diff --git a/user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java b/user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java
index 249c3db..398c95a 100644
--- a/user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java
+++ b/user/test/com/google/gwt/uibinder/test/client/TestParameterizedWidgets.java
@@ -28,6 +28,6 @@
 
   public void testHappy() {
     ParameterizedWidget<String> ui = new ParameterizedWidget<String>();
-    assertNotNull(ui.a);
+    assertNotNull(ui.fromFactory);
   }
 }