Introduces support for DataResource to UiBinder, and you know what
that means: custom mouse cursors! Shaped like hearts!

Review by bobv@google.com

http://gwt-code-reviews.appspot.com/77808

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6341 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
index b134c8b..2fbc076 100644
--- a/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
@@ -19,12 +19,14 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.resources.client.CssResource.Strict;
 import com.google.gwt.resources.client.ImageResource.ImageOptions;
 import com.google.gwt.resources.client.ImageResource.RepeatStyle;
 import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
 import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
+import com.google.gwt.uibinder.rebind.model.ImplicitDataResource;
 import com.google.gwt.uibinder.rebind.model.ImplicitImageResource;
 
 /**
@@ -38,10 +40,11 @@
   private final TypeOracle oracle;
 
   private final JClassType clientBundleType;
-  private final JClassType strictAnnotationType;
+  private final JClassType dataResourceType;
   private final JClassType imageOptionType;
   private final JClassType imageResourceType;
   private final JClassType repeatStyleType;
+  private final JClassType strictAnnotationType;
 
   public BundleWriter(ImplicitClientBundle bundleClass,
       PrintWriterManager writerManager, TypeOracle oracle,
@@ -53,10 +56,11 @@
     this.oracle = oracle;
 
     clientBundleType = oracle.findType(ClientBundle.class.getName());
-    strictAnnotationType = oracle.findType(Strict.class.getCanonicalName());
+    dataResourceType = oracle.findType(DataResource.class.getCanonicalName());
     imageOptionType = oracle.findType(ImageOptions.class.getCanonicalName());
     imageResourceType = oracle.findType(ImageResource.class.getCanonicalName());
     repeatStyleType = oracle.findType(RepeatStyle.class.getCanonicalName());
+    strictAnnotationType = oracle.findType(Strict.class.getCanonicalName());
   }
 
   public void write() throws UnableToCompleteException {
@@ -76,25 +80,34 @@
     }
 
     // Imports
+    writer.write("import %s;", clientBundleType.getQualifiedSourceName());
+    // TODO(rjrjr,bobv) Remove the strict import when strict becomes default, RSN
+    writer.write("import %s;", strictAnnotationType.getQualifiedSourceName());
+    writer.write("import %s;", dataResourceType.getQualifiedSourceName());
     writer.write("import %s;", imageResourceType.getQualifiedSourceName());
     writer.write("import %s;", imageOptionType.getQualifiedSourceName());
-    writer.write("import %s;", clientBundleType.getQualifiedSourceName());
-    writer.write("import %s;", strictAnnotationType.getQualifiedSourceName());
     writer.newline();
 
     // Open interface
     writer.write("public interface %s extends ClientBundle {",
         bundleClass.getClassName());
     writer.indent();
-
+    
     // Write css methods
     for (ImplicitCssResource css : bundleClass.getCssMethods()) {
+      // TODO(rjrjr,bobv) Remove the @Strict when strict becomes default, RSN
       writer.write("@Strict @Source(\"%s\")", css.getSource());
       writer.write("%s %s();", css.getClassName(), css.getName());
       writer.newline();
     }
+    
+    // Write data methods
+    for (ImplicitDataResource data : bundleClass.getDataMethods()) {
+      writer.write("@Source(\"%s\")", data.getSource());
+      writer.write("%s %s();", dataResourceType.getName(), data.getName());
+      writer.newline();
+    }
 
-    writer.newline();
     writeImageMethods();
 
     // Close interface.
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index c5aa2a7..b3a895d 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -20,11 +20,13 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.resources.client.ImageResource.RepeatStyle;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
 import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
 import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
+import com.google.gwt.uibinder.rebind.model.ImplicitDataResource;
 import com.google.gwt.uibinder.rebind.model.ImplicitImageResource;
 import com.google.gwt.uibinder.rebind.model.OwnerField;
 
@@ -50,6 +52,7 @@
   private final ImplicitClientBundle bundleClass;
   private final JClassType cssResourceType;
   private final JClassType imageResourceType;
+  private final JClassType dataResourceType;
 
   public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter,
       FieldManager fieldManager, TypeOracle oracle,
@@ -61,6 +64,7 @@
     this.bundleClass = bundleClass;
     this.cssResourceType = oracle.findType(CssResource.class.getCanonicalName());
     this.imageResourceType = oracle.findType(ImageResource.class.getCanonicalName());
+    this.dataResourceType = oracle.findType(DataResource.class.getCanonicalName());
   }
 
   /**
@@ -70,9 +74,7 @@
   public String parse(XMLElement elem) throws UnableToCompleteException {
     // TODO(rjrjr) Clearly need to break these find* methods out into their own
     // parsers, an so need a registration scheme for uibinder-specific parsers
-    findStyles(elem);
     findResources(elem);
-    findImages(elem);
     messagesWriter.findMessagesConfig(elem);
     XMLElement uiRoot = elem.consumeSingleChildElement();
     return writer.parseElementToField(uiRoot);
@@ -111,14 +113,25 @@
   }
 
   /**
+   * Interprets <ui:data> elements
+   */
+  private void createData(XMLElement elem)  throws UnableToCompleteException {
+    String name = elem.consumeRequiredAttribute(FIELD_ATTRIBUTE);
+    String source = elem.consumeRequiredAttribute(SOURCE_ATTRIBUTE);
+    ImplicitDataResource dataMethod = bundleClass.createDataResource(name, source);
+    FieldWriter field = fieldManager.registerField(dataResourceType,
+        dataMethod.getName());
+    field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(),
+        dataMethod.getName()));
+  }
+  
+  /**
    * Interprets <ui:image> elements
    */
   private void createImage(XMLElement elem) throws UnableToCompleteException {
     String name = elem.consumeRequiredAttribute(FIELD_ATTRIBUTE);
-    String source = elem.consumeAttribute(SOURCE_ATTRIBUTE, null); // @source is
-                                                                   // optional
-                                                                   // on
-                                                                   // ImageResource
+    // @source is optional on ImageResource
+    String source = elem.consumeAttribute(SOURCE_ATTRIBUTE, null);
 
     Boolean flipRtl = null;
     if (elem.hasAttribute(FLIP_RTL_ATTRIBUTE)) {
@@ -209,50 +222,32 @@
         cssMethod.getName()));
   }
 
-  private void findImages(XMLElement binderElement)
-      throws UnableToCompleteException {
-    binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
-      public Boolean interpretElement(XMLElement elem)
-          throws UnableToCompleteException {
-        if (!(writer.isBinderElement(elem) && "image".equals(elem.getLocalName()))) {
-          return false; // Not of interest, do not consume
-        }
-
-        createImage(elem);
-
-        return true; // Yum
-      }
-    });
-  }
-
   private void findResources(XMLElement binderElement)
       throws UnableToCompleteException {
     binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
       public Boolean interpretElement(XMLElement elem)
           throws UnableToCompleteException {
-        if (!(writer.isBinderElement(elem) && "with".equals(elem.getLocalName()))) {
-          return false; // Not of interest, do not consume
+
+        if (writer.isBinderElement(elem)) {
+          final String localName = elem.getLocalName();
+          if ("with".equals(localName)) {
+            createResource(elem);
+          } 
+          else if ("image".equals(localName)) {
+            createImage(elem);
+          }
+          else if ("style".equals(localName)) {
+            createStyle(elem);
+          }
+          else if ("data".equals(localName)) {
+            createData(elem);
+          }
+          else {
+            writer.die("%s unrecognized, or not appropriate as a top level element");
+          }
+          return true;
         }
-
-        createResource(elem);
-
-        return true; // Yum
-      }
-    });
-  }
-
-  private void findStyles(XMLElement binderElement)
-      throws UnableToCompleteException {
-    binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
-      public Boolean interpretElement(XMLElement elem)
-          throws UnableToCompleteException {
-        if (!(writer.isBinderElement(elem) && "style".equals(elem.getLocalName()))) {
-          return false; // Not of interest, do not consume
-        }
-
-        createStyle(elem);
-
-        return true; // consume
+        return false; // leave it be
       }
     });
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
index 7727606..937f09a 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
@@ -20,16 +20,17 @@
 import com.google.gwt.uibinder.rebind.MortalLogger;
 
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
  * Models the ClientBundle to be generated from a ui.xml.
  */
 public class ImplicitClientBundle {
-
-  private final Set<ImplicitCssResource> cssMethods = new HashSet<ImplicitCssResource>();
-  private final Set<ImplicitImageResource> imageMethods = new HashSet<ImplicitImageResource>();
+  // LinkedHashSets for consistent order across recompiles
+  private final LinkedHashSet<ImplicitCssResource> cssMethods = new LinkedHashSet<ImplicitCssResource>();
+  private final LinkedHashSet<ImplicitImageResource> imageMethods = new LinkedHashSet<ImplicitImageResource>();
+  private final LinkedHashSet<ImplicitDataResource> dataMethods =  new LinkedHashSet<ImplicitDataResource>();
   private final String packageName;
   private final String className;
   private final String fieldName;
@@ -54,7 +55,7 @@
   /**
    * Called to declare a new CssResource accessor on this bundle.
    * 
-   * @param name the method name
+   * @param name the method name and the ui:field name
    * @param source path to the .css file resource
    * @param extendedInterface the public interface implemented by this
    *          CssResource, or null
@@ -70,10 +71,24 @@
   }
 
   /**
+   * Called to declare a new DataResource accessor on this bundle. 
+   * All params must be non-null
+   * 
+   * @param name the method name and the ui:field name
+   * @param source path to the resource
+   * @return
+   */
+  public ImplicitDataResource createDataResource(String name, String source) {
+    ImplicitDataResource data = new ImplicitDataResource(name, source);
+    dataMethods.add(data);
+    return data;
+  }
+
+  /**
    * Called to declare a new ImageResource accessor on this bundle.
    * 
-   * @param name the method name
-   * @param source path the image resource, or null if none was specified
+   * @param name the method name and the ui:field name
+   * @param source path to the image resource, or null if none was specified
    * @param flipRtl value for the flipRtl ImageOption, or null if none was
    *          specified
    * @param repeatStyle value of the RepeatStyle ImageOption, or null if none
@@ -95,10 +110,14 @@
     return Collections.unmodifiableSet(cssMethods);
   }
 
+  public Set<ImplicitDataResource> getDataMethods() {
+    return Collections.unmodifiableSet(dataMethods);
+  }
+
   public String getFieldName() {
     return fieldName;
   }
-
+  
   public Set<ImplicitImageResource> getImageMethods() {
     return Collections.unmodifiableSet(imageMethods);
   }
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
index b940167..46b3948 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
@@ -44,7 +44,7 @@
   private final MortalLogger logger;
   private File generatedFile;
 
-  public ImplicitCssResource(String packageName, String className, String name,
+  ImplicitCssResource(String packageName, String className, String name,
       String source, JClassType extendedInterface, String body,
       MortalLogger logger) {
     this.packageName = packageName;
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitDataResource.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitDataResource.java
new file mode 100644
index 0000000..428ea8d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitDataResource.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.rebind.model;
+
+/**
+ * Models a method returning a DataResource on a generated ClientBundle.
+ */
+public class ImplicitDataResource {
+  private final String name;
+  private final String source;
+
+  ImplicitDataResource(String name, String source) {
+    this.name = name;
+    this.source = source;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public String getSource() {
+    return source;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitImageResource.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitImageResource.java
index 4ae80d9..066609b 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitImageResource.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitImageResource.java
@@ -26,7 +26,7 @@
   private final Boolean flipRtl;
   private final RepeatStyle repeatStyle;
 
-  public ImplicitImageResource(
+  ImplicitImageResource(
       String name, String source, Boolean flipRtl, RepeatStyle repeatStyle) {
     this.name = name;
     this.source = source;
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
index ebe0b27..4126a0e 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -25,6 +25,7 @@
 import com.google.gwt.dom.client.StyleInjector;
 import com.google.gwt.dom.client.TableElement;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.resources.client.CssResource.Shared;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -119,6 +120,7 @@
   @UiField ImageResource prettyTilingImage;
   @UiField Image babyWidget;
   @UiField ParagraphElement simpleSpriteParagraph;
+  @UiField DataResource heartCursorResource;
 
   public WidgetBasedUi() {
     this.bundledLabel = new Label();
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
index 2a12e97..f13963f 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -96,6 +96,14 @@
   }
 </ui:style>
 
+<ui:style field='cursorifficStyle'>
+  @url cursor heartCursorResource;
+  .cursor {
+    cursor:cursor,pointer;
+  }
+</ui:style>
+<ui:data field='heartCursorResource' src='heart.cur'/>
+
 <ui:style field='mySpritelyStyle'>
   @sprite .simpleSprite { 
     gwt-image: "prettyImage"; 
@@ -174,14 +182,18 @@
       </p>
 
       <p>I bet you like babies in your Image widgets.</p>
+      <div class='{cursorifficStyle.cursor}'>
       <gwt:Image ui:field='babyWidget' resource='{prettyImage}'/>
+      </div>
 
       <p ui:field='simpleSpriteParagraph' 
-          class='{mySpritelyStyle.simpleSprite} {mySpritelyStyle.garish}' >
+          class='{mySpritelyStyle.simpleSprite} {mySpritelyStyle.garish}
+            {cursorifficStyle.cursor}' >
         And sprites too
       </p>
 
-      <p class='{mySpritelyStyle.tilingSprite} {mySpritelyStyle.garish}'>
+      <p class='{mySpritelyStyle.tilingSprite} {mySpritelyStyle.garish}
+          {cursorifficStyle.cursor}'>
         Well how do you like  <br/>
         tiled sprited images...of babies!! <br/>
         Well of course you do. Who wouldn't? 
@@ -442,10 +454,12 @@
     <b>HTML:</b>
     <pre style="border: 1px dashed #666; padding: 5px 0;">
   &lt;div id="gwt-debug-joe" 
-      class="gwt-Label newStyle anotherStyle gwt-Label-dependentStyle gwt-Label-anotherDependentStyle"&gt;
+      class="gwt-Label newStyle anotherStyle gwt-Label-dependentStyle 
+        gwt-Label-anotherDependentStyle"&gt;
     A label with a debug id
   &lt;/div&gt;
-  &lt;button id="gwt-debug-myButton" class="gwt-Button buttonStyle" tabindex="0" type="button"&gt;Go&lt;/button&gt;</pre>
+  &lt;button id="gwt-debug-myButton" class="gwt-Button buttonStyle" tabindex="0" 
+    type="button"&gt;Go&lt;/button&gt;</pre>
 
     <gwt:FlowPanel>
       <gwt:Label ui:field="lblDebugId" debugId="joe" addStyleNames="newStyle, anotherStyle" addStyleDependentNames="dependentStyle, anotherDependentStyle">
diff --git a/user/src/com/google/gwt/uibinder/sample/client/heart.cur b/user/src/com/google/gwt/uibinder/sample/client/heart.cur
new file mode 100644
index 0000000..05ecd3e
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/heart.cur
Binary files differ
diff --git a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
index 1f426ea..6960e39 100644
--- a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
@@ -25,6 +25,7 @@
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.DisclosurePanel;
 import com.google.gwt.user.client.ui.DockPanel;
@@ -352,6 +353,16 @@
     assertEquals(widgetUi.prettyImage.getLeft(), widgetUi.babyWidget.getOriginLeft());
   }
 
+  interface HeartBundle extends ClientBundle {
+    @Source("heart.cur")
+    DataResource heart();
+  }
+  
+  public void testDataResource() {
+    HeartBundle b = GWT.create(HeartBundle.class);
+    assertEquals(b.heart().getUrl(), widgetUi.heartCursorResource.getUrl());
+  }
+  
   public void testSpritedElement() {
     assertEquals(widgetUi.prettyImage.getWidth(), widgetUi.simpleSpriteParagraph.getOffsetWidth());
     assertEquals(widgetUi.prettyImage.getHeight(), widgetUi.simpleSpriteParagraph.getOffsetHeight());