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><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">