Added style getters to UiRenderers

Now UiRendered templates with <ui:style>s can be retrieved with a getter.

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

Review by: rdayal@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11028 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 63241fa..fd6347c 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -1132,6 +1132,18 @@
   }
 
   /**
+   * Add call to {@code com.google.gwt.resources.client.CssResource#ensureInjected()}
+   * on each CSS resource field.
+   */
+  private void ensureInjectedCssFields() {
+    for (ImplicitCssResource css : bundleClass.getCssMethods()) {
+      String fieldName = css.getName();
+      FieldWriter cssField = fieldManager.require(fieldName);
+      cssField.addStatement("%s.ensureInjected();", fieldName);
+    }
+  }
+
+  /**
    * Evaluate whether all @UiField attributes are also defined in the template.
    * Dies if not.
    */
@@ -1364,13 +1376,10 @@
     IndentedWriter niceWriter = new IndentedWriter(new PrintWriter(stringWriter));
 
     if (isRenderer) {
+      ensureInjectedCssFields();
       writeRenderer(niceWriter, rootField);
     } else if (useLazyWidgetBuilders) {
-      for (ImplicitCssResource css : bundleClass.getCssMethods()) {
-        String fieldName = css.getName();
-        FieldWriter cssField = fieldManager.require(fieldName);
-        cssField.addStatement("%s.ensureInjected();", fieldName);
-      }
+      ensureInjectedCssFields();
       writeBinderForRenderableStrategy(niceWriter, rootField);
     } else {
       writeBinder(niceWriter, rootField);
@@ -1558,32 +1567,43 @@
   /**
    * Scan the base class for the getter methods. Assumes getters begin with
    * "get" and validates that each corresponds to a field declared with
-   * {@code ui:field}, it has a single parameter, the parameter type is
-   * assignable to {@code Element} and its return type is assignable to
-   * {@code Element}.
+   * {@code ui:field}. If the getter return type is assignable to
+   * {@code Element}, the getter must have a single parameter and the parameter
+   * must be assignable to {@code Element}. If the getter return type is assignable
+   * to {@code com.google.gwt.resources.client.CssResource}, the getter must
+   * have no parameters.
    */
   private void validateRendererGetters(JClassType owner) throws UnableToCompleteException {
     for (JMethod jMethod : owner.getInheritableMethods()) {
       String getterName = jMethod.getName();
       if (getterName.startsWith("get")) {
-        if (jMethod.getParameterTypes().length != 1) {
-          die("Getter %s must have exactly one parameter in %s", getterName,
-              owner.getQualifiedSourceName());
-        }
-        String elementClassName = com.google.gwt.dom.client.Element.class.getCanonicalName();
-        JClassType elementType = oracle.findType(elementClassName);
-        JClassType getterParamType =
-            jMethod.getParameterTypes()[0].getErasedType().isClassOrInterface();
-
-        if (!elementType.isAssignableFrom(getterParamType)) {
-          die("Getter %s must have exactly one parameter of type assignable to %s in %s",
-              getterName, elementClassName, owner.getQualifiedSourceName());
-        }
         String fieldName = getterToFieldName(getterName);
         FieldWriter field = fieldManager.lookup(fieldName);
-        if (field == null || !FieldWriterType.DEFAULT.equals(field.getFieldType())) {
-          die("%s does not match a \"ui:field='%s'\" declaration in %s", getterName, fieldName,
+        if (field == null || (!FieldWriterType.DEFAULT.equals(field.getFieldType())
+            && !FieldWriterType.GENERATED_CSS.equals(field.getFieldType()))) {
+          die("%s does not match a \"ui:field='%s'\" declaration in %s, "
+              + "or '%s' refers to something other than a ui:style"
+              + " or an HTML element in the template", getterName, fieldName,
+              owner.getQualifiedSourceName(), fieldName);
+        }
+        if (FieldWriterType.DEFAULT.equals(field.getFieldType())
+             && jMethod.getParameterTypes().length != 1) {
+          die("Field getter %s must have exactly one parameter in %s", getterName,
               owner.getQualifiedSourceName());
+        } else if (FieldWriterType.GENERATED_CSS.equals(field.getFieldType())
+            && jMethod.getParameterTypes().length != 0) {
+          die("Style getter %s must have no parameters in %s", getterName,
+              owner.getQualifiedSourceName());
+        } else if (jMethod.getParameterTypes().length == 1) {
+          String elementClassName = com.google.gwt.dom.client.Element.class.getCanonicalName();
+          JClassType elementType = oracle.findType(elementClassName);
+          JClassType getterParamType =
+              jMethod.getParameterTypes()[0].getErasedType().isClassOrInterface();
+
+          if (!elementType.isAssignableFrom(getterParamType)) {
+            die("Getter %s must have exactly one parameter of type assignable to %s in %s",
+                getterName, elementClassName, owner.getQualifiedSourceName());
+          }
         }
       } else if (!getterName.equals("render") && !getterName.equals("onBrowserEvent")
           && !getterName.equals("isParentOrRenderer")) {
@@ -2199,18 +2219,26 @@
       // public ElementSubclass getFoo(Element parent) {
       w.write("%s {", getter.getReadableDeclaration(false, false, false, false, true));
       w.indent();
-      String elementParameter = getter.getParameters()[0].getName();
       String getterFieldName = getterToFieldName(getter.getName());
-      // The non-root elements are found by id
-      if (!getterFieldName.equals(rootFieldName)) {
-        // return (ElementSubclass) findUiField(parent);
-        w.write("return (%s) findInnerField(%s, \"%s\", RENDERED_ATTRIBUTE);",
-            getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter,
-            getterFieldName);
+      // Is this a CSS resource field?
+      FieldWriter fieldWriter = fieldManager.lookup(getterFieldName);
+      if (FieldWriterType.GENERATED_CSS.equals(fieldWriter.getFieldType())) {
+        // return (CssResourceSubclass) get_styleField;
+        w.write("return (%s) %s;", getter.getReturnType().getErasedType().getQualifiedSourceName(),
+                FieldManager.getFieldGetter(getterFieldName));
       } else {
-        // return (ElementSubclass) findPreviouslyRendered(parent);
-        w.write("return (%s) findRootElement(%s, RENDERED_ATTRIBUTE);",
-            getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter);
+        // Else the non-root elements are found by id
+        String elementParameter = getter.getParameters()[0].getName();
+        if (!getterFieldName.equals(rootFieldName)) {
+          // return (ElementSubclass) findInnerField(parent, "foo", RENDERED_ATTRIBUTE);
+          w.write("return (%s) findInnerField(%s, \"%s\", RENDERED_ATTRIBUTE);",
+              getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter,
+              getterFieldName);
+        } else {
+          // return (ElementSubclass) findRootElement(parent);
+          w.write("return (%s) findRootElement(%s, RENDERED_ATTRIBUTE);",
+              getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter);
+        }
       }
       w.outdent();
       w.write("}");
diff --git a/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java b/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
index e8fb5f3..b9cd55f 100644
--- a/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
@@ -102,6 +102,17 @@
     }
   };
 
+  public static final MockJavaResource UI_STYLE = new MockJavaResource("foo.UiStyle") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;");
+      code.append("public interface UiStyle {");
+      code.append("}");
+      return code;
+    }
+  };
+
   protected static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder";
 
   protected static final W3cDomHelper docHelper = new W3cDomHelper(TreeLogger.NULL,
@@ -153,6 +164,7 @@
     resources.add(CLIENT_BUNDLE);
     resources.add(DIV_ELEMENT);
     resources.add(SPAN_ELEMENT);
+    resources.add(UI_STYLE);
     resources.add(FOO);
     resources.add(rendererClass);
     resources.addAll(Arrays.asList(otherClasses));
diff --git a/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java b/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
index ec5e469..97f821d 100644
--- a/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
@@ -26,6 +26,7 @@
 public class UiRendererValidationTest extends AbstractUiBinderWriterTest {
 
   private static String UI_XML = "<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>"
+      + "<ui:style field='styleField'>.foo {color:black;}</ui:style>"
       + "<ui:with field='withField' />"
       + "  <div ui:field='root'>"
       + "    <span ui:field='someField'><ui:text from='{withField.toString}'/></span>"
@@ -45,6 +46,7 @@
     declaredMethods.append("    public void render(SafeHtmlBuilder sb, foo.Foo withField);");
     declaredMethods.append("    public DivElement getRoot(Element foo);");
     declaredMethods.append("    public SpanElement getSomeField(Element bar);");
+    declaredMethods.append("    public UiStyle getStyleField();");
     init(UI_XML, generateRendererResource(declaredMethods));
     writer.parseDocument(doc, printWriter);
   }
@@ -74,7 +76,16 @@
     init(UI_XML, generateRendererResource(declaredMethods));
 
     assertParseFailure("Expected failure due to getter with no parameters.",
-        "Getter getRoot must have exactly one parameter in renderer.OwnerClass.Renderer");
+        "Field getter getRoot must have exactly one parameter in renderer.OwnerClass.Renderer");
+  }
+
+  public void testStyleGetterWithParameter() throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public UiStyle getStyleField(DivElement bar);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+
+    assertParseFailure("Expected failure due to style getter with a parameter.",
+        "Style getter getStyleField must have no parameters in renderer.OwnerClass.Renderer");
   }
 
   public void testGetterTooManyParameters() throws SAXParseException, UnableToCompleteException {
@@ -83,7 +94,7 @@
     init(UI_XML, generateRendererResource(declaredMethods));
 
     assertParseFailure("Expected failure due to bad getter signature.",
-        "Getter getRoot must have exactly one parameter in renderer.OwnerClass.Renderer");
+        "Field getter getRoot must have exactly one parameter in renderer.OwnerClass.Renderer");
   }
 
   public void testGetterUnknownField() throws SAXParseException, UnableToCompleteException {
@@ -91,7 +102,9 @@
     declaredMethods.append("    public DivElement getQuux(Element parent);");
     init(UI_XML, generateRendererResource(declaredMethods));
     assertParseFailure("Expected failure due to getter for an unexpected field name.",
-        "getQuux does not match a \"ui:field='quux'\" declaration in renderer.OwnerClass.Renderer");
+        "getQuux does not match a \"ui:field='quux'\" declaration in renderer.OwnerClass.Renderer,"
+            + " or 'quux' refers to something other than a ui:style or an HTML element in"
+            + " the template");
   }
 
   public void testRenderBadReturnType() throws SAXParseException, UnableToCompleteException {
@@ -171,6 +184,7 @@
         code.append("import com.google.gwt.dom.client.Element;\n");
         code.append("import com.google.gwt.dom.client.SpanElement;\n");
         code.append("import foo.Foo;\n");
+        code.append("import foo.UiStyle;\n");
         code.append("public class OwnerClass {");
         code.append("  public interface Renderer");
         code.append("      extends UiRenderer {");
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java b/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
index db69ab4..0474a0e 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
@@ -248,6 +248,14 @@
         spanNode2.getFirstChild());
   }
 
+  public void testStyleManipulation() {
+    SpanElement nameSpan = renderer.getNameSpan(docDiv);
+    assertEquals(renderer.getUiStyle().enabled(), nameSpan.getClassName());
+    nameSpan.replaceClassName(renderer.getUiStyle().enabled(),
+        renderer.getUiStyle().disabled());
+    assertEquals(renderer.getUiStyle().disabled(), nameSpan.getClassName());
+  }
+
   @Override
   protected void gwtTearDown() {
     docDiv.removeFromParent();
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
index 61758ff..12a6ebd 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
@@ -77,7 +77,19 @@
     }
   }
 
-  interface HtmlRenderer extends UiRenderer {
+  /**
+   * A style defined within the UiBinder file.
+   */
+  public interface UiStyle extends CssResource {
+    String enabled();
+    String disabled();
+  }
+
+  /**
+   * A UiRinder Cell renderer.
+   */
+  public interface HtmlRenderer extends UiRenderer {
+    UiStyle getUiStyle();
     SpanElement getNameSpan(Element owner);
     TableColElement getNarrowColumn(Element owner);
     DivElement getRoot(Element owner);
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml
index 9a1de11..e0eb3ba 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml
@@ -17,12 +17,16 @@
 
   <ui:with field="aValue"/>
   <ui:with field="aValueTwice"/>
+  <ui:style field="uiStyle"  type="com.google.gwt.uibinder.test.client.UiRendererUi.UiStyle">
+    .enabled { color:black; }
+    .disabled { color:gray; }
+  </ui:style>
 
   <div ui:field='root' class="{res.style.bodyColor} {res.style.bodyFont}"
       title="The title of this div is localizable">
     <ui:attribute name='title'/>
     <span><ui:text from="{constants.getText}" /></span>
-    Hello, <span ui:field="nameSpan"><ui:text from="{aValue.getBar}"/></span>.
+    Hello, <span ui:field="nameSpan" class="{uiStyle.enabled}"><ui:text from="{aValue.getBar}"/></span>.
     <ui:msg>How goes it?</ui:msg>
     <span/><span><ui:text from="{aValueTwice.getBar}{aValueTwice.getBar}"/></span>
       <h2 class="{res.style.bodyColor} {res.style.bodyFont}">Placeholders in localizables</h2>