Hello UiBinder
Reviewed by: jgw et al.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5896 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt16_20userApi.conf b/tools/api-checker/config/gwt16_20userApi.conf
index fd86b8d..0794a4b 100644
--- a/tools/api-checker/config/gwt16_20userApi.conf
+++ b/tools/api-checker/config/gwt16_20userApi.conf
@@ -67,6 +67,8 @@
:user/src/com/google/gwt/rpc/linker\
:user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java\
:user/src/com/google/gwt/user/linker\
+:user/src/com/google/gwt/uibinder/parsers\
+:user/src/com/google/gwt/uibinder/testing\
##############################################
#excluded packages
diff --git a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
new file mode 100644
index 0000000..75f6535
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
@@ -0,0 +1,23 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<!-- GWT UI Binder support. -->
+<module>
+ <!-- Inheriting the debug module -->
+ <inherits name="com.google.gwt.debug.Debug"/>
+
+ <generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
+ <when-type-assignable class="com.google.gwt.uibinder.client.UiBinder"/>
+ </generate-with>
+</module>
diff --git a/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java b/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java
new file mode 100644
index 0000000..1718aa5
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java
@@ -0,0 +1,43 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+/**
+ * Convenient base class for implementations of {@link UiBinder},
+ * generated or otherwise.
+ *
+ * @param <U> The type of the UI's root object
+ * @param <O> The type of the owner of the UI
+ */
+public abstract class AbstractUiBinder<U,O> implements UiBinder<U, O> {
+
+ /**
+ * An interface used by some generated implementations of UiBinder.
+ *
+ * @param <U> The type of the UI's root object
+ * @param <O> The type of the owner of the UI
+ */
+ protected interface InstanceBinder<U, O> {
+ U makeUi();
+ void doBind(O owner);
+ }
+
+ public U createAndBindUi(O owner) {
+ U ui = createUiRoot(owner);
+ bindUi(ui, owner);
+ return ui;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/DomHolder.java b/user/src/com/google/gwt/uibinder/client/DomHolder.java
new file mode 100644
index 0000000..00d7306
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/DomHolder.java
@@ -0,0 +1,28 @@
+/*
+ * 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
+ * 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.user.client.ui.UIObject;
+
+/**
+ * Runtime helper for templates.
+ */
+public class DomHolder extends UIObject {
+ public DomHolder(Element element) {
+ setElement(element);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinder.java b/user/src/com/google/gwt/uibinder/client/UiBinder.java
new file mode 100644
index 0000000..bfff7f6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiBinder.java
@@ -0,0 +1,64 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+/**
+ * Interface implemented by classes that generate DOM or Widget structures from
+ * ui.xml template files, and which inject portions of the generated UI into the
+ * fields of an owner.
+ * <p>
+ * The generated UiBinder implementation will be based on an xml file resource
+ * in the same package as the owner class, with the same name and a "ui.xml"
+ * suffix. For example, a UI owned by class {@code bar.baz.Foo} will be sought
+ * in {@code /bar/baz/Foo.ui.xml}. (To use a different template file, put the
+ * {@link UiTemplate} annotation on your UiBinder interface declaration to point
+ * the code generator at it.)
+ *
+ * @param <U> The type of the root object of the generated UI, typically a
+ * subclass of {@link com.google.gwt.dom.client.Element} or
+ * {@link com.google.gwt.user.client.ui.UIObject}
+ * @param <O> The type of the object that will own the generated UI
+ */
+public interface UiBinder<U, O> {
+
+ /**
+ * Creates and returns the root object of the UI. If possible the creation of
+ * the rest of the UI is deferred until {@link #bindUi} is called.
+ *
+ * @deprecated The use case for this complexity never materialized. Use
+ * {@link #createAndBindUi}
+ */
+ @Deprecated
+ U createUiRoot(O owner);
+
+ /**
+ * Completes the creation of a UI started with a call to {@link #createUiRoot},
+ * and fills any owner fields tagged with {@link UiField}.
+ *
+ * @deprecated The use case for this complexity never materialized. Use
+ * {@link #createAndBindUi}
+ */
+ @Deprecated
+ void bindUi(U root, O owner);
+
+ /**
+ * Creates and returns the root object of the UI, and fills any fields of owner
+ * tagged with {@link UiField}.
+ *
+ * @param owner the object whose {@literal @}UiField needs will be filled
+ */
+ U createAndBindUi(O owner);
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
new file mode 100644
index 0000000..55763d5
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.UIObject;
+
+/**
+ * Static helper methods used by UiBinder. These methods are likely to move,
+ * so please don't use them for non-UiBinder code.
+ */
+public class UiBinderUtil {
+ private static Element hiddenDiv;
+
+ public static Element attachToDomAndGetChild(Element element, String id) {
+ // 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();
+ Element origSibling = element.getNextSiblingElement();
+
+ // Attach the panel's element to the hidden div.
+ hiddenDiv.appendChild(element);
+
+ // Now that we're attached to the DOM, we can use getElementById.
+ Element child = Document.get().getElementById(id);
+
+ // Put the panel's element back where it was.
+ if (origParent != null) {
+ origParent.insertBefore(element, origSibling);
+ } else {
+ orphan(element);
+ }
+
+ return child;
+ }
+
+ public static Element fromHtml(String html) {
+ ensureHiddenDiv();
+ hiddenDiv.setInnerHTML(html);
+ Element newbie = hiddenDiv.getFirstChildElement();
+ orphan(newbie);
+ return newbie;
+ }
+
+ private static void ensureHiddenDiv() {
+ // If the hidden DIV has not been created, create it.
+ if (hiddenDiv == null) {
+ hiddenDiv = Document.get().createDivElement();
+ UIObject.setVisible(hiddenDiv, false);
+ RootPanel.getBodyElement().appendChild(hiddenDiv);
+ }
+ }
+
+ private static void orphan(Node node) {
+ node.getParentNode().removeChild(node);
+ }
+
+ /**
+ * Not to be instantiated.
+ */
+ private UiBinderUtil() { }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiConstructor.java b/user/src/com/google/gwt/uibinder/client/UiConstructor.java
new file mode 100644
index 0000000..78463b6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiConstructor.java
@@ -0,0 +1,33 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a constructor that may be used as an alternative to a widget's
+ * zero args construtor in a {@link UiBinder} template. The parameter names
+ * of the constructor may be filled as xml element attribute values.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.CONSTRUCTOR)
+public @interface UiConstructor {
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiFactory.java b/user/src/com/google/gwt/uibinder/client/UiFactory.java
new file mode 100644
index 0000000..148aae0
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method that may be called as an alternative to a GWT.create call in a
+ * {@link UiBinder} template. The parameter names of the constructor are treated
+ * as required xml element attribute values.
+ * <p>
+ * It is an error to apply this annotation to more than one method of a given
+ * return type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface UiFactory {
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiField.java b/user/src/com/google/gwt/uibinder/client/UiField.java
new file mode 100644
index 0000000..e0e9d8d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiField.java
@@ -0,0 +1,34 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks fields in a UiBinder client that must be filled by the binder's
+ * {@link UiBinder#bindUi} method. If provided is true the field creation is
+ * delegated to the client (owner).
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface UiField {
+ boolean provided() default false;
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiHandler.java b/user/src/com/google/gwt/uibinder/client/UiHandler.java
new file mode 100644
index 0000000..43d1aeb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method to be automatically bound as an event handler. See examples
+ * in {@link com.google.gwt.uibinder.sample.client.HandlerDemo}.
+ *
+ * <p>The annotation values must be declared in the "ui:field"
+ * template attribute.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface UiHandler {
+ String[] value();
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiTemplate.java b/user/src/com/google/gwt/uibinder/client/UiTemplate.java
new file mode 100644
index 0000000..f677d44
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiTemplate.java
@@ -0,0 +1,33 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates the template from which to generate a {@link UiBinder}.
+ */
+@Documented
+@Target(ElementType.TYPE)
+public @interface UiTemplate {
+
+ /**
+ * @return The template name
+ */
+ String value();
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
new file mode 100644
index 0000000..4b034b5
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.AttributeMessage;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+
+/**
+ * Examines each element for child <m:attr/> elements, and replaces the
+ * corresponding attributes of the examinee with references to the translatable
+ * messages created.
+ * <p>
+ * That is, when examining element foo in
+ * <pre>
+ * <foo bar="baz">
+ * <m:attr name="baz">
+ * </foo></pre>
+ * cosume the m:attr element, and declare a method on the Messages interface
+ * with {@literal @}Default("baz")
+ */
+ class AttributeMessageInterpreter implements XMLElement.Interpreter<String> {
+
+ private final UiBinderWriter writer;
+
+ public AttributeMessageInterpreter(UiBinderWriter writer) {
+ this.writer = writer;
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ MessagesWriter messages = writer.getMessages();
+ for (AttributeMessage am : messages.consumeAttributeMessages(elem)) {
+ elem.setAttribute(am.getAttribute(),
+ writer.tokenForExpression(am.getMessageAsHtmlAttribute()));
+ }
+
+ /*
+ * Return null because we don't want to replace the dom element with any
+ * particular string (though we may have consumed its id or gwt:field)
+ */
+ return null;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeMessageParser.java b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageParser.java
new file mode 100644
index 0000000..e6cacda
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageParser.java
@@ -0,0 +1,35 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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;
+
+/**
+ * This parser is not tied to a particular class of element, but rather is run
+ * as the first parser in any parser stack. It looks for attribute values that
+ * are set as calls to the template's generated Messages interface, by calling
+ * {@link com.google.gwt.uibinder.rebind.messages.MessagesWriter#storeMessageAttributesFor}
+ */
+public class AttributeMessageParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ writer.getMessages().consumeAndStoreMessageAttributesFor(elem);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
new file mode 100644
index 0000000..a39a779
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Attribute parsers are classes that parse xml attribute values, turning them
+ * into valid Java expressions.
+ */
+public interface AttributeParser {
+
+ /**
+ * Parse the given attribute value.
+ *
+ * @param value the attribute value to be parsed
+ * @param writer the writer
+ * @return a valid Java expression
+ * @throws UnableToCompleteException
+ */
+ String parse(String value, UiBinderWriter writer)
+ throws UnableToCompleteException;
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
new file mode 100644
index 0000000..dec5021
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+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.NotFoundException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLAttribute;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.AttributeMessage;
+import com.google.gwt.uibinder.rebind.model.OwnerFieldClass;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Utility methods for discovering bean-like properties and generating code to
+ * initialize them.
+ */
+public class BeanParser implements ElementParser {
+
+ /**
+ * Generates code to initialize all bean attributes on the given element.
+ * Includes support for <m:attribute /> children that will apply
+ * to setters
+ * @throws UnableToCompleteException
+ */
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ final Map<String, String> setterValues = new HashMap<String, String>();
+ final Map<String, String> localizedValues =
+ fetchLocalizedAttributeValues(elem, writer);
+
+ final Map<String, String> requiredValues = new HashMap<String, String>();
+ final Map<String, JParameter> unfilledRequiredParams =
+ new HashMap<String, JParameter>();
+
+ final OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type);
+
+ // See if there's a factory method
+ JAbstractMethod creator = writer.getOwnerClass().getUiFactoryMethod(type);
+ if (creator == null) {
+ // If not, see if there's a @UiConstructor
+ creator = ownerFieldClass.getUiConstructor();
+ }
+
+ if (creator != null) {
+ for (JParameter param : creator.getParameters()) {
+ unfilledRequiredParams.put(param.getName(), param);
+ }
+ }
+
+ // Work through the localized attribute values and assign them
+ // to appropriate constructor params or setters (which had better be
+ // ready to accept strings)
+
+ for (Entry<String, String> property : localizedValues.entrySet()) {
+ String key = property.getKey();
+ String value = property.getValue();
+
+ JParameter param = unfilledRequiredParams.get(key);
+ if (param != null) {
+ if (!isString(writer, param)) {
+ writer.die("In %s, cannot appply message attribute to non-string "
+ + "constructor argument %s %s.", elem,
+ param.getType().getSimpleSourceName(), key);
+ }
+
+ requiredValues.put(key, value);
+ unfilledRequiredParams.remove(key);
+ } else {
+ JMethod setter = ownerFieldClass.getSetter(key);
+ JParameter[] params = setter == null ? null : setter.getParameters();
+
+ if (setter == null || !(params.length == 1)
+ || !isString(writer, params[0])) {
+ writer.die("In %s, no method found to apply message attribute %s",
+ elem, key);
+ } else {
+ setterValues.put(key, value);
+ }
+ }
+ }
+
+ // Now go through the element and dispatch its attributes, remembering
+ // that constructor arguments get first dibs
+ for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
+ // Backward traversal b/c we're deleting attributes from the xml element
+
+ XMLAttribute attribute = elem.getAttribute(i);
+
+ // Ignore xmlns attributes
+ if (attribute.getName().startsWith("xmlns:")) {
+ continue;
+ }
+
+ String propertyName = attribute.getLocalName();
+ if (setterValues.keySet().contains(propertyName)
+ || requiredValues.containsKey(propertyName)) {
+ // TODO(rjrjr) A fine example of why res: namespace hack must die
+ writer.die("Duplicate attribute name: %s", propertyName);
+ }
+
+ if (unfilledRequiredParams.keySet().contains(propertyName)) {
+ JParameter parameter = unfilledRequiredParams.get(propertyName);
+ AttributeParser parser =
+ writer.getAttributeParser(attribute, parameter);
+ if (parser == null) {
+ writer.die("In %s, unable to parse %s as constructor argument "
+ + "of type %s", elem, attribute,
+ parameter.getType().getSimpleSourceName());
+ }
+ requiredValues.put(propertyName, parser.parse(attribute.consumeValue(),
+ writer));
+ unfilledRequiredParams.remove(propertyName);
+ } else {
+ JMethod setter = ownerFieldClass.getSetter(propertyName);
+ if (setter == null) {
+ writer.die("In %s, class %s has no appropriate set%s() method", elem,
+ elem.getLocalName(), initialCap(propertyName));
+ }
+
+ JParameter[] params = setter.getParameters();
+
+ AttributeParser parser =
+ writer.getAttributeParser(attribute, params);
+ if (parser == null) {
+ if (params.length == 1 && JPrimitiveType.INT == params[0].getType()) {
+ writer.die("In %s, %s cannot be parsed for %s.",
+ elem, attribute, asString(setter));
+ }
+ } else {
+ setterValues.put(propertyName, parser.parse(attribute.consumeValue(),
+ writer));
+ }
+ }
+ }
+
+ if (!unfilledRequiredParams.isEmpty()) {
+ StringBuilder b =
+ new StringBuilder(String.format("%s missing required arguments:",
+ elem));
+ for (String name : unfilledRequiredParams.keySet()) {
+ b.append(" ").append(name);
+ }
+ writer.die(b.toString());
+ }
+
+ if (creator != null) {
+ String[] args = makeArgsList(requiredValues, creator);
+ if (creator instanceof JMethod) { // Factory method
+ String factoryMethod = String.format("owner.%s(%s)", creator.getName(),
+ UiBinderWriter.asCommaSeparatedList(args));
+ writer.setFieldInitializer(fieldName, factoryMethod);
+ } else { // Annotated Constructor
+ writer.setFieldInitializerAsConstructor(fieldName, type, args);
+ }
+ }
+
+ for (String propertyName : setterValues.keySet()) {
+ writer.addStatement("%s.set%s(%s);", fieldName,
+ initialCap(propertyName), setterValues.get(propertyName));
+ }
+ }
+
+ private String asString(JMethod setter) {
+ String sig = String.format("%s#%s(%s)", setter.getEnclosingType().getName(),
+ setter.getName(), getParamTypes(setter));
+ return sig;
+ }
+
+ /**
+ * Fetch the localized attributes that were stored by the
+ * AttributeMessageParser.
+ */
+ private Map<String, String> fetchLocalizedAttributeValues(XMLElement elem,
+ UiBinderWriter writer) {
+ final Map<String, String> localizedValues = new HashMap<String, String>();
+
+ Collection<AttributeMessage> attributeMessages =
+ writer.getMessages().retrieveMessageAttributesFor(elem);
+
+ if (attributeMessages != null) {
+ for (AttributeMessage att : attributeMessages) {
+ String propertyName = att.getAttribute();
+ localizedValues.put(propertyName, att.getMessageUnescaped());
+ }
+ }
+ return localizedValues;
+ }
+
+ private StringBuilder getParamTypes(JMethod setter) {
+ StringBuilder args = new StringBuilder();
+ for (Iterator<JParameter> i =
+ Arrays.asList(setter.getParameters()).iterator(); i.hasNext();) {
+ args.append(i.next().getType().getSimpleSourceName());
+ if (i.hasNext()) {
+ args.append(", ");
+ }
+ }
+ return args;
+ }
+
+ private String initialCap(String propertyName) {
+ return propertyName.substring(0, 1).toUpperCase() +
+ propertyName.substring(1);
+ }
+
+ private boolean isString(UiBinderWriter writer, JParameter param) {
+ JClassType stringType;
+ try {
+ stringType = writer.getOracle().getType(String.class.getName());
+ } catch (NotFoundException e) {
+ return false; // Inconceivable!
+ }
+ return stringType.equals(param.getType());
+ }
+
+ private String[] makeArgsList(final Map<String, String> valueMap,
+ JAbstractMethod method) {
+ JParameter[] params = method.getParameters();
+ String[] args = new String[params.length];
+ int i = 0;
+ for (JParameter param : params) {
+ args[i++] = valueMap.get(param.getName());
+ }
+ return args;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
new file mode 100644
index 0000000..3c48775
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses a single boolean attribute.
+ */
+public class BooleanAttributeParser implements AttributeParser {
+
+ public String parse(String value, UiBinderWriter writer) {
+ // TODO(jgw): check validity.
+ return value;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
new file mode 100644
index 0000000..2dce510
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
@@ -0,0 +1,72 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Interprets an attribute's contents as a method call on a resource class (one
+ * tied to an xmnls prefix via a "with://" url).
+ */
+public class BundleAttributeParser implements AttributeParser {
+
+ private final JClassType bundleClass;
+ private String bundleInstance;
+ private boolean isBundleStatic;
+
+ public BundleAttributeParser(JClassType bundleClass, String bundleInstance,
+ boolean isBundleStatic) {
+ this.bundleClass = bundleClass;
+ this.bundleInstance = bundleInstance;
+ this.isBundleStatic = isBundleStatic;
+ }
+
+ public JClassType bundleClass() {
+ return bundleClass;
+ }
+
+ public String bundleInstance() {
+ return bundleInstance;
+ }
+
+ public String fullBundleClassName() {
+ return bundleClass.getPackage().getName() + "." + bundleClass.getName();
+ }
+
+ public boolean isBundleStatic() {
+ return isBundleStatic;
+ }
+
+ public String parse(String attribute, UiBinderWriter writer) {
+ StringBuilder b = new StringBuilder();
+ String[] values = attribute.split(" ");
+ boolean first = true;
+ for (String value : values) {
+ if (first) {
+ first = false;
+ } else {
+ b.append(" + \" \" + ");
+ }
+ b.append(bundleInstance() + "." + parenthesizeDots(value) + "()");
+ }
+ return b.toString();
+ }
+
+ private String parenthesizeDots(String value) {
+ return value.replaceAll("\\.", "().");
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
new file mode 100644
index 0000000..4a91e84
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.CellPanel} widgets.
+ */
+public class CellPanelParser implements ElementParser {
+
+ private static final String HALIGN_ATTR = "horizontalAlignment";
+ private static final String VALIGN_ATTR = "verticalAlignment";
+ private static final String WIDTH_ATTR = "width";
+ private static final String HEIGHT_ATTR = "height";
+ private static final String CELL_TAG = "Cell";
+
+ private static HorizontalAlignmentConstantParser halignParser =
+ new HorizontalAlignmentConstantParser();
+ private static VerticalAlignmentConstantParser valignParser =
+ new VerticalAlignmentConstantParser();
+ private static StringAttributeParser stringParser =
+ new StringAttributeParser();
+
+ /**
+ * Parses the alignment and size attributes common to all CellPanels.
+ *
+ * This is exposed publicly because there is a DockPanelParser that overrides
+ * the default behavior, but still needs to parse these attributes.
+ *
+ * @throws UnableToCompleteException
+ */
+ public static void parseCellAttributes(XMLElement cellElem, String fieldName,
+ String childFieldName, UiBinderWriter writer) throws UnableToCompleteException {
+ // Parse horizontal and vertical alignment attributes.
+ if (cellElem.hasAttribute(HALIGN_ATTR)) {
+ String value =
+ halignParser.parse(cellElem.consumeAttribute(HALIGN_ATTR), writer);
+ writer.addStatement("%1$s.setCellHorizontalAlignment(%2$s, %3$s);", fieldName,
+ childFieldName, value);
+ }
+
+ if (cellElem.hasAttribute(VALIGN_ATTR)) {
+ String value =
+ valignParser.parse(cellElem.consumeAttribute(VALIGN_ATTR), writer);
+ writer.addStatement("%1$s.setCellVerticalAlignment(%2$s, %3$s);", fieldName,
+ childFieldName, value);
+ }
+
+ // Parse width and height attributes.
+ if (cellElem.hasAttribute(WIDTH_ATTR)) {
+ String value =
+ stringParser.parse(cellElem.consumeAttribute(WIDTH_ATTR), writer);
+ writer.addStatement("%1$s.setCellWidth(%2$s, %3$s);", fieldName, childFieldName,
+ value);
+ }
+
+ if (cellElem.hasAttribute(HEIGHT_ATTR)) {
+ String value =
+ stringParser.parse(cellElem.consumeAttribute(HEIGHT_ATTR), writer);
+ writer.addStatement("%1$s.setCellHeight(%2$s, %3$s);", fieldName, childFieldName,
+ value);
+ }
+ }
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ for (XMLElement child : elem.consumeChildElements()) {
+ String ns = child.getNamespaceUri();
+ String tagName = child.getLocalName();
+ if (ns.equals(elem.getNamespaceUri()) && tagName.equals(CELL_TAG)) {
+ // It's a cell element, so parse its single child as a widget.
+ XMLElement widget = child.consumeSingleChildElement();
+ if (widget == null) {
+ writer.die("Cell must contain a single child widget");
+ }
+ String childFieldName = writer.parseWidget(widget);
+ writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+
+ // Parse the cell tag's alignment & size attributes.
+ parseCellAttributes(child, fieldName, childFieldName, writer);
+ } else {
+ // It's just a normal child, so parse it as a widget.
+ String childFieldName = writer.parseWidget(child);
+ writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
new file mode 100644
index 0000000..f9ebf80
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
@@ -0,0 +1,61 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLAttribute;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Assigns computed values to element attributes, e.g.
+ * res:styleName="style.pretty" which will become something like
+ * myWidget.setStyleName(resources.style().pretty()) in the generated code.
+ */
+ class ComputedAttributeInterpreter implements XMLElement.Interpreter<String> {
+
+ private final UiBinderWriter writer;
+
+ public ComputedAttributeInterpreter(UiBinderWriter writer) {
+ this.writer = writer;
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ Map<String, String> attNameToToken = new HashMap<String, String>();
+
+ for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
+ XMLAttribute att = elem.getAttribute(i);
+
+ AttributeParser parser = writer.getAttributeParser(att);
+ if (parser != null) {
+ String parsedValue = parser.parse(att.consumeValue(), writer);
+ String attToken = writer.tokenForExpression(parsedValue);
+ attNameToToken.put(att.getLocalName(), attToken);
+ }
+ }
+
+ for (Map.Entry<String, String> attr : attNameToToken.entrySet()) {
+ elem.setAttribute(attr.getKey(), attr.getValue());
+ }
+
+ // Return null because we don't want to replace the dom element
+ return null;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java b/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
new file mode 100644
index 0000000..70c428b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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 java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Parses CustomButton widgets.
+ */
+public class CustomButtonParser implements ElementParser {
+
+ private static final Set<String> faceNames = new HashSet<String>();
+ private static final Object IMAGE_CLASS =
+ "com.google.gwt.user.client.ui.Image";
+
+ static {
+ faceNames.add("UpFace");
+ faceNames.add("DownFace");
+ faceNames.add("UpHoveringFace");
+ faceNames.add("DownHoveringFace");
+ faceNames.add("UpDisabledFace");
+ faceNames.add("DownDisabledFace");
+ }
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ // CustomButton can only contain Face elements.
+ String ns = child.getNamespaceUri();
+ String faceName = child.getLocalName();
+
+ if (!ns.equals(elem.getNamespaceUri())) {
+ writer.die("Invalid CustomButton child namespace: " + ns);
+ }
+ if (!faceNames.contains(faceName)) {
+ writer.die("Invalid CustomButton face: " + faceName);
+ }
+
+ // Look for innerHTML first.
+ HtmlInterpreter interpreter =
+ HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+ String innerHtml = child.consumeInnerHtml(interpreter).trim();
+ if (innerHtml.length() > 0) {
+ writer.addStatement("%1$s.get%2$s().setHTML(\"%3$s\");", fieldName, faceName,
+ innerHtml);
+ }
+
+ // Then look for html, text, and image attributes.
+ if (child.hasAttribute("html")) {
+ String html = child.consumeAttribute("html");
+ writer.addStatement("%1$s.get%2$s().setHTML(\"%3$s\");", fieldName, faceName, html);
+ }
+
+ if (child.hasAttribute("text")) {
+ String text = child.consumeAttribute("text");
+ writer.addStatement("%1$s.get%2$s().setText(\"%3$s\");", fieldName, faceName, text);
+ }
+
+ if (child.hasAttribute("image")) {
+ String image = child.consumeAttribute("image");
+ writer.addStatement("%1$s.get%2$s().setImage(new %3$s(\"%4$s\"));", fieldName,
+ faceName, IMAGE_CLASS, image);
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java
new file mode 100644
index 0000000..722db79
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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.DisclosurePanel;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.DisclosurePanel} widgets.
+ */
+public class DisclosurePanelParser implements ElementParser {
+
+ private static final String ATTRIBUTE_HEADER_WIDGET = "DisclosurePanel-header";
+
+ private static final String ATTRIBUTE_HEADER_BUNDLE = "imageBundle";
+
+ private static final String ATTRIBUTE_INITIALLY_OPEN = "initiallyOpen";
+
+ private static final String ATTRIBUTE_ENABLE_ANIMATION = "enableAnimation";
+
+ /**
+ * @return the type oracle's DisclosurePanel class
+ */
+ private static JClassType getDisclosurePanelClass(UiBinderWriter w) {
+ return w.getOracle().findType(DisclosurePanel.class.getName());
+ }
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+
+ String text = null;
+ // They must specify a label
+ if (elem.hasAttribute("text")) {
+ text = elem.consumeAttribute("text");
+ text = '"' + UiBinderWriter.escapeTextForJavaStringLiteral(text) + '"';
+ }
+
+ // They may specify an image bundle
+ String imageBundle = null;
+ if (elem.hasAttribute(ATTRIBUTE_HEADER_BUNDLE)) {
+ imageBundle = elem.consumeAttribute(ATTRIBUTE_HEADER_BUNDLE);
+ }
+
+ // They may specify an initial closed state
+ String initiallyOpen = "false";
+ if (elem.hasAttribute(ATTRIBUTE_INITIALLY_OPEN)) {
+ initiallyOpen = elem.consumeAttribute(ATTRIBUTE_INITIALLY_OPEN);
+ }
+
+ // They may enable animation
+ String enableAnimation = "true";
+ if (elem.hasAttribute(ATTRIBUTE_ENABLE_ANIMATION)) {
+ enableAnimation = elem.consumeAttribute(ATTRIBUTE_ENABLE_ANIMATION);
+ }
+
+ String childFieldName = null;
+ String headerFieldName = null;
+
+ for (XMLElement child : elem.consumeChildElements()) {
+ // Disclosure panel header optionally comes from the DisclosurePanel-header attribute of the
+ // child
+ boolean childIsHeader = false;
+ String headerAttributeName = elem.getPrefix() + ":" + ATTRIBUTE_HEADER_WIDGET;
+ if (child.hasAttribute(headerAttributeName)) {
+ if (headerFieldName != null) {
+ writer.die("In %s, DisclosurePanel cannot contain more than one header widget.", elem);
+ }
+ child.consumeAttribute(headerAttributeName);
+ headerFieldName = writer.parseWidget(child);
+ childIsHeader = true;
+ }
+ if (!childIsHeader) {
+ if (childFieldName != null) {
+ writer.die("In %s, DisclosurePanel cannot contain more than one content widget.", elem);
+ }
+ childFieldName = writer.parseWidget(child);
+ }
+ }
+
+ // To use the image bundle, you must provide a text header.
+ if (imageBundle != null) {
+ writer.setFieldInitializerAsConstructor(fieldName,
+ getDisclosurePanelClass(writer), imageBundle, (text != null ? text : "\"\""),
+ initiallyOpen);
+ } else {
+ JClassType panelClass = getDisclosurePanelClass(writer);
+ if (text != null) {
+ writer.setFieldInitializerAsConstructor(fieldName, panelClass, text);
+ } else {
+ writer.setFieldInitializerAsConstructor(fieldName, panelClass);
+ }
+ }
+ if (childFieldName != null) {
+ writer.addStatement("%1$s.setContent(%2$s);", fieldName, childFieldName);
+ }
+ if (headerFieldName != null) {
+ writer.addStatement("%1$s.setHeader(%2$s);", fieldName, headerFieldName);
+ }
+ writer.addStatement("%1$s.setAnimationEnabled(%2$s);", fieldName, enableAnimation);
+ writer.addStatement("%1$s.setOpen(%2$s);", fieldName, initiallyOpen);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
new file mode 100644
index 0000000..1d4be30
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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 java.util.HashMap;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.DockPanel} widgets.
+ */
+public class DockPanelParser implements ElementParser {
+
+ private static final String TAG_DOCK = "Dock";
+ private static final HashMap<String, String> values =
+ new HashMap<String, String>();
+
+ static {
+ values.put("NORTH", "com.google.gwt.user.client.ui.DockPanel.NORTH");
+ values.put("SOUTH", "com.google.gwt.user.client.ui.DockPanel.SOUTH");
+ values.put("EAST", "com.google.gwt.user.client.ui.DockPanel.EAST");
+ values.put("WEST", "com.google.gwt.user.client.ui.DockPanel.WEST");
+ values.put("CENTER", "com.google.gwt.user.client.ui.DockPanel.CENTER");
+ }
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ // DockPanel can only contain Dock elements.
+ String ns = child.getNamespaceUri();
+ String tagName = child.getLocalName();
+
+ if (!ns.equals(elem.getNamespaceUri())) {
+ writer.die("Invalid DockPanel child namespace: " + ns);
+ }
+ if (!tagName.equals(TAG_DOCK)) {
+ writer.die("Invalid DockPanel child element: " + tagName);
+ }
+
+ // And they must specify a direction.
+ if (!child.hasAttribute("direction")) {
+ writer.die("Dock must specify the 'direction' attribute");
+ }
+ String value = child.consumeAttribute("direction");
+ String translated = values.get(value);
+ if (translated == null) {
+ writer.die("Invalid value: dockDirection='" + value + "'");
+ }
+
+ // And they can only have a single child widget.
+ XMLElement widget = child.consumeSingleChildElement();
+ if (widget == null) {
+ writer.die("Dock must contain a single child widget.");
+ }
+ String childFieldName = writer.parseWidget(widget);
+ writer.addStatement("%1$s.add(%2$s, %3$s);", fieldName, childFieldName, translated);
+
+ // And they can optionally have a width.
+ if (child.hasAttribute("width")) {
+ writer.addStatement("%1$s.setCellWidth(%2$s, \"%3$s\");",
+ fieldName, childFieldName, child.consumeAttribute("width"));
+ }
+
+ // And they can optionally have a height.
+ if (child.hasAttribute("height")) {
+ writer.addStatement("%1$s.setCellHeight(%2$s, \"%3$s\");",
+ fieldName, childFieldName, child.consumeAttribute("height"));
+ }
+
+ // Parse the CellPanel-specific attributes on the Dock element.
+ CellPanelParser.parseCellAttributes(child, fieldName, childFieldName, writer);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DomElementParser.java b/user/src/com/google/gwt/uibinder/parsers/DomElementParser.java
new file mode 100644
index 0000000..ba2619a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DomElementParser.java
@@ -0,0 +1,56 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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;
+
+/**
+ * Parses a raw dom element, as opposed to a widget or uiobject. Note that this
+ * parser does not get a crack at every dom element--it only handles the case of
+ * a dom element as ui root. Note also that it is intended to be used only by
+ * {@link UiBinderWriter}, and will generate cast class exceptions if used
+ * otherwise.
+ */
+public class DomElementParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ HtmlInterpreter interpreter =
+ new HtmlInterpreter(writer, "root", new HtmlMessageInterpreter(writer,
+ "root"));
+
+ writer.setFieldInitializer(fieldName, "null");
+
+ interpreter.interpretElement(elem);
+ String rootHtml = elem.consumeOpeningTag() + elem.getClosingTag();
+ String rootType = getFQTypeName(type);
+ writer.genRoot(String.format(
+ "(%1$s) UiBinderUtil.fromHtml(\"%2$s\")", rootType, rootHtml));
+
+ String innerHtml = elem.consumeInnerHtml(interpreter);
+
+ if (innerHtml.trim().length() > 0) { // TODO(rjrjr) Really want this check?
+ writer.addStatement("root.setInnerHTML(\"%s\");", innerHtml);
+ }
+ }
+
+ private String getFQTypeName(JClassType type) {
+ return type.getPackage().getName() + "." + type.getName();
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/ElementParser.java b/user/src/com/google/gwt/uibinder/parsers/ElementParser.java
new file mode 100644
index 0000000..e1b629d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/ElementParser.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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;
+
+/**
+ * Element parsers are classes that parse xml elements, using the context to
+ * generate widget initialization code.
+ */
+public interface ElementParser {
+
+ /**
+ * Parse the given element, generating the code to initialize it from the
+ * element's attributes and children.
+ *
+ * @param elem the element to be parsed
+ * @param fieldName the name of the widget field to be initialized
+ * @param type TODO
+ * @param writer the writer
+ * @throws UnableToCompleteException on error
+ */
+ void parse(XMLElement elem, String fieldName,
+ JClassType type, UiBinderWriter writer) throws UnableToCompleteException;
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
new file mode 100644
index 0000000..433cea0
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
@@ -0,0 +1,58 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Generates fields to hold named dom elements (e.g. <div
+ * ui:field="importantDiv">)
+ */
+ class FieldInterpreter implements XMLElement.Interpreter<String> {
+
+ private final String element;
+ private final UiBinderWriter writer;
+
+ public FieldInterpreter(UiBinderWriter writer, String ancestorExpression) {
+ this.writer = writer;
+ this.element = ancestorExpression;
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ String fieldName = writer.declareFieldIfNeeded(elem);
+ if (fieldName != null) {
+ String token = writer.declareDomField(fieldName, element);
+
+ if (elem.hasAttribute("id")) {
+ writer.die(String.format(
+ "Cannot declare id=\"%s\" and %s=\"%s\" on the same element",
+ elem.consumeAttribute("id"), writer.getGwtFieldAttributeName(),
+ fieldName));
+ }
+
+ elem.setAttribute("id", token);
+ }
+
+ /*
+ * Return null because we don't want to replace the dom element with any
+ * particular string (though we may have consumed its id or gwt:field)
+ */
+ return null;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
new file mode 100644
index 0000000..48b4003
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
@@ -0,0 +1,88 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.parsers.HtmlMessageInterpreter.PlaceholderInterpreterProvider;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.HTMLPanel} widgets.
+ */
+public class HTMLPanelParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ final UiBinderWriter writer) throws UnableToCompleteException {
+ String customTag =
+ UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute("tag"));
+
+ /*
+ * Gathers up elements that indicate nested widgets (but only those
+ * that are not inside msg elements).
+ */
+ WidgetInterpreter widgetInterpreter = new WidgetInterpreter(fieldName, writer);
+
+ /*
+ * Handles non-widget elements like msg, and dom elements with ui:field
+ * attributes. There may be widgets inside a msg, which is why
+ * the construction in makeHtmlInterpreter is so complicated.
+ */
+ HtmlInterpreter htmlInterpreter = makeHtmlInterpreter(fieldName, writer);
+
+ String html = elem.consumeInnerHtml(InterpreterPipe.newPipe(widgetInterpreter,
+ htmlInterpreter));
+
+ /*
+ * HTMLPanel has no no-arg ctor, so we have to generate our own, using the
+ * element's innerHTML and perhaps its tag attribute. Do this in a way that
+ * will not break subclasses if they happen to have the same constructor
+ * signature (by passing in type).
+ */
+ if ("".equals(customTag)) {
+ writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + html + "\"");
+ } else {
+ writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + customTag + "\"",
+ "\"" + html + "\"");
+ }
+ }
+
+ /**
+ * Creates an HtmlInterpreter with our specialized placeholder interpreter,
+ * which will allow widget instances to be declared inside of m:msg elements.
+ */
+ private HtmlInterpreter makeHtmlInterpreter(final String fieldName,
+ final UiBinderWriter uiWriter) {
+ final String ancestorExpression = fieldName + ".getElement()";
+
+ PlaceholderInterpreterProvider placeholderInterpreterProvider =
+ new PlaceholderInterpreterProvider() {
+ public PlaceholderInterpreter get(MessageWriter message) {
+ return new WidgetPlaceholderInterpreter(fieldName, uiWriter,
+ message, ancestorExpression);
+ }
+ };
+
+ HtmlInterpreter htmlInterpreter =
+ new HtmlInterpreter(uiWriter, ancestorExpression,
+ new HtmlMessageInterpreter(uiWriter, placeholderInterpreterProvider));
+
+ return htmlInterpreter;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HasHTMLParser.java b/user/src/com/google/gwt/uibinder/parsers/HasHTMLParser.java
new file mode 100644
index 0000000..5d06369
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HasHTMLParser.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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;
+
+/**
+ * Parses widgets that implement {@link com.google.gwt.user.client.ui.HasHTML}.
+ */
+public class HasHTMLParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+
+ HtmlInterpreter interpreter =
+ HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+ String html = elem.consumeInnerHtml(interpreter);
+
+ // TODO(jgw): throw an error if there's a conflicting 'html' attribute.
+ if (html.trim().length() > 0) {
+ writer.genStringPropertySet(fieldName, "HTML", html);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HasTextParser.java b/user/src/com/google/gwt/uibinder/parsers/HasTextParser.java
new file mode 100644
index 0000000..742205b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HasTextParser.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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;
+
+/**
+ * Parses widgets that implement {@link com.google.gwt.user.client.ui.HasText}.
+ */
+public class HasTextParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Widgets that implement HasText will use their elements' inner text.
+ String text = elem.consumeInnerText(new TextInterpreter(writer));
+ if (text.trim().length() > 0) {
+ writer.genStringPropertySet(fieldName, "text", text);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HasWidgetsParser.java b/user/src/com/google/gwt/uibinder/parsers/HasWidgetsParser.java
new file mode 100644
index 0000000..808bb38
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HasWidgetsParser.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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;
+
+/**
+ * Parses any widgets that implement
+ * {@link com.google.gwt.user.client.ui.HasWidgets}.
+ *
+ * This handles all panels that support a single-argument add() method.
+ */
+public class HasWidgetsParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ String childFieldName = writer.parseWidget(child);
+ writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
new file mode 100644
index 0000000..230abfc
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+import java.util.HashMap;
+
+/**
+ * Parses a
+ * {@link com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant}.
+ */
+public class HorizontalAlignmentConstantParser implements AttributeParser {
+
+ private static final HashMap<String, String> values =
+ new HashMap<String, String>();
+
+ static {
+ values.put("ALIGN_LEFT",
+ "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_LEFT");
+ values.put("ALIGN_RIGHT",
+ "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_RIGHT");
+ values.put("ALIGN_CENTER",
+ "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_CENTER");
+ }
+
+ public String parse(String value, UiBinderWriter writer)
+ throws UnableToCompleteException {
+ String translated = values.get(value);
+ if (translated == null) {
+ writer.die("Invalid value: horizontalAlignment='" + value + "'");
+ }
+ return translated;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java
new file mode 100644
index 0000000..18e1d6c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java
@@ -0,0 +1,82 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
+
+/**
+ * This is the most generally useful interpreter, and the most likely to be used
+ * by a custom parser when calling {@link XMLElement#consumeInnerHtml}
+ * <ul>
+ * <li>Assigns computed values to element attributes (e.g.
+ * res:class="style.pretty")
+ * <li>Generates fields to hold named dom elements (e.g. <div
+ * gwt:field="importantDiv">)
+ * <li>Turns <m:msg> and <m:attr> elements into methods on a
+ * generated Messages interface
+ * </ul>
+ */
+public class HtmlInterpreter implements XMLElement.Interpreter<String> {
+
+ /**
+ * A convenience factory method for the most common use of this class, to work
+ * with HTML that will eventually be rendered under a
+ * {@link com.google.gwt.user.client.ui.UIObject} (or really, any object that
+ * responds to <code>getElement()</code>). Uses an instance of
+ * {@link HtmlMessageInterpreter} to process message elements.
+ *
+ * @param uiExpression An expression that can be evaluated at runtime to find
+ * an object whose getElement() method can be called to get an
+ * ancestor of all Elements generated from the interpreted HTML.
+ */
+ public static HtmlInterpreter newInterpreterForUiObject(
+ UiBinderWriter writer, String uiExpression) {
+ String ancestorExpression = uiExpression + ".getElement()";
+ return new HtmlInterpreter(writer, ancestorExpression,
+ new HtmlMessageInterpreter(writer, ancestorExpression));
+ }
+
+ private final InterpreterPipe<String> pipe;
+
+ /**
+ * Rather than using this constructor, you probably want to use the
+ * {@link #newInterpreterForUiObject} factory method.
+ *
+ * @param ancestorExpression An expression that can be evaluated at runtime to
+ * find an Element that will be an ancestor of all Elements generated
+ * from the interpreted HTML.
+ * @param messageInterpreter an interpreter to handle msg and ph elements, typically an instance
+ * of {@link HtmlMessageInterpreter}. This interpreter gets last
+ * crack
+ */
+ public HtmlInterpreter(UiBinderWriter writer, String ancestorExpression,
+ Interpreter<String> messageInterpreter) {
+ this.pipe = new InterpreterPipe<String>();
+
+ pipe.add(new FieldInterpreter(writer, ancestorExpression));
+ pipe.add(new ComputedAttributeInterpreter(writer));
+ pipe.add(new AttributeMessageInterpreter(writer));
+ pipe.add(messageInterpreter);
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ return pipe.interpretElement(elem);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
new file mode 100644
index 0000000..4c9d48d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
@@ -0,0 +1,82 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Processes <m:msg> elements inside HTML values, which themselves
+ * are allowed to contain HTML. That HTML may hold elements with
+ * ui:field attributes and computed attributes, which must be
+ * replaced by placeholders in the generated message.
+ */
+public class HtmlMessageInterpreter implements XMLElement.Interpreter<String> {
+ /**
+ * Provides instances of {@link PlaceholderInterpreter}, to allow
+ * customized handling of the innards of a msg element.
+ */
+ public interface PlaceholderInterpreterProvider {
+ PlaceholderInterpreter get(MessageWriter message);
+ }
+
+ private final UiBinderWriter uiWriter;
+ private final PlaceholderInterpreterProvider phiProvider;
+
+ /**
+ * Build a HtmlMessageInterpreter that uses customized placeholder interpreter
+ * instances.
+ */
+ public HtmlMessageInterpreter(UiBinderWriter uiWriter,
+ PlaceholderInterpreterProvider phiProvider) {
+ this.uiWriter = uiWriter;
+ this.phiProvider = phiProvider;
+ }
+
+ /**
+ * Build a normally configured HtmlMessageInterpreter, able to handle
+ * put placeholders around dom elements with m:ph attributes and computed
+ * attributes.
+ */
+ public HtmlMessageInterpreter(final UiBinderWriter uiWriter,
+ final String ancestorExpression) {
+ this(uiWriter, new PlaceholderInterpreterProvider() {
+ public PlaceholderInterpreter get(MessageWriter message) {
+ return new HtmlPlaceholderInterpreter(uiWriter, message, ancestorExpression);
+ }
+ });
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ MessagesWriter messages = uiWriter.getMessages();
+ if (messages.isMessage(elem)) {
+ if (!elem.hasChildNodes()) {
+ uiWriter.die("Empty message: " + elem);
+ }
+
+ MessageWriter message = messages.newMessage(elem);
+ message.setDefaultMessage(elem.consumeInnerHtml(phiProvider.get(message)));
+ return uiWriter.tokenForExpression(messages.declareMessage(message));
+ }
+
+ return null;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
new file mode 100644
index 0000000..81d2f15
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
@@ -0,0 +1,96 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Interprets the interior of an html message. Extends the basic
+ * PlaceholderInterpreter to handle dom elements with field names and computed
+ * attributes--basically any dom attribute that cannot live in a static value
+ * and so must be wrapped in a placeholder. (Note that the interior of such an
+ * element is processed with a recursive call to
+ * {@link XMLElement#consumeInnerHtml}.)
+ */
+class HtmlPlaceholderInterpreter extends PlaceholderInterpreter {
+ private int serial = 0;
+ private final XMLElement.Interpreter<String> fieldAndComputed;
+
+ HtmlPlaceholderInterpreter(UiBinderWriter writer,
+ MessageWriter message, String ancestorExpression) {
+ super(writer, message);
+ fieldAndComputed =
+ InterpreterPipe.newPipe(new FieldInterpreter(writer,
+ ancestorExpression), new ComputedAttributeInterpreter(writer));
+ }
+
+ @Override
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ fieldAndComputed.interpretElement(elem);
+
+ if (isDomPlaceholder(elem)) {
+ MessagesWriter mw = uiWriter.getMessages();
+
+ String name = mw.consumeMessageAttribute("ph", elem);
+ if ("".equals(name)) {
+ name = "htmlElement" + (++serial);
+ }
+
+ String openTag = elem.consumeOpeningTag();
+ String openPlaceholder =
+ nextPlaceholder(name + "Begin", stripTokens(openTag),
+ uiWriter.detokenate(openTag));
+
+ String body = elem.consumeInnerHtml(this);
+
+ String closeTag = elem.getClosingTag();
+ String closePlaceholder =
+ nextPlaceholder(name + "End", closeTag, closeTag);
+
+ return openPlaceholder + body + closePlaceholder;
+ }
+
+ return super.interpretElement(elem);
+ }
+
+ @Override
+ protected String consumePlaceholderInnards(XMLElement elem)
+ throws UnableToCompleteException {
+ return elem.consumeInnerHtml(fieldAndComputed);
+ }
+
+ /**
+ * @return true if it has an m:ph attribute, or has a token in any attribute
+ */
+ private boolean isDomPlaceholder(XMLElement elem) {
+ MessagesWriter mw = uiWriter.getMessages();
+ if (mw.hasMessageAttribute("ph", elem)) {
+ return true;
+ }
+ for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
+ if (elem.getAttribute(i).hasToken()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java b/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
new file mode 100644
index 0000000..070718c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses a pair of integer values.
+ */
+public class IntPairParser implements AttributeParser {
+
+ public String parse(String value, UiBinderWriter writer) {
+ // TODO(jgw): parse & validate
+ return value;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntParser.java b/user/src/com/google/gwt/uibinder/parsers/IntParser.java
new file mode 100644
index 0000000..11a85ee
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/IntParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses an integer value.
+ */
+public class IntParser implements AttributeParser {
+
+ public String parse(String value, UiBinderWriter writer) {
+ // TODO(jgw): validate
+ return value;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/InterpreterPipe.java b/user/src/com/google/gwt/uibinder/parsers/InterpreterPipe.java
new file mode 100644
index 0000000..f9adbce
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/InterpreterPipe.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
+import com.google.gwt.uibinder.rebind.XMLElement.PostProcessingInterpreter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pairs {@link XMLElement.Interpreter} instances.
+ *
+ * @param <T> The type returned by all members of the pipe
+ */
+ class InterpreterPipe<T> implements PostProcessingInterpreter<T> {
+ public static <T>InterpreterPipe<T> newPipe(Interpreter<T> a, Interpreter<T> b) {
+ InterpreterPipe<T> rtn = new InterpreterPipe<T>();
+ rtn.add(a);
+ rtn.add(b);
+ return rtn;
+ }
+
+ private final List<Interpreter<T>> pipe =
+ new ArrayList<Interpreter<T>>();
+
+ public void add(Interpreter<T> i) {
+ pipe.add(i);
+ }
+
+ /**
+ * Interpreters are fired in the order they were handed to the constructor. If
+ * an interpretter gives a non-null result, downstream interpretters don't
+ * fire.
+ *
+ * @return The T or null returned by the last pipelined interpretter to run
+ * @throws UnableToCompleteException on error
+ */
+ public T interpretElement(XMLElement elem) throws UnableToCompleteException {
+ T rtn = null;
+ for (XMLElement.Interpreter<T> i : pipe) {
+ rtn = i.interpretElement(elem);
+ if (null != rtn) {
+ break;
+ }
+ }
+ return rtn;
+ }
+
+ /**
+ * Called by various {@link XMLElement} consumeInner*() methods after all
+ * elements have been handed to {@link #interpretElement}. Passes the
+ * text to be post processed to each pipe member that is instanceof
+ * {@link PostProcessingInterpreter}.
+ */
+ public String postProcess(String consumedText) throws UnableToCompleteException {
+ for (XMLElement.Interpreter<T> i : pipe) {
+ if (i instanceof PostProcessingInterpreter) {
+ consumedText = ((PostProcessingInterpreter<T>) i).postProcess(consumedText);
+ }
+ }
+ return consumedText;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java b/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
new file mode 100644
index 0000000..0325997
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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.MenuBar;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.MenuBar} widgets.
+ */
+public class MenuBarParser implements ElementParser {
+ private static final String TAG_MENUITEM = "MenuItem";
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Generate instantiation (Vertical MenuBars require a ctor param).
+ if (elem.hasAttribute("vertical")) {
+ boolean vertical = elem.consumeBooleanAttribute("vertical");
+ if (vertical) {
+ writer.setFieldInitializerAsConstructor(fieldName,
+ writer.getOracle().findType(MenuBar.class.getName()), "true");
+ }
+ }
+
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ // MenuBar can only contain MenuItem elements.
+ {
+ String ns = child.getNamespaceUri();
+ String tagName = child.getLocalName();
+
+ if (!elem.getNamespaceUri().equals(ns) || !tagName.equals(TAG_MENUITEM)) {
+ writer.die("In %s, only <%s:%s> are valid children", elem,
+ elem.getPrefix(), TAG_MENUITEM);
+ }
+ }
+
+ String itemFieldName = writer.parseWidget(child);
+
+ writer.addStatement("%1$s.addItem(%2$s);", fieldName, itemFieldName);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/MenuItemParser.java b/user/src/com/google/gwt/uibinder/parsers/MenuItemParser.java
new file mode 100644
index 0000000..128aa96
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/MenuItemParser.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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.MenuItem;
+
+/**
+ * A parser for menu items.
+ */
+public class MenuItemParser implements ElementParser {
+
+ /**
+ * Used by {@link XMLElement#consumeInnerHtml}. Gets to examine
+ * each dom element. Removes gwt:MenuBar elements and hands them
+ * to the template writer to be interpreted as widgets. Replaces
+ * gwt:MenuItemHTML elements with their consumeInnerHtml contents,
+ * and warns that they are deprecated.
+ */
+ private static class MenuItemGutsInterpreter
+ implements XMLElement.Interpreter<String> {
+ private final String namespaceUri;
+ private final HtmlInterpreter htmlInterpreter;
+ private final UiBinderWriter writer;
+ private final String errorContext;
+
+ private String menuBarField;
+
+ public MenuItemGutsInterpreter(UiBinderWriter writer, String namespaceUri,
+ String errorContext, HtmlInterpreter htmlInterpreter) {
+ this.writer = writer;
+ this.errorContext = errorContext;
+ this.namespaceUri = namespaceUri;
+ this.htmlInterpreter = htmlInterpreter;
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ if (isMenuHtml(elem)) {
+ writer.warn("In %s, the MenuItemHTML element is no longer required, "
+ + "and its contents have been inlined. This will become an error.",
+ errorContext);
+ return elem.consumeInnerHtml(htmlInterpreter);
+ }
+
+ if (isMenuBar(elem)) {
+ if (menuBarField != null) {
+ writer.die("In %s, only one MenuBar may be contained in a MenuItem",
+ errorContext);
+ }
+ menuBarField = writer.parseWidget(elem);
+ return "";
+ }
+
+ return null;
+ }
+
+ String getMenuBarField() {
+ return menuBarField;
+ }
+
+ private boolean isMenuBar(XMLElement child) {
+ return namespaceUri.equals(child.getNamespaceUri())
+ && child.getLocalName().equals(TAG_MENUBAR);
+ }
+
+ private boolean isMenuHtml(XMLElement child) {
+ return namespaceUri.equals(child.getNamespaceUri())
+ && child.getLocalName().equals(TAG_MENUITEMHTML);
+ }
+ }
+ private static final String TAG_MENUBAR = "MenuBar";
+
+ private static final String TAG_MENUITEMHTML = "MenuItemHTML";
+
+ public void parse(final XMLElement elem, String fieldName, JClassType type,
+ final UiBinderWriter writer) throws UnableToCompleteException {
+ writer.setFieldInitializerAsConstructor(fieldName,
+ writer.getOracle().findType(MenuItem.class.getName()),
+ "\"\"", "(com.google.gwt.user.client.Command) null");
+
+ InterpreterPipe<String> interpreter = new InterpreterPipe<String>();
+
+ // Build an interpreter pipeline to handle MenuItemHTML and MenuBar
+ // children, and to interpret everything other than those as HTML.
+ // TODO(rjrjr) Once MenuItemHTML goes away, we can reduce this to
+ // just handling MenuBar, and rely on HasHTMLParser to do the
+ // "everything other than those" bit.
+
+ final HtmlInterpreter htmlInterpreter =
+ HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+ MenuItemGutsInterpreter guts =
+ new MenuItemGutsInterpreter(writer, elem.getNamespaceUri(),
+ elem.toString(), htmlInterpreter);
+
+ interpreter.add(guts);
+ interpreter.add(htmlInterpreter);
+
+ String html = elem.consumeInnerHtml(interpreter);
+ if (html.trim().length() > 0) {
+ writer.genStringPropertySet(fieldName, "HTML", html);
+ }
+ if (guts.getMenuBarField() != null) {
+ writer.genPropertySet(fieldName, "subMenu", guts.getMenuBarField());
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/NullInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/NullInterpreter.java
new file mode 100644
index 0000000..e302db2
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/NullInterpreter.java
@@ -0,0 +1,28 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * An no-op interpreter.
+ * @param <T> The type of null to return
+ */
+public final class NullInterpreter<T> implements XMLElement.Interpreter<T> {
+ public T interpretElement(XMLElement elem) {
+ return null;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java b/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
new file mode 100644
index 0000000..0c8d6d6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
@@ -0,0 +1,40 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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.RadioButton;
+
+/**
+ * Parses {@link RadioButton} widgets.
+ */
+public class RadioButtonParser implements ElementParser {
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ if (!elem.hasAttribute("name")) {
+ writer.die("RadioButton requires a 'name' attribute: " + elem);
+ }
+
+ if (type.equals(writer.getOracle().findType(RadioButton.class.getName()))) {
+ writer.setFieldInitializerAsConstructor(fieldName, type,
+ '"' + elem.consumeAttribute("name") + '"');
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
new file mode 100644
index 0000000..3a406b9
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.StackPanel} widgets.
+ */
+public class StackPanelParser implements ElementParser {
+
+ private static final String ATTRIBUTE_TEXT = "StackPanel-text";
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+
+ // Stack panel label comes from the StackPanel-text attribute of the child
+ String stackItemLabel = null;
+ String variableAttributeName = elem.getPrefix() + ":" + ATTRIBUTE_TEXT;
+ if (child.hasAttribute(variableAttributeName)) {
+ stackItemLabel = child.consumeAttribute(variableAttributeName);
+ }
+
+ String childFieldName = writer.parseWidget(child);
+ if (stackItemLabel == null) {
+ writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+ } else {
+ writer.addStatement("%1$s.add(%2$s, \"%3$s\");", fieldName, childFieldName, stackItemLabel);
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
new file mode 100644
index 0000000..e289991
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses a string attribute.
+ */
+public class StringAttributeParser implements AttributeParser {
+
+ public String parse(String value, UiBinderWriter writer) {
+ return "\"" + UiBinderWriter.escapeTextForJavaStringLiteral(value) + "\"";
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
new file mode 100644
index 0000000..6965740
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 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.parsers;
+
+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;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.TabPanel} widgets.
+ */
+public class TabPanelParser implements ElementParser {
+
+ private static final String TAG_TAB = "Tab";
+ private static final String TAG_TABHTML = "TabHTML";
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+ // Parse children.
+ for (XMLElement child : elem.consumeChildElements()) {
+ // TabPanel can only contain Tab elements.
+ String ns = child.getNamespaceUri();
+ String tagName = child.getLocalName();
+
+ if (!ns.equals(elem.getNamespaceUri())) {
+ writer.die("Invalid TabPanel child namespace: " + ns);
+ }
+ if (!tagName.equals(TAG_TAB)) {
+ writer.die("Invalid TabPanel child element: " + tagName);
+ }
+
+ // Get the caption, if any.
+ String tabCaption = "";
+ if (child.hasAttribute("text")) {
+ tabCaption = child.consumeAttribute("text");
+ }
+
+ // Get the single required child widget.
+ String tabHTML = null;
+ String childFieldName = null;
+ for (XMLElement tabChild : child.consumeChildElements()) {
+ if (tabChild.getLocalName().equals(TAG_TABHTML)) {
+ HtmlInterpreter interpreter =
+ HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+ tabHTML = tabChild.consumeInnerHtml(interpreter);
+ } else {
+ if (childFieldName != null) {
+ writer.die("gwt:Tab may only have a single child widget");
+ }
+ childFieldName = writer.parseWidget(tabChild);
+ }
+ }
+
+ if (tabHTML != null) {
+ writer.addStatement("%1$s.add(%2$s, \"%3$s\", true);", fieldName,
+ childFieldName, tabHTML);
+ } else {
+ writer.addStatement("%1$s.add(%2$s, \"%3$s\");", fieldName,
+ childFieldName, tabCaption);
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/TextInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/TextInterpreter.java
new file mode 100644
index 0000000..342dfd7
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/TextInterpreter.java
@@ -0,0 +1,64 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * The interpreter of choice for calls to {@link XMLElement#consumeInnerText}.
+ * It handles <m:msg/> elements, replacing them with references to Messages
+ * interface calls.
+ * <P>
+ * Calls to {@link XMLElement#consumeInnerHtml} should instead use
+ * {@link HtmlInterpreter}
+ */
+public class TextInterpreter implements XMLElement.Interpreter<String> {
+
+ private final UiBinderWriter writer;
+
+ public TextInterpreter(UiBinderWriter writer) {
+ this.writer = writer;
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ MessagesWriter messages = writer.getMessages();
+ if (messages.isMessage(elem)) {
+ String messageInvocation = consumeAsTextMessage(elem, messages);
+ return writer.tokenForExpression(messageInvocation);
+ }
+
+ return null;
+ }
+
+ private String consumeAsTextMessage(XMLElement elem, MessagesWriter messages)
+ throws UnableToCompleteException {
+ if (!elem.hasChildNodes()) {
+ writer.die("Empty message: " + elem);
+ }
+
+ MessageWriter message = messages.newMessage(elem);
+ PlaceholderInterpreter interpreter =
+ new TextPlaceholderInterpreter(writer, message);
+ message.setDefaultMessage(elem.consumeInnerText(interpreter));
+ return messages.declareMessage(message);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/TextPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/TextPlaceholderInterpreter.java
new file mode 100644
index 0000000..fadb67b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/TextPlaceholderInterpreter.java
@@ -0,0 +1,39 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Interprets the interior of text message. Any ph elements in the message will
+ * be consumed by a call to their {@link XMLElement#consumeInnerText} method.
+ * A ph inside a text message may contain no other elements.
+ */
+public final class TextPlaceholderInterpreter extends PlaceholderInterpreter {
+ public TextPlaceholderInterpreter(UiBinderWriter writer,
+ MessageWriter message) {
+ super(writer, message);
+ }
+
+ @Override protected String consumePlaceholderInnards(XMLElement elem)
+ throws UnableToCompleteException {
+ return elem.consumeInnerText(new NullInterpreter<String>());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java b/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
new file mode 100644
index 0000000..4ce0858
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
@@ -0,0 +1,88 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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;
+
+/**
+ * Parser of all UIObject types. Basically bats cleanup if more
+ * specialized parsers have left things around, or haven't been run.
+ */
+public class UIObjectParser implements ElementParser {
+
+ private static final String ATTRIBUTE_DEBUG_ID = "debugId";
+ private static final String ATTRIBUTE_ADD_STYLE_NAMES = "addStyleNames";
+ private static final String ATTRIBUTE_ADD_STYLE_DEPENDENT_NAMES = "addStyleDependentNames";
+
+ public void parse(XMLElement elem, String fieldName, JClassType type,
+ UiBinderWriter writer) throws UnableToCompleteException {
+
+ addCustomAttribute(elem, fieldName, writer, "ensureDebugId", ATTRIBUTE_DEBUG_ID, false);
+ addCustomAttribute(elem, fieldName, writer, "addStyleName", ATTRIBUTE_ADD_STYLE_NAMES, true);
+ addCustomAttribute(elem, fieldName, writer, "addStyleDependentName",
+ ATTRIBUTE_ADD_STYLE_DEPENDENT_NAMES, true);
+
+ HtmlInterpreter interpreter =
+ HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+
+ String html = elem.consumeInnerHtml(interpreter);
+ if (html.trim().length() > 0) {
+ writer.setFieldInitializer(fieldName,
+ String.format("new DomHolder(UiBinderUtil.fromHtml(\"%s\"))", html));
+ }
+ }
+
+ /**
+ * @param elem The actual XmlElement which the parser is inspecting
+ * @param fieldName The object/type to which a custom attribute will be added
+ * @param writer The TemplateWriter for generating the code
+ * @param targetSetterMethod The real method/setter used by UIObject to set
+ * the attribute
+ * @param attribute The attribute as it will appear in the binder template
+ * @param isMultiValue Specifies if the attributes value can have multiple
+ * values that are comma separated
+ * @throws UnableToCompleteException
+ */
+ private void addCustomAttribute(XMLElement elem, String fieldName, UiBinderWriter writer,
+ String targetSetterMethod, String attribute, boolean isMultiValue)
+ throws UnableToCompleteException {
+ String attributeValue = null;
+ if (elem.hasAttribute(attribute)) {
+ attributeValue = elem.consumeAttribute(attribute);
+
+ if ("".equals(attributeValue)) {
+ writer.die("In %s, value for attribute %s cannot be empty", elem, attribute);
+ }
+
+ // Check if the value is comma separated
+ if (isMultiValue && attributeValue.contains(",")) {
+ String[] values = attributeValue.split(",");
+ for (String value : values) {
+ value = value.trim();
+ if ("".equals(value)) {
+ writer.die("In %s, value for attribute %s cannot be empty", elem, attribute);
+ }
+ writer.addStatement("%1$s.%2$s(\"%3$s\");", fieldName, targetSetterMethod, value);
+ }
+ } else {
+ writer.addStatement("%1$s.%2$s(\"%3$s\");", fieldName, targetSetterMethod, attributeValue);
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
new file mode 100644
index 0000000..70ba1ca
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+import java.util.HashMap;
+
+/**
+ * Parses a
+ * {@link com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant}.
+ */
+public class VerticalAlignmentConstantParser implements AttributeParser {
+
+ private static final HashMap<String, String> values =
+ new HashMap<String, String>();
+
+ static {
+ values.put("ALIGN_TOP",
+ "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_TOP");
+ values.put("ALIGN_MIDDLE",
+ "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_MIDDLE");
+ values.put("ALIGN_BOTTOM",
+ "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_BOTTOM");
+ }
+
+ public String parse(String value, UiBinderWriter writer) {
+ String translated = values.get(value);
+ if (translated == null) {
+ throw new RuntimeException("Invalid value: vorizontalAlignment='"
+ + value + "'");
+ }
+ return translated;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/WidgetInterpreter.java
new file mode 100644
index 0000000..78f7a22
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/WidgetInterpreter.java
@@ -0,0 +1,111 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.PostProcessingInterpreter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Used by {@link HTMLPanelParser} to interpret elements that call for widget
+ * instances. Declares the appropriate widget, and replaces them in the markup
+ * with a <span>.
+ */
+class WidgetInterpreter implements PostProcessingInterpreter<String> {
+
+ private static final Map<String, String> LEGAL_CHILD_ELEMENTS;
+ private static final String DEFAULT_CHILD_ELEMENT = "span";
+ static {
+ // Other cases?
+ HashMap<String, String> map = new HashMap<String, String>();
+ map.put("table", "tbody");
+ map.put("thead", "tr");
+ map.put("tbody", "tr");
+ map.put("tfoot", "tr");
+ map.put("ul", "li");
+ map.put("ol", "li");
+ map.put("dl", "dt");
+ LEGAL_CHILD_ELEMENTS = Collections.unmodifiableMap(map);
+ }
+
+ private static String getLegalPlaceholderTag(XMLElement elem) {
+ XMLElement parent = elem.getParent();
+ String tag = null;
+ if (parent != null) {
+ tag = LEGAL_CHILD_ELEMENTS.get(parent.getLocalName());
+ }
+ if (tag == null) {
+ tag = DEFAULT_CHILD_ELEMENT;
+ }
+ return tag;
+ }
+
+ /**
+ * A map of widget idHolder field names to the element it references.
+ */
+ private final Map<String, XMLElement> idToWidgetElement =
+ new HashMap<String, XMLElement>();
+ private final String fieldName;
+
+ private final UiBinderWriter uiWriter;
+
+ public WidgetInterpreter(String fieldName, UiBinderWriter writer) {
+ this.fieldName = fieldName;
+ this.uiWriter = writer;
+ }
+
+ public String interpretElement(XMLElement elem) throws UnableToCompleteException {
+ if (uiWriter.isWidget(elem)) {
+ // Allocate a local variable to hold the dom id for this widget. Note
+ // 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();
+ idToWidgetElement.put(idHolder, elem);
+
+ // Create an element to hold the widget.
+ String tag = getLegalPlaceholderTag(elem);
+ return "<" + tag + " id='\" + " + idHolder + " + \"'></" + tag + ">";
+ }
+ return null;
+ }
+
+ /**
+ * Called by {@link XMLElement#consumeInnerHtml} after all elements have
+ * been handed to {@link #interpretElement}. Parses each widget element
+ * that was seen, and generates a
+ * {@link com.google.gwt.user.client.ui.HTMLPanel#addAndReplaceElement} for
+ * each.
+ */
+ public String postProcess(String consumedHtml) throws UnableToCompleteException {
+ /*
+ * Generate an addAndReplaceElement(widget, idHolder) for each widget found
+ * while parsing the element's inner HTML.
+ */
+ for (String idHolder : idToWidgetElement.keySet()) {
+ XMLElement childElem = idToWidgetElement.get(idHolder);
+ String childField = uiWriter.parseWidget(childElem);
+ uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);", fieldName,
+ childField, idHolder);
+ }
+ return consumedHtml;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/WidgetPlaceholderInterpreter.java
new file mode 100644
index 0000000..b337b4a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/WidgetPlaceholderInterpreter.java
@@ -0,0 +1,184 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.parsers;
+
+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.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.HasText;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Used by {@link HTMLPanelParser}. Refines {@link HtmlPlaceholderInterpreter}
+ * to allow widgets to appear inside msg elements in an HTMLPanel.
+ * <p>
+ * HasText and HasHTML get special treatment, where their innerText or innerHTML
+ * become part of the {@literal @}Default value of the message being generated.
+ * E.g., this markup in an HTMLPanel:
+ *
+ * <pre>
+ * <m:msg>Hello <gwt:HyperLink>click here</gwt:HyperLink> thank you.</m:msg></pre>
+ *
+ * becomes a message like this: <pre>
+ * {@literal @}Default("Hello {0}click here{1} thank you.")
+ * String getMessage1(
+ * {@literal @}Example("<span>") String widget1Begin,
+ * {@literal @}Example("</span>") String widget1End
+ * );</pre>
+ *
+ * <p>
+ * The contents of other widget types are opaque to the message, and are covered
+ * by a single placeholder. One implication of this is that the content of an
+ * HTMLPanel inside a msg in another HTMLPanel must always be in a separate
+ * message.
+ */
+class WidgetPlaceholderInterpreter extends HtmlPlaceholderInterpreter {
+ // Could break this up into three further classes, for HasText, HasHTML
+ // and Other, but that seems more trouble than it's worth.
+
+ private int serial = 0;
+ private final String ancestorExpression;
+ private final String fieldName;
+ private final Map<String, XMLElement> idToWidgetElement =
+ new HashMap<String, XMLElement>();
+ private final Set<String> idIsHasHTML = new HashSet<String>();
+ private final Set<String> idIsHasText = new HashSet<String>();
+
+ WidgetPlaceholderInterpreter(String fieldName, UiBinderWriter writer,
+ MessageWriter message, String ancestorExpression) {
+ super(writer, message, ancestorExpression);
+ this.fieldName = fieldName;
+ this.ancestorExpression = ancestorExpression;
+ }
+
+ @Override
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+
+ if (!uiWriter.isWidget(elem)) {
+ return super.interpretElement(elem);
+ }
+
+ JClassType type = uiWriter.findWidgetOrElementType(elem);
+ TypeOracle oracle = uiWriter.getOracle();
+
+ MessagesWriter mw = uiWriter.getMessages();
+ String name = mw.consumeMessageAttribute("ph", elem);
+ if ("".equals(name)) {
+ name = "widget" + (++serial);
+ }
+
+ String idHolder = uiWriter.declareDomIdHolder();
+ idToWidgetElement.put(idHolder, elem);
+
+ if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) {
+ return handleHasHTMLPlaceholder(elem, name, idHolder);
+ }
+
+ if (oracle.findType(HasText.class.getName()).isAssignableFrom(type)) {
+ return handleHasTextPlaceholder(elem, name, idHolder);
+ }
+
+ return handleOpaqueWidgetPlaceholder(elem, name, idHolder);
+ }
+
+ /**
+ * Called by {@link XMLElement#consumeInnerHtml} after all elements
+ * have been handed to {@link #interpretElement}.
+ */
+ @Override
+ public String postProcess(String consumed) throws UnableToCompleteException {
+ for (String idHolder : idToWidgetElement.keySet()) {
+ XMLElement childElem = idToWidgetElement.get(idHolder);
+ String childField = uiWriter.parseWidget(childElem);
+
+ genSetWidgetTextCall(idHolder, childField);
+ uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);",
+ fieldName, childField, idHolder);
+ }
+ return super.postProcess(consumed);
+ }
+
+ private String genCloseTag(String name) {
+ String closePlaceholder =
+ nextPlaceholder(name + "End", "</span>", "</span>");
+ return closePlaceholder;
+ }
+
+ private String genOpenTag(String name, String idHolder) {
+ String openTag = String.format("<span id='\" + %s + \"'>", idHolder);
+ String openPlaceholder =
+ nextPlaceholder(name + "Begin", "<span>", openTag);
+ 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 String handleHasHTMLPlaceholder(XMLElement elem, String name,
+ String idHolder) throws UnableToCompleteException {
+ idIsHasHTML.add(idHolder);
+ String openPlaceholder = genOpenTag(name, idHolder);
+
+ String body =
+ elem.consumeInnerHtml(new HtmlPlaceholderInterpreter(uiWriter,
+ message, ancestorExpression));
+ String bodyToken = tokenator.nextToken(body);
+
+ String closePlaceholder = genCloseTag(name);
+ return openPlaceholder + bodyToken + closePlaceholder;
+ }
+
+ private String handleHasTextPlaceholder(XMLElement elem, String name,
+ String idHolder) throws UnableToCompleteException {
+ idIsHasText.add(idHolder);
+ String openPlaceholder = genOpenTag(name, idHolder);
+
+ String body =
+ elem.consumeInnerText(new TextPlaceholderInterpreter(uiWriter,
+ message));
+ String bodyToken = tokenator.nextToken(body);
+
+ String closePlaceholder = genCloseTag(name);
+ return openPlaceholder + bodyToken + closePlaceholder;
+ }
+
+ private String handleOpaqueWidgetPlaceholder(XMLElement elem, String name,
+ String idHolder) throws UnableToCompleteException {
+ String tag = String.format("<span id='\" + %s + \"'></span>", idHolder);
+ String placeholder = nextPlaceholder(name, "<span></span>", tag);
+ return placeholder;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/ChildWalker.java b/user/src/com/google/gwt/uibinder/rebind/ChildWalker.java
new file mode 100644
index 0000000..fc6ed3c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/ChildWalker.java
@@ -0,0 +1,77 @@
+/*
+ * 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
+ * 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 org.w3c.dom.CDATASection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * Traverses the children of a {@link Node}.
+ */
+class ChildWalker {
+
+ /**
+ * Take a {@link NodeVisitor} and show it each child of the given {@link Node}
+ * that is of a type relevant to our templates.
+ * <p>
+ * Note that this is not a recursive call, though the visitor itself may
+ * choose to recurse
+ *
+ * @throws UnableToCompleteException
+ */
+ void accept(Node n, NodeVisitor v) throws UnableToCompleteException {
+
+ NodeList childNodes = n.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); ++i) {
+ Node child = childNodes.item(i);
+
+ switch (child.getNodeType()) {
+ case Node.ELEMENT_NODE:
+ v.visitElement((Element) child);
+ break;
+
+ case Node.TEXT_NODE:
+ v.visitText((Text) child);
+ break;
+
+ case Node.COMMENT_NODE:
+ // Ditch comment nodes.
+ break;
+
+ case Node.CDATA_SECTION_NODE:
+ v.visitCData((CDATASection) child);
+ break;
+
+ case Node.ENTITY_NODE:
+ case Node.ENTITY_REFERENCE_NODE:
+ case Node.ATTRIBUTE_NODE:
+ case Node.DOCUMENT_NODE:
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ case Node.NOTATION_NODE:
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ default: {
+ // None of these are expected node types.
+ throw new RuntimeException("Unexpected XML node");
+ }
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
new file mode 100644
index 0000000..cdeb811
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+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 java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+/**
+ * This class handles all {@link FieldWriter} instances created for the
+ * current template.
+ */
+class FieldManager {
+
+ 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 %2$s, or annotate a constructor of %3$s with"
+ + " @UiConstructor.";
+
+ private static final String DUPLICATED_FIELD_ERROR =
+ "Duplicate declaration of field %1$s.";
+
+ /**
+ * 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>();
+
+ /**
+ * A stack of the fields.
+ */
+ private final LinkedList<FieldWriter> parsedFieldStack =
+ new LinkedList<FieldWriter>();
+
+ private TypeOracle oracle;
+ private TreeLogger logger;
+
+ /**
+ * Basic constructor just injects an oracle instance.
+ */
+ public FieldManager(TypeOracle oracle, TreeLogger logger) {
+ this.oracle = oracle;
+ this.logger = logger;
+ }
+
+ /**
+ * Post an error message and halt processing. This method always throws an
+ * {@link UnableToCompleteException}
+ */
+ public void die(String message, Object... params)
+ throws UnableToCompleteException {
+ logger.log(TreeLogger.ERROR, String.format(message, params));
+ throw new UnableToCompleteException();
+ }
+
+ /**
+ * @param fieldName the name of the {@link FieldWriter} to find
+ * @return the {@link FieldWriter} instance indexed by fieldName or
+ * <b>null</b> in case fieldName is not found
+ */
+ public FieldWriter lookup(String fieldName) {
+ return fieldsMap.get(fieldName);
+ }
+
+ /**
+ * Remove the field at the top of the {@link #parsedFieldStack}.
+ */
+ public void pop() {
+ parsedFieldStack.removeFirst();
+ }
+
+ /**
+ * @param fieldWriter the field to push on the top of the
+ * {@link #parsedFieldStack}
+ */
+ public void push(FieldWriter fieldWriter) {
+ parsedFieldStack.addFirst(fieldWriter);
+ }
+
+ /**
+ * This is where {@link FieldWriter} instances come from. 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 #declareDomIdHolder()}) used by an HTMLPanel will be declared
+ * before it is.
+ *
+ * @param typeName the full qualified name for the class associated with the
+ * field
+ * @param fieldName the name of the field
+ * @return a new {@link FieldWriter} instance, <b>null</b> in case fieldName
+ * is already indexed
+ */
+ public FieldWriter registerField(String typeName, String fieldName)
+ throws UnableToCompleteException {
+ JClassType fieldType = oracle.findType(typeName);
+ if (fieldsMap.containsKey(fieldName)) {
+ die(DUPLICATED_FIELD_ERROR, fieldName);
+ }
+
+ FieldWriter field = new FieldWriter(fieldType, fieldName);
+ fieldsMap.put(fieldName, field);
+
+ if (parsedFieldStack.size() > 0) {
+ parsedFieldStack.getFirst().needs(field);
+ }
+
+ return field;
+ }
+
+ /**
+ * Writes all stored gwt fields.
+ *
+ * @param writer the writer to output
+ * @param ownerTypeName the name of the class being processed
+ */
+ public void writeGwtFieldsDeclaration(IndentedWriter writer,
+ String ownerTypeName) throws UnableToCompleteException {
+ Collection<FieldWriter> fields = fieldsMap.values();
+ for (FieldWriter field : fields) {
+ if (!field.write(writer)) {
+ die(NO_DEFAULT_CTOR_ERROR,
+ field.getType().getQualifiedSourceName(),
+ ownerTypeName,
+ field.getType().getName());
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
new file mode 100644
index 0000000..28172e9
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Models a field to be written in the generated binder code. Note that this is
+ * not necessarily a field that the user has declared. It's basically any
+ * instance variable the generator will need.
+ * <p>
+ * A field can have a custom initialization statement, set via
+ * {@link #setInitializer}. Without one it will be initialized via a
+ * {@link GWT#create} call. (In the rare case that you need a field not to be
+ * initialized, initialize it to "null".)
+ * <p>
+ * Dependencies can be declared between fields via {@link #needs}, to ensure
+ * that one can be initialized via reference to another. Circular references are
+ * not supported, nor detected.
+ */
+public class FieldWriter {
+ private final JClassType type;
+ private final String name;
+ private final Set<FieldWriter> needs = new HashSet<FieldWriter>();
+
+ private String initializer;
+ private boolean written;
+
+ /**
+ * Package protected, only TemplateWriter is allowed to instantiate
+ * FieldWriters.
+ * <p>
+ * Public for testing only
+ */
+ FieldWriter(JClassType type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ /**
+ * @return the fully qualified name of this field's type
+ */
+ public String getFullTypeName() {
+ return type.getPackage().getName() + "." + type.getName();
+ }
+
+ /**
+ * @return the type of this field
+ */
+ public JClassType getType() {
+ return type;
+ }
+
+ /**
+ * Declares that the receiver depends upon the give field.
+ */
+ public void needs(FieldWriter f) {
+ needs.add(f);
+ }
+
+ /**
+ * 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 ';'.
+ */
+ public void setInitializer(String initializer) {
+ if (this.initializer != null) {
+ throw new IllegalStateException(String.format(
+ "Second attempt to set initializer for field \"%s\", "
+ + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
+ }
+ this.initializer = initializer;
+ }
+
+ /**
+ * Write the field delcaration.
+ *
+ * @return false if unable to write for lack of a default constructor
+ */
+ // TODO(rjrjr) This return code thing is silly. We should
+ // just be calling {@link TemplateWriter#die}, but that's made complicated
+ // by an unfortunate override in UiBinderWriter. Fix this when that
+ // subclassing goes away.
+ public boolean write(IndentedWriter w) {
+ if (written) {
+ return true;
+ }
+
+ for (FieldWriter f : needs) {
+ // TODO(rdamazio, rjrjr) This is simplistic, and will fail when
+ // we support more interesting contexts (e.g. the same need being used
+ // inside two different
+ // LazyPanels)
+ f.write(w);
+ }
+
+ if (initializer == null) {
+ if (type.findConstructor(new JType[0]) == null) {
+ return false;
+ }
+ initializer =
+ String.format("(%1$s) GWT.create(%1$s.class)", getFullTypeName());
+ }
+
+ w.write("%s %s = %s;", getFullTypeName(), name, initializer);
+
+ this.written = true;
+ return true;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
new file mode 100644
index 0000000..1f7d7a4
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
@@ -0,0 +1,56 @@
+/*
+ * 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
+ * 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.uibinder.rebind.XMLElement.Interpreter;
+
+import org.w3c.dom.Element;
+
+class GetInnerHtmlVisitor extends GetInnerTextVisitor {
+
+ /**
+ * Recursively gathers an HTML representation of the children of the given
+ * Elem, and stuffs it into the given StringBuffer. Applies the interpreter to
+ * each descendant, and uses the writer to report errors.
+ */
+ public static void getEscapedInnerHtml(Element elem, StringBuffer buffer,
+ Interpreter<String> interpreter, UiBinderWriter writer)
+ throws UnableToCompleteException {
+ new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter,
+ writer));
+ }
+
+ private GetInnerHtmlVisitor(StringBuffer buffer,
+ Interpreter<String> interpreter, UiBinderWriter writer) {
+ super(buffer, interpreter, writer);
+ }
+
+ @Override
+ public void visitElement(Element elem) throws UnableToCompleteException {
+ XMLElement xmlElement = new XMLElement(elem, writer);
+ String replacement = interpreter.interpretElement(xmlElement);
+ if (replacement != null) {
+ buffer.append(replacement);
+ return;
+ }
+
+ // TODO(jgw): Ditch the closing tag when there are no children.
+ buffer.append(xmlElement.consumeOpeningTag());
+ getEscapedInnerHtml(elem, buffer, interpreter, writer);
+ buffer.append(xmlElement.getClosingTag());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerTextVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerTextVisitor.java
new file mode 100644
index 0000000..4cf561a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerTextVisitor.java
@@ -0,0 +1,78 @@
+/*
+ * 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
+ * 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.uibinder.rebind.XMLElement.Interpreter;
+
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+class GetInnerTextVisitor implements NodeVisitor {
+
+ /**
+ * Gathers a text representation of the children of the given Elem, and stuffs
+ * it into the given StringBuffer. Applies the interpreter to each descendant,
+ * and uses the writer to report errors.
+ */
+ public static void getEscapedInnerText(Element elem, StringBuffer buffer,
+ Interpreter<String> interpreter, UiBinderWriter writer)
+ throws UnableToCompleteException {
+ new ChildWalker().accept(elem, new GetInnerTextVisitor(buffer, interpreter,
+ writer));
+ }
+
+ protected final StringBuffer buffer;
+ protected final Interpreter<String> interpreter;
+ protected final UiBinderWriter writer;
+
+ protected GetInnerTextVisitor(StringBuffer buffer,
+ Interpreter<String> interpreter, UiBinderWriter writer) {
+ this.buffer = buffer;
+ this.interpreter = interpreter;
+ this.writer = writer;
+ }
+
+ public void visitCData(CDATASection d) {
+ // TODO(jgw): write this back just as it came in.
+ }
+
+ public void visitElement(Element e) throws UnableToCompleteException {
+ String replacement =
+ interpreter.interpretElement(new XMLElement(e, writer));
+
+ if (replacement != null) {
+ buffer.append(replacement);
+ }
+ }
+
+ public void visitText(Text t) {
+ String escaped =
+ UiBinderWriter.escapeText(t.getTextContent(), preserveWhiteSpace(t));
+ buffer.append(escaped);
+ }
+
+ private boolean preserveWhiteSpace(Text t) {
+ Element parent = Node.ELEMENT_NODE == t.getParentNode().getNodeType()
+ ? (Element) t.getParentNode() : null;
+
+ boolean preserveWhitespace =
+ parent != null && "pre".equals(parent.getTagName());
+ return preserveWhitespace;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GwtResourceEntityResolver.java b/user/src/com/google/gwt/uibinder/rebind/GwtResourceEntityResolver.java
new file mode 100644
index 0000000..c93dbd3
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/GwtResourceEntityResolver.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Does special handling of external entities encountered by sax xml parser,
+ * e.g. the uri in
+ *
+ * <pre>
+ * <!DOCTYPE gwt:UiBinder
+ SYSTEM "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent"></pre>
+ * <p>
+ * Specifically, if the requested uri starts with
+ * <code>http://google-web-toolkit.googlecode.com/svn/resources</code>, provide
+ * the contents from a built in resource rather than allowing sax to make a
+ * network request.
+ */
+class GwtResourceEntityResolver implements EntityResolver {
+ interface ResourceLoader {
+ InputStream fetch(String name);
+ }
+
+ private static final String EXTERNAL_ENTITY_PREFIX = "http://google-web-toolkit.googlecode.com/svn/resources";
+
+ private static final String RESOURCES = "com/google/gwt/uibinder/resources";
+
+ private final ResourceLoader resourceLoader;
+
+ public GwtResourceEntityResolver() {
+ this(new ResourceLoader() {
+ public InputStream fetch(String name) {
+ return UiBinderGenerator.class.getClassLoader().getResourceAsStream(
+ name);
+ }
+ });
+ }
+
+ /**
+ * For testing.
+ */
+ GwtResourceEntityResolver(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
+ }
+
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ if (!systemId.startsWith(EXTERNAL_ENTITY_PREFIX)) {
+ // Not our problem, return null and sax will find it
+ return null;
+ }
+
+ String name = RESOURCES
+ + systemId.substring(EXTERNAL_ENTITY_PREFIX.length());
+ InputStream resourceStream = resourceLoader.fetch(name);
+
+ if (resourceStream == null) {
+ /*
+ * Maybe, just maybe they're asking for something that has been added to
+ * svn but hasn't reached their local gwt install yet. Return null to
+ * allow sax to try to fetch it.
+ */
+ return null;
+ }
+
+ InputSource inputSource = new InputSource(resourceStream);
+ inputSource.setPublicId(publicId);
+ inputSource.setSystemId(systemId);
+ return inputSource;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
new file mode 100644
index 0000000..5237375
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+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.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+
+/**
+ * This class implements an easy way to bind widget event handlers to methods
+ * annotated with {@link com.google.gwt.uibinder.client.UiHandler} so that the
+ * user doesn't need to worry about writing code to implement these bindings.
+ *
+ * <p>For instance, the class defined below:
+ * <pre>
+ * public class MyClass {
+ * @UiField Label label;
+ *
+ * @UiBinder({"label", "link"})
+ * public void doClick(ClickEvent e) {
+ * // do something
+ * }
+ * }
+ * </pre>
+ *
+ * will generate a piece of code like:
+ * <pre>
+ * ClickHandler handler0 = new ClickHandler() {
+ * @Override
+ * public void onClick(ClickEvent event) {
+ * owner.doClick(event);
+ * }
+ * });
+ * label.addClickHandler(handler0);
+ * link.addClickHandler(handler0);
+ * </pre>
+ *
+ * Notice that the <b>link</b> object doesn't need to be annotated with
+ * {@link com.google.gwt.uibinder.client.UiField} as long as it exists
+ * (annotated with ui:field) in the template.
+ */
+class HandlerEvaluator {
+
+ private static final String HANDLER_BASE_NAME = "handler";
+
+ private int varCounter = 0;
+
+ private final TreeLogger logger;
+
+ private final JClassType handlerRegistrationJClass;
+ private final JClassType eventHandlerJClass;
+ private final OwnerClass ownerClass;
+
+ /**
+ * The verbose testable constructor.
+ *
+ * @param ownerClass a descriptor of the UI owner class
+ * @param logger the logger for warnings and errors
+ * @param oracle the type oracle
+ */
+ HandlerEvaluator(OwnerClass ownerClass, TreeLogger logger, TypeOracle oracle) {
+ this.ownerClass = ownerClass;
+ this.logger = logger;
+
+ handlerRegistrationJClass = oracle.findType(HandlerRegistration.class.getName());
+ eventHandlerJClass = oracle.findType(EventHandler.class.getName());
+ }
+
+ /**
+ * Post an error message and throws an {@link UnableToCompleteException}.
+ *
+ * @param message the error message
+ * @param params the format params
+ */
+ public void die(String message, Object... params) throws UnableToCompleteException {
+ logger.log(TreeLogger.ERROR, String.format(message, params));
+ throw new UnableToCompleteException();
+ }
+
+ /**
+ * Runs the evaluator in the given class according to the valid fields
+ * extracted from the template (via attribute ui:field).
+ *
+ * @param writer the writer used to output the results
+ * @param fieldManager the field manager instance
+ * @param uiOwner the name of the class evaluated here that owns the template
+ */
+ public void run(IndentedWriter writer, FieldManager fieldManager,
+ String uiOwner) throws UnableToCompleteException {
+
+ // Iterate through all methods defined in the class.
+ for (JMethod method : ownerClass.getUiHandlers()) {
+ // Evaluate the method.
+ String boundMethod = method.getName();
+ if (method.isPrivate()) {
+ die("Method '%s' cannot be private.", boundMethod);
+ }
+
+ // Retrieves both event and handler types.
+ JParameter[] parameters = method.getParameters();
+ if (parameters.length != 1) {
+ die("Method '%s' must have a single event parameter defined.", boundMethod);
+ }
+ JClassType eventType = parameters[0].getType().isClass();
+
+ JClassType handlerType = getHandlerForEvent(eventType);
+ if (handlerType == null) {
+ die("Parameter '%s' is not an event (subclass of GwtEvent).", eventType.getName());
+ }
+
+ // Cool to add the handler in the output.
+ String handlerVarName = HANDLER_BASE_NAME + (++varCounter);
+ writeHandler(writer, uiOwner, handlerVarName, handlerType, eventType, boundMethod);
+
+ // Adds the handler created above.
+ UiHandler annotation = method.getAnnotation(UiHandler.class);
+ for (String objectName : annotation.value()) {
+ // Is the field object valid?
+ FieldWriter fieldWriter = fieldManager.lookup(objectName);
+ if (fieldWriter == null) {
+ die("Method '%s' can not be bound. You probably missed ui:field='%s' "
+ + "in the template.", boundMethod, objectName);
+ }
+
+ // Retrieves the "add handler" method in the object.
+ JMethod addHandlerMethodType = getAddHandlerMethodForObject(
+ fieldWriter.getType(), handlerType);
+ if (addHandlerMethodType == null) {
+ die("Field '%s' does not have an 'add%s' method associated.",
+ objectName, handlerType.getName());
+ }
+
+ // Cool to tie the handler into the object.
+ writeAddHandler(writer, handlerVarName,
+ addHandlerMethodType.getName(), objectName);
+ }
+ }
+ }
+
+ /**
+ * Post a warning message.
+ *
+ * @param message the error message
+ * @param params the format params
+ */
+ public void warn(String message, Object... params) {
+ logger.log(TreeLogger.WARN, String.format(message, params));
+ }
+
+ /**
+ * Writes a handler entry using the given writer.
+ *
+ * @param writer the writer used to output the results
+ * @param uiOwner the name of the class evaluated here that owns the template
+ * @param handlerVarName the name of the handler variable
+ * @param handlerType the handler we want to create
+ * @param eventType the event associated with the handler
+ * @param boundMethod the method bound in the handler
+ */
+ protected void writeHandler(IndentedWriter writer, String uiOwner, String handlerVarName,
+ JClassType handlerType, JClassType eventType, String boundMethod)
+ throws UnableToCompleteException {
+
+ // Retrieves the single method (usually 'onSomething') related to all
+ // handlers. Ex: onClick in ClickHandler, onBlur in BlurHandler ...
+ JMethod[] methods = handlerType.getMethods();
+ if (methods.length != 1) {
+ die("'%s' has more than one method defined.", handlerType.getName());
+ }
+
+ // Checks if the method has an Event as parameter. Ex: ClickEvent in
+ // onClick, BlurEvent in onBlur ...
+ JParameter[] parameters = methods[0].getParameters();
+ if (parameters.length != 1 || parameters[0].getType() != eventType) {
+ die("Method '%s' needs '%s' as parameter", methods[0].getName(), eventType.getName());
+ }
+
+ writer.newline();
+ writer.write("final %1$s %2$s = new %1$s() {",
+ handlerType.getParameterizedQualifiedSourceName(), handlerVarName);
+ writer.indent();
+ writer.write("public void %1$s(%2$s event) {",
+ methods[0].getName(), eventType.getParameterizedQualifiedSourceName());
+ writer.indent();
+ writer.write("%1$s.%2$s(event);", uiOwner, boundMethod);
+ writer.outdent();
+ writer.write("}");
+ writer.outdent();
+ writer.write("};");
+ }
+
+ /**
+ * Adds the created handler to the given object (field).
+ *
+ * @param writer the writer used to output the results
+ * @param handlerVarName the name of the handler variable
+ * @param addHandlerMethodName the "add handler" method name associated
+ * with 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("this.%1$s.%2$s(%3$s);", objectName,
+ addHandlerMethodName, handlerVarName);
+ }
+
+ /**
+ * Checks if a specific handler is valid for a given object and return the
+ * method that ties them. The object must override a method that returns
+ * {@link com.google.gwt.event.shared.HandlerRegistration} and receives a
+ * single input parameter of the same type of handlerType.
+ *
+ * <p>Output an error in case more than one method match the conditions
+ * described above.</p>
+ *
+ * <pre>
+ * <b>Examples:</b>
+ * - HandlerRegistration addClickHandler(ClickHandler handler)
+ * - HandlerRegistration addMouseOverHandler(MouseOverHandler handler)
+ * - HandlerRegistration addSubmitCompleteHandler(
+ * FormPanel.SubmitCompleteHandler handler)
+ * </pre>
+ *
+ * @param objectType the object type we want to check
+ * @param handlerType the handler type we want to check in the object
+ *
+ * @return the method that adds handlerType into objectType, or <b>null</b>
+ * if no method was found
+ */
+ private JMethod getAddHandlerMethodForObject(JClassType objectType,
+ JClassType handlerType) throws UnableToCompleteException {
+ JMethod handlerMethod = null;
+ for (JMethod method : objectType.getOverridableMethods()) {
+
+ // Condition 1: returns HandlerRegistration?
+ if (method.getReturnType() == handlerRegistrationJClass) {
+
+ // Condition 2: single parameter of the same type of handlerType?
+ JParameter[] parameters = method.getParameters();
+ if ((parameters.length == 1) && handlerType.equals(parameters[0].getType())) {
+
+ // Condition 3: does more than one method match the condition?
+ if (handlerMethod != null) {
+ die("This handler cannot be generated. Methods '%s' and '%s' are "
+ + "ambiguous. Which one to pick?", method, handlerMethod);
+ }
+
+ handlerMethod = method;
+ }
+ }
+ }
+ return handlerMethod;
+ }
+
+ /**
+ * Retrieves the handler associated with the event.
+ *
+ * @param eventType the given event
+ * @return the associated handler, <code>null</code> if not found
+ */
+ private JClassType getHandlerForEvent(JClassType eventType) {
+
+ // All handlers event must have an overrided method getAssociatedType().
+ // We take advantage of this information to get the associated handler.
+ // Ex:
+ // com.google.gwt.event.dom.client.ClickEvent
+ // ---> com.google.gwt.event.dom.client.ClickHandler
+ //
+ // com.google.gwt.event.dom.client.BlurEvent
+ // ---> com.google.gwt.event.dom.client.BlurHandler
+
+ if (eventType == null) {
+ return null;
+ }
+
+ JMethod method = eventType.findMethod("getAssociatedType", new JType[0]);
+ if (method == null) {
+ warn("Method 'getAssociatedType()' could not be found in the event '%s'.",
+ eventType.getName());
+ return null;
+ }
+
+ JType returnType = method.getReturnType();
+ if (returnType == null) {
+ warn("The method 'getAssociatedType()' in the event '%s' returns void.",
+ eventType.getName());
+ return null;
+ }
+
+ JParameterizedType isParameterized = returnType.isParameterized();
+ if (isParameterized == null) {
+ warn("The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
+ eventType.getName());
+ return null;
+ }
+
+ JClassType[] argTypes = isParameterized.getTypeArgs();
+ if ((argTypes.length != 1) && !argTypes[0].isAssignableTo(eventHandlerJClass)) {
+ warn("The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
+ eventType.getName());
+ return null;
+ }
+
+ return argTypes[0];
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/IndentedWriter.java b/user/src/com/google/gwt/uibinder/rebind/IndentedWriter.java
new file mode 100644
index 0000000..08ce64f
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/IndentedWriter.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import java.io.PrintWriter;
+
+/**
+ * Pleasant wrapper for PrintWriter, manages indentation levels.
+ * Name is a misnomer, as this doesn't implement Writer.
+ */
+public class IndentedWriter {
+ private final PrintWriter pw;
+
+ private int indent;
+
+ public IndentedWriter(PrintWriter pw) {
+ super();
+ this.pw = pw;
+ }
+
+ /**
+ * Indents the generated code.
+ */
+ public void indent() {
+ ++indent;
+ }
+
+ /**
+ * Outputs a new line.
+ */
+ public void newline() {
+ pw.println();
+ }
+
+ /**
+ * Un-indents the generated code.
+ */
+ public void outdent() {
+ if (indent == 0) {
+ throw new IllegalStateException("Tried to outdent below zero");
+ }
+ --indent;
+ }
+
+ /**
+ * Outputs the given string.
+ */
+ public void write(String format) {
+ printIndent();
+ pw.println(format);
+ }
+
+ /**
+ * Outputs the given string with replacements, using the Java message format.
+ */
+ public void write(String format, Object... args) {
+ printIndent();
+ pw.printf(format, args);
+ pw.println();
+ }
+
+ private void printIndent() {
+ for (int i = 0; i < indent; ++i) {
+ pw.print(" ");
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/NodeVisitor.java b/user/src/com/google/gwt/uibinder/rebind/NodeVisitor.java
new file mode 100644
index 0000000..6ee212a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/NodeVisitor.java
@@ -0,0 +1,34 @@
+/*
+ * 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
+ * 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 org.w3c.dom.CDATASection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+
+/**
+ * Implements methods to interpret the {@link org.w3c.dom.Node} types
+ * we actually care about.
+ */
+interface NodeVisitor {
+ void visitCData(CDATASection d) throws UnableToCompleteException;
+
+ void visitElement(Element e) throws UnableToCompleteException;
+
+ void visitText(Text t) throws UnableToCompleteException;
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/Tokenator.java b/user/src/com/google/gwt/uibinder/rebind/Tokenator.java
new file mode 100644
index 0000000..60824bc
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/Tokenator.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Methods to dispense unique text tokens to be stitched into text, and
+ * to help replace the tokens with arbitrary content.
+ */
+public class Tokenator {
+ /**
+ * Resolves a token to its literal value.
+ */
+ public interface Resolver {
+ String resolveToken(String token);
+ }
+
+ private static final String TOKEN = "--token--";
+ private static final String TOKEN_REGEXP = "\\-\\-token\\-\\-";
+ private static int curId = 0;
+
+ public static String detokenate(String betokened, Resolver resolver) {
+ StringBuilder detokenated = new StringBuilder();
+
+ int index = 0, nextToken = 0;
+ while ((nextToken = betokened.indexOf(TOKEN, index)) > -1) {
+ detokenated.append(betokened.substring(index, nextToken));
+
+ int endToken = betokened.indexOf(TOKEN, nextToken + TOKEN.length());
+ String token = betokened.substring(nextToken, endToken + TOKEN.length());
+ detokenated.append(resolver.resolveToken(token));
+
+ index = endToken + TOKEN.length();
+ }
+
+ detokenated.append(betokened.substring(index));
+ return detokenated.toString();
+ }
+
+ public static boolean hasToken(String s) {
+ return s.matches(".*" + TOKEN_REGEXP + "\\d+" + TOKEN_REGEXP + ".*");
+ }
+
+ private static String nextToken() {
+ return TOKEN + (curId++) + TOKEN;
+ }
+
+ private Map<String, String> tokenToResolved =
+ new HashMap<String, String>();
+
+ public String detokenate(String betokened) {
+ return detokenate(betokened, new Resolver() {
+ public String resolveToken(String token) {
+ return tokenToResolved.get(token);
+ }
+ });
+ }
+
+ public String nextToken(final String resolved) {
+ String token = nextToken();
+ tokenToResolved.put(token, resolved);
+ return token;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
new file mode 100644
index 0000000..6b1dd49
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -0,0 +1,189 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+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.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Enumeration;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Generator for implementations of
+ * {@link com.google.gwt.uibinder.client.UiBinder}.
+ */
+public class UiBinderGenerator extends Generator {
+
+ private static final String TEMPLATE_SUFFIX = ".ui.xml";
+
+ private static void commit(TreeLogger logger, GeneratorContext genCtx,
+ String packageName, PrintWriter pw, UiBinderWriter uiWriter) {
+ uiWriter.write(pw);
+ genCtx.commit(logger, pw);
+
+ MessagesWriter messages = uiWriter.getMessages();
+ if (messages.hasMessages()) {
+ PrintWriter mpw = genCtx.tryCreate(logger, packageName,
+ messages.getMessagesClassName());
+ if (mpw == null) {
+ throw new RuntimeException("Tried to gen messages twice.");
+ }
+
+ messages.write(mpw);
+ genCtx.commit(logger, mpw);
+ }
+ }
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext genCtx,
+ String fqInterfaceName) throws UnableToCompleteException {
+ TypeOracle oracle = genCtx.getTypeOracle();
+ JClassType interfaceType;
+ try {
+ interfaceType = oracle.getType(fqInterfaceName);
+ } catch (NotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ String implName = interfaceType.getName();
+ implName = implName.replace('.', '_') + "Impl";
+
+ String packageName = interfaceType.getPackage().getName();
+ PrintWriter printWriter = genCtx.tryCreate(logger, packageName, implName);
+
+ if (printWriter != null) {
+ String templateName = deduceTemplateFile(logger, interfaceType);
+
+ Document document;
+ try {
+ document = parseXmlResource(logger, templateName);
+ } catch (SAXParseException e) {
+ logger.log(TreeLogger.ERROR, "Error parsing XML (line "
+ + e.getLineNumber() + "): " + e.getMessage(), e);
+ throw new UnableToCompleteException();
+ }
+
+ UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType,
+ implName, templateName, oracle, logger);
+ uiBinderWriter.parseDocument(document);
+ commit(logger, genCtx, packageName, printWriter, uiBinderWriter);
+ }
+ return packageName + "." + implName;
+ }
+
+ /**
+ * Given a UiBinder interface, return the path to its ui.xml file, suitable
+ * for any classloader to find it as a resource.
+ */
+ private String deduceTemplateFile(TreeLogger logger, JClassType interfaceType)
+ throws UnableToCompleteException {
+ String templateName = null;
+ UiTemplate annotation = interfaceType.getAnnotation(UiTemplate.class);
+ if (annotation == null) {
+ // if the interface is defined as a nested class, use the name of the
+ // enclosing type
+ if (interfaceType.getEnclosingType() != null) {
+ interfaceType = interfaceType.getEnclosingType();
+ }
+ return slashify(interfaceType.getQualifiedSourceName()) + TEMPLATE_SUFFIX;
+ } else {
+ templateName = annotation.value();
+ if (!templateName.endsWith(TEMPLATE_SUFFIX)) {
+ logger.log(TreeLogger.ERROR, "Template file name must end with "
+ + TEMPLATE_SUFFIX);
+ throw new UnableToCompleteException();
+ }
+
+ /*
+ * If the template file name (minus suffix) has no dots, make it relative
+ * to the binder's package, otherwise slashify the dots
+ */
+ String unsuffixed = templateName.substring(0,
+ templateName.lastIndexOf(TEMPLATE_SUFFIX));
+ if (!unsuffixed.contains(".")) {
+ templateName = slashify(interfaceType.getPackage().getName()) + "/"
+ + templateName;
+ } else {
+ templateName = slashify(unsuffixed) + TEMPLATE_SUFFIX;
+ }
+ }
+ return templateName;
+ }
+
+ private Document parseXmlResource(TreeLogger logger, final String resourcePath)
+ throws SAXParseException, UnableToCompleteException {
+ // Get the document builder. We need namespaces, and automatic expanding
+ // of entity references (the latter of which makes life somewhat easier
+ // for XMLElement).
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setExpandEntityReferences(true);
+ DocumentBuilder builder;
+ try {
+ builder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ ClassLoader classLoader = UiBinderGenerator.class.getClassLoader();
+ Enumeration<URL> urls = classLoader.getResources(resourcePath);
+ if (!urls.hasMoreElements()) {
+ logger.log(TreeLogger.ERROR, "Unable to find resource: " + resourcePath);
+ throw new UnableToCompleteException();
+ }
+ URL url = urls.nextElement();
+
+ InputStream stream = url.openStream();
+ InputSource input = new InputSource(stream);
+ input.setSystemId(url.toExternalForm());
+
+ builder.setEntityResolver(new GwtResourceEntityResolver());
+
+ return builder.parse(input);
+ } catch (SAXParseException e) {
+ // Let SAXParseExceptions through.
+ throw e;
+ } catch (SAXException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String slashify(String s) {
+ return s.replace(".", "/");
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
new file mode 100644
index 0000000..723db3e
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -0,0 +1,1234 @@
+/*
+ * 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
+ * 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.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dom.client.TagName;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.parsers.AttributeMessageParser;
+import com.google.gwt.uibinder.parsers.AttributeParser;
+import com.google.gwt.uibinder.parsers.BeanParser;
+import com.google.gwt.uibinder.parsers.BundleAttributeParser;
+import com.google.gwt.uibinder.parsers.ElementParser;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+import com.google.gwt.uibinder.rebind.model.OwnerField;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Writer for UiBinder generated classes.
+ *
+ * TODO(rdamazio): Refactor this, extract model classes, improve ordering
+ * guarantees, etc.
+ * TODO(rjrjr): Improve error messages
+ */
+public class UiBinderWriter {
+ private static final String GWT_URI = "urn:ui:com.google.gwt.uibinder";
+ private static final String BUNDLE_URI_SCHEME = "urn:with:";
+ private static final String PACKAGE_URI_SCHEME = "urn:import:";
+
+ private static int domId = 0;
+
+ public static String asCommaSeparatedList(String... args) {
+ StringBuilder b = new StringBuilder();
+ for (String arg : args) {
+ if (b.length() > 0) {
+ b.append(", ");
+ }
+ b.append(arg);
+ }
+
+ return b.toString();
+ }
+
+ /**
+ * Escape text that will be part of a string literal to be interpreted at
+ * runtime as an HTML attribute value.
+ */
+ public static String escapeAttributeText(String text) {
+ text = escapeText(text, false);
+
+ /*
+ * Escape single-quotes to make them safe to be interpreted at runtime as an
+ * HTML attribute value (for which we by convention use single quotes).
+ */
+ text = text.replaceAll("'", "'");
+ return text;
+ }
+
+ /**
+ * Escape text that will be part of a string literal to be interpreted at
+ * runtime as HTML, optionally preserving whitespace.
+ */
+ public static String escapeText(String text, boolean preserveWhitespace) {
+ // Replace reserved XML characters with entities. Note that we *don't*
+ // replace single- or double-quotes here, because they're safe in text
+ // nodes.
+ text = text.replaceAll("&", "&");
+ text = text.replaceAll("<", "<");
+ text = text.replaceAll(">", ">");
+
+ if (!preserveWhitespace) {
+ text = text.replaceAll("\\s+", " ");
+ }
+
+ return escapeTextForJavaStringLiteral(text);
+ }
+ /**
+ * Escape characters that would mess up interpretation of this string as a
+ * string literal in generated code (that is, protect \n and " ).
+ */
+ public static String escapeTextForJavaStringLiteral(String text) {
+ text = text.replaceAll("\"", "\\\\\"");
+ text = text.replaceAll("\n", "\\\\n");
+
+ return text;
+ }
+
+ private static String capitalizePropName(String propName) {
+ return propName.substring(0, 1).toUpperCase() + propName.substring(1);
+ }
+ private static AttributeParser getAttributeParserByClassName(
+ String parserClassName) {
+ try {
+ Class<? extends AttributeParser> parserClass =
+ Class.forName(parserClassName).asSubclass(AttributeParser.class);
+ return parserClass.newInstance();
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Unable to instantiate parser", e);
+ } catch (ClassCastException e) {
+ throw new RuntimeException(parserClassName
+ + " must extend AttributeParser");
+ } catch (InstantiationException e) {
+ throw new RuntimeException("Unable to instantiate parser", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Unable to instantiate parser", e);
+ }
+ }
+ /**
+ * Returns a list of the given type and all its superclasses and implemented
+ * interfaces in a breadth-first traversal.
+ *
+ * @param type the base type
+ * @return a breadth-first collection of its type hierarchy
+ */
+ private static Iterable<JClassType> getClassHierarchyBreadthFirst(
+ JClassType type) {
+ LinkedList<JClassType> list = new LinkedList<JClassType>();
+ LinkedList<JClassType> q = new LinkedList<JClassType>();
+
+ q.add(type);
+ while (!q.isEmpty()) {
+ // Pop the front of the queue and add it to the result list.
+ JClassType curType = q.removeFirst();
+ list.add(curType);
+
+ // Add the superclass and implemented interfaces to the back of the queue.
+ JClassType superClass = curType.getSuperclass();
+ if (superClass != null) {
+ q.add(superClass);
+ }
+ for (JClassType intf : curType.getImplementedInterfaces()) {
+ q.add(intf);
+ }
+ }
+
+ return list;
+ }
+
+ private static String getFullTypeName(JClassType type) {
+ StringBuilder b = new StringBuilder(type.getPackage().getName());
+ if (b.length() > 0) {
+ b.append(".");
+ }
+ b.append(type.getName());
+ return b.toString();
+ }
+ /**
+ * Class names of parsers for values of attributes with no namespace prefix,
+ * keyed by method parameter signatures.
+ *
+ * TODO(rjrjr) Seems like the attribute parsers belong in BeanParser, which is
+ * the only thing that uses them.
+ */
+ private final Map<String, String> attributeParsers =
+ new HashMap<String, String>();
+
+ /**
+ * Class names of parsers for various ui types, keyed by the classname of the
+ * UI class they can build.
+ */
+ private final Map<String, String> elementParsers =
+ new HashMap<String, String>();
+ /**
+ * Map of bundle parsers, keyed by bundle class name.
+ */
+ private final Map<String, BundleAttributeParser> bundleParsers =
+ new HashMap<String, BundleAttributeParser>();
+
+ private final List<String> initStatements = new ArrayList<String>();
+
+ private final List<String> statements = new ArrayList<String>();
+ private final HandlerEvaluator handlerEvaluator;
+ private final MessagesWriter messages;
+ private final Tokenator tokenator = new Tokenator();
+ private final TreeLogger logger;
+
+ private final String templatePath;
+ private final TypeOracle oracle;
+ /**
+ * The type we have been asked to generated, e.g. MyUiBinder
+ */
+ private final JClassType baseClass;
+ /**
+ * The name of the class we're creating, e.g. MyUiBinderImpl
+ */
+ private final String implClassName;
+ private final JClassType uiOwnerType;
+
+ private final JClassType uiRootType;
+
+ private final OwnerClass ownerClass;
+
+ private final FieldManager fieldManager;
+
+ private int fieldIndex;
+
+ private String gwtPrefix;
+
+ private String rendered;
+
+ private String rootDeclaration;
+
+ UiBinderWriter(JClassType baseClass, String implClassName,
+ String templatePath, TypeOracle oracle, TreeLogger logger)
+ throws UnableToCompleteException {
+ this.baseClass = baseClass;
+ this.implClassName = implClassName;
+ this.oracle = oracle;
+ this.logger = logger;
+ this.templatePath = templatePath;
+ this.messages = createMessagesWriter(templatePath, logger);
+
+ JClassType uiBinderType = baseClass.getImplementedInterfaces()[0];
+ JClassType[] typeArgs = uiBinderType.isParameterized().getTypeArgs();
+ uiRootType = typeArgs[0];
+ uiOwnerType = typeArgs[1];
+
+ ownerClass = new OwnerClass(uiOwnerType);
+ handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
+ fieldManager = new FieldManager(oracle, logger);
+ }
+
+ /**
+ * Add a statement to be run after everything has been instantiated.
+ */
+ public void addInitStatement(String format, Object... params) {
+ initStatements.add(String.format(format, params));
+ }
+
+ // TODO(rjrjr) These declare* methods should be returning FieldWriter
+ // instances, not strings.
+
+ /**
+ * Adds a statement to the block run after fields are declared.
+ */
+ public void addStatement(String statement) {
+ statements.add(statement);
+ }
+
+ /**
+ * Adds a statement to the block run after fields are declared, using the Java
+ * message format.
+ */
+ public void addStatement(String format, Object... args) {
+ statements.add(String.format(format, args));
+ }
+
+ /**
+ * Declare a field that will hold an Element instance. Returns a token that
+ * the caller must set as the id attribute of that element in whatever
+ * innerHTML expression will reproduce it at runtime.
+ * <P>
+ * In the generated code, this token will be replaced by an expression to
+ * generate a unique dom id at runtime. Further code will be generated to be
+ * run after widgets are instantiated, to use that dom id in a getElementById
+ * call and assign the Element instance to its field.
+ *
+ * @param fieldName The name of the field being declared
+ * @param parentElementExpression an expression to be evaluated at runtime,
+ * which will return an Element that is an ancestor of this one
+ * (needed by the getElementById call mentioned above).
+ */
+ public String declareDomField(String fieldName,
+ String parentElementExpression) throws UnableToCompleteException {
+ String name = declareDomIdHolder();
+ setFieldInitializer(fieldName, "null");
+ addInitStatement(
+ "%s = UiBinderUtil.attachToDomAndGetChild(%s, %s).cast();", fieldName,
+ parentElementExpression, name);
+ addInitStatement("%s.removeAttribute(\"id\");", fieldName);
+ return tokenForExpression(name);
+ }
+
+ /**
+ * Declare a variable that will be filled at runtime with a unique id, safe
+ * for use as a dom element's id attribute.
+ *
+ * @return that variable's name.
+ */
+ public String declareDomIdHolder() throws UnableToCompleteException {
+ String domHolderName = "domId" + domId++;
+ FieldWriter domField = fieldManager.registerField(
+ "java.lang.String", domHolderName);
+ domField.setInitializer(
+ "com.google.gwt.dom.client.Document.get().createUniqueId()");
+ return domHolderName;
+ }
+
+ /**
+ * Declares a field of the given type name, returning the name of the declared
+ * field. If the element has a field or id attribute, use its value.
+ * Otherwise, create and return a new, private field name for it.
+ */
+ public String declareField(String typeName, XMLElement elem)
+ throws UnableToCompleteException {
+ String fieldName = getFieldName(elem);
+ if (fieldName == null) {
+ fieldName = ("f_" + elem.getLocalName() + (++fieldIndex));
+ }
+ fieldManager.registerField(typeName, fieldName);
+ return fieldName;
+ }
+
+ /**
+ * If this element has a gwt:field attribute, create a field for it of the
+ * appropriate type, and return the field name. If no gwt:field attribute is
+ * found, do nothing and return null
+ *
+ * @return The new field name, or null if no field is created
+ */
+ public String declareFieldIfNeeded(XMLElement elem)
+ throws UnableToCompleteException {
+ String fieldName = getFieldName(elem);
+ if (fieldName != null) {
+ String typeName = findWidgetOrElementType(elem).getQualifiedSourceName();
+ fieldManager.registerField(typeName, fieldName);
+ }
+ return fieldName;
+ }
+
+ /**
+ * Given a string containing tokens returned by {@link #tokenForExpression} 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) {
+ return tokenator.detokenate(betokened);
+ }
+
+ /**
+ * Post an error message and halt processing. This method always throws an
+ * {@link UnableToCompleteException}
+ */
+ public void die(String message) throws UnableToCompleteException {
+ logger.log(TreeLogger.ERROR, message);
+ throw new UnableToCompleteException();
+ }
+
+ /**
+ * Post an error message and halt processing. This method always throws an
+ * {@link UnableToCompleteException}
+ */
+ public void die(String message, Object... params)
+ throws UnableToCompleteException {
+ logger.log(TreeLogger.ERROR, String.format(message, params));
+ throw new UnableToCompleteException();
+ }
+
+ /**
+ * Finds the JClassType that corresponds to this XMLElement, which must be a
+ * Widget or an Element.
+ *
+ * @throws UnableToCompleteException If no such widget class exists
+ * @throws RuntimeException if asked to handle a non-widget, non-DOM element
+ */
+ public JClassType findWidgetOrElementType(XMLElement elem)
+ throws UnableToCompleteException {
+ String tagName = elem.getLocalName();
+
+ if (!isWidget(elem)) {
+ return findElementTypeForTag(tagName);
+ }
+
+ String ns = elem.getNamespaceUri();
+
+ JPackage pkg = parseNamespacePackage(ns);
+ if (pkg == null) {
+ throw new RuntimeException("No such package: " + ns);
+ }
+
+ JClassType rtn = null;
+ if (pkg != null) {
+ rtn = pkg.findType(tagName);
+ if (rtn == null) {
+ die("No class matching \"%s\" in %s",tagName, ns);
+ }
+ }
+
+ return rtn;
+ }
+
+ /**
+ * Generates the code to set a property value (assumes that 'value' is a valid
+ * Java expression).
+ */
+ public void genPropertySet(String fieldName, String propName, String value) {
+ addStatement("%1$s.set%2$s(%3$s);", fieldName,
+ capitalizePropName(propName), value);
+ }
+
+ /**
+ * Provide the expression to generate only the root element of the UI. If this
+ * is called, the code to create the bulk of the ui will be in the
+ * {@link UiBinder#bindUi} method. If it is not called, all code to create the
+ * ui will be in the {@link UiBinder#createUiRoot} method.
+ */
+ public void genRoot(String initString) {
+ rootDeclaration = initString;
+ }
+
+ /**
+ * Generates the code to set a string property.
+ */
+ public void genStringPropertySet(String fieldName, String propName,
+ String value) {
+ genPropertySet(fieldName, propName, "\"" + value + "\"");
+ }
+
+ /**
+ * Find and return an appropriate attribute parser for a set of parameters, or
+ * return null.
+ */
+ public AttributeParser getAttributeParser(JParameter... params) {
+ String paramTypeNames = getParametersKey(params);
+ String parserClassName = attributeParsers.get(paramTypeNames);
+
+ if (parserClassName != null) {
+ return getAttributeParserByClassName(parserClassName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds an attribute parser for the given xml attribute.
+ */
+ public AttributeParser getAttributeParser(XMLAttribute attribute)
+ throws UnableToCompleteException {
+ if (attribute.getNamespaceUri() == null) {
+ return null;
+ }
+
+ String attributePrefixUri = attribute.getNamespaceUri();
+ if (!attributePrefixUri.startsWith(BUNDLE_URI_SCHEME)) {
+ return null;
+ }
+
+ String bundleClassName =
+ attributePrefixUri.substring(BUNDLE_URI_SCHEME.length());
+ BundleAttributeParser parser = bundleParsers.get(bundleClassName);
+ if (parser == null) {
+ JClassType bundleClass = getOracle().findType(bundleClassName);
+ if (bundleClass == null) {
+ die("No such bundle class: " + bundleClassName);
+ }
+ parser = createBundleParser(bundleClass, attribute);
+
+ bundleParsers.put(bundleClassName, parser);
+ }
+
+ return parser;
+ }
+
+ /**
+ * Find and return an appropriate attribute parser for an attribute and set of
+ * parameters, or return null.
+ */
+ public AttributeParser getAttributeParser(XMLAttribute attribute,
+ JParameter... params) throws UnableToCompleteException {
+ AttributeParser parser = getAttributeParser(attribute);
+ if (parser == null) {
+ parser = getAttributeParser(params);
+ }
+ return parser;
+ }
+
+ public String getGwtFieldAttributeName() {
+ return gwtPrefix + ":field";
+ }
+
+ /**
+ * Get the {@link MessagesWriter} for this UI, generating it if necessary.
+ */
+ public MessagesWriter getMessages() {
+ return messages;
+ }
+
+ /**
+ * Gets the type oracle.
+ */
+ public TypeOracle getOracle() {
+ return oracle;
+ }
+
+ public OwnerClass getOwnerClass() {
+ return ownerClass;
+ }
+
+ public boolean isWidget(XMLElement elem) {
+ String uri = elem.getNamespaceUri();
+ return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);
+ }
+
+ /**
+ * Parses the widget associated with the specified element, and returns the
+ * name of the field (possibly private) that will hold the object it
+ * describes.
+ *
+ * @param elem the xml element to be parsed
+ * @return the name of the field containing the parsed widget
+ */
+ public String parseWidget(XMLElement elem) throws UnableToCompleteException {
+ if (elementParsers.isEmpty()) {
+ registerParsers();
+ }
+
+ // Get the class associated with this element.
+ JClassType type = findWidgetOrElementType(elem);
+
+ // Declare its field.
+ String fieldName = declareField(type.getQualifiedSourceName(), elem);
+
+ FieldWriter field = fieldManager.lookup(fieldName);
+
+ // Push the field that will hold this widget to the top of parsedFieldStack
+ // to ensure that fields registered by its parsers will be noted as
+ // dependencies of the new widget. See registerField.
+ fieldManager.push(field);
+
+ // Give all the parsers a chance to generate their code.
+ for (ElementParser parser : getParsersForClass(type)) {
+ parser.parse(elem, fieldName, type, this);
+ }
+ fieldManager.pop();
+ return fieldName;
+ }
+
+ /**
+ * Gives the writer the initializer to use for this field instead of the
+ * default GWT.create call.
+ *
+ * @throws IllegalStateException if an initializer has already been set
+ */
+ public void setFieldInitializer(String fieldName, String factoryMethod) {
+ fieldManager.lookup(fieldName).setInitializer(factoryMethod);
+ }
+
+ /**
+ * Instructs the writer to initialize the field with a specific
+ * contructor invocaction, instead of the default GWT.create call.
+ */
+ public void setFieldInitializerAsConstructor(String fieldName,
+ JClassType type, String... args) {
+ setFieldInitializer(fieldName,
+ String.format("new %s(%s)", getFullTypeName(type),
+ asCommaSeparatedList(args)));
+ }
+
+ /**
+ * Returns a string token that can be used in place the given expression
+ * inside any string literals. Before the generated code is written, the
+ * expression will be stiched back into the generated code in place of the
+ * token, surrounded by plus signs. This is useful in strings to be handed to
+ * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
+ * other runtime expression in the string.
+ *
+ * @param expression
+ */
+ public String tokenForExpression(String expression) {
+ return tokenator.nextToken(("\" + " + expression + " + \""));
+ }
+
+ /**
+ * Post a warning message.
+ */
+ public void warn(String message) {
+ logger.log(TreeLogger.WARN, message);
+ }
+
+ /**
+ * Post a warning message.
+ */
+ public void warn(String message, Object... params) {
+ logger.log(TreeLogger.WARN, String.format(message, params));
+ }
+
+ /**
+ * Entry point for the code generation logic. It generates the
+ * implementation's superstructure, and parses the root widget (leading to all
+ * of its children being parsed as well).
+ */
+ void parseDocument(Document doc) throws UnableToCompleteException {
+ Element documentElement = doc.getDocumentElement();
+ this.rendered = tokenator.detokenate(parseElement(documentElement));
+ }
+
+ void write(PrintWriter printWriter) {
+ printWriter.print(rendered);
+ }
+
+ private void addAttributeParser(String signature, String className) {
+ attributeParsers.put(signature, className);
+ }
+
+ private void addParser(String gwtClass, String parser) {
+ elementParsers.put(gwtClass, parser);
+ }
+
+ private void addWidgetParser(String className) {
+ String gwtClass = "com.google.gwt.user.client.ui." + className;
+ String parser = "com.google.gwt.uibinder.parsers." + className + "Parser";
+ addParser(gwtClass, parser);
+ }
+
+ /**
+ * Creates a parser for the given bundle class.
+ */
+ private BundleAttributeParser createBundleParser(JClassType bundleClass,
+ XMLAttribute attribute) throws UnableToCompleteException {
+
+ // Try to find any bundle instance created with @UiFactory.
+ JMethod method = getOwnerClass().getUiFactoryMethod(bundleClass);
+ if (method != null) {
+ return new BundleAttributeParser(bundleClass, "owner." + method.getName()
+ + "()", false);
+ }
+
+ // Try to find any bundle instance created with UiField.
+ OwnerField field = getOwnerClass().getUiFieldForType(bundleClass);
+ if (field != null) {
+ String[] tmp = attribute.getName().split(":");
+ String templateName = tmp[0];
+ if (!templateName.equals(field.getName())) {
+ die("Template %s has no \"xmlns:%s='urn:with:%s'\" for %s.%s#%s",
+ templatePath,
+ field.getName(),
+ bundleClass.getQualifiedSourceName(),
+ uiOwnerType.getPackage().getName(),
+ uiOwnerType.getName(),
+ field.getName());
+ }
+
+ if (field.isProvided()) {
+ return new BundleAttributeParser(bundleClass,
+ "owner." + field.getName(), false);
+ }
+ }
+
+ return new BundleAttributeParser(bundleClass, "my"
+ + bundleClass.getName().replace('.', '_') + "Instance", true);
+ }
+
+ private MessagesWriter createMessagesWriter(String templatePath,
+ TreeLogger logger) {
+ return new MessagesWriter(GWT_URI, logger, templatePath, getPackageName(),
+ this.implClassName);
+ }
+
+ /**
+ * Outputs a bundle resource for a given bundle attribute parser.
+ */
+ private String declareStaticField(BundleAttributeParser parser) {
+ if (!parser.isBundleStatic()) {
+ return null;
+ }
+
+ String fullBundleClassName = parser.fullBundleClassName();
+
+ StringBuilder b = new StringBuilder();
+ b.append("static ").append(fullBundleClassName).append(" ").append(
+ parser.bundleInstance()).append(" = GWT.create(").append(
+ fullBundleClassName).append(".class);");
+
+ return b.toString();
+ }
+
+ /**
+ * Given a DOM tag name, return the corresponding Element subclass.
+ */
+ private JClassType findElementTypeForTag(String tag) {
+ JClassType elementClass =
+ oracle.findType("com.google.gwt.dom.client.Element");
+ JClassType[] types = elementClass.getSubtypes();
+ for (JClassType type : types) {
+ TagName annotation = type.getAnnotation(TagName.class);
+ if (annotation != null) {
+ for (String annotationTag : annotation.value()) {
+ if (annotationTag.equals(tag)) {
+ return type;
+ }
+ }
+ }
+ }
+
+ return elementClass;
+ }
+
+ /**
+ * Inspects this element for a gwt:field attribute. If one is found, the
+ * attribute is consumed and its value returned.
+ *
+ * @return The field name declared by an element, or null if none is declared
+ */
+ private String getFieldName(XMLElement elem)
+ throws UnableToCompleteException {
+ String fieldName = null;
+ boolean hasOldSchoolId = false;
+ if (elem.hasAttribute("id") && isWidget(elem)) {
+ hasOldSchoolId = true;
+ // If an id is specified on the element, use that.
+ fieldName = elem.consumeAttribute("id");
+ logger.log(TreeLogger.WARN, String.format(
+ "Deprecated use of id=\"%1$s\" for field name. "
+ + "Please switch to gwt:field=\"%1$s\" instead. "
+ + "This will soon be a compile error!", fieldName));
+ }
+ if (elem.hasAttribute(getGwtFieldAttributeName())) {
+ if (hasOldSchoolId) {
+ die("Cannot declare both id and field on the same element: " + elem);
+ }
+ fieldName = elem.consumeAttribute(getGwtFieldAttributeName());
+ }
+ return fieldName;
+ }
+
+ private String getPackageName() {
+ return baseClass.getPackage().getName();
+ }
+
+ /**
+ * Given a parameter array, return a key for the attributeParsers table.
+ */
+ private String getParametersKey(JParameter[] params) {
+ String paramTypeNames = "";
+ for (int i = 0; i < params.length; ++i) {
+ paramTypeNames +=
+ params[i].getType().getParameterizedQualifiedSourceName();
+ if (i != params.length - 1) {
+ paramTypeNames += ",";
+ }
+ }
+ return paramTypeNames;
+ }
+
+ private Class<? extends ElementParser> getParserForClass(JClassType uiClass) {
+ // Find the associated parser.
+ String uiClassName = uiClass.getQualifiedSourceName();
+ String parserClassName = elementParsers.get(uiClassName);
+ if (parserClassName == null) {
+ return null;
+ }
+
+ // And instantiate it.
+ try {
+ return Class.forName(parserClassName).asSubclass(ElementParser.class);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Unable to instantiate parser", e);
+ } catch (ClassCastException e) {
+ throw new RuntimeException(
+ parserClassName + " must extend ElementParser");
+ }
+ }
+
+ /**
+ * Find a set of element parsers for the given ui type.
+ *
+ * The list of parsers will be returned in order from most- to least-specific.
+ */
+ private Iterable<ElementParser> getParsersForClass(JClassType type) {
+ List<ElementParser> parsers = new ArrayList<ElementParser>();
+
+ /*
+ * Let this non-widget parser go first (it finds <m:attribute/> elements).
+ * Any other such should land here too.
+ *
+ * TODO(rjrjr) Need a scheme to associate these with a namespace uri or
+ * something?
+ */
+ parsers.add(new AttributeMessageParser());
+
+ for (JClassType curType : getClassHierarchyBreadthFirst(type)) {
+ try {
+ Class<? extends ElementParser> cls = getParserForClass(curType);
+ if (cls != null) {
+ ElementParser parser = cls.newInstance();
+ parsers.add(parser);
+ }
+ } catch (InstantiationException e) {
+ throw new RuntimeException(
+ "Unable to instantiate " + curType.getName(), e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "Unable to instantiate " + curType.getName(), e);
+ }
+ }
+
+ parsers.add(new BeanParser());
+
+ return parsers;
+ }
+
+ private String instanceBinderType() {
+ return String.format("AbstractUiBinder.InstanceBinder<%s, %s>",
+ uiRootType.getName(), uiOwnerType.getName());
+ }
+
+ /**
+ * Writes a field setter if the field is not provided and the field class
+ * is compatible with its respective template field.
+ */
+ private void maybeWriteFieldSetter(IndentedWriter niceWriter,
+ OwnerField ownerField, JClassType templateClass, String templateField)
+ throws UnableToCompleteException {
+ JClassType fieldType = ownerField.getType().getRawType();
+
+ if (!templateClass.isAssignableTo(fieldType)) {
+ die("Template field and owner field types don't match: %s != %s",
+ templateClass.getQualifiedSourceName(),
+ fieldType.getQualifiedSourceName());
+ }
+
+ if (!ownerField.isProvided()) {
+ niceWriter.write("owner.%1$s = %2$s;",
+ ownerField.getName(), templateField);
+ }
+ }
+
+ private String parseElement(Element documentElement)
+ throws UnableToCompleteException {
+ gwtPrefix = documentElement.lookupPrefix(GWT_URI);
+
+ JClassType uiBinderClass = getOracle().findType(UiBinder.class.getName());
+ if (!baseClass.isAssignableTo(uiBinderClass)) {
+ die(baseClass.getName() + " must implement UiBinder");
+ }
+
+ XMLElement elem = new XMLElement(documentElement, this);
+ messages.findMessagesConfig(elem);
+ elem = elem.consumeSingleChildElement();
+ String rootField = parseWidget(elem);
+
+ StringWriter stringWriter = new StringWriter();
+ IndentedWriter niceWriter =
+ new IndentedWriter(new PrintWriter(stringWriter));
+
+ if (rootDeclaration != null) {
+ writeBifurcatedBinder(niceWriter);
+ } else {
+ writeEagerBinder(niceWriter, rootField);
+ }
+
+ return stringWriter.toString();
+ }
+
+ /**
+ * Parses a package uri (i.e. package://com.google...).
+ *
+ * @throws UnableToCompleteException on bad package name
+ */
+ private JPackage parseNamespacePackage(String ns)
+ throws UnableToCompleteException {
+ if (ns.startsWith(PACKAGE_URI_SCHEME)) {
+ String pkgName = ns.substring(PACKAGE_URI_SCHEME.length());
+
+ JPackage pkg = oracle.findPackage(pkgName);
+ if (pkg == null) {
+ die("Package not found: " + pkgName);
+ }
+
+ return pkg;
+ }
+
+ return null;
+ }
+
+ private void registerParsers() {
+ // TODO(rjrjr): Allow third-party parsers to register themselves
+ // automagically, according to http://b/issue?id=1867118
+ // Parses the root element in UiBinder, for non-widget templates
+ addParser("com.google.gwt.dom.client.Element",
+ "com.google.gwt.uibinder.parsers.DomElementParser");
+
+ // Register widget parsers.
+ addWidgetParser("UIObject");
+ addWidgetParser("HasText");
+ addWidgetParser("HasHTML");
+ addWidgetParser("HasWidgets");
+ addWidgetParser("HTMLPanel");
+ addWidgetParser("DockPanel");
+ addWidgetParser("StackPanel");
+ addWidgetParser("DisclosurePanel");
+ addWidgetParser("TabPanel");
+ addWidgetParser("MenuItem");
+ addWidgetParser("MenuBar");
+ addWidgetParser("RadioButton");
+ addWidgetParser("CellPanel");
+ addWidgetParser("CustomButton");
+
+ addAttributeParser("boolean",
+ "com.google.gwt.uibinder.parsers.BooleanAttributeParser");
+
+ addAttributeParser("java.lang.String",
+ "com.google.gwt.uibinder.parsers.StringAttributeParser");
+
+ addAttributeParser("int", "com.google.gwt.uibinder.parsers.IntParser");
+
+ addAttributeParser("int,int",
+ "com.google.gwt.uibinder.parsers.IntPairParser");
+
+ addAttributeParser(
+ "com.google.gwt.user.client.ui.HasHorizontalAlignment."
+ + "HorizontalAlignmentConstant",
+ "com.google.gwt.uibinder.parsers.HorizontalAlignmentConstantParser");
+ }
+
+ /**
+ * Write statements that parsers created via calls to
+ * {@link #addInitStatement}. Such statements will assume that
+ * {@link #writeGwtFields} has already been called.
+ */
+ private void writeAddedStatements(IndentedWriter niceWriter) {
+ for (String s : statements) {
+ niceWriter.write(s);
+ }
+ }
+
+ /**
+ * Writes a binder that creates a root element in
+ * {@link UiBinder#createUiRoot}, and the rest of the dom in
+ * {@link UiBinder#bindUi}. See a sample in {@link "http://go/gwt-bif-binder"}
+ */
+ private void writeBifurcatedBinder(IndentedWriter w)
+ throws UnableToCompleteException {
+ writePackage(w);
+ writeImports(w);
+ w.newline();
+
+ writeClassOpen(w);
+ writeStatics(w);
+ w.newline();
+
+ // createUiRoot method
+ w.write("public %s createUiRoot(final %s owner) {", uiRootType.getName(),
+ uiOwnerType.getName());
+ w.indent();
+ w.write("return %s;", rootDeclaration);
+ w.outdent();
+ w.write("}");
+ w.newline();
+
+ // bindUi method
+ w.write("public void bindUi(%s root, %s owner) {", uiRootType.getName(),
+ uiOwnerType.getName());
+ w.indent();
+
+ writeGwtFields(w);
+ writeAddedStatements(w);
+ writeInitStatements(w);
+ writeFieldSetters(w);
+ writeHandlers(w);
+ w.newline();
+
+ // close bindUi method
+ w.outdent();
+ w.write("}");
+ w.newline();
+
+ // Close class
+ w.outdent();
+ w.write("}");
+ }
+
+ private void writeClassOpen(IndentedWriter w) {
+ w.write("public class %s extends AbstractUiBinder<%s, %s> \n"
+ + "implements %s {",
+ implClassName, uiRootType.getName(), uiOwnerType.getName(),
+ baseClass.getName());
+ w.indent();
+ }
+
+ /**
+ * Writes a binder that creates the entire ui in {@link UiBinder#createUiRoot}
+ * and does nothing but fill the clients fields in {@link UiBinder#bindUi}.
+ * <p>
+ * Because UiBinder instances tend to be static and reused, createUiRoot
+ * creates an object with references to the generated ui's fields, and stores
+ * it in a hash map keyed by the root UI object. This binding object is
+ * removed from the map during the call to bindUi and used to fill the
+ * client's <code>{@literal @}UiField</code>s. I would prefer to find a
+ * way to do without the map, but am stumped.
+ * <p>
+ * See a sample in {@link "http://go/gwt-eager-binder"}
+ *
+ * @throws UnableToCompleteException
+ */
+ private void writeEagerBinder(IndentedWriter w, String rootField)
+ throws UnableToCompleteException {
+ writePackage(w);
+
+ // Add a few extra imports
+ w.write("import java.util.HashMap;");
+ w.write("import java.util.Map;");
+ writeImports(w);
+ w.newline();
+
+ writeClassOpen(w);
+ writeStatics(w);
+ w.newline();
+
+ // Map from root objects to instance binders
+ w.write("final Map<%1$s, %2$s> rootToBindCommand "
+ + "= new HashMap<%1$s, %2$s>();", uiRootType.getName(),
+ instanceBinderType());
+ w.newline();
+
+ // createUiRoot method
+ w.write("public %s createUiRoot(final %s owner) {", uiRootType.getName(),
+ uiOwnerType.getName());
+ w.indent();
+ w.newline();
+
+ // write instance binder anon class
+ w.write("%1$s instanceBinder = new %1$s() {", instanceBinderType());
+ w.indent();
+ writeGwtFields(w);
+ w.newline();
+
+ w.write("public %s makeUi() {", uiRootType.getName());
+ w.indent();
+ writeAddedStatements(w);
+ w.newline();
+ writeInitStatements(w);
+ w.newline();
+ writeHandlers(w);
+ w.newline();
+ w.write("return %s;", rootField);
+ w.outdent();
+ w.write("}");
+ w.newline();
+
+ w.write("public void doBind(%s owner) {", uiOwnerType.getName());
+ w.indent();
+ writeFieldSetters(w);
+ w.outdent();
+ w.write("}");
+
+ // close instance binder anon class
+ w.outdent();
+ w.write("};");
+ w.newline();
+
+ // use instance binder to make the ui, and store the binder away
+ // to wait for the call to bindUi
+ w.write("%s root = instanceBinder.makeUi();", uiRootType.getName());
+ w.write("rootToBindCommand.put(root, instanceBinder);");
+ w.write("return root;");
+
+ // close createUiRoot method
+ w.outdent();
+ w.write("}");
+ w.newline();
+
+ // bindUi method
+ w.write("public void bindUi(%s root, %s owner) {", uiRootType.getName(),
+ uiOwnerType.getName());
+ w.indent();
+
+ // fetch command and run it
+ w.write("%s instanceBinder = rootToBindCommand.remove(root);",
+ instanceBinderType());
+ w.write("assert instanceBinder != null : "
+ + "\"Attempted to bind an unknown UI, or to bind the same one twice\""
+ + ";");
+ w.write("instanceBinder.doBind(owner);");
+
+ // close bindUiMethod
+ w.outdent();
+ w.write("}");
+ w.newline();
+
+ // Close class
+ w.outdent();
+ w.write("}");
+ }
+
+ /**
+ * Write the statements to fill in the fields of the UI owner.
+ */
+ private void writeFieldSetters(IndentedWriter niceWriter)
+ throws UnableToCompleteException {
+ // For each owner field
+ Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
+ for (OwnerField ownerField : ownerFields) {
+ FieldWriter fieldWriter = fieldManager.lookup(ownerField.getName());
+
+ BundleAttributeParser bundleParser = bundleParsers.get(
+ ownerField.getType().getRawType().getQualifiedSourceName());
+
+ if (bundleParser != null) {
+ // ownerField is a bundle resource.
+ maybeWriteFieldSetter(niceWriter, ownerField,
+ bundleParser.bundleClass(), bundleParser.bundleInstance());
+
+ } else if (fieldWriter != null) {
+ // ownerField is a widget.
+ maybeWriteFieldSetter(niceWriter, ownerField,
+ fieldWriter.getType(), ownerField.getName());
+
+ } else {
+ // ownerField was not found as bundle resource or widget, must die.
+ die("Template %s has no %s attribute for %s.%s#%s",
+ templatePath,
+ getGwtFieldAttributeName(),
+ uiOwnerType.getPackage().getName(),
+ uiOwnerType.getName(),
+ ownerField.getName());
+ }
+ }
+ }
+
+ /**
+ * Write declarations for variables or fields to hold elements declared with
+ * gwt:field in the template. For those that have not had constructor
+ * generation suppressed, emit GWT.create() calls instantiating them (or die
+ * if they have no default constructor).
+ *
+ * @throws UnableToCompleteException on constructor problem
+ */
+ private void writeGwtFields(IndentedWriter niceWriter)
+ throws UnableToCompleteException {
+ // For each provided field in the owner class, initialize from the owner
+ Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
+ for (OwnerField ownerField : ownerFields) {
+ if (ownerField.isProvided()) {
+ String fieldName = ownerField.getName();
+ FieldWriter fieldWriter = fieldManager.lookup(fieldName);
+
+ // TODO(hermes) This can be null due to http://b/1836504. If that
+ // is fixed properly, a null fieldWriter will be an error
+ // (would that be a user error or a runtime error? Not sure)
+ if (fieldWriter != null) {
+ setFieldInitializer(fieldName,
+ String.format("owner.%1$s", fieldName));
+ }
+ }
+ }
+
+ // Write gwt field declarations.
+ fieldManager.writeGwtFieldsDeclaration(niceWriter, uiOwnerType.getName());
+ }
+
+ private void writeHandlers(IndentedWriter w)
+ throws UnableToCompleteException {
+ handlerEvaluator.run(w, fieldManager, "owner");
+ }
+
+ private void writeImports(IndentedWriter w) {
+ w.write("import com.google.gwt.core.client.GWT;");
+ w.write("import com.google.gwt.dom.client.Element;");
+ w.write("import com.google.gwt.uibinder.client.AbstractUiBinder;");
+ w.write("import com.google.gwt.uibinder.client.DomHolder;");
+ w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
+ w.write("import %s.%s;", uiRootType.getPackage().getName(),
+ uiRootType.getName());
+ }
+
+ /**
+ * Write statements created by {@link #addInitStatement}. This code must be
+ * placed after all instnatiation code.
+ */
+ private void writeInitStatements(IndentedWriter niceWriter) {
+ for (String s : initStatements) {
+ niceWriter.write(s);
+ }
+ }
+
+ private void writePackage(IndentedWriter w) {
+ String packageName = baseClass.getPackage().getName();
+ if (packageName.length() > 0) {
+ w.write("package %1$s;", packageName);
+ w.newline();
+ }
+ }
+
+ /**
+ * Generates instances of any bundle classes that have been referenced by a
+ * namespace entry in the top level element. This must be called *after* all
+ * parsing is through, as the bundle list is generated lazily as dom elements
+ * are parsed.
+ */
+ private void writeStaticBundleInstances(IndentedWriter niceWriter) {
+ // TODO(rjrjr) It seems bad that this method has special
+ // knowledge of BundleAttributeParser
+ for (String key : bundleParsers.keySet()) {
+ String declaration = declareStaticField(bundleParsers.get(key));
+ if (declaration != null) {
+ niceWriter.write(declaration);
+ }
+ }
+ }
+
+ private void writeStaticMessagesInstance(IndentedWriter niceWriter) {
+ if (messages.hasMessages()) {
+ niceWriter.write(messages.getDeclaration());
+ }
+ }
+
+ private void writeStatics(IndentedWriter w) {
+ writeStaticMessagesInstance(w);
+ writeStaticBundleInstances(w);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
new file mode 100644
index 0000000..e14e5d9
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import org.w3c.dom.Attr;
+
+/**
+ * Like {@link XMLElement}, a wrapper around {@link Attr} to keep
+ * parser writers out of trouble.
+ */
+public class XMLAttribute {
+
+ private XMLElement xmlElem;
+ private Attr w3cAttr;
+
+ public XMLAttribute(XMLElement element, Attr attr) {
+ this.xmlElem = element;
+ this.w3cAttr = attr;
+ }
+
+ public String consumeValue() {
+ return xmlElem.consumeAttribute(w3cAttr.getName());
+ }
+
+ public String getLocalName() {
+ return w3cAttr.getLocalName();
+ }
+
+ public String getName() {
+ return w3cAttr.getName();
+ }
+
+ public String getNamespaceUri() {
+ return w3cAttr.getNamespaceURI();
+ }
+
+ public boolean hasToken() {
+ return Tokenator.hasToken(w3cAttr.getValue());
+ }
+
+ public boolean isConsumed() {
+ return !xmlElem.hasAttribute(w3cAttr.getName());
+ }
+
+ @Override
+ public String toString() {
+ return w3cAttr.getName() + "='" + w3cAttr.getValue() + "'";
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
new file mode 100644
index 0000000..07e4002
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -0,0 +1,400 @@
+/*
+ * 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
+ * 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 org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A wrapper for {@link Element} that limits the way parsers can interact with
+ * the XML document, and provides some convenience methods.
+ * <p>
+ * The main function of this wrapper is to ensure that parsers can only read
+ * elements and attributes by 'consuming' them, which removes the given value.
+ * This allows for a natural hierarchy among parsers -- more specific parsers
+ * will run first, and if they consume a value, less-specific parsers will not
+ * see it.
+ */
+public class XMLElement {
+ private static final Set<String> NO_END_TAG = new HashSet<String>();
+
+ /**
+ * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and
+ * {@link #consumeChildElements(Interpreter)}.
+ */
+ public interface Interpreter<T> {
+ /**
+ * Given an XMLElement, return its filtered value.
+ *
+ * @throws UnableToCompleteException on error
+ */
+ T interpretElement(XMLElement elem) throws UnableToCompleteException;
+ }
+
+ /**
+ * Extends {@link Interpreter} with a method to be called after
+ * all elements have been processed.
+ */
+ public interface PostProcessingInterpreter<T> extends Interpreter<T> {
+ String postProcess(String consumedText) throws UnableToCompleteException;
+ }
+
+ private static class NoBrainInterpeter<T> implements Interpreter<T> {
+ private final T rtn;
+
+ public NoBrainInterpeter(T rtn) {
+ this.rtn = rtn;
+ }
+
+ public T interpretElement(XMLElement elem) {
+ return rtn;
+ }
+ }
+
+ private static void clearChildren(Element elem) {
+ Node child;
+ while ((child = elem.getFirstChild()) != null) {
+ elem.removeChild(child);
+ }
+ }
+
+ private final UiBinderWriter writer;
+
+ private final Element elem;
+
+ private final String debugString;
+
+ {
+ // from com/google/gxp/compiler/schema/html.xml
+ NO_END_TAG.add("area");
+ NO_END_TAG.add("base");
+ NO_END_TAG.add("basefont");
+ NO_END_TAG.add("br");
+ NO_END_TAG.add("col");
+ NO_END_TAG.add("frame");
+ NO_END_TAG.add("hr");
+ NO_END_TAG.add("img");
+ NO_END_TAG.add("input");
+ NO_END_TAG.add("isindex");
+ NO_END_TAG.add("link");
+ NO_END_TAG.add("meta");
+ NO_END_TAG.add("param");
+ NO_END_TAG.add("wbr");
+ }
+
+ public XMLElement(Element elem, UiBinderWriter writer) {
+ this.elem = elem;
+ this.writer = writer;
+ this.debugString = getOpeningTag();
+ }
+
+ /**
+ * Consumes the given attribute and returns its trimmed value, or null
+ * if it was undeclared. The returned string is not escaped.
+ *
+ * @param name the attribute's full name (including prefix)
+ * @return the attribute's value, or null
+ */
+ public String consumeAttribute(String name) {
+ String value = elem.getAttribute(name);
+ elem.removeAttribute(name);
+ return value.trim();
+ }
+
+ /**
+ * Consumes the given attribute as a boolean value.
+ *
+ * @throws UnableToCompleteException
+ */
+ public boolean consumeBooleanAttribute(String attr)
+ throws UnableToCompleteException {
+ String value = consumeAttribute(attr);
+ if (value.equals("true")) {
+ return true;
+ } else if (value.equals("false")) {
+ return false;
+ }
+ writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+ + "as a boolean value", attr, this));
+ return false; // unreachable line for happy compiler
+ }
+
+ /**
+ * Consumes and returns all child elements, and erases any text nodes.
+ */
+ public Iterable<XMLElement> consumeChildElements() {
+ try {
+ Iterable<XMLElement> rtn =
+ consumeChildElements(new NoBrainInterpeter<Boolean>(true));
+ clearChildren(elem);
+ return rtn;
+ } catch (UnableToCompleteException e) {
+ throw new RuntimeException("Impossible exception", e);
+ }
+ }
+
+ /**
+ * Consumes and returns all child elements selected by the interpreter. Note
+ * that text nodes are not elements, and so are not presented for
+ * interpretation, and are not consumed.
+ *
+ * @param interpreter Should return true for any child that should be consumed
+ * and returned.
+ * @throws UnableToCompleteException
+ */
+ public Collection<XMLElement> consumeChildElements(
+ Interpreter<Boolean> interpreter) throws UnableToCompleteException {
+ List<XMLElement> elements = new ArrayList<XMLElement>();
+ List<Node> doomed = new ArrayList<Node>();
+
+ NodeList childNodes = elem.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); ++i) {
+ Node childNode = childNodes.item(i);
+ if (childNode.getNodeType() == Node.ELEMENT_NODE) {
+ XMLElement childElement = new XMLElement((Element) childNode, writer);
+ if (interpreter.interpretElement(childElement)) {
+ elements.add(childElement);
+ doomed.add(childNode);
+ }
+ }
+ }
+
+ for (Node n : doomed) {
+ elem.removeChild(n);
+ }
+ return elements;
+ }
+
+ /**
+ * Consumes all child elements, and returns an HTML interpretation of them.
+ * Trailing and leading whitespace is trimmed.
+ * <p>
+ * Each element encountered will be passed to the given Interpreter for
+ * possible replacement. Escaping is performed to allow the returned text to
+ * serve as a Java string literal used as input to a setInnerHTML call.
+ * <p>
+ * This call requires an interpreter to make sense of any special children.
+ * The odds are you want to use
+ * {@link com.google.gwt.templates.parsers.HtmlInterpreter} for an HTML value,
+ * or {@link com.google.gwt.templates.parsers.TextInterpreter} for text.
+ *
+ * @param interpreter Called for each element, expected to return a string
+ * replacement for it, or null if it should be left as is
+ */
+ public String consumeInnerHtml(Interpreter<String> interpreter)
+ throws UnableToCompleteException {
+ if (interpreter == null) {
+ throw new NullPointerException("interpreter must not be null");
+ }
+ StringBuffer buf = new StringBuffer();
+ GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, writer);
+
+ clearChildren(elem);
+ return buf.toString().trim();
+ }
+
+ /**
+ * Refines {@link #consumeInnerHtml(Interpreter)} to handle
+ * PostProcessingInterpreter.
+ */
+ public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter)
+ throws UnableToCompleteException {
+ String html = consumeInnerHtml((Interpreter<String>) interpreter);
+ return interpreter.postProcess(html);
+ }
+
+ /**
+ * Consumes all child text nodes, and asserts that this element held only
+ * text. Trailing and leading whitespace is trimmed.
+ * <p>
+ * This call requires an interpreter to make sense of any special children.
+ * The odds are you want to use
+ * {@link com.google.gwt.templates.parsers.TextInterpreter}
+ *
+ * @throws UnableToCompleteException If any elements present are not consumed
+ * by the interpreter
+ */
+ public String consumeInnerText(Interpreter<String> interpreter)
+ throws UnableToCompleteException {
+ if (interpreter == null) {
+ throw new NullPointerException("interpreter must not be null");
+ }
+ StringBuffer buf = new StringBuffer();
+
+ GetInnerTextVisitor.getEscapedInnerText(elem, buf, interpreter, writer);
+
+ // Make sure there are no children left but empty husks
+ for (XMLElement child : consumeChildElements()) {
+ if (child.hasChildNodes() || child.getAttributeCount() > 0) {
+ // TODO(rjrjr) This is not robust enough, and consumeInnerHtml needs
+ // a similar check
+ writer.die("Text value of \"%s\" has illegal child \"%s\"", this,
+ child);
+ }
+ }
+
+ clearChildren(elem);
+ return buf.toString().trim();
+ }
+
+ /**
+ * Refines {@link #consumeInnerText(Interpreter)} to handle
+ * PostProcessingInterpreter.
+ */
+ public String consumeInnerText(PostProcessingInterpreter<String> interpreter)
+ throws UnableToCompleteException {
+ String text = consumeInnerText((Interpreter<String>) interpreter);
+ return interpreter.postProcess(text);
+ }
+
+ /**
+ * Consumes all attributes, and returns a string representing the
+ * entire opening tag. E.g., "<div able='baker'>"
+ */
+ public String consumeOpeningTag() {
+ String rtn = getOpeningTag();
+
+ for (int i = getAttributeCount() - 1; i >= 0; i--) {
+ getAttribute(i).consumeValue();
+ }
+ return rtn;
+ }
+
+ /**
+ * Consumes a single child element, ignoring any text nodes and throwing an
+ * exception if more than one child element is found.
+ */
+ public XMLElement consumeSingleChildElement() {
+ XMLElement ret = null;
+ for (XMLElement child : consumeChildElements()) {
+ if (ret != null) {
+ throw new RuntimeException(getLocalName()
+ + " may only contain a single child element.");
+ }
+
+ ret = child;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Get the attribute at the given index. If you are consuming attributes,
+ * remember to traverse them in reverse.
+ */
+ public XMLAttribute getAttribute(int i) {
+ return new XMLAttribute(XMLElement.this,
+ (Attr) elem.getAttributes().item(i));
+ }
+
+ /**
+ * @return The number of attributes this element has
+ */
+ public int getAttributeCount() {
+ return elem.getAttributes().getLength();
+ }
+
+ public String getClosingTag() {
+ if (NO_END_TAG.contains(elem.getTagName())) {
+ return "";
+ }
+ return String.format("</%s>", elem.getTagName());
+ }
+
+ /**
+ * Gets this element's local name (sans namespace prefix).
+ */
+ public String getLocalName() {
+ return elem.getLocalName();
+ }
+
+ /**
+ * Gets this element's namespace URI.
+ */
+ public String getNamespaceUri() {
+ return elem.getNamespaceURI();
+ }
+
+ public String getNamespaceUriForAttribute(String fieldName) {
+ Attr attr = elem.getAttributeNode(fieldName);
+ return attr.getNamespaceURI();
+ }
+
+ /**
+ * @return the parent element, or null if parent is null or a node type
+ * other than Element
+ */
+ public XMLElement getParent() {
+ Node parent = elem.getParentNode();
+ if (parent == null || Node.ELEMENT_NODE != parent.getNodeType()) {
+ return null;
+ }
+ return new XMLElement((Element) parent, writer);
+ }
+
+ public String getPrefix() {
+ return elem.getPrefix();
+ }
+
+ /**
+ * Determines whether the element has a given attribute.
+ */
+ public boolean hasAttribute(String name) {
+ return elem.hasAttribute(name);
+ }
+
+ public boolean hasChildNodes() {
+ return elem.hasChildNodes();
+ }
+
+ public String lookupPrefix(String prefix) {
+ return elem.lookupPrefix(prefix);
+ }
+
+ public void setAttribute(String name, String value) {
+ elem.setAttribute(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return debugString;
+ }
+
+ private String getOpeningTag() {
+ StringBuilder b = new StringBuilder().append("<").append(elem.getTagName());
+
+ NamedNodeMap attrs = elem.getAttributes();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr = (Attr) attrs.item(i);
+ b.append(String.format(" %s='%s'", attr.getName(),
+ UiBinderWriter.escapeAttributeText(attr.getValue())));
+ }
+ b.append(">");
+
+ return b.toString();
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/AttributeMessage.java b/user/src/com/google/gwt/uibinder/rebind/messages/AttributeMessage.java
new file mode 100644
index 0000000..123704d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/AttributeMessage.java
@@ -0,0 +1,48 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.messages;
+
+/**
+ * Associates an attribute name and a message expression. Can generate
+ * code that refers to the message.
+ */
+public class AttributeMessage {
+ private final String attribute;
+ private final String message;
+
+ public AttributeMessage(String attribute, String message) {
+ super();
+ this.attribute = attribute;
+ this.message = message;
+ }
+
+ public String getAttribute() {
+ return attribute;
+ }
+
+ /**
+ * Return an expression to fetch the message and escape it, suitable
+ * for concatenation into the middle of an innerHTML string.
+ */
+ public String getMessageAsHtmlAttribute() {
+ return message
+ + ".replaceAll(\"&\", \"&\").replaceAll(\"'\", \"'\")";
+ }
+
+ public String getMessageUnescaped() {
+ return message;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/GenerateAnnotationWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/GenerateAnnotationWriter.java
new file mode 100644
index 0000000..69a089a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/GenerateAnnotationWriter.java
@@ -0,0 +1,86 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.messages;
+
+import com.google.gwt.uibinder.rebind.IndentedWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a {@literal @}Generate annotation in a Messages interface,
+ * and can write it out at code gen time.
+ */
+class GenerateAnnotationWriter {
+ private static String toArgsList(List<String> strings) {
+ StringBuilder b = new StringBuilder();
+ boolean first = true;
+ for (String s : strings) {
+ if (first) {
+ first = false;
+ } else {
+ b.append(",\n ");
+ }
+ b.append(s);
+ }
+ return b.toString();
+ }
+
+ private static String toArrayLiteral(String[] strings) {
+ StringBuilder b = new StringBuilder("{");
+ for (String s : strings) {
+ b.append(String.format("\"%s\", ", s));
+ }
+ b.append('}');
+ return b.toString();
+ }
+
+ private final String[] formats;
+ private final String fileName;
+ private final String[] locales;
+
+ public GenerateAnnotationWriter(String[] formats, String fileName,
+ String[] locales) {
+ this.formats = formats;
+ this.fileName = fileName;
+ this.locales = locales;
+ }
+
+ public void write(IndentedWriter w) {
+ boolean hasFormats = formats.length > 0;
+ boolean hasFileName = fileName.length() > 0;
+ boolean hasLocales = locales.length > 0;
+
+ if (hasFormats || hasFileName || hasLocales) {
+ List<String> args = new ArrayList<String>();
+ if (hasFormats) {
+ args.add(String.format("format = %s", toArrayLiteral(formats)));
+ }
+ if (hasFileName) {
+ args.add(String.format("fileName = \"%s\"", fileName));
+ }
+ if (hasLocales) {
+ args.add(String.format("locales = %s", toArrayLiteral(locales)));
+ }
+
+ w.write("@Generate(");
+ w.indent();
+ w.write(toArgsList(args));
+ w.outdent();
+ w.write(")");
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessageWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessageWriter.java
new file mode 100644
index 0000000..9c0ff70
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessageWriter.java
@@ -0,0 +1,115 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.messages;
+
+import com.google.gwt.uibinder.rebind.IndentedWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a method in a Messages interface. Can write both the method
+ * declaration and its invocation.
+ */
+public class MessageWriter {
+ /**
+ * Escapes ' and { chars, which have special meaning to Messages
+ * interfaces.
+ */
+ public static String escapeMessageFormat(String messageFormatStyleText) {
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < messageFormatStyleText.length(); i++) {
+ char c = messageFormatStyleText.charAt(i);
+ if (c == '\'') {
+ b.append("''");
+ } else if (c == '{') {
+ b.append("'{'");
+ } else {
+ b.append(c);
+ }
+ }
+ return b.toString();
+ }
+ private String defaultMessage;
+ private final String description;
+ private final String key;
+ private final String name;
+ private final List<PlaceholderWriter> placeholders =
+ new ArrayList<PlaceholderWriter>();
+
+ private final String meaning;
+
+ MessageWriter(String description, String key, String meaning,
+ String name) {
+ this.description = description;
+ this.key = key;
+ this.meaning = meaning;
+ this.name = name;
+ }
+
+ public void addPlaceholder(PlaceholderWriter placeholder) {
+ this.placeholders.add(placeholder);
+ }
+
+ public String getInvocation() {
+ StringBuilder b = new StringBuilder(String.format("%s(", name));
+ int countdown = placeholders.size();
+ for (PlaceholderWriter ph : placeholders) {
+ b.append(ph.getValue());
+ if (--countdown > 0) {
+ b.append(",");
+ }
+ }
+ b.append(")");
+ return b.toString();
+ }
+
+ public int getPlaceholderCount() {
+ return placeholders.size();
+ }
+
+ public void setDefaultMessage(String defaultMessage) {
+ this.defaultMessage = defaultMessage;
+ }
+
+ public void writeDeclaration(IndentedWriter pw) {
+ pw.write("@DefaultMessage(\"%s\")", defaultMessage);
+ if (description.length() > 0) {
+ pw.write("@Description(\"%s\")", description);
+ }
+ if (key.length() > 0) {
+ pw.write("@Key(\"%s\")", key);
+ }
+ if (meaning.length() > 0) {
+ pw.write("@Meaning(\"%s\")", meaning);
+ }
+ if (placeholders.isEmpty()) {
+ pw.write("String %s();", name);
+ } else {
+ pw.write("String %s(", name);
+ pw.indent();
+
+ int countdown = placeholders.size();
+ for (PlaceholderWriter ph : placeholders) {
+ String comma = --countdown > 0 ? "," : "";
+ pw.write(ph.getDeclaration() + comma);
+ }
+ pw.write(");");
+ pw.outdent();
+ }
+ pw.newline();
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
new file mode 100644
index 0000000..31b5fee
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
@@ -0,0 +1,378 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.messages;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.IndentedWriter;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * TemplateWriter for messages.
+ */
+public class MessagesWriter {
+
+ public static final String ATTRIBUTE = "attribute";
+
+ private static final String MESSAGES_URI = "urn:messages:com.google.gwt.i18n";
+ private static final String NAME = "name";
+ private static final String[] EMPTY_ARRAY = {};
+
+ private final String messagesNamespaceURI;
+ private final String packageName;
+ private final String messagesClassName;
+ private final TreeLogger logger;
+ private final List<MessageWriter> messages = new ArrayList<MessageWriter>();
+ private final String generatedFrom;
+
+ private String defaultLocale;
+ private String messagesPrefix;
+ private String generateKeys;
+ private GenerateAnnotationWriter generate;
+
+ private Map<XMLElement, Collection<AttributeMessage>> elemToAttributeMessages =
+ new HashMap<XMLElement, Collection<AttributeMessage>>();
+
+ public MessagesWriter(String nameSpaceUri, TreeLogger logger, String generatedFrom,
+ String packageName, String implClassName) {
+ this.messagesNamespaceURI = nameSpaceUri;
+ this.generatedFrom = generatedFrom;
+ this.packageName = packageName;
+
+ // Localizable classes cannot have underscores in their names.
+ this.messagesClassName = implClassName.replaceAll("_", "") + "GenMessages";
+
+ this.logger = logger;
+ }
+
+ public MessagesWriter(TreeLogger logger, String generatedFrom,
+ String packageName, String implClassName) {
+ this(MESSAGES_URI, logger, generatedFrom, packageName, implClassName);
+ }
+
+ /**
+ * Call {@link #consumeAttributeMessages}, but instead of returning the
+ * results store them for retrieval by a later call to
+ * {@link #retrieveMessageAttributesFor}.
+ *
+ * @throws UnableToCompleteException on error
+ */
+ public void consumeAndStoreMessageAttributesFor(XMLElement elem)
+ throws UnableToCompleteException {
+ Collection<AttributeMessage> attributeMessages =
+ consumeAttributeMessages(elem);
+ if (!attributeMessages.isEmpty()) {
+ elemToAttributeMessages.put(elem, attributeMessages);
+ }
+ }
+
+ /**
+ * Examine the children of the given element. Consume those tagged m:attribute
+ * and return a set of {@link AttributeMessage} instances. E.g.:
+ * <p>
+ *
+ * <pre>
+ * <img src="blueSky.jpg" alt="A blue sky">
+ * <m:attribute m:name="alt" description="blue sky image alt text"/>
+ * </img>
+ * </pre>
+ *
+ * <p>
+ */
+ public Collection<AttributeMessage> consumeAttributeMessages(XMLElement elem)
+ throws UnableToCompleteException {
+ Collection<XMLElement> messageChildren = getAttributeMessageChildren(elem);
+ if (messageChildren.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set<AttributeMessage> attributeMessages = new HashSet<AttributeMessage>();
+ for (XMLElement child : messageChildren) {
+ String attributeName = consumeMessageElementAttribute(NAME, child);
+ if (attributeName.length() == 0) {
+ die(String.format("Missing name attribute in %s", child));
+ }
+ if (!elem.hasAttribute(attributeName)) {
+ die(String.format("%s has no attribute matching %s", elem, child));
+ }
+
+ String defaultMessage =
+ MessageWriter.escapeMessageFormat(elem.consumeAttribute(attributeName));
+ defaultMessage =
+ UiBinderWriter.escapeTextForJavaStringLiteral(defaultMessage);
+ attributeMessages.add(new AttributeMessage(attributeName, declareMessage(
+ child, defaultMessage)));
+ }
+ return attributeMessages;
+ }
+
+ /**
+ * Consume an m:blah attribute on a non-message element, e.g.
+ * {@code <span m:ph="fnord"/>}
+ */
+ public String consumeMessageAttribute(String attName, XMLElement elem) {
+ String fullAttName = getMessagesPrefix() + ":" + attName;
+ return elem.consumeAttribute(fullAttName);
+ }
+
+ /**
+ * Declares a message created by a previous call to {@link #newMessage}, and
+ * returns its invocation expression to be stitched into an innerHTML block.
+ */
+ public String declareMessage(MessageWriter newMessage) {
+ messages.add(newMessage);
+ return String.format("messages.%s", newMessage.getInvocation());
+ }
+
+ /**
+ * Expected to be called with the root element, to allow configuration from
+ * various messages related attributes.
+ */
+ public void findMessagesConfig(XMLElement elem) {
+ String prefix = elem.lookupPrefix(getMessagesUri());
+ if (prefix != null) {
+ messagesPrefix = prefix;
+ defaultLocale = consumeMessageAttribute("defaultLocale", elem);
+ generateKeys = consumeMessageAttribute("generateKeys", elem);
+ generate =
+ new GenerateAnnotationWriter(getMessageAttributeStringArray(
+ "generateFormat", elem), consumeMessageAttribute(
+ "generateFilename", elem), getMessageAttributeStringArray(
+ "generateLocales", elem));
+ }
+ }
+
+ /**
+ * @return the expression that will instantiate the Messages interface
+ */
+ public String getDeclaration() {
+ return String.format(
+ "static %1$s messages = (%1$s) GWT.create(%1$s.class);",
+ getMessagesClassName());
+ }
+
+ public String getMessagesClassName() {
+ return messagesClassName;
+ }
+
+ /**
+ * @return The namespace prefix (not including :) declared by the template for
+ * message elements and attributes
+ */
+ public String getMessagesPrefix() {
+ return messagesPrefix;
+ }
+
+ /**
+ * Confirm existence of an m:blah attribute on a non-message element, e.g.
+ * {@code <span m:ph="fnord"/>}
+ */
+ public boolean hasMessageAttribute(String attName, XMLElement elem) {
+ String fullAttName = getMessagesPrefix() + ":" + attName;
+ return elem.hasAttribute(fullAttName);
+ }
+
+ /**
+ * @return true iff any messages have been declared
+ */
+ public boolean hasMessages() {
+ return !messages.isEmpty();
+ }
+
+ public boolean isMessage(XMLElement elem) {
+ return isMessagePrefixed(elem) && "msg".equals(elem.getLocalName());
+ }
+
+ /**
+ * Creates a new MessageWriter instance with description, key and meaning
+ * values consumed from the given XMLElement. Note that this message will not
+ * be written in the generated code unless it is later declared via
+ * {@link #declareMessage(MessageWriter)}
+ */
+ public MessageWriter newMessage(XMLElement elem) {
+ MessageWriter newMessage =
+ new MessageWriter(consumeMessageElementAttribute("description", elem),
+ consumeMessageElementAttribute("key", elem),
+ consumeMessageElementAttribute("meaning", elem),
+ nextMessageName());
+ return newMessage;
+ }
+
+ /**
+ * @return The set of AttributeMessages that were found in elem and stored by
+ * a previous call to {@link #consumeAndStoreMessageAttributesFor}
+ */
+ public Collection<AttributeMessage> retrieveMessageAttributesFor(
+ XMLElement elem) {
+ return elemToAttributeMessages.get(elem);
+ }
+
+ public void write(PrintWriter printWriter) {
+ IndentedWriter writer = new IndentedWriter(printWriter);
+
+ // Package declaration.
+ if (packageName.length() > 0) {
+ writer.write("package %1$s;", packageName);
+ writer.newline();
+ }
+
+ // Imports.
+ writer.write("import com.google.gwt.i18n.client.Messages;");
+ writer.write("import static com.google.gwt.i18n.client.LocalizableResource.*;");
+ writer.newline();
+
+ // Open interface.
+ genInterfaceAnnotations(writer);
+ writer.write("public interface %s extends Messages {", getMessagesClassName());
+ writer.newline();
+ writer.indent();
+
+ // Write message methods
+ for (MessageWriter m : messages) {
+ m.writeDeclaration(writer);
+ }
+
+ // Close interface.
+ writer.outdent();
+ writer.write("}");
+ }
+
+ /**
+ * Consume an attribute on a messages related element (as oppposed to a
+ * messages attribute in some other kind of element), e.g. the description in
+ * {@code <m:msg description="described!">}
+ */
+ String consumeMessageElementAttribute(String attName, XMLElement elem) {
+ if (elem.hasAttribute(attName)) {
+ return UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute(attName));
+ }
+
+ String fullAttName = getMessagesPrefix() + ":" + attName;
+ if (elem.hasAttribute(fullAttName)) {
+ String value = elem.consumeAttribute(fullAttName);
+ logger.log(TreeLogger.WARN, String.format(
+ "In %s, deprecated prefix \"%s:\" on \"%s\". Use \"%s\" instead.",
+ elem, getMessagesPrefix(), fullAttName, attName));
+ return value;
+ }
+
+ return "";
+ }
+
+ String consumeRequiredMessageElementAttribute(String attName,
+ XMLElement elem) throws UnableToCompleteException {
+ String value = consumeMessageElementAttribute(attName, elem);
+ if ("".equals(value)) {
+ die("%s does not have required attribute %s", elem, attName);
+ }
+ return value;
+ }
+
+ boolean isMessagePrefixed(XMLElement elem) {
+ String uri = elem.getNamespaceUri();
+ return uri != null && uri.startsWith(getMessagesUri());
+ }
+
+ private String declareMessage(XMLElement elem, String defaultMessage)
+ throws UnableToCompleteException {
+ List<PlaceholderWriter> emptyList = Collections.emptyList();
+ MessageWriter newMessage = newMessage(elem);
+ newMessage.setDefaultMessage(defaultMessage);
+ for (PlaceholderWriter placeholder : emptyList) {
+ newMessage.addPlaceholder(placeholder);
+ }
+ return declareMessage(newMessage);
+ }
+
+ private void die(String message) throws UnableToCompleteException {
+ // TODO(rjrjr) copied from TemplateWriter. Move to common superclass or
+ // something
+ logger.log(TreeLogger.ERROR, message);
+ throw new UnableToCompleteException();
+ }
+
+ /**
+ * Post an error message and halt processing. This method always throws an
+ * {@link UnableToCompleteException}
+ */
+ private void die(String message, Object... params)
+ throws UnableToCompleteException {
+ // TODO(rjrjr) copied from TemplateWriter. Move to common superclass or
+ // something
+ logger.log(TreeLogger.ERROR, String.format(message, params));
+ throw new UnableToCompleteException();
+ }
+
+ private void genInterfaceAnnotations(IndentedWriter pw) {
+ pw.write("@GeneratedFrom(\"%s\")", generatedFrom);
+ if (defaultLocale.length() > 0) {
+ pw.write("@DefaultLocale(\"%s\")", defaultLocale);
+ }
+ if (generateKeys.length() > 0) {
+ pw.write("@GenerateKeys(\"%s\")", generateKeys);
+ }
+ generate.write(pw);
+ }
+
+ private Collection<XMLElement> getAttributeMessageChildren(
+ final XMLElement elem) throws UnableToCompleteException {
+ return elem.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
+ public Boolean interpretElement(XMLElement child)
+ throws UnableToCompleteException {
+ if (isAttributeMessage(child)) {
+ if (child.hasChildNodes()) {
+ die(String.format("Illegal body for %s in %s.", child, elem));
+ }
+ return true;
+ }
+
+ return false;
+ }
+ });
+ }
+
+ private String[] getMessageAttributeStringArray(String attName,
+ XMLElement elem) {
+ String value = consumeMessageAttribute(attName, elem);
+ if (value == null) {
+ return EMPTY_ARRAY;
+ }
+ return value.split("\\s*,\\s*");
+ }
+
+ private String getMessagesUri() {
+ return messagesNamespaceURI;
+ }
+
+ private boolean isAttributeMessage(XMLElement elem) {
+ return isMessagePrefixed(elem) && ATTRIBUTE.equals(elem.getLocalName());
+ }
+
+ private String nextMessageName() {
+ return String.format("message%d", messages.size() + 1);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
new file mode 100644
index 0000000..3d55938
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
@@ -0,0 +1,131 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.messages;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.Tokenator;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.Tokenator.Resolver;
+import com.google.gwt.uibinder.rebind.XMLElement.PostProcessingInterpreter;
+
+/**
+ * Abstract class performs the heavy lifting for processing ph children of a msg
+ * element.
+ */
+public abstract class PlaceholderInterpreter implements
+ PostProcessingInterpreter<String> {
+
+ protected final UiBinderWriter uiWriter;
+ protected final MessageWriter message;
+ protected final Tokenator tokenator = new Tokenator();
+
+ public PlaceholderInterpreter(UiBinderWriter writer, MessageWriter message) {
+ this.uiWriter = writer;
+ this.message = message;
+ }
+
+ public String interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+
+ if (isPlaceholderElement(elem)) {
+ /*
+ * The innerHTML or innerText of the <m:ph> will be provided as the value
+ * of the appropriate parameter when the Messages method is called.
+ *
+ * E.g.
+ *
+ * <m:msg>Able <m:ph name="foo" example"baz">baker</m:ph> charlie</m:msg>
+ *
+ * becomes
+ *
+ * @Default("Able {0} charlie)
+ * String message1(@Example("baz") String foo)
+ *
+ * and in the generated innerHTML is invoked
+ *
+ * message1("baker")
+ */
+
+ MessagesWriter mw = getMessagesWriter();
+ String name = mw.consumeRequiredMessageElementAttribute("name", elem);
+ String example = mw.consumeMessageElementAttribute("example", elem);
+ String value = consumePlaceholderInnards(elem);
+
+ // Use the value as the example if none is provided
+ if ("".equals(example)) {
+
+ /*
+ * The value may contain tokens vended by the TemplateWriter, which will
+ * be substituted for runtime-computed values (like unique dom ids). We
+ * don't want those in the example text shown to the translator, so snip
+ * them
+ */
+ example = stripTokens(value);
+ }
+
+ /*
+ * Again, if there are TemplateWriter tokens in the value string, we need
+ * it to have it replace them with the real expresions.
+ */
+ value = uiWriter.detokenate(value);
+
+ return nextPlaceholder(name, example, value);
+ }
+
+ if (uiWriter.isWidget(elem)) {
+ uiWriter.die("Found %s in a message that cannot contain widgets", elem);
+ }
+ return null;
+ }
+
+ /**
+ * Called by various {@link XMLElement} consumeInner*() methods after all
+ * elements have been handed to {@link #interpretElement}. Performs escaping
+ * on the consumed text to make it safe for use as a Messages {@literal @}Default
+ * value.
+ */
+ public String postProcess(String consumed) throws UnableToCompleteException {
+ return tokenator.detokenate(MessageWriter.escapeMessageFormat(consumed));
+ }
+
+ protected abstract String consumePlaceholderInnards(XMLElement elem)
+ throws UnableToCompleteException;
+
+ protected String nextPlaceholder(String name, String example, String value) {
+ message.addPlaceholder(new PlaceholderWriter(name, example, value));
+ return tokenator.nextToken(String.format("{%d}",
+ message.getPlaceholderCount() - 1));
+ }
+
+ protected String stripTokens(String value) {
+ String rtn = Tokenator.detokenate(value, new Resolver() {
+ public String resolveToken(String token) {
+ return "";
+ }
+ });
+ return rtn;
+ }
+
+ private MessagesWriter getMessagesWriter() {
+ return uiWriter.getMessages();
+ }
+
+ private boolean isPlaceholderElement(XMLElement elem) {
+ return getMessagesWriter().isMessagePrefixed(elem)
+ && "ph".equals(elem.getLocalName());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderWriter.java
new file mode 100644
index 0000000..127b3d3
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderWriter.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.messages;
+
+/**
+ * Represents a parameter in a Messages interface method, and
+ * can write out its declaration.
+ */
+class PlaceholderWriter {
+ private final String name;
+ private final String example;
+ private final String value;
+
+ /**
+ * @param name Parameter name for this placeholder
+ * @param example Contents of the {@literal @}Example annotation
+ * @param value The value to provide for this param when writing
+ * an invocation of its message method.
+ */
+ public PlaceholderWriter(String name, String example, String value) {
+ this.name = name;
+ this.example = inQuotes(example);
+ this.value = inQuotes(value);
+ }
+
+ public String getDeclaration() {
+ return String.format("@Example(%s) String %s", example, name);
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ private String inQuotes(String s) {
+ return String.format("\"%s\"", s);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
new file mode 100644
index 0000000..00381df
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Model class with all attributes of the owner class.
+ * This includes factories, fields and handlers.
+ */
+public class OwnerClass {
+
+ /**
+ * Map from field name to model.
+ */
+ private final Map<String, OwnerField> uiFields =
+ new HashMap<String, OwnerField>();
+
+ /**
+ * Map from field type to model.
+ *
+ * This is used for binding resources - for widgets, there may be multiple
+ * widgets with the same type, in which case this becomes meaningless.
+ */
+ private final Map<JClassType, OwnerField> uiFieldTypes =
+ new HashMap<JClassType, OwnerField>();
+
+ /**
+ * Map from type to the method that produces it.
+ */
+ private final Map<JClassType, JMethod> uiFactories =
+ new HashMap<JClassType, JMethod>();
+
+ /**
+ * List of all @UiHandler methods in the owner class.
+ */
+ private final List<JMethod> uiHandlers = new ArrayList<JMethod>();
+
+ /**
+ * Constructor.
+ *
+ * @param ownerType the type of the owner class
+ */
+ public OwnerClass(JClassType ownerType) throws UnableToCompleteException {
+ findUiFields(ownerType);
+ findUiFactories(ownerType);
+ findUiHandlers(ownerType);
+ }
+
+ /**
+ * Returns the method annotated with @UiFactory which returns the given type.
+ *
+ * @param forType the type to look for a factory of
+ * @return the factory method, or null if none exists
+ */
+ public JMethod getUiFactoryMethod(JClassType forType) {
+ return uiFactories.get(forType);
+ }
+
+ /**
+ * Returns the method annotated with @UiFactory which returns the given type.
+ *
+ * @param forType the type to look for a factory of
+ * @return the factory method, or null if none exists
+ */
+ public JMethod getUiFactoryMethod(OwnerFieldClass forType) {
+ return getUiFactoryMethod(forType.getRawType());
+ }
+
+ /**
+ * Gets a field with the given name.
+ * It's important to notice that a field may not exist on the owner class even
+ * if it has a name in the XML and even has handlers attached to it - such a
+ * field will only exist in the generated binder class.
+ *
+ * @param name the name of the field to get
+ * @return the field descriptor, or null if the owner doesn't have that field
+ */
+ public OwnerField getUiField(String name) {
+ return uiFields.get(name);
+ }
+
+ /**
+ * Gets the field with the given type.
+ * Note that multiple fields can have the same type, so it only makes sense to
+ * call this to retrieve resource fields, such as messages and image bundles,
+ * for which only one instance is expected.
+ *
+ * @param type the type of the field
+ * @return the field descriptor
+ */
+ public OwnerField getUiFieldForType(JClassType type) {
+ return uiFieldTypes.get(type);
+ }
+
+ /**
+ * Returns a collection of all fields in the owner class.
+ */
+ public Collection<OwnerField> getUiFields() {
+ return uiFields.values();
+ }
+
+ /**
+ * Returns all the UiHandler methods defined in the owner class.
+ */
+ public List<JMethod> getUiHandlers() {
+ return uiHandlers;
+ }
+
+ /**
+ * Scans the owner class to find all methods annotated with @UiFactory, and
+ * puts them in {@link #uiFactories}.
+ *
+ * @param ownerType the type of the owner class
+ * @throws UnableToCompleteException
+ */
+ private void findUiFactories(JClassType ownerType)
+ throws UnableToCompleteException {
+ JMethod[] methods = ownerType.getMethods();
+ for (JMethod method : methods) {
+ if (method.isAnnotationPresent(UiFactory.class)) {
+ JClassType factoryType = method.getReturnType().isClassOrInterface();
+
+ if (factoryType == null) {
+ // TODO(rdamazio): proper logging
+ System.out.println("Factory return type is not a class in method "
+ + method.getName());
+ throw new UnableToCompleteException();
+ }
+
+ if (uiFactories.containsKey(factoryType)) {
+ // TODO(rdamazio): proper logging
+ System.out.println("Duplicate factory in class "
+ + method.getEnclosingType().getName() + " for type "
+ + factoryType.getName());
+ throw new UnableToCompleteException();
+ }
+
+ uiFactories.put(factoryType, method);
+ }
+ }
+
+ // Recurse to superclass
+ JClassType superclass = ownerType.getSuperclass();
+ if (superclass != null) {
+ findUiFactories(superclass);
+ }
+ }
+
+ /**
+ * Scans the owner class to find all fields annotated with @UiField, and puts
+ * them in {@link #uiFields} and {@link #uiFieldTypes}.
+ *
+ * @param ownerType the type of the owner class
+ */
+ private void findUiFields(JClassType ownerType)
+ throws UnableToCompleteException {
+ JField[] fields = ownerType.getFields();
+ for (JField field : fields) {
+ if (field.isAnnotationPresent(UiField.class)) {
+ JClassType ownerFieldType = field.getType().isClassOrInterface();
+
+ if (ownerFieldType == null) {
+ // TODO(rdamazio): proper logging
+ System.out.println("Field type is not a class in field "
+ + field.getName());
+ throw new UnableToCompleteException();
+ }
+
+ OwnerField ownerField = new OwnerField(field);
+ String ownerFieldName = field.getName();
+
+ uiFields.put(ownerFieldName, ownerField);
+ uiFieldTypes.put(ownerFieldType, ownerField);
+ }
+ }
+
+ // Recurse to superclass
+ JClassType superclass = ownerType.getSuperclass();
+ if (superclass != null) {
+ findUiFields(superclass);
+ }
+ }
+
+ /**
+ * Scans the owner class to find all methods annotated with @UiHandler, and
+ * adds them to their respective fields.
+ *
+ * @param ownerType the type of the owner class
+ */
+ private void findUiHandlers(JClassType ownerType) {
+ JMethod[] methods = ownerType.getMethods();
+ for (JMethod method : methods) {
+ if (method.isAnnotationPresent(UiHandler.class)) {
+ uiHandlers.add(method);
+ }
+ }
+
+ // Recurse to superclass
+ JClassType superclass = ownerType.getSuperclass();
+ if (superclass != null) {
+ findUiHandlers(superclass);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
new file mode 100644
index 0000000..73e7edd
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.uibinder.client.UiField;
+
+/**
+ * Descriptor for a field of the owner class.
+ *
+ * Please notice that some fields defined in the XML and in the generated binder
+ * class may not be present in the owner class - for instance, they may not be
+ * relevant to the code of the owner class.
+ * The fields in the binder class are instead represented by an instance of
+ * {@link com.google.gwt.uibinder.rebind.FieldWriter}.
+ */
+public class OwnerField {
+ private final String name;
+ private final OwnerFieldClass fieldType;
+ private final boolean isProvided;
+
+ /**
+ * Constructor.
+ *
+ * @param field the field of the owner class
+ */
+ public OwnerField(JField field) throws UnableToCompleteException {
+ this.name = field.getName();
+
+ // Get the field type and ensure it's a class or interface
+ JClassType fieldClassType = field.getType().isClassOrInterface();
+
+ if (fieldClassType == null) {
+ // TODO(rdamazio): proper logging
+ System.out.println("Type for field " + name + " is not a class: "
+ + field.getType().getSimpleSourceName());
+ throw new UnableToCompleteException();
+ }
+
+ // TODO(rdamazio): For non-widget classes (resources), this will be useless
+ // since there are no setters, no uiconstructor, etc.
+ this.fieldType = OwnerFieldClass.getFieldClass(fieldClassType);
+
+ // Get the UiField annotation and process it
+ UiField annotation = field.getAnnotation(UiField.class);
+
+ if (annotation == null) {
+ // TODO(rdamazio): proper logging
+ System.out.println("Field " + name + " is not annotated with @UiField");
+ throw new UnableToCompleteException();
+ }
+
+ isProvided = annotation.provided();
+ }
+
+ /**
+ * Returns the name of the field in the owner class.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns a descriptor for the type of the field.
+ */
+ public OwnerFieldClass getType() {
+ return fieldType;
+ }
+
+ /**
+ * Returns whether this field's value is provided by owner class.
+ * If it's not provided, then it's the binder's responsibility to assign it.
+ */
+ public boolean isProvided() {
+ return isProvided;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
new file mode 100644
index 0000000..f102a6f
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
+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.uibinder.client.UiConstructor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Descriptor for a class which can be used as a @UiField.
+ * This is usually a widget, but can also be a resource (such as Messages or
+ * an ImageBundle). Also notice that the existence of an OwnerFieldClass doesn't
+ * mean the class is actually present as a field in the owner.
+ */
+public class OwnerFieldClass {
+
+ /**
+ * Global map of field classes.
+ * This serves as a cache so each class is only processed once.
+ */
+ private static final Map<JClassType, OwnerFieldClass> FIELD_CLASSES =
+ new HashMap<JClassType, OwnerFieldClass>();
+
+ /**
+ * Gets or creates the descriptor for the given field class.
+ *
+ * @param forType the field type to get a descriptor for
+ * @return the descriptor
+ */
+ public static OwnerFieldClass getFieldClass(JClassType forType)
+ throws UnableToCompleteException {
+ OwnerFieldClass clazz = FIELD_CLASSES.get(forType);
+ if (clazz == null) {
+ clazz = new OwnerFieldClass(forType);
+ FIELD_CLASSES.put(forType, clazz);
+ }
+ return clazz;
+ }
+
+ private final JClassType rawType;
+ private final Map<String, JMethod> setters = new HashMap<String, JMethod>();
+ private Set<String> ambiguousSetters;
+ private JConstructor uiConstructor;
+
+ /**
+ * Default constructor.
+ * This is package-visible for testing only.
+ *
+ * @param forType the type of the field class
+ * @throws UnableToCompleteException if the class is not valid
+ */
+ OwnerFieldClass(JClassType forType) throws UnableToCompleteException {
+ this.rawType = forType;
+
+ findUiConstructor(forType);
+ findSetters(forType);
+ }
+
+ /**
+ * Returns the field's raw type.
+ */
+ public JClassType getRawType() {
+ return rawType;
+ }
+
+ /**
+ * Finds the setter method for a given property.
+ *
+ * @param propertyName the name of the property
+ * @return the setter method, or null if none exists
+ */
+ public JMethod getSetter(String propertyName)
+ throws UnableToCompleteException {
+ if (ambiguousSetters != null && ambiguousSetters.contains(propertyName)) {
+ // TODO(rdamazio): proper logging
+ System.out.println("Ambiguous setter requested: " + rawType.getName()
+ + "." + propertyName);
+ throw new UnableToCompleteException();
+ }
+
+ return setters.get(propertyName);
+ }
+
+ /**
+ * Returns the constructor annotated with @UiConstructor, or null if none
+ * exists.
+ */
+ public JConstructor getUiConstructor() {
+ return uiConstructor;
+ }
+
+ /**
+ * Given a collection of setters for the same property, picks which one to
+ * use.
+ * Not having a proper setter is not an error unless of course the user tries
+ * to use it.
+ *
+ * @param propertySetters the collection of setters
+ * @return the setter to use, or null if none is good enough
+ */
+ private JMethod disambiguateSetters(Collection<JMethod> propertySetters) {
+ if (propertySetters.size() == 1) {
+ return propertySetters.iterator().next();
+ }
+
+ // Pick the string setter, if there's one
+ for (JMethod method : propertySetters) {
+ JParameter[] parameters = method.getParameters();
+ if (parameters.length == 1
+ && parameters[0].getType().getQualifiedSourceName().equals(
+ "java.lang.String")) {
+ return method;
+ }
+ }
+
+ // Check if all setters aren't just the same one being overridden in parent
+ // classes.
+ JMethod firstMethod = null;
+ for (JMethod method : propertySetters) {
+ if (firstMethod == null) {
+ firstMethod = method;
+ continue;
+ }
+
+ // If the method is not the same as the first one, there's still an
+ // ambiguity. Being equal means having the same parameter types.
+ if (!sameParameterTypes(method, firstMethod)) {
+ return null;
+ }
+ }
+
+ return firstMethod;
+ }
+
+ /**
+ * Recursively finds all setters for the given class and its superclasses.
+ *
+ * @param fieldType the leaf type to look at
+ * @return a multimap of property name to the setter methods
+ */
+ private Map<String, Collection<JMethod>> findAllSetters(
+ JClassType fieldType) {
+ Map<String, Collection<JMethod>> allSetters;
+
+ // First, get all setters from the parent class, recursively.
+ JClassType superClass = fieldType.getSuperclass();
+ if (superClass != null) {
+ allSetters = findAllSetters(superClass);
+ } else {
+ // Stop recursion - deepest level creates return value
+ allSetters = new HashMap<String, Collection<JMethod>>();
+ }
+
+ JMethod[] methods = fieldType.getMethods();
+ for (JMethod method : methods) {
+ if (!isSetterMethod(method)) {
+ continue;
+ }
+
+ // Take out "set"
+ String propertyName = method.getName().substring(3);
+
+ // turn "PropertyName" into "propertyName"
+ propertyName = propertyName.substring(0, 1).toLowerCase()
+ + propertyName.substring(1);
+
+ Collection<JMethod> propertyMethods = allSetters.get(propertyName);
+ if (propertyMethods == null) {
+ propertyMethods = new ArrayList<JMethod>();
+ allSetters.put(propertyName, propertyMethods);
+ }
+
+ propertyMethods.add(method);
+ }
+
+ return allSetters;
+ }
+
+ /**
+ * Finds all setters in the class, and puts them in the {@link #setters}
+ * field.
+ *
+ * @param fieldType the type of the field
+ */
+ private void findSetters(JClassType fieldType) {
+ // Pass one - get all setter methods
+ Map<String, Collection<JMethod>> allSetters = findAllSetters(fieldType);
+
+ // Pass two - disambiguate
+ for (String propertyName : allSetters.keySet()) {
+ Collection<JMethod> propertySetters = allSetters.get(propertyName);
+ JMethod setter = disambiguateSetters(propertySetters);
+
+ // If no setter could be disambiguated for this property, add it to the
+ // set of ambiguous setters. This is later consulted if and only if the
+ // setter is used.
+ if (setter == null) {
+ if (ambiguousSetters == null) {
+ ambiguousSetters = new HashSet<String>();
+ }
+
+ ambiguousSetters.add(propertyName);
+ }
+
+ setters.put(propertyName, setter);
+ }
+ }
+
+ /**
+ * Finds the constructor annotated with @UiConcontructor if there is one, and
+ * puts it in the {@link #uiConstructor} field.
+ *
+ * @param fieldType the type of the field
+ */
+ private void findUiConstructor(JClassType fieldType)
+ throws UnableToCompleteException {
+ for (JConstructor ctor : fieldType.getConstructors()) {
+ if (ctor.getAnnotation(UiConstructor.class) != null) {
+ if (uiConstructor != null) {
+ // TODO(rdamazio): proper logging
+ System.out.println(fieldType.getName()
+ + " has more than one constructor annotated with @UiConstructor");
+ throw new UnableToCompleteException();
+ }
+ uiConstructor = ctor;
+ }
+ }
+ }
+
+ /**
+ * Checks whether the given method qualifies as a setter.
+ * This looks at the method qualifiers, name and return type, but not at the
+ * parameter types.
+ *
+ * @param method the method to look at
+ * @return whether it's a setter
+ */
+ private boolean isSetterMethod(JMethod method) {
+ // All setter methods should be public void setSomething(...)
+ return method.isPublic() && !method.isStatic()
+ && method.getName().startsWith("set")
+ && method.getReturnType() == JPrimitiveType.VOID;
+ }
+
+ /**
+ * Checks whether two methods have the same parameter types.
+ *
+ * @param m1 the first method to compare
+ * @param m2 the second method to compare
+ * @return whether the methods have the same parameter types
+ */
+ private boolean sameParameterTypes(JMethod m1, JMethod m2) {
+ JParameter[] p1 = m1.getParameters();
+ JParameter[] p2 = m2.getParameters();
+
+ if (p1.length != p2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < p1.length; i++) {
+ JType type1 = p1[i].getType();
+ JType type2 = p2[i].getType();
+
+ if (!type1.equals(type2)) {
+ return false;
+ }
+ }
+
+ // No types were different
+ return true;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/resources/xhtml.ent b/user/src/com/google/gwt/uibinder/resources/xhtml.ent
new file mode 100644
index 0000000..aa80147
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/resources/xhtml.ent
@@ -0,0 +1,488 @@
+<!--
+ Portions Copyright 1986 International Organization for Standardization.
+ Permission to copy in any form is granted for use with conforming SGML
+ systems and applications as defined in ISO 8879, provided this notice is
+ included in all copies.
+-->
+
+<!--
+ This is the complete set of named character entites defined in XHTML1.0. It's
+ essentially a union of:
+
+ http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
+ http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
+ http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
+-->
+
+<!-- Latin-1 characters -->
+
+ <!ENTITY nbsp " "> <!-- no-break space = non-breaking space,
+ U+00A0 ISOnum -->
+ <!ENTITY iexcl "¡"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+ <!ENTITY cent "¢"> <!-- cent sign, U+00A2 ISOnum -->
+ <!ENTITY pound "£"> <!-- pound sign, U+00A3 ISOnum -->
+ <!ENTITY curren "¤"> <!-- currency sign, U+00A4 ISOnum -->
+ <!ENTITY yen "¥"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+ <!ENTITY brvbar "¦"> <!-- broken bar = broken vertical bar,
+ U+00A6 ISOnum -->
+ <!ENTITY sect "§"> <!-- section sign, U+00A7 ISOnum -->
+ <!ENTITY uml "¨"> <!-- diaeresis = spacing diaeresis,
+ U+00A8 ISOdia -->
+ <!ENTITY copy "©"> <!-- copyright sign, U+00A9 ISOnum -->
+ <!ENTITY ordf "ª"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+ <!ENTITY laquo "«"> <!-- left-pointing double angle quotation mark
+ = left pointing guillemet, U+00AB ISOnum -->
+ <!ENTITY not "¬"> <!-- not sign = angled dash,
+ U+00AC ISOnum -->
+ <!ENTITY shy "­"> <!-- soft hyphen = discretionary hyphen,
+ U+00AD ISOnum -->
+ <!ENTITY reg "®"> <!-- registered sign = registered trade mark sign,
+ U+00AE ISOnum -->
+ <!ENTITY macr "¯"> <!-- macron = spacing macron = overline
+ = APL overbar, U+00AF ISOdia -->
+ <!ENTITY deg "°"> <!-- degree sign, U+00B0 ISOnum -->
+ <!ENTITY plusmn "±"> <!-- plus-minus sign = plus-or-minus sign,
+ U+00B1 ISOnum -->
+ <!ENTITY sup2 "²"> <!-- superscript two = superscript digit two
+ = squared, U+00B2 ISOnum -->
+ <!ENTITY sup3 "³"> <!-- superscript three = superscript digit three
+ = cubed, U+00B3 ISOnum -->
+ <!ENTITY acute "´"> <!-- acute accent = spacing acute,
+ U+00B4 ISOdia -->
+ <!ENTITY micro "µ"> <!-- micro sign, U+00B5 ISOnum -->
+ <!ENTITY para "¶"> <!-- pilcrow sign = paragraph sign,
+ U+00B6 ISOnum -->
+ <!ENTITY middot "·"> <!-- middle dot = Georgian comma
+ = Greek middle dot, U+00B7 ISOnum -->
+ <!ENTITY cedil "¸"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+ <!ENTITY sup1 "¹"> <!-- superscript one = superscript digit one,
+ U+00B9 ISOnum -->
+ <!ENTITY ordm "º"> <!-- masculine ordinal indicator,
+ U+00BA ISOnum -->
+ <!ENTITY raquo "»"> <!-- right-pointing double angle quotation mark
+ = right pointing guillemet, U+00BB ISOnum -->
+ <!ENTITY frac14 "¼"> <!-- vulgar fraction one quarter
+ = fraction one quarter, U+00BC ISOnum -->
+ <!ENTITY frac12 "½"> <!-- vulgar fraction one half
+ = fraction one half, U+00BD ISOnum -->
+ <!ENTITY frac34 "¾"> <!-- vulgar fraction three quarters
+ = fraction three quarters, U+00BE ISOnum -->
+ <!ENTITY iquest "¿"> <!-- inverted question mark
+ = turned question mark, U+00BF ISOnum -->
+ <!ENTITY Agrave "À"> <!-- latin capital letter A with grave
+ = latin capital letter A grave,
+ U+00C0 ISOlat1 -->
+ <!ENTITY Aacute "Á"> <!-- latin capital letter A with acute,
+ U+00C1 ISOlat1 -->
+ <!ENTITY Acirc "Â"> <!-- latin capital letter A with circumflex,
+ U+00C2 ISOlat1 -->
+ <!ENTITY Atilde "Ã"> <!-- latin capital letter A with tilde,
+ U+00C3 ISOlat1 -->
+ <!ENTITY Auml "Ä"> <!-- latin capital letter A with diaeresis,
+ U+00C4 ISOlat1 -->
+ <!ENTITY Aring "Å"> <!-- latin capital letter A with ring above
+ = latin capital letter A ring,
+ U+00C5 ISOlat1 -->
+ <!ENTITY AElig "Æ"> <!-- latin capital letter AE
+ = latin capital ligature AE,
+ U+00C6 ISOlat1 -->
+ <!ENTITY Ccedil "Ç"> <!-- latin capital letter C with cedilla,
+ U+00C7 ISOlat1 -->
+ <!ENTITY Egrave "È"> <!-- latin capital letter E with grave,
+ U+00C8 ISOlat1 -->
+ <!ENTITY Eacute "É"> <!-- latin capital letter E with acute,
+ U+00C9 ISOlat1 -->
+ <!ENTITY Ecirc "Ê"> <!-- latin capital letter E with circumflex,
+ U+00CA ISOlat1 -->
+ <!ENTITY Euml "Ë"> <!-- latin capital letter E with diaeresis,
+ U+00CB ISOlat1 -->
+ <!ENTITY Igrave "Ì"> <!-- latin capital letter I with grave,
+ U+00CC ISOlat1 -->
+ <!ENTITY Iacute "Í"> <!-- latin capital letter I with acute,
+ U+00CD ISOlat1 -->
+ <!ENTITY Icirc "Î"> <!-- latin capital letter I with circumflex,
+ U+00CE ISOlat1 -->
+ <!ENTITY Iuml "Ï"> <!-- latin capital letter I with diaeresis,
+ U+00CF ISOlat1 -->
+ <!ENTITY ETH "Ð"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+ <!ENTITY Ntilde "Ñ"> <!-- latin capital letter N with tilde,
+ U+00D1 ISOlat1 -->
+ <!ENTITY Ograve "Ò"> <!-- latin capital letter O with grave,
+ U+00D2 ISOlat1 -->
+ <!ENTITY Oacute "Ó"> <!-- latin capital letter O with acute,
+ U+00D3 ISOlat1 -->
+ <!ENTITY Ocirc "Ô"> <!-- latin capital letter O with circumflex,
+ U+00D4 ISOlat1 -->
+ <!ENTITY Otilde "Õ"> <!-- latin capital letter O with tilde,
+ U+00D5 ISOlat1 -->
+ <!ENTITY Ouml "Ö"> <!-- latin capital letter O with diaeresis,
+ U+00D6 ISOlat1 -->
+ <!ENTITY times "×"> <!-- multiplication sign, U+00D7 ISOnum -->
+ <!ENTITY Oslash "Ø"> <!-- latin capital letter O with stroke
+ = latin capital letter O slash,
+ U+00D8 ISOlat1 -->
+ <!ENTITY Ugrave "Ù"> <!-- latin capital letter U with grave,
+ U+00D9 ISOlat1 -->
+ <!ENTITY Uacute "Ú"> <!-- latin capital letter U with acute,
+ U+00DA ISOlat1 -->
+ <!ENTITY Ucirc "Û"> <!-- latin capital letter U with circumflex,
+ U+00DB ISOlat1 -->
+ <!ENTITY Uuml "Ü"> <!-- latin capital letter U with diaeresis,
+ U+00DC ISOlat1 -->
+ <!ENTITY Yacute "Ý"> <!-- latin capital letter Y with acute,
+ U+00DD ISOlat1 -->
+ <!ENTITY THORN "Þ"> <!-- latin capital letter THORN,
+ U+00DE ISOlat1 -->
+ <!ENTITY szlig "ß"> <!-- latin small letter sharp s = ess-zed,
+ U+00DF ISOlat1 -->
+ <!ENTITY agrave "à"> <!-- latin small letter a with grave
+ = latin small letter a grave,
+ U+00E0 ISOlat1 -->
+ <!ENTITY aacute "á"> <!-- latin small letter a with acute,
+ U+00E1 ISOlat1 -->
+ <!ENTITY acirc "â"> <!-- latin small letter a with circumflex,
+ U+00E2 ISOlat1 -->
+ <!ENTITY atilde "ã"> <!-- latin small letter a with tilde,
+ U+00E3 ISOlat1 -->
+ <!ENTITY auml "ä"> <!-- latin small letter a with diaeresis,
+ U+00E4 ISOlat1 -->
+ <!ENTITY aring "å"> <!-- latin small letter a with ring above
+ = latin small letter a ring,
+ U+00E5 ISOlat1 -->
+ <!ENTITY aelig "æ"> <!-- latin small letter ae
+ = latin small ligature ae, U+00E6 ISOlat1 -->
+ <!ENTITY ccedil "ç"> <!-- latin small letter c with cedilla,
+ U+00E7 ISOlat1 -->
+ <!ENTITY egrave "è"> <!-- latin small letter e with grave,
+ U+00E8 ISOlat1 -->
+ <!ENTITY eacute "é"> <!-- latin small letter e with acute,
+ U+00E9 ISOlat1 -->
+ <!ENTITY ecirc "ê"> <!-- latin small letter e with circumflex,
+ U+00EA ISOlat1 -->
+ <!ENTITY euml "ë"> <!-- latin small letter e with diaeresis,
+ U+00EB ISOlat1 -->
+ <!ENTITY igrave "ì"> <!-- latin small letter i with grave,
+ U+00EC ISOlat1 -->
+ <!ENTITY iacute "í"> <!-- latin small letter i with acute,
+ U+00ED ISOlat1 -->
+ <!ENTITY icirc "î"> <!-- latin small letter i with circumflex,
+ U+00EE ISOlat1 -->
+ <!ENTITY iuml "ï"> <!-- latin small letter i with diaeresis,
+ U+00EF ISOlat1 -->
+ <!ENTITY eth "ð"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+ <!ENTITY ntilde "ñ"> <!-- latin small letter n with tilde,
+ U+00F1 ISOlat1 -->
+ <!ENTITY ograve "ò"> <!-- latin small letter o with grave,
+ U+00F2 ISOlat1 -->
+ <!ENTITY oacute "ó"> <!-- latin small letter o with acute,
+ U+00F3 ISOlat1 -->
+ <!ENTITY ocirc "ô"> <!-- latin small letter o with circumflex,
+ U+00F4 ISOlat1 -->
+ <!ENTITY otilde "õ"> <!-- latin small letter o with tilde,
+ U+00F5 ISOlat1 -->
+ <!ENTITY ouml "ö"> <!-- latin small letter o with diaeresis,
+ U+00F6 ISOlat1 -->
+ <!ENTITY divide "÷"> <!-- division sign, U+00F7 ISOnum -->
+ <!ENTITY oslash "ø"> <!-- latin small letter o with stroke,
+ = latin small letter o slash,
+ U+00F8 ISOlat1 -->
+ <!ENTITY ugrave "ù"> <!-- latin small letter u with grave,
+ U+00F9 ISOlat1 -->
+ <!ENTITY uacute "ú"> <!-- latin small letter u with acute,
+ U+00FA ISOlat1 -->
+ <!ENTITY ucirc "û"> <!-- latin small letter u with circumflex,
+ U+00FB ISOlat1 -->
+ <!ENTITY uuml "ü"> <!-- latin small letter u with diaeresis,
+ U+00FC ISOlat1 -->
+ <!ENTITY yacute "ý"> <!-- latin small letter y with acute,
+ U+00FD ISOlat1 -->
+ <!ENTITY thorn "þ"> <!-- latin small letter thorn,
+ U+00FE ISOlat1 -->
+ <!ENTITY yuml "ÿ"> <!-- latin small letter y with diaeresis,
+ U+00FF ISOlat1 -->
+
+<!-- Special characters -->
+
+ <!-- Relevant ISO entity set is given unless names are newly introduced.
+ New names (i.e., not in ISO 8879 list) do not clash with any
+ existing ISO 8879 entity names. ISO 10646 character numbers
+ are given for each character, in hex. values are decimal
+ conversions of the ISO 10646 values and refer to the document
+ character set. Names are Unicode names.
+ -->
+
+ <!-- C0 Controls and Basic Latin -->
+ <!ENTITY quot """> <!-- quotation mark, U+0022 ISOnum -->
+ <!ENTITY amp "&#38;"> <!-- ampersand, U+0026 ISOnum -->
+ <!ENTITY lt "&#60;"> <!-- less-than sign, U+003C ISOnum -->
+ <!ENTITY gt ">"> <!-- greater-than sign, U+003E ISOnum -->
+ <!ENTITY apos "'"> <!-- apostrophe = APL quote, U+0027 ISOnum -->
+
+ <!-- Latin Extended-A -->
+ <!ENTITY OElig "Œ"> <!-- latin capital ligature OE,
+ U+0152 ISOlat2 -->
+ <!ENTITY oelig "œ"> <!-- latin small ligature oe, U+0153 ISOlat2 -->
+ <!-- ligature is a misnomer, this is a separate character in some languages -->
+ <!ENTITY Scaron "Š"> <!-- latin capital letter S with caron,
+ U+0160 ISOlat2 -->
+ <!ENTITY scaron "š"> <!-- latin small letter s with caron,
+ U+0161 ISOlat2 -->
+ <!ENTITY Yuml "Ÿ"> <!-- latin capital letter Y with diaeresis,
+ U+0178 ISOlat2 -->
+
+ <!-- Spacing Modifier Letters -->
+ <!ENTITY circ "ˆ"> <!-- modifier letter circumflex accent,
+ U+02C6 ISOpub -->
+ <!ENTITY tilde "˜"> <!-- small tilde, U+02DC ISOdia -->
+
+ <!-- General Punctuation -->
+ <!ENTITY ensp " "> <!-- en space, U+2002 ISOpub -->
+ <!ENTITY emsp " "> <!-- em space, U+2003 ISOpub -->
+ <!ENTITY thinsp " "> <!-- thin space, U+2009 ISOpub -->
+ <!ENTITY zwnj "‌"> <!-- zero width non-joiner,
+ U+200C NEW RFC 2070 -->
+ <!ENTITY zwj "‍"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+ <!ENTITY lrm "‎"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+ <!ENTITY rlm "‏"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+ <!ENTITY ndash "–"> <!-- en dash, U+2013 ISOpub -->
+ <!ENTITY mdash "—"> <!-- em dash, U+2014 ISOpub -->
+ <!ENTITY lsquo "‘"> <!-- left single quotation mark,
+ U+2018 ISOnum -->
+ <!ENTITY rsquo "’"> <!-- right single quotation mark,
+ U+2019 ISOnum -->
+ <!ENTITY sbquo "‚"> <!-- single low-9 quotation mark, U+201A NEW -->
+ <!ENTITY ldquo "“"> <!-- left double quotation mark,
+ U+201C ISOnum -->
+ <!ENTITY rdquo "”"> <!-- right double quotation mark,
+ U+201D ISOnum -->
+ <!ENTITY bdquo "„"> <!-- double low-9 quotation mark, U+201E NEW -->
+ <!ENTITY dagger "†"> <!-- dagger, U+2020 ISOpub -->
+ <!ENTITY Dagger "‡"> <!-- double dagger, U+2021 ISOpub -->
+ <!ENTITY permil "‰"> <!-- per mille sign, U+2030 ISOtech -->
+ <!ENTITY lsaquo "‹"> <!-- single left-pointing angle quotation mark,
+ U+2039 ISO proposed -->
+ <!-- lsaquo is proposed but not yet ISO standardized -->
+ <!ENTITY rsaquo "›"> <!-- single right-pointing angle quotation mark,
+ U+203A ISO proposed -->
+ <!-- rsaquo is proposed but not yet ISO standardized -->
+
+ <!-- Currency Symbols -->
+ <!ENTITY euro "€"> <!-- euro sign, U+20AC NEW -->
+
+<!-- Mathematical, Greek and Symbolic characters for XHTML -->
+
+ <!-- Latin Extended-B -->
+ <!ENTITY fnof "ƒ"> <!-- latin small letter f with hook = function
+ = florin, U+0192 ISOtech -->
+
+ <!-- Greek -->
+ <!ENTITY Alpha "Α"> <!-- greek capital letter alpha, U+0391 -->
+ <!ENTITY Beta "Β"> <!-- greek capital letter beta, U+0392 -->
+ <!ENTITY Gamma "Γ"> <!-- greek capital letter gamma,
+ U+0393 ISOgrk3 -->
+ <!ENTITY Delta "Δ"> <!-- greek capital letter delta,
+ U+0394 ISOgrk3 -->
+ <!ENTITY Epsilon "Ε"> <!-- greek capital letter epsilon, U+0395 -->
+ <!ENTITY Zeta "Ζ"> <!-- greek capital letter zeta, U+0396 -->
+ <!ENTITY Eta "Η"> <!-- greek capital letter eta, U+0397 -->
+ <!ENTITY Theta "Θ"> <!-- greek capital letter theta,
+ U+0398 ISOgrk3 -->
+ <!ENTITY Iota "Ι"> <!-- greek capital letter iota, U+0399 -->
+ <!ENTITY Kappa "Κ"> <!-- greek capital letter kappa, U+039A -->
+ <!ENTITY Lambda "Λ"> <!-- greek capital letter lamda,
+ U+039B ISOgrk3 -->
+ <!ENTITY Mu "Μ"> <!-- greek capital letter mu, U+039C -->
+ <!ENTITY Nu "Ν"> <!-- greek capital letter nu, U+039D -->
+ <!ENTITY Xi "Ξ"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+ <!ENTITY Omicron "Ο"> <!-- greek capital letter omicron, U+039F -->
+ <!ENTITY Pi "Π"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+ <!ENTITY Rho "Ρ"> <!-- greek capital letter rho, U+03A1 -->
+ <!-- there is no Sigmaf, and no U+03A2 character either -->
+ <!ENTITY Sigma "Σ"> <!-- greek capital letter sigma,
+ U+03A3 ISOgrk3 -->
+ <!ENTITY Tau "Τ"> <!-- greek capital letter tau, U+03A4 -->
+ <!ENTITY Upsilon "Υ"> <!-- greek capital letter upsilon,
+ U+03A5 ISOgrk3 -->
+ <!ENTITY Phi "Φ"> <!-- greek capital letter phi,
+ U+03A6 ISOgrk3 -->
+ <!ENTITY Chi "Χ"> <!-- greek capital letter chi, U+03A7 -->
+ <!ENTITY Psi "Ψ"> <!-- greek capital letter psi,
+ U+03A8 ISOgrk3 -->
+ <!ENTITY Omega "Ω"> <!-- greek capital letter omega,
+ U+03A9 ISOgrk3 -->
+
+ <!ENTITY alpha "α"> <!-- greek small letter alpha,
+ U+03B1 ISOgrk3 -->
+ <!ENTITY beta "β"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+ <!ENTITY gamma "γ"> <!-- greek small letter gamma,
+ U+03B3 ISOgrk3 -->
+ <!ENTITY delta "δ"> <!-- greek small letter delta,
+ U+03B4 ISOgrk3 -->
+ <!ENTITY epsilon "ε"> <!-- greek small letter epsilon,
+ U+03B5 ISOgrk3 -->
+ <!ENTITY zeta "ζ"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+ <!ENTITY eta "η"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+ <!ENTITY theta "θ"> <!-- greek small letter theta,
+ U+03B8 ISOgrk3 -->
+ <!ENTITY iota "ι"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+ <!ENTITY kappa "κ"> <!-- greek small letter kappa,
+ U+03BA ISOgrk3 -->
+ <!ENTITY lambda "λ"> <!-- greek small letter lamda,
+ U+03BB ISOgrk3 -->
+ <!ENTITY mu "μ"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+ <!ENTITY nu "ν"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+ <!ENTITY xi "ξ"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+ <!ENTITY omicron "ο"> <!-- greek small letter omicron, U+03BF NEW -->
+ <!ENTITY pi "π"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+ <!ENTITY rho "ρ"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+ <!ENTITY sigmaf "ς"> <!-- greek small letter final sigma,
+ U+03C2 ISOgrk3 -->
+ <!ENTITY sigma "σ"> <!-- greek small letter sigma,
+ U+03C3 ISOgrk3 -->
+ <!ENTITY tau "τ"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+ <!ENTITY upsilon "υ"> <!-- greek small letter upsilon,
+ U+03C5 ISOgrk3 -->
+ <!ENTITY phi "φ"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+ <!ENTITY chi "χ"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+ <!ENTITY psi "ψ"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+ <!ENTITY omega "ω"> <!-- greek small letter omega,
+ U+03C9 ISOgrk3 -->
+ <!ENTITY thetasym "ϑ"> <!-- greek theta symbol,
+ U+03D1 NEW -->
+ <!ENTITY upsih "ϒ"> <!-- greek upsilon with hook symbol,
+ U+03D2 NEW -->
+ <!ENTITY piv "ϖ"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+ <!-- General Punctuation -->
+ <!ENTITY bull "•"> <!-- bullet = black small circle,
+ U+2022 ISOpub -->
+ <!-- bullet is NOT the same as bullet operator, U+2219 -->
+ <!ENTITY hellip "…"> <!-- horizontal ellipsis = three dot leader,
+ U+2026 ISOpub -->
+ <!ENTITY prime "′"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+ <!ENTITY Prime "″"> <!-- double prime = seconds = inches,
+ U+2033 ISOtech -->
+ <!ENTITY oline "‾"> <!-- overline = spacing overscore,
+ U+203E NEW -->
+ <!ENTITY frasl "⁄"> <!-- fraction slash, U+2044 NEW -->
+
+ <!-- Letterlike Symbols -->
+ <!ENTITY weierp "℘"> <!-- script capital P = power set
+ = Weierstrass p, U+2118 ISOamso -->
+ <!ENTITY image "ℑ"> <!-- black-letter capital I = imaginary part,
+ U+2111 ISOamso -->
+ <!ENTITY real "ℜ"> <!-- black-letter capital R = real part symbol,
+ U+211C ISOamso -->
+ <!ENTITY trade "™"> <!-- trade mark sign, U+2122 ISOnum -->
+ <!ENTITY alefsym "ℵ"> <!-- alef symbol = first transfinite cardinal,
+ U+2135 NEW -->
+ <!-- alef symbol is NOT the same as hebrew letter alef,
+ U+05D0 although the same glyph could be used to depict both characters -->
+
+ <!-- Arrows -->
+ <!ENTITY larr "←"> <!-- leftwards arrow, U+2190 ISOnum -->
+ <!ENTITY uarr "↑"> <!-- upwards arrow, U+2191 ISOnum-->
+ <!ENTITY rarr "→"> <!-- rightwards arrow, U+2192 ISOnum -->
+ <!ENTITY darr "↓"> <!-- downwards arrow, U+2193 ISOnum -->
+ <!ENTITY harr "↔"> <!-- left right arrow, U+2194 ISOamsa -->
+ <!ENTITY crarr "↵"> <!-- downwards arrow with corner leftwards
+ = carriage return, U+21B5 NEW -->
+ <!ENTITY lArr "⇐"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+ <!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+ but also does not have any other character for that function. So lArr can
+ be used for 'is implied by' as ISOtech suggests -->
+ <!ENTITY uArr "⇑"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+ <!ENTITY rArr "⇒"> <!-- rightwards double arrow,
+ U+21D2 ISOtech -->
+ <!-- Unicode does not say this is the 'implies' character but does not have
+ another character with this function so rArr can be used for 'implies'
+ as ISOtech suggests -->
+ <!ENTITY dArr "⇓"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+ <!ENTITY hArr "⇔"> <!-- left right double arrow,
+ U+21D4 ISOamsa -->
+
+ <!-- Mathematical Operators -->
+ <!ENTITY forall "∀"> <!-- for all, U+2200 ISOtech -->
+ <!ENTITY part "∂"> <!-- partial differential, U+2202 ISOtech -->
+ <!ENTITY exist "∃"> <!-- there exists, U+2203 ISOtech -->
+ <!ENTITY empty "∅"> <!-- empty set = null set, U+2205 ISOamso -->
+ <!ENTITY nabla "∇"> <!-- nabla = backward difference,
+ U+2207 ISOtech -->
+ <!ENTITY isin "∈"> <!-- element of, U+2208 ISOtech -->
+ <!ENTITY notin "∉"> <!-- not an element of, U+2209 ISOtech -->
+ <!ENTITY ni "∋"> <!-- contains as member, U+220B ISOtech -->
+ <!ENTITY prod "∏"> <!-- n-ary product = product sign,
+ U+220F ISOamsb -->
+ <!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+ the same glyph might be used for both -->
+ <!ENTITY sum "∑"> <!-- n-ary summation, U+2211 ISOamsb -->
+ <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+ though the same glyph might be used for both -->
+ <!ENTITY minus "−"> <!-- minus sign, U+2212 ISOtech -->
+ <!ENTITY lowast "∗"> <!-- asterisk operator, U+2217 ISOtech -->
+ <!ENTITY radic "√"> <!-- square root = radical sign,
+ U+221A ISOtech -->
+ <!ENTITY prop "∝"> <!-- proportional to, U+221D ISOtech -->
+ <!ENTITY infin "∞"> <!-- infinity, U+221E ISOtech -->
+ <!ENTITY ang "∠"> <!-- angle, U+2220 ISOamso -->
+ <!ENTITY and "∧"> <!-- logical and = wedge, U+2227 ISOtech -->
+ <!ENTITY or "∨"> <!-- logical or = vee, U+2228 ISOtech -->
+ <!ENTITY cap "∩"> <!-- intersection = cap, U+2229 ISOtech -->
+ <!ENTITY cup "∪"> <!-- union = cup, U+222A ISOtech -->
+ <!ENTITY int "∫"> <!-- integral, U+222B ISOtech -->
+ <!ENTITY there4 "∴"> <!-- therefore, U+2234 ISOtech -->
+ <!ENTITY sim "∼"> <!-- tilde operator = varies with = similar to,
+ U+223C ISOtech -->
+ <!-- tilde operator is NOT the same character as the tilde, U+007E,
+ although the same glyph might be used to represent both -->
+ <!ENTITY cong "≅"> <!-- approximately equal to, U+2245 ISOtech -->
+ <!ENTITY asymp "≈"> <!-- almost equal to = asymptotic to,
+ U+2248 ISOamsr -->
+ <!ENTITY ne "≠"> <!-- not equal to, U+2260 ISOtech -->
+ <!ENTITY equiv "≡"> <!-- identical to, U+2261 ISOtech -->
+ <!ENTITY le "≤"> <!-- less-than or equal to, U+2264 ISOtech -->
+ <!ENTITY ge "≥"> <!-- greater-than or equal to,
+ U+2265 ISOtech -->
+ <!ENTITY sub "⊂"> <!-- subset of, U+2282 ISOtech -->
+ <!ENTITY sup "⊃"> <!-- superset of, U+2283 ISOtech -->
+ <!ENTITY nsub "⊄"> <!-- not a subset of, U+2284 ISOamsn -->
+ <!ENTITY sube "⊆"> <!-- subset of or equal to, U+2286 ISOtech -->
+ <!ENTITY supe "⊇"> <!-- superset of or equal to,
+ U+2287 ISOtech -->
+ <!ENTITY oplus "⊕"> <!-- circled plus = direct sum,
+ U+2295 ISOamsb -->
+ <!ENTITY otimes "⊗"> <!-- circled times = vector product,
+ U+2297 ISOamsb -->
+ <!ENTITY perp "⊥"> <!-- up tack = orthogonal to = perpendicular,
+ U+22A5 ISOtech -->
+ <!ENTITY sdot "⋅"> <!-- dot operator, U+22C5 ISOamsb -->
+ <!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+ <!-- Miscellaneous Technical -->
+ <!ENTITY lceil "⌈"> <!-- left ceiling = APL upstile,
+ U+2308 ISOamsc -->
+ <!ENTITY rceil "⌉"> <!-- right ceiling, U+2309 ISOamsc -->
+ <!ENTITY lfloor "⌊"> <!-- left floor = APL downstile,
+ U+230A ISOamsc -->
+ <!ENTITY rfloor "⌋"> <!-- right floor, U+230B ISOamsc -->
+ <!ENTITY lang "〈"> <!-- left-pointing angle bracket = bra,
+ U+2329 ISOtech -->
+ <!-- lang is NOT the same character as U+003C 'less than sign'
+ or U+2039 'single left-pointing angle quotation mark' -->
+ <!ENTITY rang "〉"> <!-- right-pointing angle bracket = ket,
+ U+232A ISOtech -->
+ <!-- rang is NOT the same character as U+003E 'greater than sign'
+ or U+203A 'single right-pointing angle quotation mark' -->
+
+ <!-- Geometric Shapes -->
+ <!ENTITY loz "◊"> <!-- lozenge, U+25CA ISOpub -->
+
+ <!-- Miscellaneous Symbols -->
+ <!ENTITY spades "♠"> <!-- black spade suit, U+2660 ISOpub -->
+ <!-- black here seems to mean filled as opposed to hollow -->
+ <!ENTITY clubs "♣"> <!-- black club suit = shamrock,
+ U+2663 ISOpub -->
+ <!ENTITY hearts "♥"> <!-- black heart suit = valentine,
+ U+2665 ISOpub -->
+ <!ENTITY diams "♦"> <!-- black diamond suit, U+2666 ISOpub -->
+
diff --git a/user/src/com/google/gwt/uibinder/sample/UiBinderDemo.gwt.xml b/user/src/com/google/gwt/uibinder/sample/UiBinderDemo.gwt.xml
new file mode 100644
index 0000000..948c463
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/UiBinderDemo.gwt.xml
@@ -0,0 +1,19 @@
+<!-- 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<!-- GWT UiBinder support. -->
+<module>
+ <inherits name="com.google.gwt.resources.Resources" />
+ <inherits name="com.google.gwt.uibinder.UiBinder" />
+ <entry-point class="com.google.gwt.uibinder.sample.client.UiBinderDemo" />
+</module>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/AnnotatedStrictLabel.java b/user/src/com/google/gwt/uibinder/sample/client/AnnotatedStrictLabel.java
new file mode 100644
index 0000000..ed5fab4
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/AnnotatedStrictLabel.java
@@ -0,0 +1,28 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.uibinder.client.UiConstructor;
+
+/**
+ * To prove that a bound Ui can use a widget that is not
+ * default instantiable, via @UiConstructor.
+ */
+public class AnnotatedStrictLabel extends StrictLabel {
+ @UiConstructor public AnnotatedStrictLabel(String text) {
+ super(text);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/ClickyLink.java b/user/src/com/google/gwt/uibinder/sample/client/ClickyLink.java
new file mode 100644
index 0000000..60e2381
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/ClickyLink.java
@@ -0,0 +1,86 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A silly little widget to make sure that silly custom widgets
+ * actually work.
+ */
+
+public class ClickyLink extends Widget implements HasText {
+
+ /**
+ * This static method exists purely to ensure BeanParser handles overloaded
+ * methods correctly.
+ */
+ public static void setPopupText(HasText t, String text) {
+ t.setText(text);
+ }
+
+ private String popupText;
+
+ /**
+ * Creates an empty hyperlink.
+ */
+ public ClickyLink() {
+ setElement(DOM.createAnchor());
+ DOM.setElementAttribute(getElement(), "href", "#");
+ sinkEvents(Event.ONCLICK);
+
+ addDomHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ if (popupText != null) {
+ Window.alert(popupText);
+ }
+ event.preventDefault();
+ }
+ }, ClickEvent.getType());
+ }
+
+ /**
+ * Creates a hyperlink with its text specified.
+ *
+ * @param text the hyperlink's text
+ */
+ public ClickyLink(String text) {
+ this();
+ setText(text);
+ }
+
+ public String getPopupText() {
+ return popupText;
+ }
+
+ public String getText() {
+ return DOM.getInnerText(getElement());
+ }
+
+ public void setPopupText(String text) {
+ popupText = text;
+ }
+
+ public void setText(String text) {
+ DOM.setInnerText(getElement(), text);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.css b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.css
new file mode 100644
index 0000000..d277a99
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.css
@@ -0,0 +1,8 @@
+.bodyColor {
+ color: indigo;
+}
+
+.bodyFont {
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: small;
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
new file mode 100644
index 0000000..398e4f2
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
@@ -0,0 +1,93 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Strict;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+
+/**
+ * Sample use of a {@code UiBinder} with no dependency on
+ * com.google.gwt.user.
+ */
+public class DomBasedUi {
+ /**
+ * Resources for this template.
+ */
+ public interface Resources extends ClientBundle {
+ @Strict
+ @Source("DomBasedUi.css")
+ Style style();
+ }
+
+ /**
+ * CSS for this template.
+ */
+ public interface Style extends CssResource {
+ String bodyColor();
+ String bodyFont();
+ }
+
+ @UiTemplate("DomBasedUi.ui.xml")
+ // Actually, it will default to this name w/o annotation
+ interface Binder extends UiBinder<Element, DomBasedUi> {
+ }
+
+ private static final Resources res = GWT.create(Resources.class);
+ private static final Binder binder = GWT.create(Binder.class);
+
+ private static boolean stylesInjected = false;
+
+ @UiField SpanElement nameSpan;
+ @UiField Element tmElement;
+
+ private final Element root;
+ private final String yourNameHere;
+
+ private boolean gotRoot = false;
+
+ public DomBasedUi(String yourNameHere) {
+ this.yourNameHere = yourNameHere;
+ root = binder.createUiRoot(this);
+
+ // Inject only once.
+ if (!stylesInjected) {
+ StyleInjector.injectStylesheet(res.style().getText());
+ stylesInjected = true;
+ }
+
+ // Or if you don't care about deferred rendering...
+// root = binder.createAndBindUi(this);
+// nameSpan.setInnerText(yourNameHere);
+ // ...and delete gotRoot and the bulk of #getElement
+ }
+
+ public Element getRoot() {
+ if (!gotRoot) {
+ binder.bindUi(root, this);
+ nameSpan.setInnerText(yourNameHere);
+ gotRoot = true;
+ }
+ return root;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.ui.xml
new file mode 100644
index 0000000..776910d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.ui.xml
@@ -0,0 +1,33 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:res='urn:with:com.google.gwt.uibinder.sample.client.DomBasedUi.Resources'
+
+ ui:defaultLocale="en_us"
+ ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
+ ui:generateFormat="com.google.gwt.i18n.rebind.format.PropertiesFormat"
+ ui:generateFilename="myapp_translate_source"
+ ui:generateLocales="default"
+ >
+ <div res:class="style.bodyColor style.bodyFont">
+ Hello, <span ui:field="nameSpan" />.
+ <ui:msg>How goes it?</ui:msg>
+ <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
+ these with <span style="font-weight:bold"
+ ui:ph="boldSpan">placeholders</span><ui:ph name="tm"><sup ui:field="tmElement">TM</sup></ui:ph>.</ui:msg></p>
+ </div>
+</ui:UiBinder>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementPanel.java b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementPanel.java
new file mode 100644
index 0000000..07c6374
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementPanel.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Used to test UiBinder, a widget that can be based on arbitrary element
+ * tags at the whim of its user.
+ */
+public class ExplicitElementPanel extends ComplexPanel {
+ @UiConstructor
+ ExplicitElementPanel(String tag) {
+ setElement(Document.get().createElement(tag));
+ }
+
+ @Override
+ public void add(Widget child) {
+ add(child, getElement());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementWidget.java b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementWidget.java
new file mode 100644
index 0000000..4028b45
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementWidget.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Used to test UiBinder, a widget that can be based on arbitrary element
+ * tags at the whim of its user.
+ */
+public class ExplicitElementWidget extends Widget implements HasHTML {
+ @UiConstructor
+ ExplicitElementWidget(String tag) {
+ setElement(Document.get().createElement(tag));
+ }
+
+ public String getHTML() {
+ return getElement().getInnerHTML();
+ }
+
+ public String getText() {
+ return getElement().getInnerText();
+ }
+
+ public void setHTML(String html) {
+ getElement().setInnerHTML(html);
+ }
+
+ public void setText(String text) {
+ getElement().setInnerText(text);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java b/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
new file mode 100644
index 0000000..1b64c58
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
@@ -0,0 +1,29 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+/**
+ * Faux bundle used by test.
+ */
+public class FakeBundle {
+ public Foo foo() {
+ return new Foo();
+ }
+
+ public String helloText() {
+ return "hello";
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/Foo.java b/user/src/com/google/gwt/uibinder/sample/client/Foo.java
new file mode 100644
index 0000000..c858727
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/Foo.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+/**
+ * Used when testing non-primitive property setting in templates.
+ */
+public class Foo {
+ @Override
+ public String toString() {
+ return "I am a foo!";
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java b/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
new file mode 100644
index 0000000..b859688
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+
+/**
+ * A widget tied to a non-primitive type, used to show that
+ * such properties can be set via templates.
+ */
+public class FooLabel extends Composite {
+ @UiConstructor
+ public FooLabel() {
+ Label l = new Label();
+ initWidget(l);
+ }
+
+ /**
+ * @return the text
+ */
+ public String getText() {
+ return getLabel().getText();
+ }
+
+ public void setFoo(Foo foo) {
+ getLabel().setText("This widget has non primitive properties: "
+ + foo.toString());
+ }
+
+ private Label getLabel() {
+ return (Label) getWidget();
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.java b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.java
new file mode 100644
index 0000000..062f2e6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
+
+/**
+ * A simple demo showing how UiHandler works.
+ */
+public class HandlerDemo extends Composite {
+
+ @UiTemplate("HandlerDemo.ui.xml")
+ interface MyUiBinder extends UiBinder<Panel, HandlerDemo> {
+ }
+ private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
+
+ @UiField FormPanel panelForm;
+ @UiField TextBox textBoxValueChange;
+
+ public HandlerDemo() {
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ @UiHandler("panelForm")
+ public void doSubmit(SubmitEvent event) {
+ eventMessage(event);
+ event.cancel();
+ }
+
+ @UiHandler("textBoxValueChange")
+ protected void doChangeValue(ValueChangeEvent<String> event) {
+ eventMessage(event);
+ }
+
+ @UiHandler({"buttonClick", "labelClick"})
+ void doClick(ClickEvent event) {
+ eventMessage(event);
+ }
+
+ @UiHandler("buttonSubmit")
+ void doClickSubmit(ClickEvent event) {
+ panelForm.submit();
+ }
+
+ @UiHandler("buttonMouseOut")
+ void doMouseOut(MouseOutEvent event) {
+ eventMessage(event);
+ }
+
+ @UiHandler("buttonMouseOver")
+ void doMouseOver(MouseOverEvent event) {
+ eventMessage(event);
+ }
+
+ private void eventMessage(GwtEvent<?> event) {
+ Window.alert(event.toDebugString());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.ui.xml
new file mode 100644
index 0000000..6e9f727
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.ui.xml
@@ -0,0 +1,19 @@
+<gwt:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:gwt='urn:import:com.google.gwt.user.client.ui'>
+
+ <gwt:FlowPanel>
+ <gwt:Button ui:field="buttonClick" text="ClickEvent test for Button"/>
+ <gwt:Button ui:field="buttonMouseOver" text="MouseOverEvent test for Button"/>
+ <gwt:Button ui:field="buttonMouseOut" text="MouseOutEvent test for Button"/>
+ <gwt:Label ui:field="labelClick" text="ClickEvent test for Label"/>
+
+ <gwt:FormPanel ui:field="panelForm">
+ <gwt:Button ui:field="buttonSubmit" text="SubmitEvent test" />
+ </gwt:FormPanel>
+
+ <gwt:Label text="ValueChangeEvent test" />
+ <gwt:TextBox ui:field="textBoxValueChange"/>
+ </gwt:FlowPanel>
+
+</gwt:UiBinder>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/MyDatePicker.java b/user/src/com/google/gwt/uibinder/sample/client/MyDatePicker.java
new file mode 100644
index 0000000..1d329a1
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/MyDatePicker.java
@@ -0,0 +1,33 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.user.datepicker.client.DatePicker;
+
+import java.util.Date;
+
+/**
+ * A custom widget used by UiBinder samples to make sure that custom widgets
+ * actually work.
+ */
+public class MyDatePicker extends DatePicker {
+
+ @SuppressWarnings("deprecation")
+ public MyDatePicker() {
+ Date importantDate = new Date(1997, 4, 19);
+ setValue(importantDate);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/MyEntities.ent b/user/src/com/google/gwt/uibinder/sample/client/MyEntities.ent
new file mode 100644
index 0000000..7c068fe
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/MyEntities.ent
@@ -0,0 +1,23 @@
+<!--
+ 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
+ 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. License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Sample custom entity definitions, demonstrating that such
+ things work with UiBinder.
+-->
+
+ <!ENTITY point-right "☞"> <!-- right pointing hand -->
+ <!ENTITY point-left "☜"> <!-- left pointing hand -->
+
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/sample/client/NeedlesslyAnnotatedLabel.java b/user/src/com/google/gwt/uibinder/sample/client/NeedlesslyAnnotatedLabel.java
new file mode 100644
index 0000000..4ac51bb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/NeedlesslyAnnotatedLabel.java
@@ -0,0 +1,28 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.Label;
+
+/**
+ * A widget with a UiConstructor annotation that it doesn't need,
+ * to confirm these work.
+ */
+public class NeedlesslyAnnotatedLabel extends Label {
+ @UiConstructor public NeedlesslyAnnotatedLabel() {
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/PointlessRadioButtonSubclass.java b/user/src/com/google/gwt/uibinder/sample/client/PointlessRadioButtonSubclass.java
new file mode 100644
index 0000000..62b8163
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/PointlessRadioButtonSubclass.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.sample.client;
+
+import com.google.gwt.user.client.ui.RadioButton;
+
+/**
+ * Just to prove we can subclass radio button.
+ */
+public class PointlessRadioButtonSubclass extends RadioButton {
+
+ public PointlessRadioButtonSubclass() {
+ super("");
+ }
+
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/StrictLabel.java b/user/src/com/google/gwt/uibinder/sample/client/StrictLabel.java
new file mode 100644
index 0000000..331f7ba4
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/StrictLabel.java
@@ -0,0 +1,28 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.user.client.ui.InlineLabel;
+
+/**
+ * To prove that a bound Ui can use a widget that is not
+ * default instantiable.
+ */
+public class StrictLabel extends InlineLabel {
+ public StrictLabel(String text) {
+ super(text);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java b/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
new file mode 100644
index 0000000..8275c62
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
@@ -0,0 +1,34 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Demonstration of templated UI. Used by UiBinderTest
+ */
+public class UiBinderDemo implements EntryPoint {
+
+ public void onModuleLoad() {
+ DomBasedUi boundUi = new DomBasedUi("Mr. User Man");
+ Document.get().getBody().appendChild((boundUi).getRoot());
+
+ RootPanel.get().add((Widget) new WidgetBasedUi());
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
new file mode 100644
index 0000000..599ea37
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
@@ -0,0 +1,14 @@
+.prettyText {
+ color: green;
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+.tmText {
+ color: purple;
+ font-family: serif;
+}
+
+.menuBar {
+ border:solid;
+ background-color:white;
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
new file mode 100644
index 0000000..310e037
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -0,0 +1,286 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DListElement;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.OListElement;
+import com.google.gwt.dom.client.ParagraphElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DisclosurePanel;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.MenuBar;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.StackPanel;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Sample use of a {@code UiBinder} with the com.google.gwt.user Widget set, and
+ * custom widgets.
+ */
+public class WidgetBasedUi extends Composite {
+ @UiTemplate("WidgetBasedUi.ui.xml")
+ // Actually, it will default to this name w/o annotation
+ interface Binder extends UiBinder<Widget, WidgetBasedUi> {
+ }
+
+ private static final Binder binder = GWT.create(Binder.class);
+
+ private static boolean stylesInjected = false;
+
+ @UiField(provided = true)
+ final WidgetBasedUiResources res;
+
+ @UiField(provided = true)
+ final Label bundledLabel;
+
+ @UiField
+ ClickyLink customLinkWidget;
+ @UiField
+ PointlessRadioButtonSubclass emptyRadio;
+ @UiField
+ ClickyLink funnyCharsAttributeWidget;
+ @UiField
+ ParagraphElement funnyCharsDomAttributeParagraph;
+ @UiField
+ ClickyLink funnyCharsMessageAttributeWidget;
+ @UiField
+ ParagraphElement funnyCharsMessageDomAttributeParagraph;
+ @UiField
+ ParagraphElement funnyCharsMessageParagraph;
+ @UiField
+ ParagraphElement funnyCharsParagraph;
+ @UiField
+ Label gwtFieldLabel;
+ @UiField
+ ParagraphElement main;
+ @UiField
+ Button myButton;
+ @UiField
+ RadioButton myRadioAble;
+ @UiField
+ RadioButton myRadioBaker;
+ @UiField
+ StackPanel myStackPanel;
+ @UiField
+ Widget myStackPanelItem;
+ @UiField
+ DisclosurePanel myDisclosurePanel;
+ @UiField
+ Widget myDisclosurePanelItem;
+ @UiField
+ Tree myTree;
+ @UiField
+ Element nonStandardElement;
+ @UiField
+ DockPanel root;
+ @UiField
+ DivElement sideBar;
+ @UiField
+ SpanElement spanInMsg;
+ @UiField
+ Element tmElement;
+ @UiField
+ Element tmElementJr;
+ @UiField
+ SpanElement trimmedMessage;
+ @UiField
+ NeedlesslyAnnotatedLabel needlessLabel;
+ @UiField
+ AnnotatedStrictLabel strictLabel;
+ @UiField
+ AnnotatedStrictLabel translatedStrictLabel;
+ @UiField
+ StrictLabel veryStrictLabel;
+ @UiField
+ StrictLabel translatedVeryStrictLabel;
+ @UiField
+ FooLabel theFoo;
+ @UiField
+ MenuBar dropdownMenuBar;
+ @UiField
+ MenuItem menuItemMop;
+ @UiField
+ MenuItem menuItemLegacy;
+ @UiField
+ SpanElement messageInMain;
+ @UiField
+ TableElement widgetCrazyTable;
+ @UiField
+ OListElement widgetCrazyOrderedList;
+ @UiField
+ DListElement widgetCrazyDefinitionList;
+ @UiField
+ HTMLPanel customTagHtmlPanel;
+
+ public WidgetBasedUi() {
+ this.bundledLabel = new Label();
+ this.res = GWT.create(WidgetBasedUiResources.class);
+
+ // Inject only once.
+ if (!stylesInjected) {
+ StyleInjector.injectStylesheet(res.style().getText());
+ stylesInjected = true;
+ }
+
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ public Label getBundledLabel() {
+ return bundledLabel;
+ }
+
+ public ClickyLink getCustomLinkWidget() {
+ return customLinkWidget;
+ }
+
+ public HTMLPanel getCustomTagHtmlPanel() {
+ return customTagHtmlPanel;
+ }
+
+ public MenuBar getDropdownMenuBar() {
+ return dropdownMenuBar;
+ }
+
+ public ClickyLink getFunnyCharsAttributeWidget() {
+ return funnyCharsAttributeWidget;
+ }
+
+ public ParagraphElement getFunnyCharsDomAttributeParagraph() {
+ return funnyCharsDomAttributeParagraph;
+ }
+
+ public ClickyLink getFunnyCharsMessageAttributeWidget() {
+ return funnyCharsMessageAttributeWidget;
+ }
+
+ public ParagraphElement getFunnyCharsMessageDomAttributeParagraph() {
+ return funnyCharsMessageDomAttributeParagraph;
+ }
+
+ public ParagraphElement getFunnyCharsMessageParagraph() {
+ return funnyCharsMessageParagraph;
+ }
+
+ public ParagraphElement getFunnyCharsParagraph() {
+ return funnyCharsParagraph;
+ }
+
+ public Label getGwtFieldLabel() {
+ return gwtFieldLabel;
+ }
+
+ public ParagraphElement getMain() {
+ return main;
+ }
+
+ public MenuItem getMenuItemLegacy() {
+ return menuItemLegacy;
+ }
+
+ public MenuItem getMenuItemMop() {
+ return menuItemMop;
+ }
+
+ public SpanElement getMessageInMain() {
+ return messageInMain;
+ }
+
+ public Button getMyButton() {
+ return myButton;
+ }
+
+ public DisclosurePanel getMyDisclosurePanel() {
+ return myDisclosurePanel;
+ }
+
+ public Widget getMyDisclosurePanelItem() {
+ return myDisclosurePanelItem;
+ }
+
+ public RadioButton getMyRadioAble() {
+ return myRadioAble;
+ }
+
+ public RadioButton getMyRadioBaker() {
+ return myRadioBaker;
+ }
+
+ public StackPanel getMyStackPanel() {
+ return myStackPanel;
+ }
+
+ public Widget getMyStackPanelItem() {
+ return myStackPanelItem;
+ }
+ public Element getNonStandardElement() {
+ return nonStandardElement;
+ }
+
+ public DockPanel getRoot() {
+ return root;
+ }
+
+ public DivElement getSideBar() {
+ return sideBar;
+ }
+
+ public SpanElement getSpanInMsg() {
+ return spanInMsg;
+ }
+
+ public FooLabel getTheFoo() {
+ return theFoo;
+ }
+
+ public Element getTmElement() {
+ return tmElement;
+ }
+
+ public SpanElement getTrimmedMessage() {
+ return trimmedMessage;
+ }
+
+ public DListElement getWidgetCrazyDefinitionList() {
+ return widgetCrazyDefinitionList;
+ }
+
+ public OListElement getWidgetCrazyOrderedList() {
+ return widgetCrazyOrderedList;
+ }
+ public TableElement getWidgetCrazyTable() {
+ return widgetCrazyTable;
+ }
+
+ @UiFactory
+ StrictLabel createStrictLabel(String text) {
+ return new StrictLabel(text);
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
new file mode 100644
index 0000000..6fd1345
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -0,0 +1,456 @@
+<!--
+ 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
+ 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. License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!DOCTYPE gwt:UiBinder
+ SYSTEM "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent"
+ [
+ <!ENTITY % MyEntities SYSTEM "MyEntities.ent">
+ %MyEntities;
+ ]
+>
+<!--
+ This scary DOCTYPE section is not required. It's here to demonstrate two
+ things.
+
+ First, this bit:
+
+ SYSTEM "http://google-web-toolkit.googlecode.com/svn/trunk/resources/xhtml.ent"
+
+ allows you to use familiar HTML entities like %nbsp; and •,
+ which are not part of XML.
+
+ Next, the bit in square brackets pulls in additional definitions for
+ &point-left; and &point-right; from local file MyEntities.ent.
+
+ You don't have to be so verbose to include a local file! For
+ example, you might instead grab your own copy of xhtml.ent
+ and add your custom entity definitions to it, and use this
+ for your DOCTYPE:
+
+ <!DOCTYPE gwt:UiBinder SYSTEM "my-xhtml.ent">
+-->
+
+<gwt:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:gwt='urn:import:com.google.gwt.user.client.ui'
+ xmlns:demo='urn:import:com.google.gwt.uibinder.sample.client'
+
+ xmlns:foo='urn:with:com.google.gwt.uibinder.sample.client.FakeBundle'
+ xmlns:res='urn:with:com.google.gwt.uibinder.sample.client.WidgetBasedUiResources'
+
+ ui:defaultLocale="en_us"
+ ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
+ ui:generateFormat="com.google.gwt.i18n.rebind.format.PropertiesFormat"
+ ui:generateFilename="myapp_translate_source"
+ ui:generateLocales="default"
+>
+<gwt:DockPanel ui:field="root" width="100%">
+ <gwt:Dock direction='NORTH'>
+ <gwt:HTML>
+ <div style='border: 4px solid gray; padding: 4px; margin: 4px;'>
+ <img src='http://www.google.com/images/logo_sm.gif' alt="logo" />
+ <span ui:field="trimmedMessage"><ui:msg description='"title" of the doc'
+ meaning='would not normally do a "meaning" here'
+ key='and I would never use "key" at all'>
+ <!-- A test expects this *not* to be on one line -->
+ Title area, specified largely in HTML.
+ </ui:msg></span>
+ </div>
+ </gwt:HTML>
+ </gwt:Dock>
+ <gwt:Dock direction='WEST'>
+ <gwt:HTML>
+ <div ui:field="sideBar"
+ style='border: 4px solid gray; padding: 4px; margin: 4px;'
+ >This could<br/>
+ be a<br/>
+ side bar<br/>
+ or something<br/>
+ like that...</div>
+ </gwt:HTML>
+ </gwt:Dock>
+ <gwt:Dock direction='CENTER'>
+ <gwt:HTMLPanel>
+ <p><ui:msg>This is a demonstration and test bed of GWT's nascent UiBinder
+ package. At the moment it works mainly as described in
+ <a href="http://code.google.com/p/google-web-toolkit-incubator/wiki/DeclarativeUi"
+ ui:ph="oldBlogLink">
+ this blog post</a>, but it is en route to the use cases desribed at
+ <a href="http://go/gwt-uibinder" ui:ph="newBlogLink">http://go/gwt-uibinder</a> and
+ <a href="http://go/gwt-uibinder-i18n" ui:ph="i18nBlogLink">http://go/gwt-uibinder-i18n</a>. Early
+ adopters should recognize themselves as such, and be prepared to have the
+ api rug yanked out from under them.</ui:msg></p>
+ <p ui:field="main">
+ &point-right; <span ui:field="messageInMain"><ui:msg>This is the main area. It's an <b>HTMLPanel</b>, which allows you to mix
+ &point-right; • XHTML • &point-left; and regular GWT widgets, more or less with
+ impunity. Take the following button for example.</ui:msg></span> &point-left;
+ </p>
+ <gwt:Button ui:field='myButton'>
+ Button with <b id='shouldSayHTML'>HTML</b> contents...
+ </gwt:Button>
+ <p>
+ It's specified using the Button widget, so that it's easy to work with from
+ Java code. Its markup has a ui:field value of 'myButton', which allows you to get it using the
+ <i ui:field="nonStandardElement">getMyButton()</i> method from MyTemplate.java. </p>
+ <p>
+ Here is a similarly clickable
+ <demo:ClickyLink text="hyperlink based on a custom widget"
+ ui:field="customLinkWidget" popupText="That tickles! "/>.
+ </p>
+ <p>
+ <ui:msg description="">
+ Of course, it could just as easily be a Tree under a MenuBar...
+ </ui:msg>
+ </p>
+ <div res:class="style.menuBar">
+ <gwt:MenuBar ui:field="topMenuBar" vertical="false">
+ <gwt:MenuItem>
+ <div id="higgledy">
+ <ui:msg description="Last 7 Day Period">
+ Higgeldy
+ </ui:msg>
+ </div>
+ <gwt:MenuBar vertical="true" res:styleName="style.menuBar">
+ <gwt:MenuItem>able</gwt:MenuItem>
+ <gwt:MenuItem>baker</gwt:MenuItem>
+ <gwt:MenuItem>charlie</gwt:MenuItem>
+ </gwt:MenuBar>
+ </gwt:MenuItem>
+ <gwt:MenuItem>
+ <div id="piggledy">
+ <ui:msg description="Last 7 Day Period">
+ Piggledy
+ </ui:msg>
+ </div>
+ <gwt:MenuBar vertical="true" res:styleName="style.menuBar">
+ <gwt:MenuItem>delta</gwt:MenuItem>
+ <gwt:MenuItem>echo</gwt:MenuItem>
+ <gwt:MenuItem>foxtrot</gwt:MenuItem>
+ </gwt:MenuBar>
+ </gwt:MenuItem>
+ <gwt:MenuItem>
+ <div id="pop-can-has-submenu">
+ <ui:msg description="Last 7 Day Period">Pop</ui:msg>
+ </div>
+ <gwt:MenuBar vertical="true" ui:field="dropdownMenuBar"
+ res:styleName="style.menuBar">
+ <gwt:MenuItem ui:field='menuItemCustomDateRange'>
+ <ui:msg description="Custom date range">
+ the Dog
+ </ui:msg>
+ </gwt:MenuItem>
+ <gwt:MenuItem>
+ <ui:msg>has eaten</ui:msg>
+ </gwt:MenuItem>
+ <gwt:MenuItem ui:field="menuItemMop" styleName="moppy">
+ <ui:msg>the Mop</ui:msg>
+ </gwt:MenuItem>
+ <gwt:MenuItem ui:field='menuItemLegacy'>
+ <span style="white-space:nowrap">The <gwt:MenuItemHTML>pig's in</gwt:MenuItemHTML> a hurry</span>
+ </gwt:MenuItem>
+ </gwt:MenuBar>
+ </gwt:MenuItem>
+ </gwt:MenuBar>
+ </div>
+ <gwt:Tree ui:field='myTree' width="100px" />
+
+ <p>...TextBoxes...</p>
+ <gwt:TextBox maxLength="21">21 chars only, please</gwt:TextBox>
+
+ <p>...or perhaps a handful of RadioButtons:</p>
+ <gwt:RadioButton ui:field='myRadioAble' name="radios" text="Able" checked="true">
+ <ui:attribute name="text" description="radio button name"/>
+ </gwt:RadioButton>
+ <gwt:RadioButton ui:field='myRadioBaker' name="radios" text="Baker">
+ <ui:attribute name="text" description="radio button name"/>
+ </gwt:RadioButton>
+ <demo:PointlessRadioButtonSubclass ui:field='emptyRadio' name="radios"
+ text="Charlie (this one is a subclass of RadioButton)">
+ <ui:attribute name="text" description="radio button name"/>
+ </demo:PointlessRadioButtonSubclass>
+ <p> ... a StackPanel ... </p>
+ <gwt:StackPanel stylePrimaryName="myStyle" width="280px" ui:field='myStackPanel'>
+ <gwt:Label text="Stack One Text" gwt:StackPanel-text="Stack One"
+ ui:field='myStackPanelItem'>
+ <!-- ui:attribute name="gwt:StackPanel-text" description="Label for Stack One"/ -->
+ <ui:attribute name="text" description="Content for Stack One Text"/>
+ </gwt:Label>
+ <gwt:HTMLPanel gwt:StackPanel-text="Stack Two">
+ <!-- ui:attribute name="gwt:StackPanel-text" description="Label for Stack Two"/ -->
+ <div>
+ <ui:msg description="Describe div content">Some other content</ui:msg>
+ </div>
+ </gwt:HTMLPanel>
+ <gwt:Label text="Stack Three Text" gwt:StackPanel-text="Stack Three" />
+ </gwt:StackPanel>
+ <p> ... a DisclosurePanel with a text header ... </p>
+ <gwt:DisclosurePanel text="I just have a text header" ui:field='myDisclosurePanel'>
+ <!--ui:attribute name="text" description="Label for Disclosure One"/ -->
+ <gwt:Label text="Disclosure Text" ui:field='myDisclosurePanelItem'>
+ <ui:attribute name="text" description="Content for Disclosure One Text"/>
+ </gwt:Label>
+ </gwt:DisclosurePanel>
+ <p> ... a DisclosurePanel with a widget header ... </p>
+ <gwt:DisclosurePanel>
+ <gwt:Label text="Disclosure Header - closed" gwt:DisclosurePanel-header="true">
+ <ui:attribute name="text" description="Content for Disclosure Two Text"/>
+ </gwt:Label>
+ <gwt:Label text="Disclosure Two - closed">
+ <ui:attribute name="text" description="Content for Disclosure Two"/>
+ </gwt:Label>
+ </gwt:DisclosurePanel>
+ <p> ... an open DisclosurePanel with a widget header ... </p>
+ <gwt:DisclosurePanel initiallyOpen="true">
+ <gwt:Label text="Disclosure Header - open" gwt:DisclosurePanel-header="true">
+ <ui:attribute name="text" description="Content for Disclosure Two Text"/>
+ </gwt:Label>
+ <gwt:Label text="Disclosure Two - open">
+ <ui:attribute name="text" description="Content for Disclosure Two"/>
+ </gwt:Label>
+ </gwt:DisclosurePanel>
+ <p> ... a DisclosurePanel with no header ... </p>
+ <gwt:DisclosurePanel initiallyOpen="true">
+ <gwt:Label text="Disclosure Three">
+ <ui:attribute name="text" description="Content for Disclosure Three"/>
+ </gwt:Label>
+ </gwt:DisclosurePanel>
+ <p> ... an open DisclosurePanel with a text label and no animation</p>
+ <gwt:DisclosurePanel text="Disclosure Three - open" initiallyOpen="true"
+ enableAnimation="false">
+ <gwt:Label text="Disclosure Three">
+ <ui:attribute name="text" description="Content for Disclosure Three"/>
+ </gwt:Label>
+ </gwt:DisclosurePanel>
+ <p> ... a DisclosurePanel with no content ... </p>
+ <gwt:DisclosurePanel text="I just have a text header" />
+
+ <h2>Stylish</h2>
+ <p>
+ Templates work with what is now called ImmutableResourceBundle, but
+ which you will soon come to know and love as ClientBundle. For example,
+ this label gets its text from somplace tricky and its style from a resource:</p>
+ <gwt:Label ui:field='bundledLabel' foo:text="helloText" res:styleName="style.prettyText"/>
+ <p res:class="style.prettyText" id='prettyPara'>
+ This stylish paragraph also gets its good looks from a
+ resource.
+ </p>
+ <h2>Evolving</h2>
+ <p>
+ Things change. This label uses the new ui:field attribute to declare
+ its identity. Soon everything
+ will work that way:
+ </p>
+ <gwt:Label ui:field='gwtFieldLabel'>Tommy can you hear me? Can you field me near you?</gwt:Label>
+ <h2>Localization and character escaping</h2>
+ <p ui:field="funnyCharsParagraph">Templates can be marked up for <b>localization</b>,
+ which presents alls kinds of exciting opportunities for bugs related to
+ character escaping. Consider these funny characters " " ' ' & < > > { },
+ and the various places they might make your life miserable, like this
+ untranslated paragraph.</p>
+ <p ui:field="funnyCharsMessageParagraph"><ui:msg>They might show up
+ in body text that has been <b>marked for translation</b>: funny characters " " ' ' & < > > { }</ui:msg></p>
+ <p ui:field="funnyCharsMessageDomAttributeParagraph" title="funny characters " ' ' & < > > { }">
+ <ui:attribute name="title"/>
+ Attributes of dom elements can be translated too, like the
+ tooltip of this paragraph, which features the challenging characters.
+ </p>
+ <p ui:field="funnyCharsDomAttributeParagraph"
+ title="funny characters " ' ' & < > > { }">
+ They might also appear in an unstranslated attribute of a dom element, like
+ the tooltip for this paragraph.
+ </p>
+ <p>And all the cases above apply to widgets as well:</p>
+ <gwt:Label>I am an untranslatable HasText with funny characters " " ' ' & < > > { }</gwt:Label>
+ <gwt:HTML>I am an untranslatable HasHTML with funny characters " " ' ' & < > > { }</gwt:HTML>
+ <gwt:Label><ui:msg>I am a translatable <ui:ph name="widgetType" example="myWidget">HasText</ui:ph> with funny characters " " ' ' & < > > { }</ui:msg></gwt:Label>
+ <gwt:HTML><ui:msg>I am a translatable <ui:ph name="widgetType" example="myWidget">HasHTML</ui:ph> with funny characters " " ' ' & < > > { }</ui:msg></gwt:HTML>
+ <demo:ClickyLink ui:field="funnyCharsAttributeWidget"
+ popupText="funny characters " ' ' & < > > { }">
+ Click to see my untranslatable text passed by attribute
+ </demo:ClickyLink>
+ <br/>
+ <demo:ClickyLink ui:field="funnyCharsMessageAttributeWidget"
+ popupText="funny characters " ' ' & < > > { }">
+ <ui:attribute name="popupText"/>
+ Click to see my translatable text passed by attribute
+ </demo:ClickyLink>
+
+ <h2>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
+ these with <span id="placeholdersSpan" style="font-weight:bold" res:class="style.prettyText"
+ ui:ph="boldSpan">placeholders</span><ui:ph name="tm"><sup ui:field="tmElement"
+ res:class="style.tmText">TM</sup></ui:ph>.</ui:msg></p>
+
+ <p><ui:msg>You may also need to have <span ui:field="spanInMsg"
+ style="font-weight:bold" res:class="style.prettyText">named
+ portions</span> of <span res:class="style.prettyText">translatable text</span></ui:msg></p>
+
+ <p><ui:msg>Of course you'll want to be able to do this kind of thing
+ with widgets <demo:MyDatePicker/> as well, whether they <gwt:Label>HasText<ui:ph name="tm">*TM*</ui:ph></gwt:Label>
+ or <gwt:HTML>HasHTML<ui:ph name="TM2"><sup ui:field="tmElementJr"
+ res:class="style.tmText">TM</sup></ui:ph></gwt:HTML></ui:msg></p>
+
+ <h2>Any Widget You Like</h2>
+ <p><demo:AnnotatedStrictLabel text="This widget is not default instantiable." ui:field="strictLabel"/></p>
+ <p><demo:AnnotatedStrictLabel text="Another constructor based widget, translated this time." ui:field="translatedStrictLabel">
+ <ui:attribute name="text" description="Arbitrary text"/>
+ </demo:AnnotatedStrictLabel></p>
+ <p><demo:StrictLabel text="More of the same." ui:field="veryStrictLabel"/></p>
+ <p><demo:StrictLabel text="Likewise, again translated this time." ui:field="translatedVeryStrictLabel">
+ <ui:attribute name="text" description="Arbitrary text"/>
+ </demo:StrictLabel></p>
+ <p><demo:NeedlesslyAnnotatedLabel ui:field="needlessLabel"
+ text="Sometimes people do things they don't need to do. Don't punish them."/></p>
+ <demo:FooLabel ui:field='theFoo' foo:foo="foo"/>
+
+ <h2>How to use the debugId, addStyleNames, addDependentStyleNames attributes</h2>
+ <p>You can use the <strong>debugId</strong>, <strong>addStyleNames</strong>, and <strong>addDependentStyleNames</strong>
+ attributes on any widget that extends UIObject.
+ <ul>
+ <li><b>debugId</b> will set an <em>id</em> on the element prefixed with <em>gwt-debug</em></li>
+ <li><b>addStyleNames</b> (comma separated) will add all class names to the existing style (space separated)</li>
+ <li><b>addDependentStyleNames</b> (comma separated) will concatenate all class names to the existing style with a "-" as a separator</li>
+ </ul>
+ You can/should use FireBug or a simple view source to see the attributes that were created.
+ <br />
+ </p>
+
+ <b>Template:</b>
+ <pre style="border: 1px dashed #666; padding: 5px 0;">
+ <gwt:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:gwt='urn:import:com.google.gwt.user.client.ui'>
+
+ <gwt:FlowPanel>
+ <gwt:Label ui:field="lblDebugId" debugId="joe" addStyleNames="newStyle, anotherStyle" addStyleDependentNames="dependentStyle, anotherDependentStyle" text="a label with debug id" />
+
+ <!-- A button that only adds a single style name, no comma's needed -->
+ <gwt:Button ui:field="btnGo" debugId="myButton" addStyleNames="buttonStyle" text="a button with extra attributes" />
+ </gwt:FlowPanel>
+
+ </gwt:UiBinder></pre>
+
+ <b>HTML:</b>
+ <pre style="border: 1px dashed #666; padding: 5px 0;">
+ <div id="gwt-debug-joe" class="gwt-Label newStyle anotherStyle gwt-Label-dependentStyle gwt-Label-anotherDependentStyle">A label with a debug id</div>
+ <button id="gwt-debug-myButton" class="gwt-Button buttonStyle" tabindex="0" type="button">Go</button></pre>
+
+ <gwt:FlowPanel>
+ <gwt:Label ui:field="lblDebugId" debugId="joe" addStyleNames="newStyle, anotherStyle" addStyleDependentNames="dependentStyle, anotherDependentStyle">
+ A label with a debug id
+ </gwt:Label>
+ <gwt:Button ui:field="btnGo" debugId="myButton" addStyleNames="buttonStyle">Go</gwt:Button>
+ </gwt:FlowPanel>
+
+ <h2>How to tie handlers automatically!!</h2>
+ <p>You can add event handlers to template widgets as long as they have
+ valid ui:field attributes. If the template is shared, each widget can
+ register listeners for the events it's interested in. In the example
+ below, we listen to MouseOver events on the status label and clicks
+ on the button.</p>
+
+ <demo:HandlerDemo />
+
+ <b>Template:</b>
+ <pre style="border: 1px dashed #666; padding: 5px 0;">
+ <gwt:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:gwt='urn:import:com.google.gwt.user.client.ui'>
+
+ <gwt:FlowPanel>
+ <gwt:Label ui:field="label" text="Looking for happiness?" />
+ <gwt:Button ui:field="button" text="Click here to happiness!!" />
+ <gwt:TextBox ui:field="textbox" />
+ </gwt:FlowPanel>
+
+ </gwt:UiBinder></pre>
+
+ <b>The code:</b>
+ <pre style="border: 1px dashed #666; padding: 5px 0;">
+ public class HandlerDemo extends Composite {
+
+ @UiTemplate("HandlerDemo.ui.xml")
+ interface MyUiBinder extends UiBinder<FlowPanel, HandlerDemo> {}
+ private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
+
+ @UiField Label label;
+
+ // No need of declare button here via @UiField if it is not really used
+ // in the class. Just don't forget to "ui:field" it in the template.
+
+ private int counter;
+
+ public HandlerDemo() {
+ initWidget(binder.createAndBindUi(this));
+ }
+
+ @UiHandler({"label", "button"})
+ protected void doClick(ClickEvent event) {
+ Window.alert("Yes, now I'm happy!");
+ counter = 0;
+ label.setText("Don't mouse over, click me!!!");
+ }
+
+ @UiHandler("button")
+ protected void doMouseOver(MouseOverEvent event) {
+ ++counter;
+ label.setText(counter + ". Don't mouse over, click me!");
+ }
+
+ @UiHandler("textbox")
+ protected void doChangeValue(ValueChangeEvent<String> event) {
+ label.setText("Typing = " + event.getValue());
+ }
+ }</pre>
+
+
+ <h2>People write the darndest markup</h2>
+ <table border="1" bcolor="yellow" cellpadding="3" ui:field="widgetCrazyTable">
+ <demo:ExplicitElementPanel tag="thead">
+ <demo:ExplicitElementWidget tag="td">They have been known</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="td">to write widgets</demo:ExplicitElementWidget>
+ </demo:ExplicitElementPanel>
+ <demo:ExplicitElementPanel tag="tr">
+ <demo:ExplicitElementWidget tag="td">that masquerade</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="td">as table cells,</demo:ExplicitElementWidget>
+ </demo:ExplicitElementPanel>
+ <demo:ExplicitElementPanel tag="tfoot">
+ <demo:ExplicitElementWidget tag="td">just like these.</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="td">Burma Shave</demo:ExplicitElementWidget>
+ </demo:ExplicitElementPanel>
+ </table>
+ <ul>
+ <demo:ExplicitElementWidget tag="li">You</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="li">can</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="li">imagine</demo:ExplicitElementWidget>
+ </ul>
+ <ol ui:field="widgetCrazyOrderedList">
+ <demo:ExplicitElementWidget tag="li">similar</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="li">things</demo:ExplicitElementWidget>
+ </ol>
+ <dl ui:field="widgetCrazyDefinitionList">
+ <demo:ExplicitElementWidget tag="dt">Being</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="dd">done</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="dd">with</demo:ExplicitElementWidget>
+ <demo:ExplicitElementWidget tag="dd">lists</demo:ExplicitElementWidget>
+ </dl>
+ <gwt:HTMLPanel tag="table" ui:field="customTagHtmlPanel">
+ <tr><td>Even HTMLPanel gets in on the game</td></tr>
+ <tr><td>Lately, anyway.</td></tr>
+ </gwt:HTMLPanel>
+ </gwt:HTMLPanel>
+ </gwt:Dock>
+</gwt:DockPanel>
+</gwt:UiBinder>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java
new file mode 100644
index 0000000..4af5810
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java
@@ -0,0 +1,42 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.sample.client;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Strict;
+
+/**
+ * Sample resources used by {@link WidgetBasedUi}. This would more typically be
+ * nested in {@code WidgetBasedUi}, but it's top level to ensure that the ui
+ * code generator is able to deal with that kind of thing.
+ */
+public interface WidgetBasedUiResources extends ClientBundle {
+ /**
+ * Sample CssResource.
+ */
+ public interface MyCss extends CssResource {
+ String menuBar();
+
+ String prettyText();
+
+ String tmText();
+ }
+
+ @Source("WidgetBasedUi.css")
+ @Strict
+ MyCss style();
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/public/UiBinderDemo.html b/user/src/com/google/gwt/uibinder/sample/public/UiBinderDemo.html
new file mode 100644
index 0000000..441677c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/public/UiBinderDemo.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!-- -->
+<!-- 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+<html>
+ <head></head>
+ <body>
+ <script language='javascript' src='com.google.gwt.uibinder.sample.UiBinderDemo.nocache.js'></script>
+ </body>
+</html>
diff --git a/user/src/com/google/gwt/uibinder/testing/BUILD b/user/src/com/google/gwt/uibinder/testing/BUILD
new file mode 100644
index 0000000..1d7d5fb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/testing/BUILD
@@ -0,0 +1,11 @@
+# -*- mode: python; -*-
+
+# Description:
+# testing things
+
+gwt_module(
+ name = "testing",
+ srcs = glob([
+ "*.java",
+ ]),
+)
diff --git a/user/src/com/google/gwt/uibinder/testing/UiBinderTesting.java b/user/src/com/google/gwt/uibinder/testing/UiBinderTesting.java
new file mode 100644
index 0000000..dbba0f1
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/testing/UiBinderTesting.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2007 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.testing;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Support methods for testing uibinder. Not intended for production use!
+ */
+public class UiBinderTesting {
+ /**
+ * Creates an XML document model with the given contents.
+ *
+ * @param string the document contents
+ */
+ public static Document documentForString(String string)
+ throws ParserConfigurationException, SAXException, IOException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setExpandEntityReferences(true);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(new ByteArrayInputStream(string.getBytes()));
+ return doc;
+ }
+
+ /**
+ * Not instantiable.
+ */
+ private UiBinderTesting() {
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
new file mode 100644
index 0000000..8831922
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.uibinder.client.UiBinderTest;
+
+import junit.framework.Test;
+
+/**
+ * Test suite for UiBinder GWTTestCases.
+ */
+public class UiBinderGwtSuite {
+ public static Test suite() {
+ GWTTestSuite suite = new GWTTestSuite("Test suite for UiBinder GWTTestCases");
+
+ suite.addTestSuite(UiBinderTest.class);
+
+ return suite;
+ }
+
+ private UiBinderGwtSuite() {
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/UiBinderTestModule.gwt.xml b/user/test/com/google/gwt/uibinder/UiBinderTestModule.gwt.xml
new file mode 100644
index 0000000..73a0112
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/UiBinderTestModule.gwt.xml
@@ -0,0 +1,18 @@
+<!-- -->
+<!-- Copyright 2007 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 -->
+<!-- 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. License for the specific language governing permissions and -->
+<!-- limitations under the License. -->
+
+<!-- UiBinder test support. -->
+<module>
+ <inherits name="com.google.gwt.uibinder.sample.UiBinderDemo" />
+</module>
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
new file mode 100644
index 0000000..e014444
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.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.Element;
+import com.google.gwt.dom.client.ParagraphElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.uibinder.sample.client.ClickyLink;
+import com.google.gwt.uibinder.sample.client.DomBasedUi;
+import com.google.gwt.uibinder.sample.client.FakeBundle;
+import com.google.gwt.uibinder.sample.client.Foo;
+import com.google.gwt.uibinder.sample.client.WidgetBasedUi;
+import com.google.gwt.uibinder.sample.client.WidgetBasedUiResources;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.DisclosurePanel;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.StackPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Functional test of UiBinder.
+ */
+public class UiBinderTest extends GWTTestCase {
+ private WidgetBasedUi widgetUi;
+ private DomBasedUi domUi;
+ private DockPanel root;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.uibinder.UiBinderTestModule";
+ }
+
+ @Override
+ public void gwtSetUp() throws Exception {
+ super.gwtSetUp();
+ RootPanel.get().clear();
+ domUi = new DomBasedUi("Cherished User");
+ Document.get().getBody().appendChild(domUi.getRoot());
+
+ widgetUi = new WidgetBasedUi();
+ root = widgetUi.getRoot();
+ RootPanel.get().add(widgetUi);
+ }
+
+ @Override
+ public void gwtTearDown() throws Exception {
+ RootPanel.get().clear();
+ super.gwtTearDown();
+ }
+
+ public void testAccessToNonStandardElement() {
+ Element elm = widgetUi.getNonStandardElement();
+ assertEquals("I", elm.getTagName());
+ }
+
+ // TODO(rjrjr) The direction stuff in these tests really belongs in
+ // DockPanelParserTest
+
+ public void testAllowIdOnDomElements() {
+ Element elm = DOM.getElementById("shouldSayHTML");
+ assertEquals("HTML", elm.getInnerHTML());
+ }
+
+ public void testBundle() {
+ assertEquals(getCenter(), widgetUi.getBundledLabel().getParent());
+ assertEquals(new FakeBundle().helloText(),
+ widgetUi.getBundledLabel().getText());
+ WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ assertEquals("bundledLabel should have styleName",
+ resources.style().prettyText(),
+ widgetUi.getBundledLabel().getStyleName());
+
+ Element pretty = DOM.getElementById("prettyPara");
+ assertEquals(resources.style().prettyText(), pretty.getClassName());
+
+ Foo f = new Foo();
+ assertTrue("Expect " + f,
+ widgetUi.getTheFoo().getText().contains(f.toString()));
+ }
+
+ public void testCenter() {
+ // TODO(rjrjr) More of a test of HTMLPanelParser
+
+ Widget center = getCenter();
+ assertEquals(DockPanel.CENTER, root.getWidgetDirection(center));
+ assertEquals(HTMLPanel.class, center.getClass());
+ String html = center.getElement().getInnerHTML();
+ assertTrue(html.contains("main area"));
+ assertTrue(html.contains("Button with"));
+ assertTrue(html.contains("Of course"));
+
+ assertEquals(center, widgetUi.getMyButton().getParent());
+ }
+
+ public void testComputedAttributeInPlaceholderedElement() {
+ WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ assertEquals(resources.style().prettyText(),
+ widgetUi.getSpanInMsg().getClassName());
+ }
+
+ public void testComputedStyleInAPlaceholder() {
+ WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ assertEquals(resources.style().tmText(),
+ widgetUi.getTmElement().getClassName());
+ }
+
+ public void testDomAccessAndComputedAttributeOnPlaceholderedElement() {
+ WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ Element elem = DOM.getElementById("placeholdersSpan");
+ assertEquals("bold", elem.getStyle().getProperty("fontWeight"));
+ assertEquals(resources.style().prettyText(), elem.getClassName());
+ }
+
+ public void testDomAccessInHtml() {
+ DivElement sideBar = widgetUi.getSideBar();
+ assertTrue("sideBar should start: \"This could\"",
+ sideBar.getInnerText().startsWith("This could"));
+ assertTrue("sideBar should end: \"example:\"",
+ sideBar.getInnerText().endsWith("like that..."));
+ assertEquals("Should have no id", "", sideBar.getAttribute("id"));
+ }
+
+ public void testDomAccessInHtmlPanel() {
+ SpanElement messageInMain = widgetUi.getMessageInMain();
+ String text = messageInMain.getInnerText().trim();
+ assertTrue("sideBar should start: \"This is the main area\"",
+ text.startsWith("This is the main area"));
+ assertTrue("sideBar should end: \"example.\"", text.endsWith("example."));
+ }
+
+ public void testDomAttributeMessageWithFunnyChars() {
+ ParagraphElement p =
+ widgetUi.getFunnyCharsMessageDomAttributeParagraph();
+ String t = p.getAttribute("title");
+ assertEquals("funny characters \" ' ' & < > > { }", t);
+ }
+
+ public void testDomAttributeNoMessageWithFunnyChars() {
+ ParagraphElement p = widgetUi.getFunnyCharsDomAttributeParagraph();
+ String t = p.getAttribute("title");
+ assertEquals("funny characters \" ' ' & < > > { }", t);
+ }
+
+ public void testDomTextMessageWithFunnyChars() {
+ String t = widgetUi.getFunnyCharsMessageParagraph().getInnerText();
+ assertEquals("They might show up in body text that has been marked for "
+ + "translation: funny characters \" \" ' ' & < > > { }",
+ t);
+ }
+
+ public void testDomTextNoMessageWithFunnyChars() {
+ ParagraphElement p = widgetUi.getFunnyCharsParagraph();
+ // WebKit does \n replace thing, so let's do it everywhere
+ String t = p.getInnerHTML().replace("\n", " ").toLowerCase();
+ String expected = "Templates can be marked up for <b>localization</b>, which presents alls "
+ + "kinds of exciting opportunities for bugs related to character escaping. "
+ + "Consider these funny characters \" \" ' ' & < > > { }, and "
+ + "the various places they might make your life miserable, like this "
+ + "untranslated paragraph.";
+ expected = expected.toLowerCase();
+ assertEquals(expected, t);
+ }
+
+ public void testFieldAttribute() {
+ assertEquals(getCenter(), widgetUi.getGwtFieldLabel().getParent());
+ }
+
+ public void testFieldInPlaceholderedElement() {
+ assertEquals("named portions", widgetUi.getSpanInMsg().getInnerText());
+ }
+
+ public void testMenuAttributes() {
+ WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ assertEquals(widgetUi.getDropdownMenuBar().getStyleName(),
+ resources.style().menuBar());
+ }
+
+ public void testMenuItems() {
+ // Has a legacy MenuItemHTML in its midst
+ assertEquals("The pig's in a hurry",
+ widgetUi.getMenuItemLegacy().getElement().getInnerText());
+ assertTrue("Style should include \"moppy\"",
+ widgetUi.getMenuItemMop().getStyleName().contains("moppy"));
+ }
+
+ public void testMessageTrimming() {
+ assertEquals("Title area, specified largely in HTML.",
+ widgetUi.getTrimmedMessage().getInnerHTML());
+ assertEquals("Tommy can you hear me? Can you field me near you?",
+ widgetUi.getGwtFieldLabel().getText());
+ }
+
+ public void testMinimalDom() {
+ assertEquals("Expect no wrapper div around root", widgetUi.getElement(),
+ root.getElement());
+ }
+
+ public void testNamedElementInAPlaceholder() {
+ assertEquals("TM", widgetUi.getTmElement().getInnerText());
+ }
+
+ public void testNestedBundle() {
+ DomBasedUi.Resources resources =
+ GWT.create(DomBasedUi.Resources.class);
+ assertEquals(resources.style().bodyColor()
+ + " " + resources.style().bodyFont() ,
+ domUi.getRoot().getClassName());
+ }
+
+ public void suppressedForIEfail_testNonXmlEntities() {
+ // This fragment includes both translated and non-translated strings
+ ParagraphElement mainParagraph = widgetUi.getMain();
+ final String innerHTML = mainParagraph.getInnerHTML().trim();
+ assertTrue(innerHTML.contains(" \u261E \u2022 XHTML \u2022 \u261C"));
+ assertTrue(innerHTML.startsWith("\u261E <span>"));
+ assertTrue(innerHTML.endsWith("</span> \u261C"));
+ }
+
+ public void testNorth() {
+ Widget north = root.getWidget(0);
+ assertEquals(DockPanel.NORTH, root.getWidgetDirection(north));
+ assertEquals(HTML.class, north.getClass());
+ assertTrue(((HTML) north).getHTML().contains("Title area"));
+ }
+
+ public void testRadioButton() {
+ RadioButton able = widgetUi.getMyRadioAble();
+ RadioButton baker = widgetUi.getMyRadioBaker();
+ assertTrue("able should be checked", able.getValue());
+ assertFalse("baker should not be checked", baker.getValue());
+ assertEquals("radios", able.getName());
+ assertEquals("radios", baker.getName());
+ }
+
+ public void testStackPanel() {
+ StackPanel p = widgetUi.getMyStackPanel();
+ assertNotNull("Panel exists", p);
+ Widget w = widgetUi.getMyStackPanelItem();
+ assertNotNull("Widget exists", w);
+ boolean containsWidget = false;
+ for (int i = 0; i < p.getWidgetCount(); i++) {
+ if (p.getWidget(i) == w) {
+ containsWidget = true;
+ }
+ }
+ assertTrue("Panel contains widget", containsWidget);
+ }
+
+ public void testDisclosurePanel() {
+ DisclosurePanel p = widgetUi.getMyDisclosurePanel();
+ assertNotNull("Panel exists", p);
+ Widget w = widgetUi.getMyDisclosurePanelItem();
+ assertNotNull("Widget exists", w);
+ assertEquals("Panel contains widget", w, p.getContent());
+ }
+
+ public void testStringAttributeIgnoresStaticSetter() {
+ // Assumes setPopupText() is overloaded such that there is a static
+ // setPopupText(Foo, String) method.
+ ClickyLink clicky = widgetUi.getCustomLinkWidget();
+ assertEquals("overloaded setter should have been called",
+ "That tickles!", clicky.getPopupText());
+ }
+
+ public void testStringAttributeWithFormatChars() {
+ assertEquals("100%", root.getElement().getStyle().getProperty("width"));
+ }
+
+ public void testWest() {
+ Widget west = root.getWidget(1);
+ assertEquals(DockPanel.WEST, root.getWidgetDirection(west));
+ assertEquals(HTML.class, west.getClass());
+ String html = ((HTML) west).getHTML();
+ assertTrue(html.contains("side bar"));
+ }
+
+ public void testWidgetAttributeMessageWithFunnyChars() {
+ ClickyLink clicky = widgetUi.getFunnyCharsMessageAttributeWidget();
+ String t = clicky.getPopupText();
+ assertEquals("funny characters \" ' ' & < > > { }", t);
+ }
+
+ public void testWidgetAttributeNoMessageWithFunnyChars() {
+ ClickyLink clicky = widgetUi.getFunnyCharsAttributeWidget();
+ String t = clicky.getPopupText();
+ assertEquals("funny characters \" ' ' & < > > { }", t);
+ }
+
+ public void suppressForIEfail_testBizarrelyElementedWidgets() {
+ assertInOrder(widgetUi.getWidgetCrazyTable().getInnerHTML().toLowerCase(),
+ "<td>they have been known</td>", "<td>to write widgets</td>",
+ "<td>that masquerade</td>", "<td>as table cells,</td>",
+ "<td>just like these.</td>", "<td>burma shave</td>");
+
+ assertInOrder(widgetUi.getWidgetCrazyOrderedList().getInnerHTML(),
+ "<li>similar</li>", "<li>things</li>");
+
+ assertInOrder(widgetUi.getWidgetCrazyDefinitionList().getInnerHTML(),
+ "<dt>Being</dt>", "<dd>done</dd>", "<dd>with</dd>", "<dd>lists</dd>");
+ }
+
+ public void testCustomHtmlPanelTag() {
+ assertInOrder(widgetUi.getCustomTagHtmlPanel().getElement().getInnerHTML(),
+ "<td>Even HTMLPanel gets in on the game</td>",
+ "<td>Lately, anyway.</td>");
+ }
+
+ /**
+ * Assert that the expect strings are found in body, and in the order given.
+ * WARNING: both body and expected are normalized to lower case, to get around
+ * IE's habit of returning capitalized DOM elements.
+ */
+ private void assertInOrder(String body, String... expected) {
+ body = body.toLowerCase();
+ int lastIndex = 0;
+ String lastExpected = "";
+
+ for (String next : expected) {
+ next = next.toLowerCase();
+ int index = body.indexOf(next);
+ assertTrue(body + " should contain " + next, index > -1);
+ assertTrue("Expect " + next + " after " + lastExpected, index > lastIndex);
+ lastIndex = index;
+ }
+ }
+
+ private Widget getCenter() {
+ Widget center = root.getWidget(2);
+ return center;
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/GwtResourceEntityResolverTest.java b/user/test/com/google/gwt/uibinder/rebind/GwtResourceEntityResolverTest.java
new file mode 100644
index 0000000..da630e7
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/GwtResourceEntityResolverTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import junit.framework.TestCase;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Text of GwtResourceEntityResolver.
+ */
+public class GwtResourceEntityResolverTest extends TestCase {
+
+ private static class MockResourceLoader implements
+ GwtResourceEntityResolver.ResourceLoader {
+ String fetched;
+ InputStream stream;
+
+ public InputStream fetch(String name) {
+ return stream;
+ }
+ }
+
+ private GwtResourceEntityResolver resolver;
+ private MockResourceLoader loader;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ loader = new MockResourceLoader();
+ resolver = new GwtResourceEntityResolver(loader);
+ }
+
+ public void testNotOurProblem() throws SAXException, IOException {
+ assertNull(resolver.resolveEntity(null, "http://arbitrary"));
+ assertNull(resolver.resolveEntity("meaningless", "http://arbitrary"));
+ assertNull(resolver.resolveEntity(null, "arbitrary/relative"));
+ }
+
+ public void testOursGood() throws SAXException, IOException {
+ String publicId = "some old public thing";
+ String systemId = "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent";
+ loader.stream = new InputStream() {
+ @Override
+ public int read() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ InputSource s = resolver.resolveEntity(publicId, systemId);
+ assertEquals(publicId, s.getPublicId());
+ assertEquals(systemId, s.getSystemId());
+ assertEquals(loader.stream, s.getByteStream());
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/TokenatorTest.java b/user/test/com/google/gwt/uibinder/rebind/TokenatorTest.java
new file mode 100644
index 0000000..05442b9
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/TokenatorTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import junit.framework.TestCase;
+
+/**
+ * Having a style tool that forces one to create javadoc that communicates no
+ * more information than the class name itself is the kind of foolishness that
+ * gives Java development a bad name.
+ */
+public class TokenatorTest extends TestCase {
+ private static final String FORMAT = "Token %d replaces \"%s\"";
+ private static final String BEGIN = "This is a bunch of text.";
+ private static final String EXPECTED =
+ "Token 1 replaces \"This\" is a " + "Token 2 replaces \"bunch\" of "
+ + "Token 3 replaces \"text.\"";
+
+ private int serial = 0;
+ private Tokenator tokenator;
+ private String betokened;
+
+ @Override
+ public void setUp() throws Exception {
+ tokenator = new Tokenator();
+ betokened =
+ BEGIN.replace("This", betoken("This")).replace("bunch",
+ betoken("bunch")).replace("text.", betoken("text."));
+ serial = 0;
+ };
+
+ public void testHasToken() {
+ String noTokens = BEGIN;
+ assertFalse(Tokenator.hasToken(noTokens));
+ assertTrue(Tokenator.hasToken(betokened));
+ }
+
+ public void testSimple() {
+ assertEquals(EXPECTED, tokenator.detokenate(betokened));
+ }
+
+ public void testStatic() {
+ assertEquals("0 is a 1 of 2", Tokenator.detokenate(betokened,
+ new Tokenator.Resolver() {
+ int serial = 0;
+
+ public String resolveToken(String token) {
+ return String.format("%d", serial++);
+ }
+ }));
+ }
+
+ private String betoken(String in) {
+ return tokenator.nextToken(String.format(FORMAT, ++serial, in));
+ }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
new file mode 100644
index 0000000..7ea8294
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.uibinder.rebind;
+
+import com.google.gwt.uibinder.testing.UiBinderTesting;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests XMLElement.
+ */
+public class XMLElementTest extends TestCase {
+ private static final String dom =
+ "<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>";
+ private Document doc;
+ private Element item;
+ private XMLElement elm;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ doc = UiBinderTesting.documentForString(dom);
+ item = (Element) doc.getDocumentElement().getElementsByTagName(
+ "elm").item(0);
+ elm = new XMLElement(item, null);
+ }
+
+ public void testConsumeAttribute() {
+ assertEquals("attr1Value", elm.consumeAttribute("attr1"));
+ assertEquals("", elm.consumeAttribute("attr1"));
+ }
+
+ public void testEmptyStringOnMissingAttribute()
+ throws ParserConfigurationException, SAXException, IOException {
+ assertEquals("", elm.consumeAttribute("fnord"));
+ }
+
+ public void testIterator() throws ParserConfigurationException, SAXException,
+ IOException {
+ String[] expecteds = {"attr1", "attr2"};
+ Set<String> seen = new HashSet<String>();
+ for (int i = elm.getAttributeCount() - 1; i >= 0; i--) {
+ XMLAttribute attr = elm.getAttribute(i);
+ String expected = expecteds[i];
+ assertEquals(expected, attr.getLocalName());
+ assertFalse(attr.isConsumed());
+ assertEquals(expected + "Value", attr.consumeValue());
+ assertTrue(attr.isConsumed());
+ seen.add(expected);
+ }
+ assertEquals(2, seen.size());
+ }
+
+ public void testNoEndTags() throws Exception {
+ Document doc = UiBinderTesting.documentForString("<doc><br/></doc>");
+
+ Element item = (Element) doc.getDocumentElement().getElementsByTagName("br").item(
+ 0);
+ XMLElement elm = new XMLElement(item, null);
+ assertEquals("br", item.getTagName());
+ assertEquals("", elm.getClosingTag());
+ }
+}