Structural changes to UiBinder to make fields accessible via getters
and creation lazy loaded. LazyPanel support is also added.
Review at http://gwt-code-reviews.appspot.com/1420804
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10059 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
index 9a57445..50cbc5b 100644
--- a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
+++ b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
@@ -15,18 +15,24 @@
<!-- GWT UI Binder support. -->
<module>
<inherits name="com.google.gwt.resources.Resources" />
-
+
<source path="client"/>
<source path="resources"/>
-
- <!-- By default UiBinder implementations are generated to use SafeHtmlTemplates
- to help protect against the introduction of cross-site scripting (XSS) attacks.
- This deprecated property can be used to disable that integration while the
- kinks are worked out. Its use is strongly discouraged, and the property will
+
+ <!-- By default UiBinder implementations are generated to use SafeHtmlTemplates
+ to help protect against the introduction of cross-site scripting (XSS) attacks.
+ This deprecated property can be used to disable that integration while the
+ kinks are worked out. Its use is strongly discouraged, and the property will
be removed in the near future. -->
<define-configuration-property name="UiBinder.useSafeHtmlTemplates" is-multi-valued="false"/>
<set-configuration-property name="UiBinder.useSafeHtmlTemplates" value="true"/>
-
+
+ <!-- UiBinder can be configured to use a new strategy that enables a faster
+ rendering mode and make some widgets lazily created. This is still experimental
+ but should be the default option in a soon future. -->
+ <define-configuration-property name="UiBinder.useLazyWidgetBuilders" is-multi-valued="false"/>
+ <set-configuration-property name="UiBinder.useLazyWidgetBuilders" value="false"/>
+
<generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
<when-type-assignable class="com.google.gwt.uibinder.client.UiBinder"/>
</generate-with>
diff --git a/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java b/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java
index 9133cf9..7da4e8d 100644
--- a/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java
+++ b/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java
@@ -45,8 +45,9 @@
*/
public class FieldReferenceConverter {
/**
- * May be thrown by the {@link com.google.gwt.uibinder.attributeparsers.FieldReferenceConverter.Delegate Delegate} for badly
- * formatted input.
+ * May be thrown by the
+ * {@link com.google.gwt.uibinder.attributeparsers.FieldReferenceConverter.Delegate Delegate}
+ * for badly formatted input.
*/
@SuppressWarnings("serial")
public static class IllegalFieldReferenceException extends RuntimeException {
@@ -167,8 +168,15 @@
StringBuilder b = new StringBuilder();
String[] segments = value.split("[.]");
- for (String segment : segments) {
- segment = cssConverter.convertName(segment);
+ for (int i = 0; i < segments.length; ++i) {
+ String segment = cssConverter.convertName(segments[i]);
+
+ // The first segment is converted to a field getter. So,
+ // "bundle.whatever" becomes "get_bundle().whatever".
+ if (fieldManager != null && i == 0) {
+ segment = fieldManager.convertFieldToGetter(segment);
+ }
+
if (b.length() == 0) {
b.append(segment); // field name
} else {
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
index 4b8c1ed..05cd36f 100644
--- a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
+++ b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
@@ -26,6 +26,28 @@
* so please don't use them for non-UiBinder code.
*/
public class UiBinderUtil {
+
+ /**
+ * A helper class to enable lazy creation of DOM elements.
+ */
+ public static class LazyDomElement {
+
+ private Element element;
+ private final String domId;
+
+ public LazyDomElement(String domId) {
+ this.domId = domId;
+ }
+
+ public Element get() {
+ if (element == null) {
+ element = Document.get().getElementById(domId).cast();
+ element.removeAttribute("id");
+ }
+ return element;
+ }
+ }
+
/**
* Temporary attachment record that keeps track of where an element was
* before attachment. Use the detach method to put things back.
@@ -34,15 +56,15 @@
public static class TempAttachment {
private final Element element;
private final Element origParent;
- private final Element origSibling;
-
- private TempAttachment(Element origParent, Element origSibling,
+ private final Element origSibling;
+
+ private TempAttachment(Element origParent, Element origSibling,
Element element) {
this.origParent = origParent;
this.origSibling = origSibling;
this.element = element;
}
-
+
/**
* Restore to previous DOM state before attachment.
*/
@@ -54,21 +76,21 @@
orphan(element);
}
}
- }
-
+ }
+
private static Element hiddenDiv;
-
+
/**
- * Attaches the element to the dom temporarily. Keeps track of where it is
+ * Attaches the element to the dom temporarily. Keeps track of where it is
* attached so that things can be put back latter.
- *
+ *
* @return attachment record which can be used for reverting back to previous
* DOM state
*/
public static TempAttachment attachToDom(Element element) {
// TODO(rjrjr) This is copied from HTMLPanel. Reconcile
ensureHiddenDiv();
-
+
// Hang on to the panel's original parent and sibling elements so that it
// can be replaced.
Element origParent = element.getParentElement();
@@ -76,7 +98,7 @@
// Attach the panel's element to the hidden div.
hiddenDiv.appendChild(element);
-
+
return new TempAttachment(origParent, origSibling, element);
}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
index f5e9f22..24d55c2 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
@@ -37,7 +37,7 @@
throws UnableToCompleteException {
String fieldName = writer.declareFieldIfNeeded(elem);
if (fieldName != null) {
- String token = writer.declareDomField(fieldName);
+ String token = writer.declareDomField(fieldName, element);
if (elem.hasAttribute("id")) {
writer.die(elem, String.format(
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
index a36ae2d..83d5d40 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
@@ -61,7 +61,7 @@
if (null == customTag) {
writer.setFieldInitializerAsConstructor(fieldName, type, writer.declareTemplateCall(html));
} else {
- writer.setFieldInitializerAsConstructor(fieldName, type, customTag,
+ writer.setFieldInitializerAsConstructor(fieldName, type, customTag,
writer.declareTemplateCall(html));
}
}
@@ -72,9 +72,11 @@
*/
private HtmlInterpreter makeHtmlInterpreter(final String fieldName,
final UiBinderWriter uiWriter) {
- final String ancestorExpression = fieldName + ".getElement()";
+ final String ancestorExpression = uiWriter.useLazyWidgetBuilders()
+ ? fieldName : (fieldName + ".getElement()");
- PlaceholderInterpreterProvider placeholderInterpreterProvider = new PlaceholderInterpreterProvider() {
+ PlaceholderInterpreterProvider placeholderInterpreterProvider =
+ new PlaceholderInterpreterProvider() {
public PlaceholderInterpreter get(MessageWriter message) {
return new WidgetPlaceholderInterpreter(fieldName, uiWriter, message,
ancestorExpression);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java
index d61fa40..562c11e 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HtmlInterpreter.java
@@ -47,7 +47,8 @@
*/
public static HtmlInterpreter newInterpreterForUiObject(
UiBinderWriter writer, String uiExpression) {
- String ancestorExpression = uiExpression + ".getElement()";
+ String ancestorExpression = writer.useLazyWidgetBuilders()
+ ? uiExpression : uiExpression + ".getElement()";
return new HtmlInterpreter(writer, ancestorExpression,
new HtmlMessageInterpreter(writer, ancestorExpression));
}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/LazyPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/LazyPanelParser.java
new file mode 100644
index 0000000..96a36a8
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/elementparsers/LazyPanelParser.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.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.LazyPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.LazyPanel} widgets.
+ */
+public class LazyPanelParser implements ElementParser {
+
+ private static final String INITIALIZER_FORMAT = "new %s() {\n"
+ + " protected %s createWidget() {\n"
+ + " return %s;\n"
+ + " }\n"
+ + "}";
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+
+ if (!writer.useLazyWidgetBuilders()) {
+ writer.die("LazyPanel only works with UiBinder.useLazyWidgetBuilders enabled.");
+ }
+
+ XMLElement child = elem.consumeSingleChildElement();
+ if (!writer.isWidgetElement(child)) {
+ writer.die(child, "Expecting only widgets in %s", elem);
+ }
+
+ String childFieldName = writer.parseElementToField(child);
+
+ String lazyPanelClassPath = LazyPanel.class.getName();
+ String widgetClassPath = Widget.class.getName();
+
+ String code = String.format(
+ INITIALIZER_FORMAT, lazyPanelClassPath, widgetClassPath, childFieldName);
+ writer.setFieldInitializer(fieldName, code);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
index 7bbc33f..81784b3 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
@@ -16,6 +16,9 @@
package com.google.gwt.uibinder.elementparsers;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.client.UiBinderUtil.LazyDomElement;
+import com.google.gwt.uibinder.rebind.FieldManager;
+import com.google.gwt.uibinder.rebind.FieldWriter;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
@@ -66,7 +69,7 @@
this.uiWriter = writer;
}
- public String interpretElement(XMLElement elem)
+ public String interpretElement(XMLElement elem)
throws UnableToCompleteException {
if (uiWriter.isWidgetElement(elem)) {
// Allocate a local variable to hold the dom id for this widget. Note
@@ -76,21 +79,43 @@
String idHolder = uiWriter.declareDomIdHolder();
String childField = uiWriter.parseElementToField(elem);
uiWriter.ensureCurrentFieldAttached();
-
+
String elementPointer = idHolder + "Element";
uiWriter.addInitStatement(
"com.google.gwt.user.client.Element %s = " +
"com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
elementPointer, idHolder);
- // Delay replacing the placeholders with the widgets until after
- // detachment so as not to end up attaching the widget to the DOM
- // unnecessarily
- uiWriter.addDetachStatement(
- "%1$s.addAndReplaceElement(%2$s, %3$s);",
- fieldName, childField, elementPointer);
+
+ FieldManager fieldManager = uiWriter.getFieldManager();
+
+ if (uiWriter.useLazyWidgetBuilders()) {
+
+ // Register a DOM id field.
+ String lazyDomElementPath = LazyDomElement.class.getCanonicalName();
+ fieldManager.registerField(lazyDomElementPath, elementPointer)
+ .setInitializer(String.format("new %s(%s)",
+ lazyDomElementPath, fieldManager.convertFieldToGetter(idHolder)));
+
+ // Add attach/detach sections for this element.
+ FieldWriter fieldWriter = fieldManager.require(fieldName);
+ fieldWriter.addAttachStatement("%s.get();",
+ fieldManager.convertFieldToGetter(elementPointer));
+ fieldWriter.addDetachStatement("%s.addAndReplaceElement(%s, %s.get());",
+ fieldName, childField, fieldManager.convertFieldToGetter(elementPointer));
+
+ } else {
+
+ // Delay replacing the placeholders with the widgets until after
+ // detachment so as not to end up attaching the widget to the DOM
+ // unnecessarily
+ uiWriter.addDetachStatement(
+ "%1$s.addAndReplaceElement(%2$s, %3$s);",
+ fieldName, childField, elementPointer);
+ }
// Create an element to hold the widget.
String tag = getLegalPlaceholderTag(elem);
+ idHolder = fieldManager.convertFieldToGetter(idHolder);
if (uiWriter.useSafeHtmlTemplates()) {
idHolder = uiWriter.tokenForStringExpression(idHolder);
} else {
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
index 803b9bf..5f9edde 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
@@ -18,6 +18,7 @@
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.uibinder.rebind.FieldManager;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -60,6 +61,7 @@
* Other, but that seems more trouble than it's worth.
*/
+ private final FieldManager fieldManager;
private int serial = 0;
private final String ancestorExpression;
private final String fieldName;
@@ -73,6 +75,7 @@
super(writer, message, ancestorExpression);
this.fieldName = fieldName;
this.ancestorExpression = ancestorExpression;
+ this.fieldManager = uiWriter.getFieldManager();
}
@Override
@@ -114,14 +117,23 @@
public String postProcess(String consumed) throws UnableToCompleteException {
for (String idHolder : idToWidgetElement.keySet()) {
XMLElement childElem = idToWidgetElement.get(idHolder);
- String childField = uiWriter.parseElementToField(childElem);
+
+ String childField = uiWriter.parseElementToFieldWriter(childElem).getName();
genSetWidgetTextCall(idHolder, childField);
- uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);",
- fieldName, childField, idHolder);
+
+ if (uiWriter.useLazyWidgetBuilders()) {
+ fieldManager.require(fieldName).addDetachStatement(
+ "%1$s.addAndReplaceElement(%2$s, %3$s);",
+ fieldName, fieldManager.convertFieldToGetter(childField), idHolder);
+ } else {
+ uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);",
+ fieldName, childField, idHolder);
+ }
}
+
/*
- * We get used recursively, so this will be called again. Empty the map
+ * We get used recursively, so this will be called again. Empty the map
* or else we'll re-register things.
*/
idToWidgetElement.clear();
@@ -134,8 +146,9 @@
}
private String genOpenTag(String name, String idHolder) {
+ idHolder = fieldManager.convertFieldToGetter(idHolder);
if (uiWriter.useSafeHtmlTemplates()) {
- idHolder = uiWriter.tokenForStringExpression(idHolder);
+ idHolder = uiWriter.tokenForStringExpression(idHolder);
} else {
idHolder = "\" + " + idHolder + " + \"";
}
@@ -144,16 +157,32 @@
return openPlaceholder;
}
- private void genSetWidgetTextCall(String idHolder, String childField) {
- if (idIsHasText.contains(idHolder)) {
- uiWriter.addInitStatement(
- "%s.setText(%s.getElementById(%s).getInnerText());", childField,
- fieldName, idHolder);
- }
- if (idIsHasHTML.contains(idHolder)) {
- uiWriter.addInitStatement(
- "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField,
- fieldName, idHolder);
+ private void genSetWidgetTextCall(String idHolder, String childField)
+ throws UnableToCompleteException {
+
+ if (uiWriter.useLazyWidgetBuilders()) {
+ if (idIsHasText.contains(idHolder)) {
+ fieldManager.require(childField).addAttachStatement(
+ "%s.setText(%s.getElementById(%s).getInnerText());", childField,
+ fieldManager.convertFieldToGetter(fieldName),
+ fieldManager.convertFieldToGetter(idHolder));
+ } else if (idIsHasHTML.contains(idHolder)) {
+ fieldManager.require(childField).addAttachStatement(
+ "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField,
+ fieldManager.convertFieldToGetter(fieldName),
+ fieldManager.convertFieldToGetter(idHolder));
+ }
+ } else {
+ if (idIsHasText.contains(idHolder)) {
+ uiWriter.addInitStatement(
+ "%s.setText(%s.getElementById(%s).getInnerText());", childField,
+ fieldName, idHolder);
+ }
+ if (idIsHasHTML.contains(idHolder)) {
+ uiWriter.addInitStatement(
+ "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField,
+ fieldName, idHolder);
+ }
}
}
@@ -186,8 +215,9 @@
}
private String handleOpaqueWidgetPlaceholder(String name, String idHolder) {
+ idHolder = fieldManager.convertFieldToGetter(idHolder);
if (uiWriter.useSafeHtmlTemplates()) {
- idHolder = uiWriter.tokenForStringExpression(idHolder);
+ idHolder = uiWriter.tokenForStringExpression(idHolder);
} else {
idHolder = "\" + " + idHolder + " + \"";
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
index c61f284..b907124 100644
--- a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -20,7 +20,11 @@
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.uibinder.rebind.model.OwnerField;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
@@ -33,15 +37,26 @@
* {@link FieldWriter#getInstantiableType()}.
*/
abstract class AbstractFieldWriter implements FieldWriter {
- private static final String NO_DEFAULT_CTOR_ERROR = "%1$s has no default (zero args) constructor. To fix this, you can define"
+ private static final String NO_DEFAULT_CTOR_ERROR =
+ "%1$s has no default (zero args) constructor. To fix this, you can define"
+ " a @UiFactory method on the UiBinder's owner, or annotate a constructor of %2$s with"
+ " @UiConstructor.";
- private final String name;
+ private static int nextAttachVar;
+
+ public static String getNextAttachVar() {
+ return "attachRecord" + nextAttachVar++;
+ }
private final Set<FieldWriter> needs = new LinkedHashSet<FieldWriter>();
+ private final List<String> statements = new ArrayList<String>();
+ private final List<String> attachStatements = new ArrayList<String>();
+ private final List<String> detachStatements = new ArrayList<String>();
+
+ private final String name;
private String initializer;
private boolean written;
+ private int buildPrecedence;
private MortalLogger logger;
public AbstractFieldWriter(String name, MortalLogger logger) {
@@ -50,12 +65,38 @@
}
this.name = name;
this.logger = logger;
+ this.buildPrecedence = 1;
+ }
+
+ @Override
+ public void addAttachStatement(String format, Object... args) {
+ attachStatements.add(String.format(format, args));
+ }
+
+ @Override
+ public void addDetachStatement(String format, Object... args) {
+ detachStatements.add(String.format(format, args));
+ }
+
+ @Override
+ public void addStatement(String format, Object... args) {
+ statements.add(String.format(format, args));
+ }
+
+ @Override
+ public int getBuildPrecedence() {
+ return buildPrecedence;
}
public String getInitializer() {
return initializer;
}
+ @Override
+ public String getName() {
+ return name;
+ }
+
public JType getReturnType(String[] path, MonitoredLogger logger) {
if (!name.equals(path[0])) {
throw new RuntimeException(this
@@ -70,6 +111,11 @@
needs.add(f);
}
+ @Override
+ public void setBuildPrecendence(int precedence) {
+ this.buildPrecedence = precedence;
+ }
+
public void setInitializer(String initializer) {
this.initializer = initializer;
}
@@ -114,6 +160,113 @@
this.written = true;
}
+ @Override
+ public void writeFieldBuilder(IndentedWriter w, int getterCount,
+ OwnerField ownerField) throws UnableToCompleteException {
+ if (getterCount > 1) {
+ w.write("%s; // more than one getter call detected. Precedence: %s",
+ FieldManager.getFieldBuilder(name), getBuildPrecedence());
+ return;
+ }
+
+ if (getterCount == 0 && ownerField != null) {
+ w.write("%s; // no getter call detected but must bind to ui:field. Precedence: %s",
+ FieldManager.getFieldBuilder(name), getBuildPrecedence());
+ }
+ }
+
+ @Override
+ public void writeFieldDefinition(IndentedWriter w, TypeOracle typeOracle,
+ OwnerField ownerField, DesignTimeUtils designTime, int getterCount)
+ throws UnableToCompleteException {
+
+ // Check initializer.
+ if (initializer == null) {
+ if (ownerField != null && ownerField.isProvided()) {
+ initializer = String.format("owner.%s", name);
+ } else {
+ JClassType type = getInstantiableType();
+ if (type != null) {
+ if ((type.isInterface() == null)
+ && (type.findConstructor(new JType[0]) == null)) {
+ logger.die(NO_DEFAULT_CTOR_ERROR, type.getQualifiedSourceName(),
+ type.getName());
+ }
+ }
+ initializer = String.format("(%1$s) GWT.create(%1$s.class)",
+ getQualifiedSourceName());
+ }
+ }
+
+ w.newline();
+ w.write("/**");
+ w.write(" * Getter for %s called %s times.", name, getterCount);
+ w.write(" */");
+ if (getterCount > 1) {
+ w.write("private %1$s %2$s;", getQualifiedSourceName(), name);
+ }
+
+ w.write("private %s %s {", getQualifiedSourceName(), FieldManager.getFieldGetter(name));
+ w.indent();
+ w.write("return %s;", (getterCount > 1) ? name : FieldManager.getFieldBuilder(name));
+ w.outdent();
+ w.write("}");
+
+ w.write("private %s %s {", getQualifiedSourceName(), FieldManager.getFieldBuilder(name));
+ w.indent();
+
+ w.write("// Creation section.");
+ if (getterCount > 1) {
+ w.write("%s = %s;", name, initializer);
+ } else {
+ w.write("%s %s = %s;", getQualifiedSourceName(), name, initializer);
+ }
+
+ w.write("// Setup section.");
+ for (String s : statements) {
+ w.write(s);
+ }
+
+ String attachedVar = null;
+
+ if (attachStatements.size() > 0) {
+ w.newline();
+ w.write("// Attach section.");
+ attachedVar = getNextAttachVar();
+
+ JClassType elementType = typeOracle.findType(Element.class.getName());
+
+ String elementToAttach = getInstantiableType().isAssignableTo(elementType)
+ ? name : name + ".getElement()";
+
+ w.write("UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
+ attachedVar, elementToAttach);
+
+ for (String s : attachStatements) {
+ w.write(s);
+ }
+ }
+
+ if (attachedVar != null) {
+ w.newline();
+ w.write("// Detach section.");
+ w.write("%s.detach();", attachedVar);
+ for (String s : detachStatements) {
+ w.write(s);
+ }
+ }
+
+ if ((ownerField != null) && !ownerField.isProvided()) {
+ w.newline();
+ w.write("owner.%1$s = %1$s;", name);
+ }
+
+ w.newline();
+ w.write("return %s;", name);
+ w.outdent();
+ w.write("}");
+ }
+
private JMethod findMethod(JClassType type, String methodName) {
// TODO Move this and getClassHierarchyBreadthFirst to JClassType
for (JClassType nextType : UiBinderWriter.getClassHierarchyBreadthFirst(type)) {
@@ -153,4 +306,4 @@
}
return type;
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
index d508b62..26489ae 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,14 +15,18 @@
*/
package com.google.gwt.uibinder.rebind;
-import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
@@ -35,6 +39,21 @@
private static final String DUPLICATE_FIELD_ERROR = "Duplicate declaration of field %1$s.";
+ private static final Comparator<FieldWriter> BUILD_DEFINITION_SORT =
+ new Comparator<FieldWriter>() {
+ public int compare(FieldWriter field1, FieldWriter field2) {
+ return field2.getBuildPrecedence() - field1.getBuildPrecedence();
+ }
+ };
+
+ public static String getFieldBuilder(String fieldName) {
+ return String.format("build_%s()", fieldName);
+ }
+
+ public static String getFieldGetter(String fieldName) {
+ return String.format("get_%s()", fieldName);
+ }
+
private final TypeOracle types;
private final MortalLogger logger;
@@ -42,18 +61,67 @@
* Map of field name to FieldWriter. Note its a LinkedHashMap--we want to
* write these out in the order they're declared.
*/
- private final LinkedHashMap<String, FieldWriter> fieldsMap = new LinkedHashMap<String, FieldWriter>();
+ private final LinkedHashMap<String, FieldWriter> fieldsMap =
+ new LinkedHashMap<String, FieldWriter>();
/**
* A stack of the fields.
*/
private final LinkedList<FieldWriter> parsedFieldStack = new LinkedList<FieldWriter>();
- private LinkedHashMap<String, FieldReference> fieldReferences = new LinkedHashMap<String, FieldReference>();
+ private LinkedHashMap<String, FieldReference> fieldReferences =
+ new LinkedHashMap<String, FieldReference>();
- public FieldManager(TypeOracle types, MortalLogger logger) {
+ /**
+ * Counts the number of times a getter field is called, this important to
+ * decide which strategy to take when outputing getters and builders.
+ * {@see com.google.gwt.uibinder.rebind.FieldWriter#writeFieldDefinition}.
+ */
+ private final Map<String, Integer> gettersCounter = new HashMap<String, Integer>();
+
+ /**
+ * Whether to use the new strategy of generating UiBinder code.
+ */
+ private final boolean useLazyWidgetBuilders;
+
+ public FieldManager(TypeOracle types, MortalLogger logger, boolean useLazyWidgetBuilders) {
this.types = types;
this.logger = logger;
+ this.useLazyWidgetBuilders = useLazyWidgetBuilders;
+ }
+
+ /**
+ * Converts the given field to its getter. Example:
+ * <li> myWidgetX = get_myWidgetX()
+ * <li> f_html1 = get_f_html1()
+ */
+ public String convertFieldToGetter(String fieldName) {
+ // TODO(hermes, rjrjr, rdcastro): revisit this and evaluate if this
+ // conversion can be made directly in FieldWriter.
+ if (!useLazyWidgetBuilders) {
+ return fieldName;
+ }
+
+ int count = getGetterCounter(fieldName) + 1;
+ gettersCounter.put(fieldName, count);
+ return getFieldGetter(fieldName);
+ }
+
+ /**
+ * Initialize with field builders the generated <b>Widgets</b> inner class.
+ * {@see com.google.gwt.uibinder.rebind.FieldWriter#writeFieldBuilder}.
+ */
+ public void initializeWidgetsInnerClass(IndentedWriter w,
+ OwnerClass ownerClass) throws UnableToCompleteException {
+
+ FieldWriter[] fields = fieldsMap.values().toArray(
+ new FieldWriter[fieldsMap.size()]);
+ Arrays.sort(fields, BUILD_DEFINITION_SORT);
+
+ for (FieldWriter field : fields) {
+ int count = getGetterCounter(field.getName());
+ field.writeFieldBuilder(w, count, ownerClass.getUiField(field.getName()));
+ }
}
/**
@@ -87,9 +155,9 @@
* When making a field we peek at the {@link #parsedFieldStack} to make sure
* that the field that holds the widget currently being parsed will depended
* upon the field being declared. This ensures, for example, that dom id
- * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel
+ * fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel
* will be declared before it is.
- *
+ *
* @param fieldType the type of the new field
* @param fieldName the name of the new field
* @return a new {@link FieldWriter} instance
@@ -102,6 +170,11 @@
return registerField(fieldName, field);
}
+ public FieldWriter registerField(String type, String fieldName)
+ throws UnableToCompleteException {
+ return registerField(types.findType(type), fieldName);
+ }
+
/**
* Used to declare fields that will hold generated instances generated
* CssResource interfaces. If your field will hold a reference of an existing
@@ -114,7 +187,7 @@
* upon the field being declared. This ensures, for example, that dom id
* fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel
* will be declared before it is.
- *
+ *
* @throws UnableToCompleteException on duplicate name
* @return a new {@link FieldWriter} instance
*/
@@ -136,7 +209,7 @@
* upon the field being declared. This ensures, for example, that dom id
* fields (see {@link UiBinderWriter#declareDomIdHolder()}) used by an HTMLPanel
* will be declared before it is.
- *
+ *
* @param assignableType class or interface extened or implemented by this
* type
* @param typeName the full qualified name for the class associated with the
@@ -169,10 +242,23 @@
}
/**
+ * Gets a FieldWriter given its name or throws a RuntimeException if not found.
+ * @param fieldName the name of the {@link FieldWriter} to find
+ * @return the {@link FieldWriter} instance indexed by fieldName
+ */
+ public FieldWriter require(String fieldName) {
+ FieldWriter fieldWriter = lookup(fieldName);
+ if (fieldWriter == null) {
+ throw new RuntimeException("The required field %s doesn't exist.");
+ }
+ return fieldWriter;
+ }
+
+ /**
* To be called after parsing is complete. Surveys all
* <code>{field.reference}</code>s and checks they refer to existing types,
* and have appropriate return types.
- *
+ *
* @throws UnableToCompleteException if any <code>{field.references}</code>
* can't be resolved
*/
@@ -192,8 +278,23 @@
}
/**
+ * Outputs the getter and builder definitions for all fields.
+ * {@see com.google.gwt.uibinder.rebind.AbstractFieldWriter#writeFieldDefinition}.
+ */
+ public void writeFieldDefinitions(IndentedWriter writer, TypeOracle typeOracle,
+ OwnerClass ownerClass, DesignTimeUtils designTime)
+ throws UnableToCompleteException {
+ Collection<FieldWriter> fields = fieldsMap.values();
+ for (FieldWriter field : fields) {
+ int counter = getGetterCounter(field.getName());
+ field.writeFieldDefinition(writer, typeOracle,
+ ownerClass.getUiField(field.getName()), designTime, counter);
+ }
+ }
+
+ /**
* Writes all stored gwt fields.
- *
+ *
* @param writer the writer to output
* @param ownerTypeName the name of the class being processed
*/
@@ -205,6 +306,14 @@
}
}
+ /**
+ * Gets the number of times a getter for the given field is called.
+ */
+ private int getGetterCounter(String fieldName) {
+ Integer count = gettersCounter.get(fieldName);
+ return (count == null) ? 0 : count;
+ }
+
private FieldWriter registerField(String fieldName, FieldWriter field)
throws UnableToCompleteException {
requireUnique(fieldName);
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
index 6cabcd3..7ad8a7e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -18,6 +18,8 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.uibinder.rebind.model.OwnerField;
/**
* Models a field to be written in the generated binder code. Note that this is
@@ -36,6 +38,50 @@
public interface FieldWriter {
/**
+ * Add a statement to be executed right after the current field is attached.
+ *
+ * <pre>
+ * HTMLPanel panel = new HTMLPanel(
+ * "<span id='someId' class='someclass'></span>...");
+ *
+ * // statement section.
+ * widgetX.setStyleName("someCss");
+ * widgetX.setVisible(true);
+ *
+ * // attach section.
+ * UiBinderUtil.TempAttachment attachRecord =
+ * UiBinderUtil.attachToDom(panel.getElement());
+ * get_domId0Element().get();
+ * get_domId1Element().get();
+ *
+ * // detach section.
+ * attachRecord.detach();
+ * panel.addAndReplaceElement(get_someWidget(), get_domId0Element().get());
+ * panel.addAndReplaceElement(get_otherWidget(), get_domId1Element().get());
+ * </pre>
+ */
+ void addAttachStatement(String format, Object... args);
+
+ /**
+ * Add a statement to be executed right after the current field is detached.
+ * {@see #addAttachStatement}.
+ */
+ void addDetachStatement(String format, Object... args);
+
+ /**
+ * Add a statement for the given field, executed right after its creation. Example:
+ *
+ * <pre>
+ * WidgetX widgetX = GWT.create(WidgetX.class);
+ *
+ * // statement section.
+ * widgetX.setStyleName("someCss");
+ * widgetX.setVisible(true);
+ * </pre>
+ */
+ void addStatement(String format, Object... args);
+
+ /**
* Returns the type of this field, or for generated types the type it extends.
*/
// TODO(rjrjr) When ui:style is able to implement multiple interfaces,
@@ -43,6 +89,11 @@
JClassType getAssignableType();
/**
+ * Gets this field builder precedence.
+ */
+ int getBuildPrecedence();
+
+ /**
* Returns the custom initializer for this field, or null if it is not set.
*/
String getInitializer();
@@ -54,6 +105,11 @@
JClassType getInstantiableType();
/**
+ * Get the name of the field.
+ */
+ String getName();
+
+ /**
* Returns the qualified source name of this type.
*/
String getQualifiedSourceName();
@@ -71,6 +127,12 @@
void needs(FieldWriter f);
/**
+ * Sets the precedence of this field builder. Field with higher values are
+ * written first.
+ */
+ void setBuildPrecendence(int precedence);
+
+ /**
* Used to provide an initializer string to use instead of a
* {@link com.google.gwt.core.client.GWT#create} call. Note that this is an
* RHS expression. Don't include the leading '=', and don't end it with ';'.
@@ -83,4 +145,62 @@
* Write the field declaration.
*/
void write(IndentedWriter w) throws UnableToCompleteException;
+
+ /**
+ * Write this field builder in the <b>Widgets</b> inner class. There are 3
+ * possible situations:
+ *
+ * <dl>
+ * <dt>getter never called</dt>
+ * <dd>write builder only if there's a ui:field associated</dd>
+ * <dt>getter called only once</dt>
+ * <dd>don't need to write the builder since the getter fallback to the
+ * builder when called</dd>
+ * <dt>getter called more than once</dt>
+ * <dd>in this case a field class is created, the builder is written
+ * and the getter returns the field class</dd>
+ * </dl>
+ *
+ * {@see com.google.gwt.uibinder.rebind.FieldWriter#writeFieldGetter}.
+ */
+ void writeFieldBuilder(IndentedWriter w, int getterCount, OwnerField ownerField)
+ throws UnableToCompleteException;
+
+ /**
+ * Output the getter and builder definitions for the given field.
+ *
+ * <p>
+ * <b>Example for widgets called only once:</b>
+ * <pre>
+ * private WidgetX get_widgetX() {
+ * return build_widgetX();
+ * }
+ * private WidgetX build_widgetX() {
+ * widget = GWT.create(WidgetX.class);
+ * widget.setStyleName("css");
+ * return widgetX;
+ * }
+ * </pre>
+ * Notice that there's no field and the getter just fallback to the builder.
+ * </p>
+ *
+ * <p><b>Example for widgets called more than once:</b>
+ * <pre>
+ * private WidgetX widgetX;
+ * private WidgetX get_widgetX() {
+ * return widgetX;
+ * }
+ * private WidgetX build_widgetX() {
+ * widget = GWT.create(WidgetX.class);
+ * widget.setStyleName("css");
+ * return widgetX;
+ * }
+ * </pre>
+ * Notice that the getter just returns the field. The builder is called in
+ * the Widgets ctor.
+ * </p>
+ */
+ void writeFieldDefinition(IndentedWriter w, TypeOracle typeOracle,
+ OwnerField ownerField, DesignTimeUtils designTime, int getterCount)
+ throws UnableToCompleteException;
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
index 311f18b..83ae24e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
@@ -65,7 +65,8 @@
*/
class HandlerEvaluator {
- private static final String HANDLER_BASE_NAME = "handlerMethodWithNameVeryUnlikelyToCollideWithUserFieldNames";
+ private static final String HANDLER_BASE_NAME =
+ "handlerMethodWithNameVeryUnlikelyToCollideWithUserFieldNames";
/*
* TODO(rjrjr) The correct fix is to put the handlers in a locally defined
* class, making the generated code look like this
@@ -82,6 +83,7 @@
private final JClassType handlerRegistrationJClass;
private final JClassType eventHandlerJClass;
private final OwnerClass ownerClass;
+ private final boolean useLazyWidgetBuilders;
/**
* The verbose testable constructor.
@@ -90,9 +92,11 @@
* @param logger the logger for warnings and errors
* @param oracle the type oracle
*/
- HandlerEvaluator(OwnerClass ownerClass, MortalLogger logger, TypeOracle oracle) {
+ HandlerEvaluator(OwnerClass ownerClass, MortalLogger logger,
+ TypeOracle oracle, boolean useLazyWidgetBuilders) {
this.ownerClass = ownerClass;
this.logger = logger;
+ this.useLazyWidgetBuilders = useLazyWidgetBuilders;
handlerRegistrationJClass = oracle.findType(HandlerRegistration.class.getName());
eventHandlerJClass = oracle.findType(EventHandler.class.getName());
@@ -156,8 +160,8 @@
}
// Cool to tie the handler into the object.
- writeAddHandler(writer, handlerVarName, addHandlerMethodType.getName(),
- objectName);
+ writeAddHandler(writer, fieldManager, handlerVarName,
+ addHandlerMethodType.getName(), objectName);
}
}
}
@@ -215,10 +219,16 @@
* the object
* @param objectName the name of the object we want to tie the handler
*/
- void writeAddHandler(IndentedWriter writer, String handlerVarName,
- String addHandlerMethodName, String objectName) {
- writer.write("%1$s.%2$s(%3$s);", objectName, addHandlerMethodName,
- handlerVarName);
+ void writeAddHandler(IndentedWriter writer, FieldManager fieldManager,
+ String handlerVarName, String addHandlerMethodName, String objectName)
+ throws UnableToCompleteException {
+ if (useLazyWidgetBuilders) {
+ fieldManager.require(objectName).addStatement("%1$s.%2$s(%3$s);",
+ objectName, addHandlerMethodName, handlerVarName);
+ } else {
+ writer.write("%1$s.%2$s(%3$s);", objectName, addHandlerMethodName,
+ handlerVarName);
+ }
}
/**
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
index 10c56a4..8eace4e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -48,7 +48,8 @@
private static final String TEMPLATE_SUFFIX = ".ui.xml";
private static final String XSS_SAFE_CONFIG_PROPERTY = "UiBinder.useSafeHtmlTemplates";
-
+ private static final String LAZY_WIDGET_BUILDERS_PROPERTY = "UiBinder.useLazyWidgetBuilders";
+
/**
* Given a UiBinder interface, return the path to its ui.xml file, suitable
* for any classloader to find it as a resource.
@@ -127,10 +128,30 @@
return packageName + "." + implName;
}
+ private Boolean extractConfigProperty(MortalLogger logger,
+ PropertyOracle propertyOracle, String configProperty, boolean defaultValue) {
+ List<String> values;
+ try {
+ values = propertyOracle.getConfigurationProperty(configProperty).getValues();
+ } catch (BadPropertyValueException e) {
+ logger.warn("No value found for configuration property %s.", configProperty);
+ return defaultValue;
+ }
+
+ String value = values.get(0);
+ if (!value.equals(Boolean.FALSE.toString()) && !value.equals(Boolean.TRUE.toString())) {
+ logger.warn("Unparseable value \"%s\" found for configuration property %s", value,
+ configProperty);
+ return defaultValue;
+ }
+
+ return Boolean.valueOf(value);
+ }
+
private void generateOnce(JClassType interfaceType, String implName,
PrintWriter binderPrintWriter, TreeLogger treeLogger, TypeOracle oracle,
- ResourceOracle resourceOracle, PropertyOracle propertyOracle,
- PrintWriterManager writerManager, DesignTimeUtils designTime)
+ ResourceOracle resourceOracle, PropertyOracle propertyOracle,
+ PrintWriterManager writerManager, DesignTimeUtils designTime)
throws UnableToCompleteException {
MortalLogger logger = new MortalLogger(treeLogger);
@@ -138,10 +159,12 @@
MessagesWriter messages = new MessagesWriter(BINDER_URI, logger,
templatePath, interfaceType.getPackage().getName(), implName);
+ boolean useLazyWidgetBuilders = useLazyWidgetBuilders(logger, propertyOracle);
+ FieldManager fieldManager = new FieldManager(oracle, logger, useLazyWidgetBuilders);
+
UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType, implName,
- templatePath, oracle, logger, new FieldManager(oracle, logger),
- messages, designTime, uiBinderCtx,
- useSafeHtmlTemplates(logger, propertyOracle));
+ templatePath, oracle, logger, fieldManager, messages, designTime, uiBinderCtx,
+ useSafeHtmlTemplates(logger, propertyOracle), useLazyWidgetBuilders);
Document doc = getW3cDoc(logger, designTime, resourceOracle, templatePath);
designTime.rememberPathForElements(doc);
@@ -183,23 +206,13 @@
return doc;
}
+ private Boolean useLazyWidgetBuilders(MortalLogger logger, PropertyOracle propertyOracle) {
+ return extractConfigProperty(logger, propertyOracle, LAZY_WIDGET_BUILDERS_PROPERTY, false);
+ }
+
private Boolean useSafeHtmlTemplates(MortalLogger logger, PropertyOracle propertyOracle) {
- List<String> values;
- try {
- values = propertyOracle.getConfigurationProperty(XSS_SAFE_CONFIG_PROPERTY).getValues();
- } catch (BadPropertyValueException e) {
- logger.warn("No value found for configuration property %s.", XSS_SAFE_CONFIG_PROPERTY);
- return true;
- }
-
- String value = values.get(0);
- if (!value.equals(Boolean.FALSE.toString()) && !value.equals(Boolean.TRUE.toString())) {
- logger.warn("Unparseable value \"%s\" found for configuration property %s", value,
- XSS_SAFE_CONFIG_PROPERTY);
- return true;
- }
-
- Boolean rtn = Boolean.valueOf(value);
+ Boolean rtn = extractConfigProperty(
+ logger, propertyOracle, XSS_SAFE_CONFIG_PROPERTY, true);
if (!rtn) {
logger.warn("Configuration property %s is false! UiBinder SafeHtml integration is off, "
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index b723f9a..4cf8a47 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -171,7 +171,8 @@
source);
FieldWriter field = fieldManager.registerField(dataResourceType,
dataMethod.getName());
- field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(),
+ field.setInitializer(String.format("%s.%s()",
+ fieldManager.convertFieldToGetter(bundleClass.getFieldName()),
dataMethod.getName()));
}
@@ -200,7 +201,8 @@
FieldWriter field = fieldManager.registerField(imageResourceType,
imageMethod.getName());
- field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(),
+ field.setInitializer(String.format("%s.%s()",
+ fieldManager.convertFieldToGetter(bundleClass.getFieldName()),
imageMethod.getName()));
}
@@ -350,7 +352,8 @@
publicType, body, importTypes);
FieldWriter field = fieldManager.registerFieldForGeneratedCssResource(cssMethod);
- field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(),
+ field.setInitializer(String.format("%s.%s()",
+ fieldManager.convertFieldToGetter(bundleClass.getFieldName()),
cssMethod.getName()));
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 528a024..5fb9ce9 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -65,7 +65,8 @@
// TODO(rjrjr) Another place that we need a general anonymous field
// mechanism
- private static final String CLIENT_BUNDLE_FIELD = "clientBundleFieldNameUnlikelyToCollideWithUserSpecifiedFieldOkay";
+ private static final String CLIENT_BUNDLE_FIELD =
+ "clientBundleFieldNameUnlikelyToCollideWithUserSpecifiedFieldOkay";
public static String asCommaSeparatedList(String... args) {
StringBuilder b = new StringBuilder();
@@ -176,7 +177,7 @@
private final MessagesWriter messages;
private final DesignTimeUtils designTime;
private final Tokenator tokenator = new Tokenator();
-
+
private final String templatePath;
private final TypeOracle oracle;
/**
@@ -198,7 +199,9 @@
private final FieldManager fieldManager;
private final ImplicitClientBundle bundleClass;
-
+
+ private final boolean useLazyWidgetBuilders;
+
private final boolean useSafeHtmlTemplates;
private int domId = 0;
@@ -233,7 +236,7 @@
String templatePath, TypeOracle oracle, MortalLogger logger,
FieldManager fieldManager, MessagesWriter messagesWriter,
DesignTimeUtils designTime, UiBinderContext uiBinderCtx,
- boolean useSafeHtmlTemplates)
+ boolean useSafeHtmlTemplates, boolean useLazyWidgetBuilders)
throws UnableToCompleteException {
this.baseClass = baseClass;
this.implClassName = implClassName;
@@ -245,6 +248,7 @@
this.designTime = designTime;
this.uiBinderCtx = uiBinderCtx;
this.useSafeHtmlTemplates = useSafeHtmlTemplates;
+ this.useLazyWidgetBuilders = useLazyWidgetBuilders;
// Check for possible misuse 'GWT.create(UiBinder.class)'
JClassType uibinderItself = oracle.findType(UiBinder.class.getCanonicalName());
@@ -273,7 +277,8 @@
ownerClass = new OwnerClass(uiOwnerType, logger, uiBinderCtx);
bundleClass = new ImplicitClientBundle(baseClass.getPackage().getName(),
this.implClassName, CLIENT_BUNDLE_FIELD, logger);
- handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
+ handlerEvaluator = new HandlerEvaluator(
+ ownerClass, logger, oracle, useLazyWidgetBuilders);
attributeParsers = new AttributeParsers(oracle, fieldManager, logger);
bundleParsers = new BundleAttributeParsers(oracle, logger, getOwnerClass(),
@@ -292,7 +297,7 @@
public void addDetachStatement(String format, Object... args) {
detachStatementsStack.getFirst().add(String.format(format, args));
}
-
+
/**
* Add a statement to be run after everything has been instantiated, in the
* style of {@link String#format}.
@@ -306,7 +311,21 @@
* of {@link String#format}.
*/
public void addStatement(String format, Object... args) {
- statements.add(formatCode(format, args));
+ String code = formatCode(format, args);
+
+ if (useLazyWidgetBuilders) {
+ /**
+ * I'm intentionally over-simplifying this and assuming that the input
+ * comes always in the format: field.somestatement();
+ * Thus, field can be extracted easily and the element parsers don't
+ * need to be changed all at once.
+ */
+ int idx = code.indexOf(".");
+ String fieldName = code.substring(0, idx);
+ fieldManager.require(fieldName).addStatement(format, args);
+ } else {
+ statements.add(code);
+ }
}
/**
@@ -338,18 +357,34 @@
* call and assign the Element instance to its field.
*
* @param fieldName The name of the field being declared
+ * @param ancestorField The name of fieldName parent
*/
- public String declareDomField(String fieldName)
+ public String declareDomField(String fieldName, String ancestorField)
throws UnableToCompleteException {
ensureAttached();
String name = declareDomIdHolder();
- setFieldInitializer(fieldName, "null");
- addInitStatement(
- "%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
- fieldName, name);
- addInitStatement("%s.removeAttribute(\"id\");", fieldName);
- return tokenForStringExpression(name);
+ if (useLazyWidgetBuilders) {
+ // Initialize and add the removeAttribute('id') statement for the new
+ // DOM field.
+ FieldWriter field = fieldManager.require(fieldName);
+ field.setInitializer(formatCode(
+ "com.google.gwt.dom.client.Document.get().getElementById(%s).cast()",
+ fieldManager.convertFieldToGetter(name)));
+ field.addStatement("%s.removeAttribute(\"id\");", fieldName);
+
+ // The dom must be created by its ancestor.
+ fieldManager.require(ancestorField).addAttachStatement(
+ fieldManager.convertFieldToGetter(fieldName) + ";");
+ } else {
+ setFieldInitializer(fieldName, "null");
+ addInitStatement(
+ "%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
+ fieldName, name);
+ addInitStatement("%s.removeAttribute(\"id\");", fieldName);
+ }
+
+ return tokenForStringExpression(fieldManager.convertFieldToGetter(name));
}
/**
@@ -363,6 +398,10 @@
FieldWriter domField = fieldManager.registerField(
oracle.findType(String.class.getName()), domHolderName);
domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
+
+ // Dom IDs must be created first, that's why it gets a higher builder precedence.
+ domField.setBuildPrecendence(2);
+
return domHolderName;
}
@@ -409,25 +448,25 @@
/**
* Writes a new SafeHtml template to the generated BinderImpl.
- *
+ *
* @return The invocation of the SafeHtml template function with the arguments
* filled in
*/
- public String declareTemplateCall(String html)
+ public String declareTemplateCall(String html)
throws IllegalArgumentException {
if (!useSafeHtmlTemplates) {
return '"' + html + '"';
}
-
+
return htmlTemplates.addSafeHtmlTemplate(html, tokenator);
}
/**
* Given a string containing tokens returned by {@link #tokenForStringExpression},
- * {@link #tokenForSafeHtmlExpression} or {@link #declareDomField}, return a
- * string with those tokens replaced by the appropriate expressions. (It is
- * not normally necessary for an {@link XMLElement.Interpreter} or
- * {@link ElementParser} to make this call, as the tokens are typically
+ * {@link #tokenForSafeHtmlExpression} or {@link #declareDomField}, return a
+ * string with those tokens replaced by the appropriate expressions. (It is
+ * not normally necessary for an {@link XMLElement.Interpreter} or
+ * {@link ElementParser} to make this call, as the tokens are typically
* replaced by the TemplateWriter itself.)
*/
public String detokenate(String betokened) {
@@ -588,7 +627,11 @@
public DesignTimeUtils getDesignTime() {
return designTime;
}
-
+
+ public FieldManager getFieldManager() {
+ return fieldManager;
+ }
+
/**
* Returns the logger, at least until we get get it handed off to parsers via
* constructor args.
@@ -640,6 +683,30 @@
*/
public String parseElementToField(XMLElement elem)
throws UnableToCompleteException {
+ /**
+ * TODO(hermes,rjrjr,rdcastro): seems bad we have to run
+ * parseElementToFieldWriter(), get the field writer and
+ * then call fieldManager.convertFieldToGetter().
+ *
+ * Can't we move convertFieldToGetter() to FieldWriter?
+ *
+ * The current answer is no because convertFieldToGetter() might be called
+ * before a given FieldWriter is actually created.
+ */
+ FieldWriter field = parseElementToFieldWriter(elem);
+ return fieldManager.convertFieldToGetter(field.getName());
+ }
+
+ /**
+ * Parses the object associated with the specified element, and returns the
+ * field writer that will hold it. The element is likely to make recursive
+ * calls back to this method to have its children parsed.
+ *
+ * @param elem the xml element to be parsed
+ * @return the field holder just created
+ */
+ public FieldWriter parseElementToFieldWriter(XMLElement elem)
+ throws UnableToCompleteException {
if (elementParsers.isEmpty()) {
registerParsers();
}
@@ -662,7 +729,8 @@
parser.parse(elem, fieldName, type, this);
}
fieldManager.pop();
- return fieldName;
+
+ return field;
}
/**
@@ -690,10 +758,10 @@
}
/**
- * Like {@link #tokenForStringExpression}, but used for runtime expressions
- * that we trust to be safe to interpret at runtime as HTML without escaping,
- * like translated messages with simple formatting. Wrapped in a call to
- * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant} to
+ * Like {@link #tokenForStringExpression}, but used for runtime expressions
+ * that we trust to be safe to interpret at runtime as HTML without escaping,
+ * like translated messages with simple formatting. Wrapped in a call to
+ * {@link com.google.gwt.safehtml.shared.SafeHtmlUtils#fromSafeConstant} to
* keep the expression from being escaped by the SafeHtml template.
*
* @param expression
@@ -702,9 +770,9 @@
if (!useSafeHtmlTemplates) {
return tokenForStringExpression(expression);
}
-
+
String token = tokenator.nextToken("SafeHtmlUtils.fromSafeConstant(" +
- expression + ")");
+ expression + ")");
htmlTemplates.noteSafeConstant("SafeHtmlUtils.fromSafeConstant(" +
expression + ")");
return token;
@@ -724,6 +792,10 @@
return tokenator.nextToken(("\" + " + expression + " + \""));
}
+ public boolean useLazyWidgetBuilders() {
+ return useLazyWidgetBuilders;
+ }
+
/**
* @return true of SafeHtml integration is in effect
*/
@@ -737,7 +809,7 @@
public void warn(String message) {
logger.warn(message);
}
-
+
/**
* Post a warning message.
*/
@@ -751,7 +823,7 @@
public void warn(XMLElement context, String message, Object... params) {
logger.warn(context, message, params);
}
-
+
/**
* Entry point for the code generation logic. It generates the
* implementation's superstructure, and parses the root widget (leading to all
@@ -1000,7 +1072,18 @@
StringWriter stringWriter = new StringWriter();
IndentedWriter niceWriter = new IndentedWriter(
new PrintWriter(stringWriter));
- writeBinder(niceWriter, rootField);
+
+ if (useLazyWidgetBuilders) {
+ for (ImplicitCssResource css : bundleClass.getCssMethods()) {
+ String fieldName = css.getName();
+ FieldWriter cssField = fieldManager.require(fieldName);
+ cssField.addStatement("%s.ensureInjected();", fieldName);
+ cssField.setBuildPrecendence(2);
+ }
+ writeBinderForAttachableStrategy(niceWriter, rootField);
+ } else {
+ writeBinder(niceWriter, rootField);
+ }
ensureAttachmentCleanedUp();
return stringWriter.toString();
@@ -1061,6 +1144,7 @@
addWidgetParser("HasAlignment");
addWidgetParser("DateLabel");
addWidgetParser("NumberLabel");
+ addWidgetParser("LazyPanel");
}
/**
@@ -1087,7 +1171,7 @@
writeClassOpen(w);
writeStatics(w);
w.newline();
-
+
// Create SafeHtml Template
writeSafeHtmlTemplates(w);
@@ -1100,7 +1184,7 @@
writeGwtFields(w);
w.newline();
-
+
designTime.writeAttributes(this);
writeAddedStatements(w);
w.newline();
@@ -1124,6 +1208,73 @@
w.write("}");
}
+ /**
+ * Writes a different optimized UiBinder's source for the attachable
+ * strategy.
+ */
+ private void writeBinderForAttachableStrategy(
+ IndentedWriter w, String rootField) throws UnableToCompleteException {
+ writePackage(w);
+
+ writeImports(w);
+ w.newline();
+
+ writeClassOpen(w);
+ writeStatics(w);
+ w.newline();
+
+ // Create SafeHtml Template
+ writeSafeHtmlTemplates(w);
+
+ w.newline();
+
+ // createAndBindUi method
+ w.write("public %s createAndBindUi(final %s owner) {",
+ uiRootType.getParameterizedQualifiedSourceName(),
+ uiOwnerType.getParameterizedQualifiedSourceName());
+ w.indent();
+ w.newline();
+
+ designTime.writeAttributes(this);
+ w.newline();
+
+ w.write("return new Widgets(owner).%s;", rootField);
+ w.outdent();
+ w.write("}");
+
+ // 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();
+
+ writeHandlers(w);
+ w.newline();
+
+ w.write("public Widgets(final %s owner) {", ownerClassType);
+ w.indent();
+ w.write("this.owner = owner;");
+ fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
+ w.outdent();
+ w.write("}");
+
+ fieldManager.writeFieldDefinitions(
+ w, getOracle(), getOwnerClass(), getDesignTime());
+
+ w.outdent();
+ w.write("}");
+
+ // Close class
+ w.outdent();
+ w.write("}");
+ }
+
private void writeClassOpen(IndentedWriter w) {
w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
uiRootType.getParameterizedQualifiedSourceName(),
@@ -1253,21 +1404,21 @@
w.write("package %1$s;", packageName);
w.newline();
}
- }
-
+ }
+
/**
- * Write statements created by {@link HtmlTemplates#addSafeHtmlTemplate}. This
+ * Write statements created by {@link HtmlTemplates#addSafeHtmlTemplate}. This
* code must be placed after all instantiation code.
*/
private void writeSafeHtmlTemplates(IndentedWriter w) {
if (!(htmlTemplates.isEmpty())) {
assert useSafeHtmlTemplates : "SafeHtml is off, but templates were made.";
-
+
w.write("interface Template extends SafeHtmlTemplates {");
w.indent();
htmlTemplates.writeTemplates(w);
-
+
w.outdent();
w.write("}");
w.newline();
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/ElementParserTester.java b/user/test/com/google/gwt/uibinder/elementparsers/ElementParserTester.java
index 8aea5b1..8e6f83a 100644
--- a/user/test/com/google/gwt/uibinder/elementparsers/ElementParserTester.java
+++ b/user/test/com/google/gwt/uibinder/elementparsers/ElementParserTester.java
@@ -108,7 +108,7 @@
elemProvider = new XMLElementProviderImpl(new AttributeParsers(types, null,
logger), bundleParsers, types, logger, DesignTimeUtilsStub.EMPTY);
- fieldManager = new FieldManager(types, logger);
+ fieldManager = new FieldManager(types, logger, false);
JClassType baseType = types.findType("my.Ui.BaseClass");
MessagesWriter messages = new MessagesWriter(BINDER_URI, logger,
templatePath, baseType.getPackage().getName(), implName);
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/MockUiBinderWriter.java b/user/test/com/google/gwt/uibinder/elementparsers/MockUiBinderWriter.java
index 7264f1a..ad145ca 100644
--- a/user/test/com/google/gwt/uibinder/elementparsers/MockUiBinderWriter.java
+++ b/user/test/com/google/gwt/uibinder/elementparsers/MockUiBinderWriter.java
@@ -36,7 +36,7 @@
TypeOracle oracle, MortalLogger logger, FieldManager fieldManager,
MessagesWriter messagesWriter) throws UnableToCompleteException {
super(baseClass, implClassName, templatePath, oracle, logger, fieldManager, messagesWriter,
- DesignTimeUtilsStub.EMPTY, new UiBinderContext(), true);
+ DesignTimeUtilsStub.EMPTY, new UiBinderContext(), true, false);
}
@Override
diff --git a/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java b/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java
index 28f9cee..98de89b 100644
--- a/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java
+++ b/user/test/com/google/gwt/uibinder/rebind/HandlerEvaluatorTest.java
@@ -44,6 +44,7 @@
private OwnerClass ownerType;
private MortalLogger logger;
private TypeOracle oracle;
+ private FieldManager fieldManager;
@Override
protected void setUp() throws Exception {
@@ -55,6 +56,7 @@
mockControl = EasyMock.createControl();
ownerType = mockControl.createMock(OwnerClass.class);
oracle = mockControl.createMock(TypeOracle.class);
+ fieldManager = mockControl.createMock(FieldManager.class);
// TODO(hermes): sucks I know!!!! This class shouldn't be using EasyMock
// but for now that's the easiest way of creating new instances of
@@ -70,7 +72,7 @@
eventHandlerJClass);
mockControl.replay();
- evaluator = new HandlerEvaluator(ownerType, logger, oracle);
+ evaluator = new HandlerEvaluator(ownerType, logger, oracle, false);
mockControl.verify();
mockControl.reset();
}
@@ -78,7 +80,7 @@
public void testWriteAddHandler() throws Exception {
StringWriter sw = new StringWriter();
evaluator.writeAddHandler(new IndentedWriter(new PrintWriter(sw)),
- "handler1", "addClickHandler", "label1");
+ fieldManager, "handler1", "addClickHandler", "label1");
assertEquals("label1.addClickHandler(handler1);", sw.toString().trim());
}