Add <ui:import> to UiBinder to allow access to static fields.
Patch by: bobv
Review by: jgw
Suggested by: sonnyf

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8595 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index 8293a35..b950a24 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -17,7 +17,10 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.resources.client.DataResource;
@@ -39,28 +42,35 @@
 public class UiBinderParser {
 
   private enum Resource {
-    data {
+    DATA {
       @Override
       void create(UiBinderParser parser, XMLElement elem)
           throws UnableToCompleteException {
         parser.createData(elem);
       }
     },
-    image {
+    IMAGE {
       @Override
       void create(UiBinderParser parser, XMLElement elem)
           throws UnableToCompleteException {
         parser.createImage(elem);
       }
     },
-    style {
+    IMPORT {
+      @Override
+      void create(UiBinderParser parser, XMLElement elem)
+          throws UnableToCompleteException {
+        parser.createImport(elem);
+      }
+    },
+    STYLE {
       @Override
       void create(UiBinderParser parser, XMLElement elem)
           throws UnableToCompleteException {
         parser.createStyle(elem);
       }
     },
-    with {
+    WITH {
       @Override
       void create(UiBinderParser parser, XMLElement elem)
           throws UnableToCompleteException {
@@ -74,8 +84,9 @@
 
   private static final String FLIP_RTL_ATTRIBUTE = "flipRtl";
   private static final String FIELD_ATTRIBUTE = "field";
-  private static final String SOURCE_ATTRIBUTE = "src";
   private static final String REPEAT_STYLE_ATTRIBUTE = "repeatStyle";
+  private static final String SOURCE_ATTRIBUTE = "src";
+  private static final String TYPE_ATTRIBUTE = "type";
 
   // TODO(rjrjr) Make all the ElementParsers receive their dependencies via
   // constructor like this one does, and make this an ElementParser. I want
@@ -130,7 +141,7 @@
 
   private JClassType consumeCssResourceType(XMLElement elem)
       throws UnableToCompleteException {
-    String typeName = elem.consumeRawAttribute("type", null);
+    String typeName = elem.consumeRawAttribute(TYPE_ATTRIBUTE, null);
     if (typeName == null) {
       return cssResourceType;
     }
@@ -140,7 +151,7 @@
 
   private JClassType consumeTypeAttribute(XMLElement elem)
       throws UnableToCompleteException {
-    String resourceTypeName = elem.consumeRequiredRawAttribute("type");
+    String resourceTypeName = elem.consumeRequiredRawAttribute(TYPE_ATTRIBUTE);
 
     JClassType resourceType = oracle.findType(resourceTypeName);
     if (resourceType == null) {
@@ -194,6 +205,49 @@
   }
 
   /**
+   * Process <code>&lt;ui:import field="com.example.Blah.CONSTANT"></code>.
+   */
+  private void createImport(XMLElement elem) throws UnableToCompleteException {
+    String rawFieldName = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE);
+    if (elem.getAttributeCount() > 0) {
+      writer.die(elem, "Should only find attribute \"%s\"", FIELD_ATTRIBUTE);
+    }
+
+    int idx = rawFieldName.lastIndexOf('.');
+    if (idx < 1) {
+      writer.die(elem, "Attribute %s does not look like a static import "
+          + "reference", FIELD_ATTRIBUTE);
+    }
+    String enclosingName = rawFieldName.substring(0, idx);
+    String constantName = rawFieldName.substring(idx + 1);
+
+    JClassType enclosingType = oracle.findType(enclosingName);
+    if (enclosingType == null) {
+      writer.die(elem, "Unable to locate type %s", enclosingName);
+    }
+
+    if ("*".equals(constantName)) {
+      for (JField field : enclosingType.getFields()) {
+        if (!field.isStatic()) {
+          continue;
+        } else if (field.isPublic()) {
+          // OK
+        } else if (field.isProtected() || field.isPrivate()) {
+          continue;
+        } else if (!enclosingType.getPackage().equals(
+            writer.getOwnerClass().getOwnerType().getPackage())) {
+          // package-protected in another package
+          continue;
+        }
+        createSingleImport(elem, enclosingType, enclosingName + "."
+            + field.getName(), field.getName());
+      }
+    } else {
+      createSingleImport(elem, enclosingType, rawFieldName, constantName);
+    }
+  }
+
+  /**
    * Interprets <ui:with> elements.
    */
   private void createResource(XMLElement elem) throws UnableToCompleteException {
@@ -235,6 +289,31 @@
      */
   }
 
+  private void createSingleImport(XMLElement elem, JClassType enclosingType,
+      String rawFieldName, String constantName)
+      throws UnableToCompleteException {
+    JField field = enclosingType.findField(constantName);
+    if (field == null) {
+      writer.die(elem, "Unable to locate a field named %s in %s", constantName,
+          enclosingType.getQualifiedSourceName());
+    } else if (!field.isStatic()) {
+      writer.die(elem, "Field %s in type %s is not static", constantName,
+          enclosingType.getQualifiedSourceName());
+    }
+
+    JType importType = field.getType();
+    JClassType fieldType;
+    if (importType instanceof JPrimitiveType) {
+      fieldType = oracle.findType(((JPrimitiveType) importType).getQualifiedBoxedSourceName());
+    } else {
+      fieldType = (JClassType) importType;
+    }
+
+    FieldWriter fieldWriter = fieldManager.registerField(fieldType,
+        constantName);
+    fieldWriter.setInitializer(rawFieldName);
+  }
+
   private void createStyle(XMLElement elem) throws UnableToCompleteException {
     String body = elem.consumeUnescapedInnerText();
     String[] source = elem.consumeRawArrayAttribute(SOURCE_ATTRIBUTE);
@@ -283,8 +362,8 @@
 
         if (writer.isBinderElement(elem)) {
           try {
-            Resource.valueOf(elem.getLocalName()).create(UiBinderParser.this,
-                elem);
+            Resource.valueOf(elem.getLocalName().toUpperCase()).create(
+                UiBinderParser.this, elem);
           } catch (IllegalArgumentException e) {
             writer.die(elem,
                 "Unknown tag %s, or is not appropriate as a top level element",
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
index f3a453f..94eac5e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
@@ -67,6 +67,8 @@
 
   private final MortalLogger logger;
 
+  private final JClassType ownerType;
+
   /**
    * Constructor.
    *
@@ -75,11 +77,16 @@
    */
   public OwnerClass(JClassType ownerType, MortalLogger logger) throws UnableToCompleteException {
     this.logger = logger;
+    this.ownerType = ownerType;
     findUiFields(ownerType);
     findUiFactories(ownerType);
     findUiHandlers(ownerType);
   }
 
+  public JClassType getOwnerType() {
+    return ownerType;
+  }
+
   /**
    * Returns the method annotated with @UiFactory which returns the given type.
    *
diff --git a/user/test/com/google/gwt/uibinder/test/client/Constants.java b/user/test/com/google/gwt/uibinder/test/client/Constants.java
new file mode 100644
index 0000000..e387a7a
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/test/client/Constants.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Used to test static imports in UiBinder templates.
+ */
+public class Constants {
+  /**
+   * Tests enum imports.
+   */
+  public enum MyEnum {
+    ENUM_1, ENUM_2;
+  }
+
+  /**
+   * Used to test a wildcard import.
+   */
+  public static class Inner {
+    String instance = "instance";
+    public static String CONST_BAR = "Bar";
+    static String CONST_BAZ = "Baz";
+    protected static String PROTECTED = "protected";
+    @SuppressWarnings("unused")
+    private static String PRIVATE = "private";
+  }
+
+  public static String CONST_FOO = "Foo";
+}
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
index d9ef1f9..58e33b3 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
@@ -448,19 +448,19 @@
      * AbsolutePanelParserTest and AbsolutePanelTest are enough to make up for
      * the lack. Leaving this here as a warning to the next guy.
      */
-//    {
-//      Widget w = widgetUi.myAbsolutePanelItemA;
-//      assertNotNull("Widget exists", w);
-//      assertEquals("Widget has left", 1, p.getWidgetLeft(w));
-//      assertEquals("Widget has top", 2, p.getWidgetTop(w));
-//    }
-//
-//    {
-//      Widget w = widgetUi.myAbsolutePanelItemC;
-//      assertNotNull("Widget exists", w);
-//      assertEquals("Widget has left", 10, p.getWidgetLeft(w));
-//      assertEquals("Widget has top", 20, p.getWidgetTop(w));
-//    }
+    // {
+    // Widget w = widgetUi.myAbsolutePanelItemA;
+    // assertNotNull("Widget exists", w);
+    // assertEquals("Widget has left", 1, p.getWidgetLeft(w));
+    // assertEquals("Widget has top", 2, p.getWidgetTop(w));
+    // }
+    //
+    // {
+    // Widget w = widgetUi.myAbsolutePanelItemC;
+    // assertNotNull("Widget exists", w);
+    // assertEquals("Widget has left", 10, p.getWidgetLeft(w));
+    // assertEquals("Widget has top", 20, p.getWidgetTop(w));
+    // }
   }
 
   public void testStringAttributeIgnoresStaticSetter() {
@@ -538,6 +538,16 @@
         widgetUi.simpleSpriteParagraph.getOffsetHeight());
   }
 
+  public void testStaticImport() {
+    assertEquals(Constants.CONST_FOO,
+        widgetUi.bracedParagraph.getAttribute("foo"));
+    assertEquals(Constants.Inner.CONST_BAR + " " + Constants.Inner.CONST_BAZ,
+        widgetUi.bracedParagraph.getAttribute("bar"));
+    assertEquals(Constants.MyEnum.ENUM_1.name() + " "
+        + Constants.MyEnum.ENUM_2.name(),
+        widgetUi.bracedParagraph.getAttribute("enum"));
+  }
+
   public void suppressForIEfail_testBizarrelyElementedWidgets() {
     assertInOrder(widgetUi.widgetCrazyTable.getInnerHTML().toLowerCase(),
         "<td>they have been known</td>", "<td>to write widgets</td>",
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
index 702cdb8..1376eaa 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
@@ -72,6 +72,17 @@
   for a resource to provide arbitrary objects to arbitrary attributes (look for FooLabel)
 </ui:with>
 
+<ui:import field='com.google.gwt.uibinder.test.client.Constants.CONST_FOO'>
+  Tests the static import of a single constant into the local namespace.
+</ui:import>
+
+<ui:import field='com.google.gwt.uibinder.test.client.Constants.Inner.*'>
+  Tests the static import of multiple constants into the local namespace.
+</ui:import>
+
+<ui:import field='com.google.gwt.uibinder.test.client.Constants.MyEnum.*'>
+  Tests the static import of an enum into the local namespace.
+</ui:import>
 <!--
   Tests creating a CssResource from an external file.
  -->
@@ -164,7 +175,10 @@
   </gwt:Dock>
   <gwt:Dock direction='CENTER'>
     <gwt:HTMLPanel>
-      <p ui:field='bracedParagraph' fnord='blah di blah {{foo: "bar"} di blah'><ui:msg>This is a demonstration and test bed of GWT's shiny UiBinder
+      <p ui:field='bracedParagraph' fnord='blah di blah {{foo: "bar"} di blah'
+        foo='{CONST_FOO}' bar='{CONST_BAR} {CONST_BAZ}'
+        enum='{ENUM_1.name} {ENUM_2.name}' ><ui:msg>This is a
+      demonstration and test bed of GWT's shiny UiBinder
       package. At the moment it works mainly as described in
       <a href="http://code.google.com/p/google-web-toolkit-incubator/wiki/DeclarativeUi"
         ui:ph="oldBlogLink">