Implements UiBinder rendering for Cells.
Rendering values passed to the render() method.
Retrieving ui:fields using getters
Review at http://gwt-code-reviews.appspot.com/1466809
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10434 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt23_24userApi.conf b/tools/api-checker/config/gwt23_24userApi.conf
index 14359f9..2801c61 100644
--- a/tools/api-checker/config/gwt23_24userApi.conf
+++ b/tools/api-checker/config/gwt23_24userApi.conf
@@ -106,6 +106,7 @@
:user/src/com/google/gwt/user/client/rpc/core/**\
:user/src/com/google/gwt/user/client/rpc/impl/**\
:user/src/com/google/gwt/uibinder/attributeparsers/**\
+:user/src/com/google/gwt/uibinder/client/UiRendererUtils.java\
:user/src/com/google/gwt/uibinder/elementparsers/**\
:user/src/com/google/gwt/uibinder/testing/**\
:user/src/com/google/gwt/util/**\
diff --git a/user/src/com/google/gwt/text/shared/UiRenderer.java b/user/src/com/google/gwt/text/shared/UiRenderer.java
deleted file mode 100644
index c2064b8..0000000
--- a/user/src/com/google/gwt/text/shared/UiRenderer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2011 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.text.shared;
-
-/**
- * Marker interface for SafeHtmlRenderer implementations to be code generated
- * from ui.xml files.
- *
- * @param <T> the type to render
- */
-public interface UiRenderer<T> extends SafeHtmlRenderer<T> {
-}
diff --git a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
index 3e2b6db..749c0c9 100644
--- a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
+++ b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
@@ -34,7 +34,7 @@
<set-configuration-property name="UiBinder.useLazyWidgetBuilders" value="false"/>
<generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
- <when-type-assignable class="com.google.gwt.text.shared.UiRenderer"/>
+ <when-type-assignable class="com.google.gwt.uibinder.client.UiRenderer"/>
</generate-with>
<generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
diff --git a/user/src/com/google/gwt/uibinder/client/AbstractUiRenderer.java b/user/src/com/google/gwt/uibinder/client/AbstractUiRenderer.java
new file mode 100644
index 0000000..4dd7262
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/AbstractUiRenderer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 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.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * Abstract implementation of a safe HTML binder to make implementation of generated rendering
+ * simpler.
+ *
+ * @param <T> the type to render
+ */
+public abstract class AbstractUiRenderer<T> implements UiRenderer<T> {
+
+ /**
+ * Marker attribute for DOM structures previously generated by UiRenderer.
+ */
+ public static final String RENDERED_ATTRIBUTE = "gwtuirendered";
+
+ /**
+ * Holds the part of the id attribute common to all elements being rendered.
+ */
+ protected String uiId;
+
+ @Override
+ public boolean isParentOrRenderer(Element parent) {
+ if (parent == null) {
+ return false;
+ }
+
+ Element root;
+ try {
+ root = UiRendererUtilsImpl.findRootElement(parent, RENDERED_ATTRIBUTE);
+ return UiRendererUtilsImpl.isAttachedToDom(root)
+ && UiRendererUtilsImpl.isRenderedElementSingleChild(root);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(T cell, Element parent, NativeEvent event) {
+ // Trivial implementation when no @UiHandlers
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiRenderer.java b/user/src/com/google/gwt/uibinder/client/UiRenderer.java
new file mode 100644
index 0000000..d8bfc0a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiRenderer.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * Marker interface for classes whose implementation is to be provided via UiBinder code
+ * generation for SafeHtml rendering.
+ *
+ * @param <T> the type to render
+ */
+public interface UiRenderer<T> {
+
+ /**
+ * Checks whether {@code parent} is a valid element to use as an argument for field getters.
+ *
+ * @return {@code true} if parent contains or directly points to a previously rendered element.
+ * In DevMode it also checks whether the parent is attached to the DOM
+ */
+ boolean isParentOrRenderer(Element parent);
+
+ /**
+ * Receives an event and dispatches it to the appropriate handler method annotated
+ * with {@code @UiHandler}.
+ *
+ * @param receiver of events
+ * @param parent element containing a previously rendered element
+ * @param event received by the browser
+ */
+ void onBrowserEvent(T receiver, Element parent, NativeEvent event);
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiRendererUtilsImpl.java b/user/src/com/google/gwt/uibinder/client/UiRendererUtilsImpl.java
new file mode 100644
index 0000000..b59a48c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiRendererUtilsImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2011 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.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * UiRenderer utilities.
+ */
+public class UiRendererUtilsImpl {
+
+ /**
+ * Build id strings used to identify DOM elements related to ui:fields.
+ *
+ * @param fieldName name of the field that identifies the element
+ * @param uiId common part of the identifier for all elements in the rendered DOM structure
+ */
+ public static String buildInnerId(String fieldName, String uiId) {
+ return uiId + ":" + fieldName;
+ }
+
+ /**
+ * Retrieves a specific element within a previously rendered element.
+ *
+ * @param parent parent element containing the element of interest
+ * @param fieldName name of the field to retrieve
+ * @param attribute that identifies the root element as such
+ * @return the element identified by {@code fieldName}
+ *
+ * @throws IllegalArgumentException if the {@code parent} does not point to or contains
+ * a previously rendered element. In DevMode also when the root element is not
+ * attached to the DOM
+ * @throws IllegalStateException parent does not contain an element matching
+ * {@code filedName}
+ *
+ * @throws RuntimeException if the root element is not attached to the DOM and not running in
+ * DevMode
+ *
+ * @throws NullPointerException if {@code parent} == null
+ */
+ public static Element findInnerField(Element parent, String fieldName, String attribute) {
+ Element root = findRootElement(parent, attribute);
+
+ if (parent != root && !isRenderedElementSingleChild(root)) {
+ throw new IllegalArgumentException(
+ "Parent Element of previously rendered element contains more than one child"
+ + " while getting \"" + fieldName + "\"");
+ }
+
+ String uiId = root.getAttribute(attribute);
+ String renderedId = buildInnerId(fieldName, uiId);
+
+ Element elementById = Document.get().getElementById(renderedId);
+ if (elementById == null) {
+ if (!isAttachedToDom(root)) {
+ throw new RuntimeException("UiRendered element is not attached to DOM while getting \""
+ + fieldName + "\"");
+ } else if (!GWT.isProdMode()) {
+ throw new IllegalStateException("\"" + fieldName
+ + "\" not found within rendered element");
+ } else {
+ // In prod mode we do not distinguish between being unattached or not finding the element
+ throw new IllegalArgumentException("UiRendered element is not attached to DOM, or \""
+ + fieldName + "\" not found within rendered element");
+ }
+ }
+ return elementById;
+ }
+
+ /**
+ * Retrieves the root of a previously rendered element contained within the {@code parent}.
+ * The {@code parent} must either contain the previously rendered DOM structure as its only child,
+ * or point directly to the rendered element root.
+ *
+ * @param parent element containing, or pointing to, a previously rendered DOM structure
+ * @param attribute attribute name that identifies the root of the DOM structure
+ * @return the root element of the previously rendered DOM structure
+ *
+ * @throws NullPointerException if {@code parent} == null
+ * @throws IllegalArgumentException if {@code parent} does not contain a previously rendered
+ * element
+ */
+ public static Element findRootElement(Element parent, String attribute) {
+ if (parent == null) {
+ throw new NullPointerException("parent argument is null");
+ }
+
+ Element rendered;
+ if (parent.hasAttribute(attribute)) {
+ // The parent is the root
+ return parent;
+ } else if ((rendered = parent.getFirstChildElement()) != null
+ && rendered.hasAttribute(attribute)) {
+ // The first child is the root
+ return rendered;
+ } else {
+ throw new IllegalArgumentException(
+ "Parent element does not contain a previously rendered element");
+ }
+ }
+
+ /**
+ * Walks up the parents of the {@code rendered} element to ascertain that it is attached to the
+ * document.
+ */
+ public static boolean isAttachedToDom(Element rendered) {
+ if (GWT.isProdMode()) {
+ return true;
+ }
+
+ Element body = Document.get().getBody();
+
+ while (rendered != null && rendered.hasParentElement() && !body.equals(rendered)) {
+ rendered = rendered.getParentElement();
+ }
+ return body.equals(rendered);
+ };
+
+ /**
+ * Checks that the parent of {@code rendered} has a single child.
+ */
+ public static boolean isRenderedElementSingleChild(Element rendered) {
+ return GWT.isProdMode() || rendered.getParentElement().getChildCount() == 1;
+ }
+
+ /**
+ * Inserts an attribute into the first tag found in a {@code safeHtml} template.
+ * This method assumes that the {@code safeHtml} template begins with an open HTML tag.
+ * {@code SafeHtml} templates produced by UiBinder always meet these conditions.
+ * <p>
+ * This method does not attempt to ensure {@code atributeName} and {@code attributeValue}
+ * contain safe values.
+ *
+ * @returns the {@code safeHtml} template with "{@code attributeName}={@code attributeValue}"
+ * inserted as an attribute of the first tag found
+ */
+ public static SafeHtml stampUiRendererAttribute(SafeHtml safeHtml, String attributeName,
+ String attributeValue) {
+ String html = safeHtml.asString();
+ int endOfFirstTag = html.indexOf(">");
+
+ assert endOfFirstTag > 1 : "Safe html template does not start with an HTML open tag";
+
+ if (html.charAt(endOfFirstTag - 1) == '/') {
+ endOfFirstTag--;
+ }
+
+ html = html.substring(0, endOfFirstTag) + " " + attributeName + "=\"" + attributeValue + "\""
+ + html.substring(endOfFirstTag);
+ return SafeHtmlUtils.fromTrustedString(html);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
index c10083c..9cb1032 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
@@ -82,7 +82,7 @@
// that idHolder is a local variable reference, not a string id. We
// have to generate the ids at runtime, not compile time, or else
// we'll reuse ids for any template rendered more than once.
- String idHolder = uiWriter.declareDomIdHolder();
+ String idHolder = uiWriter.declareDomIdHolder(null);
uiWriter.ensureCurrentFieldAttached();
FieldWriter childFieldWriter = uiWriter.parseElementToFieldWriter(elem);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
index 5af8f3a..f335218 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
@@ -97,7 +97,7 @@
name = "widget" + (++serial);
}
- String idHolder = uiWriter.declareDomIdHolder();
+ String idHolder = uiWriter.declareDomIdHolder(null);
idToWidgetElement.put(idHolder, elem);
if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index 0960b06..610c07e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -19,6 +19,7 @@
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.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
@@ -41,7 +42,7 @@
*/
public class UiBinderParser {
- private enum Resource {
+ enum Resource {
DATA {
@Override
void create(UiBinderParser parser, XMLElement elem)
@@ -153,7 +154,10 @@
private JClassType consumeTypeAttribute(XMLElement elem)
throws UnableToCompleteException {
- String resourceTypeName = elem.consumeRequiredRawAttribute(TYPE_ATTRIBUTE);
+ if (!elem.hasAttribute(TYPE_ATTRIBUTE)) {
+ return null;
+ }
+ String resourceTypeName = elem.consumeRawAttribute(TYPE_ATTRIBUTE);
JClassType resourceType = oracle.findType(resourceTypeName);
if (resourceType == null) {
@@ -256,56 +260,122 @@
*/
private void createResource(XMLElement elem) throws UnableToCompleteException {
String resourceName = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE);
+
JClassType resourceType = consumeTypeAttribute(elem);
+
if (elem.getAttributeCount() > 0) {
- writer.die(elem, "Should only find attributes \"field\" and \"type\"");
+ writer.die(elem, "Should only find attributes \"%s\" and \"%s\".", FIELD_ATTRIBUTE,
+ TYPE_ATTRIBUTE);
}
- FieldWriter fieldWriter = fieldManager.registerField(
- FieldWriterType.IMPORTED, resourceType, resourceName);
- OwnerField ownerField = writer.getOwnerClass().getUiField(resourceName);
+ /* Is it a parameter passed to a render method? */
- /* Perhaps it is provided via @UiField */
-
- if (ownerField != null) {
- if (!resourceType.getErasedType().equals(ownerField.getType().getRawType()
- .getErasedType())) {
- writer.die(elem, "Type must match %s", ownerField);
- }
- if (ownerField.isProvided()) {
- String initializer;
- if (writer.getDesignTime().isDesignTime()) {
- String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
- initializer = writer.getDesignTime().getProvidedField(typeName,
- ownerField.getName());
- } else {
- initializer = "owner." + ownerField.getName();
- }
- fieldWriter.setInitializer(initializer);
+ if (writer.isRenderer()) {
+ JClassType matchingResourceType = findRenderParameterType(resourceName);
+ if (matchingResourceType != null) {
+ createResourceUiRenderer(elem, resourceName, resourceType, matchingResourceType);
return;
}
}
- /* Nope. Maybe a @UiFactory will make it */
+ /* Perhaps it is provided via @UiField */
- JMethod factoryMethod = writer.getOwnerClass().getUiFactoryMethod(
- resourceType);
- if (factoryMethod != null) {
- String initializer;
- if (writer.getDesignTime().isDesignTime()) {
- String typeName = factoryMethod.getReturnType().getQualifiedSourceName();
- initializer = writer.getDesignTime().getProvidedFactory(typeName,
- factoryMethod.getName(), "");
- } else {
- initializer = String.format("owner.%s()", factoryMethod.getName());
+ if (writer.getOwnerClass() == null) {
+ writer.die("No owner provided for %s", writer.getBaseClass().getQualifiedSourceName());
+ }
+
+ if (writer.getOwnerClass().getUiField(resourceName) != null) {
+ // If the resourceType is present, is it the same as the one in the base class?
+ OwnerField ownerField = writer.getOwnerClass().getUiField(resourceName);
+
+ // If the resourceType was given, it must match the one declared with @UiField
+ if (resourceType != null && !resourceType.getErasedType()
+ .equals(ownerField.getType().getRawType().getErasedType())) {
+ writer.die(elem, "Type must match %s.", ownerField);
}
- fieldWriter.setInitializer(initializer);
+
+ if (ownerField.isProvided()) {
+ createResourceUiField(resourceName, resourceType, ownerField);
+ return;
+ } else {
+ // Let's keep trying, but we know the type at least.
+ resourceType = ownerField.getType().getRawType().getErasedType();
+ }
+ }
+
+ /* Nope. If we know the type, maybe a @UiFactory will make it */
+
+ if (resourceType != null && writer.getOwnerClass().getUiFactoryMethod(resourceType) != null) {
+ createResourceUiFactory(elem, resourceName, resourceType);
+ return;
}
/*
* If neither of the above, the FieldWriter's default GWT.create call will
* do just fine.
*/
+ if (resourceType != null) {
+ fieldManager.registerField(FieldWriterType.IMPORTED, resourceType, resourceName);
+ } else {
+ writer.die(elem, "Could not infer type for field %s.", resourceName);
+ }
+ }
+
+ private void createResourceUiFactory(XMLElement elem, String resourceName, JClassType resourceType)
+ throws UnableToCompleteException {
+ FieldWriter fieldWriter;
+ JMethod factoryMethod = writer.getOwnerClass().getUiFactoryMethod(resourceType);
+ JClassType methodReturnType = factoryMethod.getReturnType().getErasedType()
+ .isClassOrInterface();
+ if (!resourceType.getErasedType().equals(methodReturnType)) {
+ writer.die(elem, "Type must match %s.", methodReturnType);
+ }
+
+ String initializer;
+ if (writer.getDesignTime().isDesignTime()) {
+ String typeName = factoryMethod.getReturnType().getQualifiedSourceName();
+ initializer = writer.getDesignTime().getProvidedFactory(typeName,
+ factoryMethod.getName(), "");
+ } else {
+ initializer = String.format("owner.%s()", factoryMethod.getName());
+ }
+ fieldWriter = fieldManager.registerField(
+ FieldWriterType.IMPORTED, resourceType, resourceName);
+ fieldWriter.setInitializer(initializer);
+ }
+
+ private void createResourceUiField(String resourceName, JClassType resourceType,
+ OwnerField ownerField)
+ throws UnableToCompleteException {
+ FieldWriter fieldWriter;
+ String initializer;
+
+ if (writer.getDesignTime().isDesignTime()) {
+ String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
+ initializer = writer.getDesignTime().getProvidedField(typeName, ownerField.getName());
+ } else {
+ initializer = "owner." + ownerField.getName();
+ }
+ fieldWriter = fieldManager.registerField(
+ FieldWriterType.IMPORTED,
+ ownerField.getType().getRawType().getErasedType(),
+ resourceName);
+ fieldWriter.setInitializer(initializer);
+ }
+
+ private void createResourceUiRenderer(XMLElement elem, String resourceName,
+ JClassType resourceType, JClassType matchingResourceType) throws UnableToCompleteException {
+ FieldWriter fieldWriter;
+ if (resourceType != null
+ && !resourceType.getErasedType().isAssignableFrom(matchingResourceType.getErasedType())) {
+ writer.die(elem, "Type must match the type of parameter %s in %s#render method.",
+ resourceName,
+ writer.getBaseClass().getQualifiedSourceName());
+ }
+
+ fieldWriter = fieldManager.registerField(
+ FieldWriterType.IMPORTED, matchingResourceType.getErasedType(), resourceName);
+ fieldWriter.setInitializer("this." + resourceName);
}
private void createSingleImport(XMLElement elem, JClassType enclosingType,
@@ -374,6 +444,27 @@
return publicType;
}
+ private JClassType findRenderParameterType(String resourceName) {
+ JMethod renderMethod = null;
+ for (JMethod method : writer.getBaseClass().getMethods()) {
+ if (method.getName().equals("render")) {
+ renderMethod = method;
+ break;
+ }
+ }
+ if (renderMethod == null) {
+ return null;
+ }
+ JClassType matchingResourceType = null;
+ for (JParameter jParameter : renderMethod.getParameters()) {
+ if (jParameter.getName().equals(resourceName)) {
+ matchingResourceType = jParameter.getType().isClassOrInterface();
+ break;
+ }
+ }
+ return matchingResourceType;
+ }
+
private void findResources(XMLElement binderElement)
throws UnableToCompleteException {
binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 0649c5d..255fcf0 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -17,19 +17,24 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dom.client.TagName;
import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.text.shared.UiRenderer;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.attributeparsers.AttributeParser;
import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
import com.google.gwt.uibinder.attributeparsers.BundleAttributeParser;
import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
+import com.google.gwt.uibinder.client.AbstractUiRenderer;
import com.google.gwt.uibinder.client.LazyDomElement;
import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiRenderer;
import com.google.gwt.uibinder.elementparsers.AttributeMessageParser;
import com.google.gwt.uibinder.elementparsers.BeanParser;
import com.google.gwt.uibinder.elementparsers.ElementParser;
@@ -48,9 +53,11 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import java.beans.Introspector;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
@@ -169,6 +176,67 @@
return propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
+ /**
+ * Scan the base class for the getter methods. Assumes getters begin with
+ * "get". See {@link #validateRendererGetters(JClassType)} for a method that
+ * guarantees this method will succeed.
+ */
+ private static List<JMethod> findGetterNames(JClassType owner) {
+ List<JMethod> ret = new ArrayList<JMethod>();
+ for (JMethod jMethod : owner.getMethods()) {
+ String getterName = jMethod.getName();
+ if (getterName.startsWith("get")) {
+ ret.add(jMethod);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Scans a class for a method named "render". Returns its parameters except for the
+ * first one. See {@link #validateRenderParameters(JClassType)} for a method that
+ * guarantees this method will succeed.
+ */
+ private static JParameter[] findRenderParameters(JClassType owner) {
+ JMethod[] methods = owner.getMethods();
+ JMethod renderMethod = null;
+
+ for (JMethod jMethod : methods) {
+ if (jMethod.getName().equals("render")) {
+ renderMethod = jMethod;
+ }
+ }
+
+ JParameter[] parameters = renderMethod.getParameters();
+ return Arrays.copyOfRange(parameters, 1, parameters.length);
+ }
+
+ /**
+ * Determine the field name a getter is trying to retrieve. Assumes getters begin with
+ * "get".
+ */
+ private static String getterToFieldName(String name) {
+ String fieldName = name.substring(3);
+ return Introspector.decapitalize(fieldName);
+ }
+
+ private static String renderMethodParameters(JParameter[] renderParameters) {
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < renderParameters.length; i++) {
+ JParameter parameter = renderParameters[i];
+ builder.append("final ");
+ builder.append(parameter.getType().getQualifiedSourceName());
+ builder.append(" ");
+ builder.append(parameter.getName());
+ if (i < renderParameters.length - 1) {
+ builder.append(", ");
+ }
+ }
+
+ return builder.toString();
+ }
+
private final MortalLogger logger;
/**
@@ -412,7 +480,7 @@
public String declareDomField(String fieldName, String ancestorField)
throws UnableToCompleteException {
ensureAttached();
- String name = declareDomIdHolder();
+ String name = declareDomIdHolder(fieldName);
if (useLazyWidgetBuilders) {
// Create and initialize the dom field with LazyDomElement.
@@ -450,15 +518,23 @@
/**
* Declare a variable that will be filled at runtime with a unique id, safe
- * for use as a dom element's id attribute.
+ * for use as a dom element's id attribute. For {@code UiRenderer} based code,
+ * elements corresponding to a ui:field, need and id initialized to a
+ * value that depends on the {@code fieldName}. For all other cases let {@code fieldName}
+ * be {@code null}.
*
+ * @param fieldName name of the field corresponding to this variable.
* @return that variable's name.
*/
- public String declareDomIdHolder() throws UnableToCompleteException {
+ public String declareDomIdHolder(String fieldName) throws UnableToCompleteException {
String domHolderName = "domId" + domId++;
FieldWriter domField = fieldManager.registerField(FieldWriterType.DOM_ID_HOLDER,
oracle.findType(String.class.getName()), domHolderName);
- domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
+ if (isRenderer && fieldName != null) {
+ domField.setInitializer("UiRendererUtilsImpl.buildInnerId(\"" + fieldName + "\", uiId)");
+ } else {
+ domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
+ }
return domHolderName;
}
@@ -689,6 +765,13 @@
}
/**
+ * The type we have been asked to generated, e.g. MyUiBinder
+ */
+ public JClassType getBaseClass() {
+ return baseClass;
+ }
+
+ /**
* Finds an attribute {@link BundleAttributeParser} for the given xml
* attribute, if any, based on its namespace uri.
*
@@ -814,6 +897,10 @@
return findFieldType(elem).isAssignableTo(isRenderableClassType);
}
+ public boolean isRenderer() {
+ return isRenderer;
+ }
+
public boolean isWidgetElement(XMLElement elem) throws UnableToCompleteException {
return isElementAssignableTo(elem, IsWidget.class);
}
@@ -1326,6 +1413,74 @@
}
/**
+ * 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}.
+ */
+ private void validateRendererGetters(JClassType owner) throws UnableToCompleteException {
+ for (JMethod jMethod : owner.getMethods()) {
+ String getterName = jMethod.getName();
+ if (getterName.startsWith("get")) {
+ if (jMethod.getParameterTypes().length != 1) {
+ die("Getter %s must have exactly one parameter",
+ getterName);
+ }
+ 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",
+ getterName,
+ elementClassName);
+ }
+ 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", getterName, fieldName);
+ }
+ } else if (!getterName.equals("render")) {
+ die("Unexpected method \"%s\" found", getterName);
+ }
+ }
+ }
+
+ /**
+ * Scans a class to validate that it contains a single method called render,
+ * which has a {@code void} return type, and its first parameter is of type
+ * {@code SafeHtmlBuilder}.
+ */
+ private void validateRenderParameters(JClassType owner) throws UnableToCompleteException {
+ JMethod[] methods = owner.getMethods();
+ JMethod renderMethod = null;
+
+ for (JMethod jMethod : methods) {
+ if (jMethod.getName().equals("render")) {
+ if (renderMethod == null) {
+ renderMethod = jMethod;
+ } else {
+ die("%s declares more than one method named render",
+ baseClass.getQualifiedSourceName());
+ }
+ }
+ }
+
+ if (renderMethod == null
+ || renderMethod.getParameterTypes().length < 1
+ || !renderMethod.getParameterTypes()[0].getErasedType().getQualifiedSourceName().equals(
+ SafeHtmlBuilder.class.getCanonicalName())) {
+ die("%s does not declare a render(SafeHtmlBuilder ...) method",
+ baseClass.getQualifiedSourceName());
+ }
+ if (!JPrimitiveType.VOID.equals(renderMethod.getReturnType())) {
+ die("%s#render(SafeHtmlBuilder ...) does not return void",
+ baseClass.getQualifiedSourceName());
+ }
+ }
+
+ /**
* Write statements that parsers created via calls to {@link #addStatement}.
* Such statements will assume that {@link #writeGwtFields} has already been
* called.
@@ -1462,7 +1617,8 @@
uiOwnerType.getParameterizedQualifiedSourceName(),
baseClass.getParameterizedQualifiedSourceName());
} else {
- w.write("public class %s extends AbstractSafeHtmlRenderer<%s> implements %s {", implClassName,
+ w.write("public class %s extends %s<%s> implements %s {", implClassName,
+ AbstractUiRenderer.class.getName(),
uiOwnerType.getParameterizedQualifiedSourceName(),
baseClass.getParameterizedQualifiedSourceName());
}
@@ -1537,6 +1693,7 @@
uiRootType.getName());
} else {
w.write("import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;");
+ w.write("import com.google.gwt.uibinder.client.UiRendererUtilsImpl;");
}
}
@@ -1600,11 +1757,13 @@
}
/**
- * Writes the SafeHtmlRenderer's source for the renderable
+ * Writes the UiRenderer's source for the renderable
* strategy.
*/
- private void writeRenderer(
- IndentedWriter w, String rootField) throws UnableToCompleteException {
+ private void writeRenderer(IndentedWriter w, String rootField) throws UnableToCompleteException {
+ validateRendererGetters(baseClass);
+ validateRenderParameters(baseClass);
+
writePackage(w);
writeImports(w);
@@ -1619,56 +1778,89 @@
w.newline();
- w.write("public SafeHtml render(final %s owner) {",
- uiOwnerType.getParameterizedQualifiedSourceName());
+ JParameter[] renderParameters = findRenderParameters(baseClass);
+
+ writeRenderParameterDefinitions(w, renderParameters);
+
+ String renderParameterDeclarations = renderMethodParameters(renderParameters);
+ w.write("public void render(final %s sb%s%s) {", SafeHtmlBuilder.class.getName(),
+ renderParameterDeclarations.length() != 0 ? ", " : "",
+ renderParameterDeclarations);
w.indent();
w.newline();
- w.write("return new Widgets(owner).getSafeHtml();");
- w.outdent();
+ writeRenderParameterInitializers(w, renderParameters);
- w.write("}");
+ w.write("uiId = com.google.gwt.dom.client.Document.get().createUniqueId();");
w.newline();
- // Writes the inner class Widgets.
- w.newline();
- w.write("/**");
- w.write(" * Encapsulates the access to all inner widgets");
- w.write(" */");
- w.write("class Widgets {");
- w.indent();
-
- String ownerClassType = uiOwnerType.getParameterizedQualifiedSourceName();
- w.write("private final %s owner;", ownerClassType);
- w.newline();
-
- w.write("public Widgets(final %s owner) {", ownerClassType);
- w.indent();
- w.write("this.owner = owner;");
fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
+ w.newline();
+
+ // TODO(rchandia) Find a better way to get the root field name
+ String rootFieldName = rootField.substring(4, rootField.length() - 2);
+ String safeHtml = fieldManager.lookup(rootFieldName).getSafeHtml();
+
+ // TODO(rchandia) it should be possible to add the attribute when parsing the UiBinder file
+ w.write("sb.append(UiRendererUtilsImpl.stampUiRendererAttribute(%s, RENDERED_ATTRIBUTE, uiId));",
+ safeHtml);
w.outdent();
+
w.write("}");
w.newline();
- w.write("public SafeHtml getSafeHtml() {");
- w.indent();
- // TODO Find a better way to get the root field name
- String safeHtml = fieldManager.lookup(rootField.substring(4, rootField.length() - 2)).getSafeHtml();
- w.write("return %s;", safeHtml);
- w.outdent();
- w.write("}");
+ fieldManager.writeFieldDefinitions(w, getOracle(), getOwnerClass(), getDesignTime());
- fieldManager.writeFieldDefinitions(
- w, getOracle(), getOwnerClass(), getDesignTime());
-
- w.outdent();
- w.write("}");
+ writeRendererGetters(w, baseClass, rootFieldName);
// Close class
w.outdent();
w.write("}");
}
+ private void writeRendererGetters(IndentedWriter w, JClassType owner, String rootFieldName)
+ throws UnableToCompleteException {
+ List<JMethod> getters = findGetterNames(owner);
+
+ // For every requested getter
+ for (JMethod getter : getters) {
+ // 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) UiRendererUtilsImpl.findInnerField(%s, \"%s\", RENDERED_ATTRIBUTE);",
+ getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter,
+ getterFieldName);
+ } else {
+ // return (ElementSubclass) findPreviouslyRendered(parent);
+ w.write("return (%s) UiRendererUtilsImpl.findRootElement(%s, RENDERED_ATTRIBUTE);",
+ getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter);
+ }
+ w.outdent();
+ w.write("}");
+ }
+ }
+
+ private void writeRenderParameterDefinitions(IndentedWriter w, JParameter[] renderParameters) {
+ for (int i = 0; i < renderParameters.length; i++) {
+ JParameter parameter = renderParameters[i];
+ w.write("private %s %s;", parameter.getType().getQualifiedSourceName(), parameter.getName());
+ w.newline();
+ }
+ }
+
+ private void writeRenderParameterInitializers(IndentedWriter w, JParameter[] renderParameters) {
+ for (int i = 0; i < renderParameters.length; i++) {
+ JParameter parameter = renderParameters[i];
+ w.write("this.%s = %s;", parameter.getName(), parameter.getName());
+ w.newline();
+ }
+ }
+
/**
* Write statements created by {@link HtmlTemplates#addSafeHtmlTemplate}. This
* code must be placed after all instantiation code.
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index d6e2dc0..5bf6ee3 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -53,6 +53,8 @@
import com.google.gwt.uibinder.rebind.HandlerEvaluatorTest;
import com.google.gwt.uibinder.rebind.TokenatorTest;
import com.google.gwt.uibinder.rebind.TypeOracleUtilsTest;
+import com.google.gwt.uibinder.rebind.UiBinderParserUiWithTest;
+import com.google.gwt.uibinder.rebind.UiRendererValidationTest;
import com.google.gwt.uibinder.rebind.XMLElementTest;
import com.google.gwt.uibinder.rebind.model.OwnerClassTest;
import com.google.gwt.uibinder.rebind.model.OwnerFieldClassTest;
@@ -78,6 +80,8 @@
suite.addTestSuite(XMLElementTest.class);
suite.addTestSuite(DesignTimeUtilsTest.class);
suite.addTestSuite(TypeOracleUtilsTest.class);
+ suite.addTestSuite(UiBinderParserUiWithTest.class);
+ suite.addTestSuite(UiRendererValidationTest.class);
// model
suite.addTestSuite(OwnerClassTest.class);
diff --git a/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java b/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
new file mode 100644
index 0000000..52eb316
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2011 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;
+
+import com.google.gwt.codegen.server.AbortablePrintWriter;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.javac.testing.impl.MockResourceOracle;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
+import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+import com.google.gwt.uibinder.test.UiJavaResources;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXParseException;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Set;
+
+/**
+ * Base class for tests that exercise {@link UiBinderWriter}.
+ */
+public abstract class AbstractUiBinderWriterTest extends TestCase {
+
+ public static final MockJavaResource CLIENT_BUNDLE = new MockJavaResource(
+ "com.google.gwt.resources.client.ClientBundle") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.resources.client;\n");
+ code.append("public interface ClientBundle {");
+ code.append("}");
+ return code;
+ }
+ };
+ public static final MockJavaResource DIV_ELEMENT = new MockJavaResource(
+ "com.google.gwt.dom.client.DivElement") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.dom.client;\n");
+ code.append("public class DivElement extends Element {");
+ code.append("}");
+ return code;
+ }
+ };
+ public static final MockJavaResource FOO = new MockJavaResource("foo.Foo") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package foo;");
+ code.append("public class Foo {");
+ code.append("}");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource RENDERABLE_PANEL = new MockJavaResource(
+ "com.google.gwt.user.client.ui.RenderablePanel") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.user.client.ui;\n");
+ code.append("public class RenderablePanel {");
+ code.append("}");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource SPAN_ELEMENT = new MockJavaResource(
+ "com.google.gwt.dom.client.SpanElement") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.dom.client;\n");
+ code.append("public class SpanElement extends Element {");
+ 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,
+ new MockResourceOracle());
+
+ protected static final String RENDERER_BASE_CLASS_NAME = "renderer.OwnerClass.Renderer";
+
+ protected static final String RENDERER_OWNER_CLASS_NAME = "renderer.OwnerClass";
+
+ private static TreeLogger createCompileLogger() {
+ PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(System.err, true));
+ logger.setMaxDetail(TreeLogger.ERROR);
+ return logger;
+ }
+
+ protected Document doc;
+ protected PrintWriter printWriter;
+ protected UiBinderWriter writer;
+ protected UiBinderParser parser;
+ protected BundleAttributeParsers attributeParsers;
+ protected XMLElementProvider elemProvider;
+ protected XMLElement elm;
+ protected FieldManager fieldManager;
+ protected Element item;
+ protected MockMortalLogger logger;
+ protected Set<Resource> resources = new HashSet<Resource>();
+ protected TypeOracle types;
+
+ public AbstractUiBinderWriterTest() {
+ super();
+ }
+
+ /**
+ * @param name
+ */
+ public AbstractUiBinderWriterTest(String name) {
+ super(name);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ resources.addAll(UiJavaResources.getUiResources());
+ printWriter = new AbortablePrintWriter(new PrintWriter(new StringWriter()));
+ }
+
+ protected void init(String domString, MockJavaResource rendererClass) throws SAXParseException,
+ UnableToCompleteException {
+ resources.add(RENDERABLE_PANEL);
+ resources.add(CLIENT_BUNDLE);
+ resources.add(DIV_ELEMENT);
+ resources.add(SPAN_ELEMENT);
+ resources.add(FOO);
+ resources.add(rendererClass);
+ CompilationState state = CompilationStateBuilder.buildFrom(createCompileLogger(), resources);
+ types = state.getTypeOracle();
+ logger = new MockMortalLogger();
+ JClassType ownerType = types.findType(RENDERER_OWNER_CLASS_NAME);
+ UiBinderContext uiBinderCtx = new UiBinderContext();
+ attributeParsers =
+ new BundleAttributeParsers(types, logger, new OwnerClass(ownerType, logger, uiBinderCtx),
+ "templatePath", ownerType);
+ fieldManager = new FieldManager(types, logger, true);
+ String baseClass = RENDERER_BASE_CLASS_NAME;
+ DesignTimeUtils designTime = DesignTimeUtilsStub.EMPTY;
+ elemProvider =
+ new XMLElementProviderImpl(new AttributeParsers(types, fieldManager, logger),
+ attributeParsers, types, logger, designTime);
+ doc = docHelper.documentFor(domString, rendererClass.getPath());
+ item = (Element) doc.getDocumentElement().getChildNodes().item(0);
+ elm = elemProvider.get(item);
+ JClassType aClass = types.findType(baseClass);
+ MessagesWriter messages =
+ new MessagesWriter(types, BINDER_URI, logger, rendererClass.getPath(), "rendererPackage",
+ "rendererClassName");
+ writer =
+ new UiBinderWriter(aClass, "foo", "", types, logger, fieldManager, messages,
+ DesignTimeUtilsStub.EMPTY, uiBinderCtx, true, true, BINDER_URI);
+ parser = new UiBinderParser(writer, messages, fieldManager, types, null, BINDER_URI);
+ designTime.rememberPathForElements(doc);
+ }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/uibinder/rebind/UiBinderParserUiWithTest.java b/user/test/com/google/gwt/uibinder/rebind/UiBinderParserUiWithTest.java
new file mode 100644
index 0000000..16a5e5d
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/UiBinderParserUiWithTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 2011 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.javac.testing.impl.MockResourceOracle;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
+import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
+import com.google.gwt.uibinder.test.UiJavaResources;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXParseException;
+
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * Tests UiBinderParser behavior for <ui:with> tag.
+ */
+public class UiBinderParserUiWithTest extends TestCase {
+
+ public static final MockJavaResource BAR = new MockJavaResource("bar.Bar") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package bar;");
+ code.append("public class Bar {");
+ code.append(" public Bar(int a) { }");
+ code.append("}");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource BINDER = new MockJavaResource("binder.OwnerClass") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package binder;\n");
+ code.append("import com.google.gwt.uibinder.client.UiBinder;\n");
+ code.append("import com.google.gwt.uibinder.client.UiField;\n");
+ code.append("import com.google.gwt.uibinder.client.UiFactory;\n");
+ code.append("import bar.Bar;\n");
+ code.append("import foo.Foo;\n");
+ code.append("public class OwnerClass {");
+ code.append(" public interface Binder");
+ code.append(" extends UiBinder<java.lang.String, OwnerClass> {");
+ code.append(" }");
+ code.append(" @UiField foo.Foo fieldName;");
+ code.append(" @UiFactory bar.Bar aFactory() { return new Bar(1); }");
+ code.append("}");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource FOO = new MockJavaResource("foo.Foo") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package foo;");
+ code.append("public class Foo {");
+ code.append("}");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource FOOISH = new MockJavaResource("foo.Fooish") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package foo;");
+ code.append("public class Fooish extends Foo {");
+ code.append("}");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource RENDERER = new MockJavaResource("renderer.OwnerClass") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package renderer;\n");
+ code.append("import com.google.gwt.safehtml.shared.SafeHtmlBuilder;\n");
+ code.append("import com.google.gwt.uibinder.client.UiRenderer;\n");
+ code.append("import foo.Foo;\n");
+ code.append("public class OwnerClass {");
+ code.append(" public interface Renderer");
+ code.append(" extends UiRenderer<OwnerClass> {");
+ code.append(" public void render(SafeHtmlBuilder sb, foo.Fooish fieldName);");
+ code.append(" }");
+ code.append("}");
+ return code;
+ }
+ };
+
+ private static final W3cDomHelper docHelper = new W3cDomHelper(TreeLogger.NULL,
+ new MockResourceOracle());
+
+ private static TreeLogger createCompileLogger() {
+ PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(System.err, true));
+ logger.setMaxDetail(TreeLogger.ERROR);
+ return logger;
+ }
+
+ UiBinderParser parser;
+ private BundleAttributeParsers attributeParsers;
+ private Document doc;
+ private XMLElementProvider elemProvider;
+
+ private XMLElement elm;
+
+ private FieldManager fieldManager;
+
+ private Element item;
+
+ private MockMortalLogger logger;
+
+ private Set<Resource> resources = new HashSet<Resource>();
+
+ private TypeOracle types;
+
+ private UiBinderWriter writer;
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ resources.addAll(UiJavaResources.getUiResources());
+ resources.add(FOO);
+ resources.add(FOOISH);
+ resources.add(BAR);
+ resources.add(RENDERER);
+ resources.add(BINDER);
+ CompilationState state = CompilationStateBuilder.buildFrom(createCompileLogger(), resources);
+ types = state.getTypeOracle();
+ logger = new MockMortalLogger();
+ attributeParsers = new BundleAttributeParsers(types, logger, null, "templatePath", null);
+ fieldManager = new FieldManager(types, logger, true);
+ }
+
+ public void testWithErrorMisTyped() throws SAXParseException {
+ try {
+ init("<doc><with field=\"fieldName\" type=\"bar.Bar\" bloop=\"\"/></doc>",
+ "renderer.OwnerClass.Renderer");
+ fail("Expecting UnabletoCompleteException");
+ } catch (UnableToCompleteException e) {
+ assertNotNull(logger.died);
+ }
+ }
+
+ public void testWithErrorNoField() throws SAXParseException {
+ try {
+ init("<doc><with type=\"bar.Bar\"/></doc>", "renderer.OwnerClass.Renderer");
+ fail("Expecting UnabletoCompleteException");
+ } catch (UnableToCompleteException e) {
+ assertNotNull(logger.died);
+ }
+ }
+
+ public void testWithErrorUiBinderBadUiFieldType() throws SAXParseException {
+ try {
+ init("<doc><with field=\"someName\" type=\"foo.Unknown\"/></doc>",
+ "binder.OwnerClass.Binder");
+ fail("Expecting UnabletoCompleteException");
+ } catch (UnableToCompleteException e) {
+ assertNotNull(logger.died);
+ }
+ }
+
+ public void testWithErrorUiBinderMisTypedUiField() throws SAXParseException {
+ try {
+ init("<doc><with field=\"fieldName\" type=\"bar.Bar\"/></doc>", "binder.OwnerClass.Binder");
+ fail("Expecting UnabletoCompleteException");
+ } catch (UnableToCompleteException e) {
+ assertNotNull(logger.died);
+ }
+ }
+
+ public void testWithErrorUiRendererMisTypedImport() throws SAXParseException {
+ try {
+ init("<doc><with field=\"fieldName\" type=\"bar.Bar\"/></doc>",
+ "renderer.OwnerClass.Renderer");
+ fail("Expecting UnabletoCompleteException");
+ } catch (UnableToCompleteException e) {
+ assertNotNull(logger.died);
+ }
+ }
+
+ public void testWithErrorUiRendererUnTypedNonExistingFieldName() throws SAXParseException {
+ try {
+ init("<doc><with field=\"nonExisting\"/></doc>", "renderer.OwnerClass.Renderer");
+ fail("Expecting UnabletoCompleteException");
+ } catch (UnableToCompleteException e) {
+ assertNotNull(logger.died);
+ }
+ }
+
+ public void testWithGwtCreated() throws UnableToCompleteException, SAXParseException {
+ init("<doc><with field=\"notAField\" type=\"foo.Foo\"/></doc>", "renderer.OwnerClass.Renderer");
+
+ assertNotNull(fieldManager.lookup("notAField"));
+ assertEquals("foo.Foo", fieldManager.lookup("notAField").getAssignableType()
+ .getQualifiedSourceName());
+ }
+
+ public void testWithUiBinderTypedUiField() throws UnableToCompleteException, SAXParseException {
+ init("<doc><with field=\"fieldName\" type=\"foo.Foo\"/></doc>", "binder.OwnerClass.Binder");
+
+ assertNotNull(fieldManager.lookup("fieldName"));
+ assertEquals("foo.Foo", fieldManager.lookup("fieldName").getAssignableType()
+ .getQualifiedSourceName());
+ }
+
+ public void testWithUiBinderUiFactory() throws UnableToCompleteException, SAXParseException {
+ init("<doc><with field=\"factoryProvided\" type=\"bar.Bar\"/></doc>",
+ "binder.OwnerClass.Binder");
+
+ assertNotNull(fieldManager.lookup("factoryProvided"));
+ assertEquals("bar.Bar", fieldManager.lookup("factoryProvided").getAssignableType()
+ .getQualifiedSourceName());
+ }
+
+ public void testWithUiBinderUntypedUiField() throws UnableToCompleteException, SAXParseException {
+ init("<doc><with field=\"fieldName\"/></doc>", "binder.OwnerClass.Binder");
+
+ assertNotNull(fieldManager.lookup("fieldName"));
+ assertEquals("foo.Foo", fieldManager.lookup("fieldName").getAssignableType()
+ .getQualifiedSourceName());
+ }
+
+ public void testWithUiRendererTypedImport() throws UnableToCompleteException, SAXParseException {
+ init("<doc><with field=\"fieldName\" type=\"foo.Foo\"/></doc>", "renderer.OwnerClass.Renderer");
+
+ assertNotNull(fieldManager.lookup("fieldName"));
+ assertEquals("foo.Fooish", fieldManager.lookup("fieldName").getAssignableType()
+ .getQualifiedSourceName());
+ }
+
+ public void testWithUiRendererUntypedImport()
+ throws UnableToCompleteException, SAXParseException {
+ init("<doc><with field=\"fieldName\"/></doc>", "renderer.OwnerClass.Renderer");
+
+ assertNotNull(fieldManager.lookup("fieldName"));
+ assertEquals("foo.Fooish", fieldManager.lookup("fieldName").getAssignableType()
+ .getQualifiedSourceName());
+ }
+
+ private void init(String domString, String baseClass) throws SAXParseException,
+ UnableToCompleteException {
+ DesignTimeUtils designTime = DesignTimeUtilsStub.EMPTY;
+ elemProvider =
+ new XMLElementProviderImpl(new AttributeParsers(types, null, logger), attributeParsers,
+ types, logger, designTime);
+ doc = docHelper.documentFor(domString, null);
+ item = (Element) doc.getDocumentElement().getElementsByTagName("with").item(0);
+ elm = elemProvider.get(item);
+ JClassType aClass = types.findType(baseClass);
+ writer =
+ new UiBinderWriter(aClass, "foo", "", types, logger, fieldManager, null,
+ DesignTimeUtilsStub.EMPTY, new UiBinderContext(), true, true, "");
+ parser = new UiBinderParser(writer, null, fieldManager, types, null, "");
+ designTime.rememberPathForElements(doc);
+ UiBinderParser.Resource.WITH.create(parser, elm);
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java b/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
new file mode 100644
index 0000000..22da576
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2011 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;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+
+
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * Tests UiBinderWriter validation of UiRender getters and render method.
+ */
+public class UiRendererValidationTest extends AbstractUiBinderWriterTest {
+
+ private static String UI_XML = "<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>"
+ + "<ui:with field='withField' />"
+ + " <div ui:field='root'>"
+ + " <span ui:field='someField'><ui:text from='{withField.toString}'/></span>"
+ + " </div>"
+ + "</ui:UiBinder>";
+
+ private static String UI_XML_TYPED_WITH = "<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>"
+ + "<ui:with field='withField' type='foo.Foo' />"
+ + " <div ui:field='root'>"
+ + " <span ui:field='someField'><ui:text from='{withField.toString}'/></span>"
+ + " </div>"
+ + "</ui:UiBinder>";
+
+ private StringBuffer declaredMethods = new StringBuffer();
+
+ public void testAGoodUiRendererInterface() throws UnableToCompleteException, SAXParseException {
+ declaredMethods.append(" public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+ declaredMethods.append(" public DivElement getRoot(Element foo);");
+ declaredMethods.append(" public SpanElement getSomeField(Element bar);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+ writer.parseDocument(doc, printWriter);
+ }
+
+ public void testGetterBadParameterType() throws SAXParseException, UnableToCompleteException {
+ initGetterTest();
+ declaredMethods.append(" public DivElement getRoot(String bar);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+
+ assertParseFailure("Expected failure due to getter parameter not of type Element.",
+ "Getter getRoot must have exactly one parameter of type assignable to "
+ + "com.google.gwt.dom.client.Element");
+ }
+
+ public void testGetterParameterAssignableToElement()
+ throws SAXParseException, UnableToCompleteException {
+ initGetterTest();
+ declaredMethods.append(" public DivElement getRoot(DivElement bar);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+
+ writer.parseDocument(doc, printWriter);
+ }
+
+ public void testGetterNoParameters() throws SAXParseException, UnableToCompleteException {
+ initGetterTest();
+ declaredMethods.append(" public DivElement getRoot();");
+ init(UI_XML, generateRendererResource(declaredMethods));
+
+ assertParseFailure("Expected failure due to getter with no parameters.",
+ "Getter getRoot must have exactly one parameter");
+ }
+
+ public void testGetterTooManyParameters() throws SAXParseException, UnableToCompleteException {
+ initGetterTest();
+ declaredMethods.append(" public DivElement getRoot(Element parent, Integer i);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+
+ assertParseFailure("Expected failure due to bad getter signature.",
+ "Getter getRoot must have exactly one parameter");
+ }
+
+ public void testGetterUnknownField() throws SAXParseException, UnableToCompleteException {
+ initGetterTest();
+ 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");
+ }
+
+ public void testRenderBadReturnType() throws SAXParseException, UnableToCompleteException {
+ declaredMethods.append(" public String render(SafeHtmlBuilder sb, foo.Foo withField);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+ assertParseFailure("Expected failure due to bad render() return type.",
+ "renderer.OwnerClass.Renderer#render(SafeHtmlBuilder ...) does not return void");
+ }
+
+ public void testRenderExtraParametersOk() throws SAXParseException, UnableToCompleteException {
+ declaredMethods.append(" public void render("
+ + "SafeHtmlBuilder sb, foo.Foo withField, foo.Foo withUnknownField);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+ writer.parseDocument(doc, printWriter);
+ }
+
+ public void testRenderMethodRepeated() throws SAXParseException, UnableToCompleteException {
+ declaredMethods.append(" public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+ declaredMethods.append(" public void render(SafeHtmlBuilder sb);");
+ init(UI_XML, generateRendererResource(declaredMethods));
+ assertParseFailure("Expected failure due to more than one render method.",
+ "renderer.OwnerClass.Renderer declares more than one method named render");
+ }
+
+ public void testRenderMissing() throws SAXParseException, UnableToCompleteException {
+ // No render method here
+ init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+ assertParseFailure("Expected failure due to missing render method.",
+ "renderer.OwnerClass.Renderer does not declare a render(SafeHtmlBuilder ...) method");
+ }
+
+ public void testRenderNoParameters() throws SAXParseException, UnableToCompleteException {
+ declaredMethods.append(" public void render();");
+ init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+ assertParseFailure("Expected failure due to render with no parameters.",
+ "renderer.OwnerClass.Renderer does not declare a render(SafeHtmlBuilder ...) method");
+ }
+
+ public void testRenderNoParametersRepeated() throws SAXParseException, UnableToCompleteException {
+ declaredMethods.append(" public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+ declaredMethods.append(" public void render();");
+ init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+ assertParseFailure("Expected failure due to render method repeated.",
+ "renderer.OwnerClass.Renderer declares more than one method named render");
+ }
+
+ public void testUnknownMethodDeclaration() throws SAXParseException, UnableToCompleteException {
+ initGetterTest();
+ declaredMethods.append(" public void quux();");
+ init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+ assertParseFailure("Expected failure due to unexpected method.",
+ "Unexpected method \"quux\" found");
+ }
+
+ private void assertParseFailure(String message, String expectedMessage) {
+ try {
+ writer.parseDocument(doc, printWriter);
+ fail(message);
+ } catch (UnableToCompleteException e) {
+ if (expectedMessage != null) {
+ assertEquals(expectedMessage, logger.died);
+ }
+ }
+ }
+
+ private MockJavaResource generateRendererResource(final StringBuffer declarations) {
+ return new MockJavaResource("renderer.OwnerClass") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package renderer;\n");
+ code.append("import com.google.gwt.safehtml.shared.SafeHtmlBuilder;\n");
+ code.append("import com.google.gwt.uibinder.client.UiRenderer;\n");
+ code.append("import com.google.gwt.dom.client.DivElement;\n");
+ 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("public class OwnerClass {");
+ code.append(" public interface Renderer");
+ code.append(" extends UiRenderer<OwnerClass> {");
+ code.append(declarations);
+ code.append(" }");
+ code.append("}");
+ return code;
+ }
+ };
+ }
+
+ private void initGetterTest() {
+ // Required, not part of test
+ declaredMethods.append(" public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
index 65ac2c8..e975dff 100644
--- a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
+++ b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
@@ -492,6 +492,17 @@
return code;
}
};
+ public static final MockJavaResource SAFEHTML_BUILDER = new MockJavaResource(
+ "com.google.gwt.safehtml.shared.SafeHtmlBuilder") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.safehtml.shared;\n");
+ code.append("public class SafeHtmlBuilder {");
+ code.append("}");
+ return code;
+ }
+ };
public static final MockJavaResource SPLIT_LAYOUT_PANEL = new MockJavaResource(
"com.google.gwt.user.client.ui.SplitLayoutPanel") {
@Override
@@ -640,6 +651,26 @@
return code;
}
};
+ public static final MockJavaResource UI_FIELD = new MockJavaResource(
+ "com.google.gwt.uibinder.client.UiField") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.uibinder.client;");
+ code.append("import java.lang.annotation.Documented;");
+ code.append("import java.lang.annotation.ElementType;");
+ code.append("import java.lang.annotation.Retention;");
+ code.append("import java.lang.annotation.RetentionPolicy;");
+ code.append("import java.lang.annotation.Target;");
+ code.append("@Documented");
+ code.append("@Retention(RetentionPolicy.RUNTIME)");
+ code.append("@Target(ElementType.FIELD)");
+ code.append("public @interface UiField {");
+ code.append(" boolean provided() default false;");
+ code.append("}");
+ return code;
+ }
+ };
public static final MockJavaResource UI_OBJECT = new MockJavaResource(
"com.google.gwt.user.client.ui.UIObject") {
@Override
@@ -651,6 +682,17 @@
return code;
}
};
+ public static final MockJavaResource UI_RENDERER = new MockJavaResource(
+ "com.google.gwt.uibinder.client.UiRenderer") {
+ @Override
+ public CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package com.google.gwt.uibinder.client;\n");
+ code.append("public interface UiRenderer<T> {");
+ code.append("}");
+ return code;
+ }
+ };
public static final MockJavaResource VALUE_LABEL = new MockJavaResource(
"com.google.gwt.user.client.ui.ValueLabel") {
@Override
@@ -720,6 +762,7 @@
rtn.add(NUMBER_FORMAT);
rtn.add(RENDERER);
rtn.add(SAFEHTML);
+ rtn.add(SAFEHTML_BUILDER);
rtn.add(SPLIT_LAYOUT_PANEL);
rtn.add(STACK_LAYOUT_PANEL);
rtn.add(STACK_PANEL);
@@ -734,6 +777,8 @@
rtn.add(UI_OBJECT);
rtn.add(UI_BINDER);
rtn.add(UI_FACTORY);
+ rtn.add(UI_FIELD);
+ rtn.add(UI_RENDERER);
rtn.add(VALUE_LABEL);
rtn.add(WIDGET);
return rtn;
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 15eabe0..8e4e6b0 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
@@ -15,16 +15,25 @@
*/
package com.google.gwt.uibinder.test.client;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.LabelElement;
import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.uibinder.test.client.UiRendererUi.HtmlRenderer;
/**
* Functional test of UiBinder.
*/
public class UiRendererTest extends GWTTestCase {
+
+ private static final String RENDERED_VALUE = "bar";
+
+ private DivElement docDiv;
+ private SafeHtml renderedHtml;
+ private HtmlRenderer renderer;
private UiRendererUi safeHtmlUi;
@Override
@@ -37,15 +46,172 @@
super.gwtSetUp();
UiRendererTestApp app = UiRendererTestApp.getInstance();
safeHtmlUi = app.getSafeHtmlUi();
+ renderedHtml = safeHtmlUi.render(RENDERED_VALUE);
+ renderer = safeHtmlUi.getRenderer();
+
+ docDiv = Document.get().createDivElement();
+ docDiv.setInnerHTML(renderedHtml.asString());
+ Document.get().getBody().appendChild(docDiv);
+ }
+
+ public void testFieldGetters() {
+ assertTrue(renderer.isParentOrRenderer(docDiv));
+ // Get root from parent
+ DivElement root = renderer.getRoot(docDiv);
+ assertTrue(renderer.isParentOrRenderer(root));
+ assertNotNull(root);
+
+ // For example, the rendered value should be inside
+ assertSpanContainsRenderedValue(root);
+
+ // Get nameSpan
+ SpanElement nameSpan = renderer.getNameSpan(docDiv);
+ assertSpanContainsRenderedValueText(nameSpan.getFirstChild());
+
+ // Getters also work from the root element
+ DivElement root2 = renderer.getRoot(root);
+ assertTrue(renderer.isParentOrRenderer(root2));
+ assertNotNull(root2);
+ assertSpanContainsRenderedValue(root2);
+ nameSpan = renderer.getNameSpan(root);
+ assertSpanContainsRenderedValueText(nameSpan.getFirstChild());
+ }
+
+ public void testFieldGettersDetachedRoot() {
+ // Detach root
+ DivElement root = renderer.getRoot(docDiv);
+ root.removeFromParent();
+
+ // Getting the root element is still fine
+ DivElement rootAgain = renderer.getRoot(root);
+ assertEquals(root, rootAgain);
+
+ if (GWT.isProdMode()) {
+ // In prod mode we avoid checking whether the parent is attached
+ try {
+ renderer.getNameSpan(root);
+ fail("Expected a IllegalArgumentException because root is not attached to the DOM");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ } else {
+ // In dev Mode we explicitly check to see if parent is attached
+ try {
+ renderer.getNameSpan(root);
+ fail("Expected a RuntimeException because root is not attached to the DOM");
+ } catch (RuntimeException e) {
+ // Expected
+ }
+ }
+ }
+
+ public void testFieldGettersNoPreviouslyRenderedElement() {
+ assertFalse(renderer.isParentOrRenderer(null));
+
+ try {
+ renderer.getRoot(null);
+ fail("Expected NPE");
+ } catch (NullPointerException e) {
+ // Expected
+ }
+
+ try {
+ renderer.getNameSpan(null);
+ fail("Expected NPE");
+ } catch (NullPointerException e) {
+ // Expected
+ }
+
+ DivElement root = renderer.getRoot(docDiv);
+ SpanElement nameSpan = renderer.getNameSpan(docDiv);
+
+ // remove nameSpan
+ nameSpan.removeFromParent();
+ try {
+ renderer.getNameSpan(docDiv);
+ fail("Expected IllegalStateException because nameSpan was removed");
+ } catch (IllegalStateException e) {
+ // In dev mode this is different from not being attached
+ assertFalse(GWT.isProdMode());
+ } catch (IllegalArgumentException e) {
+ // Otherwise the same error as being not attached
+ assertTrue(GWT.isProdMode());
+ }
+
+ // Add a a sibling to the root element and remove the root from the parent altogether
+ SpanElement spanElement = Document.get().createSpanElement();
+ docDiv.appendChild(spanElement);
+ root.removeFromParent();
+
+ assertFalse(renderer.isParentOrRenderer(docDiv));
+ if (GWT.isProdMode()) {
+ // In prod mode no attempt is made to check whether root is still attached
+ assertTrue(renderer.isParentOrRenderer(root));
+ } else {
+ assertFalse(renderer.isParentOrRenderer(root));
+ }
+ try {
+ renderer.getRoot(docDiv);
+ fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+
+ try {
+ renderer.getNameSpan(docDiv);
+ fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+
+ // Finally remove the spanElement too
+ spanElement.removeFromParent();
+
+ assertFalse(renderer.isParentOrRenderer(docDiv));
+ try {
+ renderer.getRoot(docDiv);
+ fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+
+ try {
+ renderer.getNameSpan(docDiv);
+ fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ public void testFieldGettersNotOnlyChild() {
+ DivElement root = renderer.getRoot(docDiv);
+
+ // Add a a sibling to the root element
+ docDiv.appendChild(Document.get().createSpanElement());
+
+ // Getting the root element is still fine
+ DivElement rootAgain = renderer.getRoot(docDiv);
+ assertEquals(root, rootAgain);
+
+ if (GWT.isProdMode()) {
+ // In prod mode we avoid checking for being the only child
+ assertTrue(renderer.isParentOrRenderer(docDiv));
+ SpanElement nameSpan = renderer.getNameSpan(docDiv);
+ assertSpanContainsRenderedValueText(nameSpan.getFirstChild());
+ } else {
+ // in dev mode an explicit check is made
+ assertFalse(renderer.isParentOrRenderer(docDiv));
+ try {
+ renderer.getNameSpan(docDiv);
+ fail("Expected an IllegalArgumentException to fail because root is not the only child");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
}
public void testSafeHtmlRendererText() {
- SafeHtml render = safeHtmlUi.render();
-
- LabelElement renderedHtml = Document.get().createLabelElement();
- renderedHtml.setInnerHTML(render.asString());
-
- Node innerDiv = renderedHtml.getFirstChild();
+ Node innerDiv = docDiv.getFirstChild();
// Was the first span rendered as a "HTML-safe" text string?
Node spanWithConstantTextNode = innerDiv.getChild(0);
@@ -58,15 +224,38 @@
assertEquals(Node.TEXT_NODE, firstRawTextNode.getNodeType());
assertEquals(" Hello, ", firstRawTextNode.getNodeValue());
- // Fields not present in owning class produce no content
- Node firstFieldNode = innerDiv.getChild(2);
- assertEquals(Node.ELEMENT_NODE, firstFieldNode.getNodeType());
- assertEquals("span", firstFieldNode.getNodeName().toLowerCase());
- assertFalse(firstFieldNode.hasChildNodes());
+ // The value passed to render() was rendered correctly
+ assertSpanContainsRenderedValue(innerDiv);
// ui:msg tags get rendered but the "<ui:msg>" tag is not
Node secondRawTextNode = innerDiv.getChild(3);
assertEquals(Node.TEXT_NODE, secondRawTextNode.getNodeType());
assertEquals(". How goes it? ", secondRawTextNode.getNodeValue());
+
+ // Fields not present in owning class produce no content
+ Node spanNode = innerDiv.getChild(4);
+ assertEquals(Node.ELEMENT_NODE, spanNode.getNodeType());
+ assertEquals("span", spanNode.getNodeName().toLowerCase());
+ assertFalse(spanNode.hasChildNodes());
+ }
+
+ @Override
+ protected void gwtTearDown() throws Exception {
+ docDiv.removeFromParent();
+ docDiv = null;
+ }
+
+ private void assertSpanContainsRenderedValue(Node root) {
+ Node firstFieldNode = root.getChild(2);
+ assertEquals(Node.ELEMENT_NODE, firstFieldNode.getNodeType());
+ assertEquals("span", firstFieldNode.getNodeName().toLowerCase());
+ assertTrue(firstFieldNode.hasChildNodes());
+ Node renderedValue = firstFieldNode.getFirstChild();
+ assertSpanContainsRenderedValueText(renderedValue);
+ }
+
+ private void assertSpanContainsRenderedValueText(Node renderedValue) {
+ assertEquals(Node.TEXT_NODE, renderedValue.getNodeType());
+ assertEquals(RENDERED_VALUE, renderedValue.getNodeValue());
}
}
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 0b26d06..5127079 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
@@ -1,31 +1,37 @@
/*
* Copyright 2011 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
- *
+ *
+ * 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
+ *
+ * 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;
import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableColElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtml;
-import com.google.gwt.text.shared.UiRenderer;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.uibinder.client.UiRenderer;
/**
- * Sample use of a {@code SafeHtmlRenderer} with no dependency on
- * com.google.gwt.user.
+ * Sample use of a {@code UiRenderer} with no dependency on com.google.gwt.user.
*/
public class UiRendererUi {
+
/**
* Resources for this template.
*/
@@ -39,16 +45,48 @@
*/
public interface Style extends CssResource {
String bodyColor();
+
String bodyFont();
}
- interface HtmlRenderer extends UiRenderer<String> { }
+ static class Foo {
+ String bar;
+
+ public Foo(String bar) {
+ this.bar = bar;
+ }
+
+ String getBar() {
+ return bar;
+ }
+ }
+
+ interface HtmlRenderer extends UiRenderer<UiRendererUi> {
+ SpanElement getNameSpan(Element owner);
+ TableColElement getNarrowColumn(Element owner);
+ DivElement getRoot(Element owner);
+ TableSectionElement getTbody(Element owner);
+ TableCellElement getTh1(Element owner);
+ TableCellElement getTh2(Element owner);
+ TableCellElement getTh4(Element owner);
+ Element getTmElement(Element owner);
+ TableRowElement getTr(Element owner);
+
+ void render(SafeHtmlBuilder sb, Foo aValue);
+ }
+
private static final HtmlRenderer renderer = GWT.create(HtmlRenderer.class);
+ public static HtmlRenderer getRenderer() {
+ return renderer;
+ }
+
public UiRendererUi() {
}
- public SafeHtml render() {
- return renderer.render(null);
+ public SafeHtml render(String value) {
+ SafeHtmlBuilder sb = new SafeHtmlBuilder();
+ getRenderer().render(sb, new Foo(value));
+ return sb.toSafeHtml();
}
}
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 2dea655..e5e228c 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
@@ -16,12 +16,14 @@
xmlns:res='urn:with:com.google.gwt.uibinder.test.client.DomBasedUi.Resources'
>
<ui:with field="constants" type="com.google.gwt.uibinder.test.client.Constants"/>
+ <ui:with field="aValue"/>
<div ui:field='root' res:class="style.bodyColor 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" />.
+ Hello, <span ui:field="nameSpan"><ui:text from="{aValue.getBar}"/></span>.
<ui:msg>How goes it?</ui:msg>
+ <span/>
<h2 res:class="style.bodyColor style.bodyFont">Placeholders in localizables</h2>
<p><ui:msg>When you mark up your text for translation, there will be bits
that you don't want the translators to mess with. You can protect