Lays out the groundwork for
http://code.google.com/p/google-web-toolkit/issues/detail?id=3984
<ui:style source="foo.css" type="com.me.and.MyCss" /> now works. It implies a
CssResource of type MyCss, provided by file foo.css, accessible as if it had
ui:field="style". We generate the ClientBundle to hold it.
Next: make source and type optional, and allow css to appear inside
the element body.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6087 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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..db42c19
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java
@@ -0,0 +1,50 @@
+/*
+ * 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.dom.client.StyleInjector;
+import com.google.gwt.resources.client.CssResource;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Extended by code generated by calls to GWT.create(Class<? extends UiBinder>).
+ *
+ * @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 abstract class AbstractUiBinder<U, O> implements UiBinder<U, O> {
+ private static final Set<Class<? extends CssResource>> injected =
+ new HashSet<Class<? extends CssResource>>();
+
+ /**
+ * Invokes {@link StyleInjector#injectStylesheet} on the given css's
+ * {@link CssResource#getText()}. Injection is performed only once for each
+ * CssResource subclass received (that is, we key on
+ * <code>css.getClass()</code>);
+ *
+ * @param css the resource to inject
+ */
+ protected static void ensureCssInjected(CssResource css) {
+ if (!injected.contains(css.getClass())) {
+ StyleInjector.injectStylesheet(css.getText());
+ injected.add(css.getClass());
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
index 433cea0..20c5133 100644
--- a/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
@@ -42,7 +42,7 @@
if (elem.hasAttribute("id")) {
writer.die(String.format(
"Cannot declare id=\"%s\" and %s=\"%s\" on the same element",
- elem.consumeAttribute("id"), writer.getGwtFieldAttributeName(),
+ elem.consumeAttribute("id"), writer.getUiFieldAttributeName(),
fieldName));
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
new file mode 100644
index 0000000..0596583
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/AbstractFieldWriter.java
@@ -0,0 +1,107 @@
+/*
+ * 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.TreeLogger.Type;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Most of the implementation of {@link FieldWriter}. Subclasses are responsible
+ * for {@link FieldWriter#getQualifiedSourceName()} and
+ * {@link FieldWriter#getType()}.
+ */
+abstract class AbstractFieldWriter implements FieldWriter {
+ private static final String NO_DEFAULT_CTOR_ERROR = "%1$s has no default (zero args) constructor. To fix this, you can define"
+ + " a @UiFactory method on the UiBinder's owner, or annotate a constructor of %2$s with"
+ + " @UiConstructor.";
+
+ private final String name;
+ private final Set<FieldWriter> needs = new HashSet<FieldWriter>();
+ private String initializer;
+ private boolean written;
+
+ public AbstractFieldWriter(String name) {
+ if (name == null) {
+ throw new RuntimeException("name cannot be null");
+ }
+ this.name = name;
+ }
+
+ public void needs(FieldWriter f) {
+ needs.add(f);
+ }
+
+ 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));
+ }
+ setInitializerMaybe(initializer);
+ }
+
+ @Deprecated
+ public void setInitializerMaybe(String initializer) {
+ if (this.initializer != null && !this.initializer.equals(initializer)) {
+ throw new IllegalStateException(String.format(
+ "Attempt to change initializer for field \"%s\", "
+ + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
+ }
+ this.initializer = initializer;
+ }
+
+ public void write(IndentedWriter w, TreeLogger logger)
+ throws UnableToCompleteException {
+ if (written) {
+ return;
+ }
+
+ 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, logger);
+ }
+
+ if (initializer == null) {
+ JClassType type = getType();
+ if (type != null) {
+ if ((type.isInterface() == null)
+ && (type.findConstructor(new JType[0]) == null)) {
+ logger.log(Type.ERROR, String.format(NO_DEFAULT_CTOR_ERROR,
+ type.getQualifiedSourceName(), type.getName()));
+ throw new UnableToCompleteException();
+ }
+ }
+ }
+
+ if (null == initializer) {
+ initializer = String.format("(%1$s) GWT.create(%1$s.class)",
+ getQualifiedSourceName());
+ }
+
+ w.write("%s %s = %s;", getQualifiedSourceName(), name, initializer);
+
+ this.written = true;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
new file mode 100644
index 0000000..a3a6922
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.TypeOracle;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource.Strict;
+import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
+import com.google.gwt.uibinder.rebind.model.CssResourceGetter;
+
+import java.io.PrintWriter;
+
+/**
+ * Writes source implementing a {@link ImplicitBundle} to a {@link PrintWriter}.
+ */
+public class BundleWriter {
+
+ private final ImplicitBundle bundleClass;
+ private final IndentedWriter writer;
+ private final TypeOracle oracle;
+
+ public BundleWriter(ImplicitBundle bundleClass, PrintWriter printWriter,
+ TypeOracle oracle) {
+ this.bundleClass = bundleClass;
+ this.writer = new IndentedWriter(printWriter);
+ this.oracle = oracle;
+ }
+
+ public void write() {
+ // Package declaration
+ String packageName = bundleClass.getPackageName();
+ if (packageName.length() > 0) {
+ writer.write("package %1$s;", packageName);
+ writer.newline();
+ }
+
+ // Imports
+ writer.write("import %s;",
+ oracle.findType(ClientBundle.class.getName()).getQualifiedSourceName());
+ writer.write("import %s;",
+ oracle.findType(Strict.class.getCanonicalName()).getQualifiedSourceName());
+ writer.newline();
+
+ // Open interface
+ writer.write("public interface %s extends ClientBundle {",
+ bundleClass.getClassName());
+ writer.indent();
+
+ // Write css methods
+ for (CssResourceGetter css : bundleClass.getCssMethods()) {
+ writer.write("@Strict @Source(\"%s\")",css.getSource());
+ writer.write("%s %s();", css.getExtendedInterface().getQualifiedSourceName(), css.getName());
+ writer.newline();
+ }
+
+ // Close interface.
+ writer.outdent();
+ writer.write("}");
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
index cdeb811..0624bc5 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
@@ -18,61 +18,40 @@
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.
+ * 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.";
+ private static final String DUPLICATE_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>();
+ private final LinkedHashMap<String, FieldWriter> fieldsMap = new LinkedHashMap<String, FieldWriter>();
/**
* A stack of the fields.
*/
- private final LinkedList<FieldWriter> parsedFieldStack =
- new LinkedList<FieldWriter>();
+ 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;
+ public FieldManager(TreeLogger logger) {
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
@@ -90,41 +69,55 @@
/**
* @param fieldWriter the field to push on the top of the
- * {@link #parsedFieldStack}
+ * {@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.
+ * Used to declare fields of an existing type. If your field will hold a type
+ * that is being generated, see {@link #registerFieldOfGeneratedType}.
+ * <p>
+ * 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 fieldType the type of the new field
+ * @param fieldName the name of the new field
+ * @return a new {@link FieldWriter} instance
+ * @throws UnableToCompleteException on duplicate name
+ */
+ public FieldWriter registerField(JClassType fieldType, String fieldName)
+ throws UnableToCompleteException {
+ FieldWriter field = new FieldWriterOfExistingType(fieldType, fieldName,
+ logger);
+ return registerField(fieldName, field);
+ }
+
+ /**
+ * Used to declare fields of a type that is to be generated. If your field
+ * will hold a reference of an existing tyupe, see {@link #registerField}.
+ * <p>
+ * 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
+ * field
* @param fieldName the name of the field
- * @return a new {@link FieldWriter} instance, <b>null</b> in case fieldName
- * is already indexed
+ * @throws UnableToCompleteException on duplicate name
+ * @return a new {@link FieldWriter} instance
*/
- 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;
+ public FieldWriter registerFieldOfGeneratedType(String typePackage,
+ String typeName, String fieldName) throws UnableToCompleteException {
+ FieldWriter field = new FieldWriterOfGeneratedType(typePackage, typeName,
+ fieldName);
+ return registerField(fieldName, field);
}
/**
@@ -137,12 +130,27 @@
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());
- }
+ field.write(writer, logger);
+ }
+ }
+
+ private FieldWriter registerField(String fieldName, FieldWriter field)
+ throws UnableToCompleteException {
+ requireUnique(fieldName);
+ fieldsMap.put(fieldName, field);
+
+ if (parsedFieldStack.size() > 0) {
+ parsedFieldStack.getFirst().needs(field);
+ }
+
+ return field;
+ }
+
+ private void requireUnique(String fieldName) throws UnableToCompleteException {
+ if (fieldsMap.containsKey(fieldName)) {
+ Object[] params = {fieldName};
+ logger.log(TreeLogger.ERROR, String.format(DUPLICATE_FIELD_ERROR, params));
+ throw new UnableToCompleteException();
}
}
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
index b42ec81..6daf715 100644
--- a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -15,16 +15,14 @@
*/
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.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.
+ * variable the generated UiBinder#createAndBindUi implementation will need.
* <p>
* A field can have a custom initialization statement, set via
* {@link #setInitializer}. Without one it will be initialized via a
@@ -35,56 +33,33 @@
* 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>();
+public interface FieldWriter {
- private String initializer;
- private boolean written;
+ String getQualifiedSourceName();
/**
- * Package protected, only TemplateWriter is allowed to instantiate
- * FieldWriters.
- * <p>
- * Public for testing only
+ * @return the type of this field, or null if this field is of a type that has
+ * not yet been generated
*/
- FieldWriter(JClassType type, String name) {
- this.type = type;
- this.name = name;
- }
+ JClassType getType();
/**
- * @return the type of this field
+ * Declares that the receiver depends upon the given field.
*/
- public JClassType getType() {
- return type;
- }
-
- /**
- * Declares that the receiver depends upon the give field.
- */
- public void needs(FieldWriter f) {
- needs.add(f);
- }
+ void needs(FieldWriter 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 ';'.
+ * <p>
+ * TODO(rjrjr) Should be able to make this a constructor argument when
+ * BundleAttributeParser dies.
*
+ * @throws UnableToCompleteException
* @throws IllegalStateException on second attempt to set the initializer
*/
- public void setInitializer(String initializer) {
- // TODO(rjrjr) Should be able to make this a constructor argument
- // when BundleAttributeParser dies
- 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;
- }
+ void setInitializer(String initializer);
/**
* @deprecated needed only by
@@ -94,49 +69,13 @@
* earlier call
*/
@Deprecated
- public void setInitializerMaybe(String initializer) {
- if (this.initializer != null && !this.initializer.equals(initializer)) {
- throw new IllegalStateException(String.format(
- "Attempt to change initializer for field \"%s\", "
- + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
- }
- this.initializer = initializer;
- }
+ void setInitializerMaybe(String initializer);
/**
* Write the field delcaration.
*
- * @return false if unable to write for lack of a default constructor
+ * @param logger
*/
- // 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.isInterface() == null)
- && (type.findConstructor(new JType[0]) == null)) {
- return false;
- }
- initializer = String.format("(%1$s) GWT.create(%1$s.class)",
- type.getQualifiedSourceName());
- }
-
- w.write("%s %s = %s;", type.getQualifiedSourceName(), name, initializer);
-
- this.written = true;
- return true;
- }
-}
+ void write(IndentedWriter w, TreeLogger logger)
+ throws UnableToCompleteException;
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
new file mode 100644
index 0000000..3c32b9b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfExistingType.java
@@ -0,0 +1,45 @@
+/*
+ * 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.typeinfo.JClassType;
+
+/**
+ * Implementation of FieldWriter for fields whose type already exists (that is,
+ * for whom we have a {@link JClassType}.
+ */
+class FieldWriterOfExistingType extends AbstractFieldWriter {
+ final JClassType type;
+ final TreeLogger logger;
+
+ FieldWriterOfExistingType(JClassType type, String name, TreeLogger logger) {
+ super(name);
+ this.logger = logger;
+ if (type == null) {
+ throw new RuntimeException("type cannot be null");
+ }
+ this.type = type;
+ }
+
+ public String getQualifiedSourceName() {
+ return type.getQualifiedSourceName();
+ }
+
+ public JClassType getType() {
+ return type;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java
new file mode 100644
index 0000000..ddefbfb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriterOfGeneratedType.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * Implementation of FieldWriter for fields whose type we haven't genereated
+ * yet, e.g. locally defined CssResources.
+ */
+class FieldWriterOfGeneratedType extends AbstractFieldWriter implements
+ FieldWriter {
+
+ private final String typePackage;
+ private final String typeName;
+
+ /**
+ * @param name
+ */
+ public FieldWriterOfGeneratedType(String typePackage, String typeName, String name) {
+ super(name);
+ if (typeName == null) {
+ throw new RuntimeException("typeName must not be null");
+ }
+ if (typePackage == null) {
+ throw new RuntimeException("typeName must not be null");
+ }
+ this.typeName = typeName;
+ this.typePackage = typePackage;
+ }
+
+ /**
+ *
+ */
+ public String getQualifiedSourceName() {
+ if (typePackage.length() == 0) {
+ return typeName;
+ }
+
+ return String.format("%s.%s", typePackage, typeName);
+ }
+
+ public JClassType getType() {
+ return null;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
index 6b1dd49..59cfa4c 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -24,6 +24,7 @@
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
@@ -55,15 +56,25 @@
MessagesWriter messages = uiWriter.getMessages();
if (messages.hasMessages()) {
- PrintWriter mpw = genCtx.tryCreate(logger, packageName,
+ PrintWriter messagesPrintWriter = genCtx.tryCreate(logger, packageName,
messages.getMessagesClassName());
- if (mpw == null) {
+ if (messagesPrintWriter == null) {
throw new RuntimeException("Tried to gen messages twice.");
}
- messages.write(mpw);
- genCtx.commit(logger, mpw);
+ messages.write(messagesPrintWriter);
+ genCtx.commit(logger, messagesPrintWriter);
}
+
+ ImplicitBundle bundleClass = uiWriter.getBundleClass();
+ PrintWriter bundlePrintWriter = genCtx.tryCreate(logger, packageName,
+ bundleClass.getClassName());
+ if (bundlePrintWriter == null) {
+ throw new RuntimeException("Tried to gen bundle twice.");
+ }
+
+ new BundleWriter(bundleClass, bundlePrintWriter, genCtx.getTypeOracle()).write();
+ genCtx.commit(logger, bundlePrintWriter);
}
@Override
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index db760a2..100fd0b 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -18,7 +18,11 @@
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.TypeOracle;
+import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.CssResourceGetter;
+import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
import com.google.gwt.uibinder.rebind.model.OwnerField;
/**
@@ -27,53 +31,76 @@
*/
public class UiBinderParser {
// TODO(rjrjr) Make all the ElementParsers receive their dependencies via
- // constructor like this one does, and make this an ElementParser
+ // constructor like this one does, and make this an ElementParser. I want
+ // guice!!!
private final UiBinderWriter writer;
+ private final TypeOracle oracle;
private final MessagesWriter messagesWriter;
private final FieldManager fieldManager;
+ private final ImplicitBundle bundleClass;
public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter,
- FieldManager fieldManager) {
+ FieldManager fieldManager, TypeOracle oracle, ImplicitBundle bundleClass) {
this.writer = writer;
+ this.oracle = oracle;
this.messagesWriter = messagesWriter;
this.fieldManager = fieldManager;
+ this.bundleClass = bundleClass;
}
/**
* Parses the root UiBinder element, and kicks off the parsing of the rest of
* the document.
*/
- public String parse(XMLElement elem)
- throws UnableToCompleteException {
+ public String parse(XMLElement elem) throws UnableToCompleteException {
+ // TODO(rjrjr) Clearly need to break these find* methods out into their own
+ // parsers, need registration scheme for ui binder's own parsers
+ findStyles(elem);
findResources(elem);
messagesWriter.findMessagesConfig(elem);
XMLElement uiRoot = elem.consumeSingleChildElement();
return writer.parseElementToField(uiRoot);
}
- /**
- * Interprets <ui:with> elements.
- */
- private void createResource(XMLElement elem)
+ private JClassType consumeCssResourceType(XMLElement elem)
throws UnableToCompleteException {
- String resourceName = elem.consumeRequiredAttribute("name");
+ JClassType publicType = consumeTypeAttribute(elem);
+ JClassType cssResourceType = oracle.findType(CssResource.class.getCanonicalName());
+ if (!publicType.isAssignableTo(cssResourceType)) {
+ writer.die("In %s, type %s does not extend %s", elem, publicType.getQualifiedSourceName(),
+ cssResourceType.getQualifiedSourceName());
+ }
+ return publicType;
+ }
+
+ private JClassType consumeTypeAttribute(XMLElement elem)
+ throws UnableToCompleteException {
String resourceTypeName = elem.consumeRequiredAttribute("type");
- JClassType resourceType = writer.getOracle().findType(resourceTypeName);
+ JClassType resourceType = oracle.findType(resourceTypeName);
if (resourceType == null) {
writer.die("In %s, no such type %s", elem, resourceTypeName);
}
+ return resourceType;
+ }
+
+ /**
+ * Interprets <ui:with> elements.
+ */
+ private void createResource(XMLElement elem) throws UnableToCompleteException {
+ String resourceName = elem.consumeRequiredAttribute("field");
+ JClassType resourceType = consumeTypeAttribute(elem);
if (elem.getAttributeCount() > 0) {
- writer.die("In %s, should only find attributes \"name\" and \"type\"");
+ writer.die("In %s, should only find attributes \"field\" and \"type\"");
}
- FieldWriter fieldWriter = fieldManager.registerField(resourceTypeName,
+ FieldWriter fieldWriter = fieldManager.registerField(resourceType,
resourceName);
OwnerField ownerField = writer.getOwnerClass().getUiField(resourceName);
- // Perhaps it is provided via @UiField
+ /* Perhaps it is provided via @UiField */
if (ownerField != null) {
if (!resourceType.equals(ownerField.getType().getRawType())) {
@@ -86,7 +113,7 @@
}
}
- // Nope. Maybe a @UiFactory will make it
+ /* Nope. Maybe a @UiFactory will make it */
JMethod factoryMethod = writer.getOwnerClass().getUiFactoryMethod(
resourceType);
@@ -95,8 +122,24 @@
factoryMethod.getName()));
}
- // If neither of the above, the FieldWriter's default GWT.create call will
- // do just fine.
+ /*
+ * If neither of the above, the FieldWriter's default GWT.create call will
+ * do just fine.
+ */
+ }
+
+ private void createStyle(XMLElement elem) throws UnableToCompleteException {
+ // Won't be required for long
+ String source = elem.consumeRequiredAttribute("source");
+ String name = elem.consumeAttribute("field", "style");
+ JClassType publicType = consumeCssResourceType(elem);
+
+ CssResourceGetter cssMethod = bundleClass.createCssResource(name, source,
+ publicType);
+ FieldWriter field = fieldManager.registerField(publicType,
+ cssMethod.getName());
+ field.setInitializer(String.format("%s.%s()", bundleClass.getFieldName(),
+ cssMethod.getName()));
}
private void findResources(XMLElement binderElement)
@@ -104,8 +147,7 @@
binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
public Boolean interpretElement(XMLElement elem)
throws UnableToCompleteException {
- if (!(writer.isBinderElement(elem)
- && "with".equals(elem.getLocalName()))) {
+ if (!(writer.isBinderElement(elem) && "with".equals(elem.getLocalName()))) {
return false; // Not of interest, do not consume
}
@@ -116,4 +158,19 @@
});
}
+ private void findStyles(XMLElement binderElement)
+ throws UnableToCompleteException {
+ binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
+ public Boolean interpretElement(XMLElement elem)
+ throws UnableToCompleteException {
+ if (!(writer.isBinderElement(elem) && "style".equals(elem.getLocalName()))) {
+ return false; // Not of interest, do not consume
+ }
+
+ createStyle(elem);
+
+ return true; // consume
+ }
+ });
+ }
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 3e9d483..9482309 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -31,6 +31,8 @@
import com.google.gwt.uibinder.parsers.ElementParser;
import com.google.gwt.uibinder.parsers.StrictAttributeParser;
import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.CssResourceGetter;
+import com.google.gwt.uibinder.rebind.model.ImplicitBundle;
import com.google.gwt.uibinder.rebind.model.OwnerClass;
import com.google.gwt.uibinder.rebind.model.OwnerField;
@@ -50,7 +52,9 @@
* Writer for UiBinder generated classes.
*
* TODO(rdamazio): Refactor this, extract model classes, improve ordering
- * guarantees, etc. TODO(rjrjr): Improve error messages
+ * guarantees, etc.
+ *
+ * TODO(rjrjr): Improve error messages
*/
@SuppressWarnings("deprecation")
public class UiBinderWriter {
@@ -60,6 +64,10 @@
private static int domId = 0;
+ // TODO(rjrjr) Another place that we need a general anonymous field
+ // mechanism
+ private static final String CLIENT_BUNDLE_FIELD = "clientBundleFieldNameUnlikelyToCollideWithUserSpecifiedFieldOkay";
+
public static String asCommaSeparatedList(String... args) {
StringBuilder b = new StringBuilder();
for (String arg : args) {
@@ -170,15 +178,6 @@
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.
@@ -187,25 +186,24 @@
* 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 TreeLogger logger;
private final String templatePath;
private final TypeOracle oracle;
/**
@@ -216,6 +214,7 @@
* The name of the class we're creating, e.g. MyUiBinderImpl
*/
private final String implClassName;
+
private final JClassType uiOwnerType;
private final JClassType uiRootType;
@@ -224,6 +223,8 @@
private final FieldManager fieldManager;
+ private final ImplicitBundle bundleClass;
+
private int fieldIndex;
private String gwtPrefix;
@@ -238,7 +239,8 @@
this.oracle = oracle;
this.logger = logger;
this.templatePath = templatePath;
- this.messages = createMessagesWriter(templatePath, logger);
+ this.messages = new MessagesWriter(BINDER_URI, logger, templatePath,
+ baseClass.getPackage().getName(), this.implClassName);
JClassType uiBinderType = baseClass.getImplementedInterfaces()[0];
JClassType[] typeArgs = uiBinderType.isParameterized().getTypeArgs();
@@ -246,8 +248,10 @@
uiOwnerType = typeArgs[1];
ownerClass = new OwnerClass(uiOwnerType);
+ bundleClass = new ImplicitBundle(baseClass.getPackage().getName(),
+ this.implClassName, CLIENT_BUNDLE_FIELD);
handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
- fieldManager = new FieldManager(oracle, logger);
+ fieldManager = new FieldManager(logger);
}
/**
@@ -306,8 +310,8 @@
*/
public String declareDomIdHolder() throws UnableToCompleteException {
String domHolderName = "domId" + domId++;
- FieldWriter domField = fieldManager.registerField("java.lang.String",
- domHolderName);
+ FieldWriter domField = fieldManager.registerField(
+ oracle.findType(String.class.getName()), domHolderName);
domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
return domHolderName;
}
@@ -319,11 +323,20 @@
*/
public String declareField(String typeName, XMLElement elem)
throws UnableToCompleteException {
+ JClassType type = oracle.findType(typeName);
+ if (type == null) {
+ die("In %s, unknown type %s", elem, typeName);
+ }
+
String fieldName = getFieldName(elem);
if (fieldName == null) {
+ // TODO(rjrjr) could collide with user declared name, as is
+ // also a worry in HandlerEvaluator. Need a general scheme for
+ // anonymous fields. See the note in HandlerEvaluator and do
+ // something like that, but in FieldManager.
fieldName = ("f_" + elem.getLocalName() + (++fieldIndex));
}
- fieldManager.registerField(typeName, fieldName);
+ fieldManager.registerField(type, fieldName);
return fieldName;
}
@@ -338,8 +351,7 @@
throws UnableToCompleteException {
String fieldName = getFieldName(elem);
if (fieldName != null) {
- String typeName = findFieldType(elem).getQualifiedSourceName();
- fieldManager.registerField(typeName, fieldName);
+ fieldManager.registerField(findFieldType(elem), fieldName);
}
return fieldName;
}
@@ -386,7 +398,7 @@
String tagName = elem.getLocalName();
if (!isWidgetElement(elem)) {
- return findElementTypeForTag(tagName);
+ return findGwtDomElementTypeForTag(tagName);
}
String ns = elem.getNamespaceUri();
@@ -447,7 +459,8 @@
* Find and return an appropriate attribute parser for an attribute and set of
* parameters, or return null.
* <p>
- * If params is of size one, a parser of some kind is guaranteed to be returned.
+ * If params is of size one, a parser of some kind is guaranteed to be
+ * returned.
*/
public AttributeParser getAttributeParser(XMLAttribute attribute,
JParameter... params) throws UnableToCompleteException {
@@ -481,19 +494,19 @@
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);
+ JClassType bundleClassType = getOracle().findType(bundleClassName);
+ if (bundleClassType == null) {
+ die("No such resource class: " + bundleClassName);
}
- parser = createBundleParser(bundleClass, attribute);
+ parser = createBundleParser(bundleClassType, attribute);
bundleParsers.put(bundleClassName, parser);
}
return parser;
}
- public String getGwtFieldAttributeName() {
- return gwtPrefix + ":field";
+ public ImplicitBundle getBundleClass() {
+ return bundleClass;
}
/**
@@ -514,6 +527,10 @@
return ownerClass;
}
+ public String getUiFieldAttributeName() {
+ return gwtPrefix + ":field";
+ }
+
public boolean isBinderElement(XMLElement elem) {
String uri = elem.getNamespaceUri();
return uri != null && BINDER_URI.equals(uri);
@@ -526,12 +543,15 @@
/**
* Parses the object associated with the specified element, and returns the
- * name of the field (possibly private) that will hold it.
+ * name of the field (possibly private) that will hold it. The element is
+ * likely to make recursive calls back to this method to have its children
+ * parsed.
*
* @param elem the xml element to be parsed
* @return the name of the field containing the parsed widget
*/
- public String parseElementToField(XMLElement elem) throws UnableToCompleteException {
+ public String parseElementToField(XMLElement elem)
+ throws UnableToCompleteException {
if (elementParsers.isEmpty()) {
registerParsers();
}
@@ -574,7 +594,7 @@
public void setFieldInitializerAsConstructor(String fieldName,
JClassType type, String... args) {
setFieldInitializer(fieldName, String.format("new %s(%s)",
- getFullTypeName(type), asCommaSeparatedList(args)));
+ type.getQualifiedSourceName(), asCommaSeparatedList(args)));
}
/**
@@ -631,14 +651,14 @@
attributeParsers.put(signature, className);
}
- private void addParser(String gwtClass, String parser) {
+ private void addElementParser(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);
+ addElementParser(gwtClass, parser);
}
/**
@@ -683,12 +703,6 @@
+ bundleClass.getName().replace('.', '_') + "Instance", true);
}
- private MessagesWriter createMessagesWriter(String templatePath,
- TreeLogger logger) {
- return new MessagesWriter(BINDER_URI, logger, templatePath, getPackageName(),
- this.implClassName);
- }
-
/**
* Outputs a bundle resource for a given bundle attribute parser.
*/
@@ -708,9 +722,10 @@
}
/**
- * Given a DOM tag name, return the corresponding Element subclass.
+ * Given a DOM tag name, return the corresponding
+ * {@link com.google.gwt.dom.client.Element} subclass.
*/
- private JClassType findElementTypeForTag(String tag) {
+ private JClassType findGwtDomElementTypeForTag(String tag) {
JClassType elementClass = oracle.findType("com.google.gwt.dom.client.Element");
JClassType[] types = elementClass.getSubtypes();
for (JClassType type : types) {
@@ -745,19 +760,15 @@
+ "Please switch to gwt:field=\"%1$s\" instead. "
+ "This will soon be a compile error!", fieldName));
}
- if (elem.hasAttribute(getGwtFieldAttributeName())) {
+ if (elem.hasAttribute(getUiFieldAttributeName())) {
if (hasOldSchoolId) {
die("Cannot declare both id and field on the same element: " + elem);
}
- fieldName = elem.consumeAttribute(getGwtFieldAttributeName());
+ fieldName = elem.consumeAttribute(getUiFieldAttributeName());
}
return fieldName;
}
- private String getPackageName() {
- return baseClass.getPackage().getName();
- }
-
/**
* Given a parameter array, return a key for the attributeParsers table.
*/
@@ -850,14 +861,17 @@
}
/**
- * Parse the document element and return the source of the Java
- * class that will implement its UiBinder.
+ * Parse the document element and return the source of the Java class that
+ * will implement its UiBinder.
*/
private String parseDocumentElement(XMLElement elem)
throws UnableToCompleteException {
+ fieldManager.registerFieldOfGeneratedType(bundleClass.getPackageName(),
+ bundleClass.getClassName(), bundleClass.getFieldName());
+ // Allow GWT.create() to init the field, the default behavior
- String rootField = new UiBinderParser(this, messages, fieldManager).parse(
- elem);
+ String rootField = new UiBinderParser(this, messages, fieldManager, oracle,
+ bundleClass).parse(elem);
StringWriter stringWriter = new StringWriter();
IndentedWriter niceWriter = new IndentedWriter(
@@ -893,7 +907,7 @@
// TODO(rjrjr): Allow third-party parsers to register themselves
// automagically, according to http://b/issue?id=1867118
- addParser("com.google.gwt.dom.client.Element",
+ addElementParser("com.google.gwt.dom.client.Element",
"com.google.gwt.uibinder.parsers.DomElementParser");
// Register widget parsers.
@@ -940,19 +954,7 @@
}
/**
- * 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
+ * Writes the UiBinder's source.
*/
private void writeBinder(IndentedWriter w, String rootField)
throws UnableToCompleteException {
@@ -966,8 +968,8 @@
w.newline();
// createAndBindUi method
- w.write("public %s createAndBindUi(final %s owner) {", uiRootType.getName(),
- uiOwnerType.getName());
+ w.write("public %s createAndBindUi(final %s owner) {",
+ uiRootType.getName(), uiOwnerType.getName());
w.indent();
w.newline();
@@ -985,6 +987,8 @@
writeFieldSetters(w);
+ writeCssInjectors(w);
+
w.write("return %s;", rootField);
w.outdent();
w.write("}");
@@ -995,20 +999,28 @@
}
private void writeClassOpen(IndentedWriter w) {
- w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName,
- uiRootType.getName(), uiOwnerType.getName(), baseClass.getName());
+ w.write("public class %s extends AbstractUiBinder<%s, %s> implements %s {",
+ implClassName, uiRootType.getName(), uiOwnerType.getName(),
+ baseClass.getName());
w.indent();
}
+ private void writeCssInjectors(IndentedWriter w) {
+ for (CssResourceGetter css : bundleClass.getCssMethods()) {
+ w.write("ensureCssInjected(%s.%s());", bundleClass.getFieldName(),
+ css.getName());
+ }
+ w.newline();
+ }
+
/**
* 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());
+ for (OwnerField ownerField : getOwnerClass().getUiFields()) {
+ String fieldName = ownerField.getName();
+ FieldWriter fieldWriter = fieldManager.lookup(fieldName);
BundleAttributeParser bundleParser = bundleParsers.get(ownerField.getType().getRawType().getQualifiedSourceName());
@@ -1019,14 +1031,22 @@
} else if (fieldWriter != null) {
// ownerField is a widget.
- maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getType(),
- ownerField.getName());
+ JClassType type = fieldWriter.getType();
+ if (type != null) {
+ maybeWriteFieldSetter(niceWriter, ownerField, fieldWriter.getType(),
+ fieldName);
+ } else {
+ // Must be a generated type
+ if (!ownerField.isProvided()) {
+ niceWriter.write("owner.%1$s = %1$s;", fieldName);
+ }
+ }
} 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());
+ getUiFieldAttributeName(), uiOwnerType.getPackage().getName(),
+ uiOwnerType.getName(), fieldName);
}
}
}
@@ -1068,7 +1088,7 @@
private void writeImports(IndentedWriter w) {
w.write("import com.google.gwt.core.client.GWT;");
- w.write("import com.google.gwt.uibinder.client.UiBinder;");
+ w.write("import com.google.gwt.uibinder.client.AbstractUiBinder;");
w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
w.write("import %s.%s;", uiRootType.getPackage().getName(),
uiRootType.getName());
@@ -1100,7 +1120,7 @@
*/
private void writeStaticBundleInstances(IndentedWriter niceWriter) {
// TODO(rjrjr) It seems bad that this method has special
- // knowledge of BundleAttributeParser
+ // knowledge of BundleAttributeParser, but that'll die soon so...
for (String key : bundleParsers.keySet()) {
String declaration = declareStaticField(bundleParsers.get(key));
if (declaration != null) {
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index e1a16ab..2f6d347 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -47,15 +47,15 @@
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.
+ * 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;
@@ -85,7 +85,7 @@
private final UiBinderWriter writer;
private final Element elem;
-
+
private final String debugString;
{
@@ -143,7 +143,7 @@
/**
* Consumes the given attribute as a boolean value.
- *
+ *
* @throws UnableToCompleteException
*/
public boolean consumeBooleanAttribute(String attr)
@@ -164,8 +164,8 @@
*/
public Iterable<XMLElement> consumeChildElements() {
try {
- Iterable<XMLElement> rtn =
- consumeChildElements(new NoBrainInterpeter<Boolean>(true));
+ Iterable<XMLElement> rtn = consumeChildElements(new NoBrainInterpeter<Boolean>(
+ true));
clearChildren(elem);
return rtn;
} catch (UnableToCompleteException e) {
@@ -177,7 +177,7 @@
* 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
@@ -217,7 +217,7 @@
* 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
*/
@@ -250,7 +250,7 @@
* 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
*/
@@ -268,8 +268,7 @@
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);
+ writer.die("Text value of \"%s\" has illegal child \"%s\"", this, child);
}
}
@@ -288,8 +287,8 @@
}
/**
- * Consumes all attributes, and returns a string representing the
- * entire opening tag. E.g., "<div able='baker'>"
+ * Consumes all attributes, and returns a string representing the entire
+ * opening tag. E.g., "<div able='baker'>"
*/
public String consumeOpeningTag() {
String rtn = getOpeningTag();
@@ -373,8 +372,8 @@
}
/**
- * @return the parent element, or null if parent is null or a node type
- * other than Element
+ * @return the parent element, or null if parent is null or a node type other
+ * than Element
*/
public XMLElement getParent() {
Node parent = elem.getParentNode();
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
index 20d4e9e..6e4fe34 100644
--- a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
@@ -38,7 +38,6 @@
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 = {};
@@ -58,22 +57,17 @@
new HashMap<XMLElement, Collection<AttributeMessage>>();
public MessagesWriter(String nameSpaceUri, TreeLogger logger, String generatedFrom,
- String packageName, String implClassName) {
+ String packageName, String uiBinderImplClassName) {
this.messagesNamespaceURI = nameSpaceUri;
this.generatedFrom = generatedFrom;
this.packageName = packageName;
// Localizable classes cannot have underscores in their names.
- this.messagesClassName = implClassName.replaceAll("_", "") + "GenMessages";
+ this.messagesClassName = uiBinderImplClassName.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
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/CssResourceGetter.java b/user/src/com/google/gwt/uibinder/rebind/model/CssResourceGetter.java
new file mode 100644
index 0000000..ae0017a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/CssResourceGetter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.typeinfo.JClassType;
+
+/**
+ * Models a method returning a CssResource on a generated ClientBundle. At the
+ * moment, they must implement a public interface and be tied to an external
+ * .css file. That should improve in the next day or so.
+ */
+public class CssResourceGetter {
+ private final String name;
+ private final String source;
+ private final JClassType extendedInterface;
+
+ public CssResourceGetter(String name, String source,
+ JClassType extendedInterface) {
+ this.name = name;
+ this.source = source;
+ this.extendedInterface = extendedInterface;
+ }
+
+ /**
+ * @return the public interface that this CssResource implements
+ */
+ public JClassType getExtendedInterface() {
+ return extendedInterface;
+ }
+
+ /**
+ * @return the name of this resource. This is both its method name in the
+ * owning {@link ImplicitBundle} and its ui:field name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the path to the associated .css file resource
+ */
+ public String getSource() {
+ return source;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitBundle.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitBundle.java
new file mode 100644
index 0000000..abee4d3
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitBundle.java
@@ -0,0 +1,78 @@
+/*
+ * 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.typeinfo.JClassType;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Models the ClientBundle to be generated from a ui.xml.
+ */
+public class ImplicitBundle {
+
+ private final Set<CssResourceGetter> cssMethods = new HashSet<CssResourceGetter>();
+ private final String packageName;
+ private final String className;
+ private final String fieldName;
+
+ /**
+ * @param packageName Where the bundle should live
+ * @param uiBinderImplClassName The name of the generated ui binder
+ * implementation that owns the bundle
+ * @param fieldName The bundle's field name
+ */
+ public ImplicitBundle(String packageName, String uiBinderImplClassName,
+ String fieldName) {
+ this.packageName = packageName;
+ this.className = uiBinderImplClassName + "GenBundle";
+ this.fieldName = fieldName;
+ }
+
+ /**
+ * Called to declare a new CssResource accessor on this bundle.
+ *
+ * @param name the method name
+ * @param source path to the .css file resource
+ * @param extendedInterface the public interface implemented by this
+ * CssResource, or null
+ * @return
+ */
+ public CssResourceGetter createCssResource(String name, String source,
+ JClassType extendedInterface) {
+ CssResourceGetter css = new CssResourceGetter(name, source, extendedInterface);
+ cssMethods.add(css);
+ return css;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public Set<CssResourceGetter> getCssMethods() {
+ return Collections.unmodifiableSet(cssMethods);
+ }
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
index 239eb93..e319827 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
@@ -74,7 +74,7 @@
}
/**
- * Returns a descriptor for the type of the field.
+ * Returns a descriptor for the type of the field.
*/
public OwnerFieldClass getType() {
return fieldType;
@@ -87,7 +87,7 @@
public boolean isProvided() {
return isProvided;
}
-
+
@Override
public String toString() {
return String.format("%s.%s#%s", fieldType.getRawType().getPackage(),
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
index 0ab9b90..b57d160 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
@@ -69,8 +69,4 @@
binder.createAndBindUi(this);
nameSpan.setInnerText(yourNameHere);
}
-
- public Element getRoot() {
- return root;
- }
}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java b/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
index 8275c62..a1d6d84 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
@@ -18,7 +18,6 @@
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
@@ -27,8 +26,8 @@
public void onModuleLoad() {
DomBasedUi boundUi = new DomBasedUi("Mr. User Man");
- Document.get().getBody().appendChild((boundUi).getRoot());
-
- RootPanel.get().add((Widget) new WidgetBasedUi());
+ Document.get().getBody().appendChild(boundUi.root);
+
+ RootPanel.get().add(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
index 599ea37..9bd75be 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
@@ -1,13 +1,3 @@
-.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
index 310e037..b32ae43 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -24,10 +24,10 @@
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.resources.client.CssResource;
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;
@@ -46,239 +46,80 @@
* 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> {
+ /**
+ * This CssResource is a requirement of the WidgetBasedUi, to be provided by
+ * its ui.xml template.
+ */
+ public interface Style extends CssResource {
+ String menuBar();
}
+ 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;
+ final WidgetBasedUiExternalResources external;
@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;
+ @UiField Style myStyle;
+ @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);
+ this.external = GWT.create(WidgetBasedUiExternalResources.class);
// Inject only once.
if (!stylesInjected) {
- StyleInjector.injectStylesheet(res.style().getText());
+ StyleInjector.injectStylesheet(external.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
index 9282b5c..d4d99db 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -52,8 +52,26 @@
ui:generateFilename="myapp_translate_source"
ui:generateLocales="default"
>
-<ui:with name='res' type='com.google.gwt.uibinder.sample.client.WidgetBasedUiResources' />
-<ui:with name='values' type='com.google.gwt.uibinder.sample.client.FakeBundle' />
+
+<ui:with field='external' type='com.google.gwt.uibinder.sample.client.WidgetBasedUiExternalResources'>
+ (This text is ignored, but it's a handy place to document a resource.)
+
+ external is used to test receiving a resource from the owner via
+ @UiField(provided = true), particularly useful for dependency
+ injection via Gin and the like
+</ui:with>
+
+<ui:with field='values' type='com.google.gwt.uibinder.sample.client.FakeBundle'>
+ Tests the default creation of a resource via GWT.create(), and also the ability
+ for a resource to provide arbitrary objects to arbitrary attributes (look for FooLabel)
+</ui:with>
+
+<!--
+ Tests creating a CssResource from an external file.
+ -->
+<!--<ui:style field='myStyle' source='WidgetBasedUi.css' type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>-->
+<ui:style field='myStyle' source='WidgetBasedUi.css' type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>
+
<gwt:DockPanel ui:field="root" width="100%">
<gwt:Dock direction='NORTH'>
<gwt:HTML>
@@ -112,7 +130,7 @@
Of course, it could just as easily be a Tree under a MenuBar...
</ui:msg>
</p>
- <div class="{res.style.menuBar}">
+ <div class="{myStyle.menuBar}">
<gwt:MenuBar ui:field="topMenuBar" vertical="false">
<gwt:MenuItem>
<div id="higgledy">
@@ -120,7 +138,7 @@
Higgeldy
</ui:msg>
</div>
- <gwt:MenuBar vertical="true" styleName="{res.style.menuBar}">
+ <gwt:MenuBar vertical="true" styleName="{myStyle.menuBar}">
<gwt:MenuItem>able</gwt:MenuItem>
<gwt:MenuItem>baker</gwt:MenuItem>
<gwt:MenuItem>charlie</gwt:MenuItem>
@@ -132,7 +150,7 @@
Piggledy
</ui:msg>
</div>
- <gwt:MenuBar vertical="true" styleName="{res.style.menuBar}">
+ <gwt:MenuBar vertical="true" styleName="{myStyle.menuBar}">
<gwt:MenuItem>delta</gwt:MenuItem>
<gwt:MenuItem>echo</gwt:MenuItem>
<gwt:MenuItem>foxtrot</gwt:MenuItem>
@@ -143,7 +161,7 @@
<ui:msg description="Last 7 Day Period">Pop</ui:msg>
</div>
<gwt:MenuBar vertical="true" ui:field="dropdownMenuBar"
- styleName="{res.style.menuBar}">
+ styleName="{myStyle.menuBar}">
<gwt:MenuItem ui:field='menuItemCustomDateRange'>
<ui:msg description="Custom date range">
the Dog
@@ -182,11 +200,13 @@
<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="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"/ -->
+ <!-- ui:attribute name="gwt:StackPanel-text" description="Label for Stack Two"/
+-->
<div>
<ui:msg description="Describe div content">Some other content</ui:msg>
</div>
@@ -239,8 +259,8 @@
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' text="{values.helloText}" styleName="{res.style.prettyText}"/>
- <p class="{res.style.prettyText}" id='prettyPara'>
+ <gwt:Label ui:field='bundledLabel' text="{values.helloText}" styleName="{external.style.prettyText}"/>
+ <p class="{external.style.prettyText}" id='prettyPara'>
This stylish paragraph also gets its good looks from a
resource.
</p>
@@ -288,18 +308,18 @@
<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" class="{res.style.prettyText}"
+ these with <span id="placeholdersSpan" style="font-weight:bold" class="{external.style.prettyText}"
ui:ph="boldSpan">placeholders</span><ui:ph name="tm"><sup ui:field="tmElement"
- class="{res.style.tmText}">TM</sup></ui:ph>.</ui:msg></p>
+ class="{external.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" class="{res.style.prettyText}">named
- portions</span> of <span class="{res.style.prettyText}">translatable text</span></ui:msg></p>
+ style="font-weight:bold" class="{external.style.prettyText}">named
+ portions</span> of <span class="{external.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"
- class="{res.style.tmText}">TM</sup></ui:ph></gwt:HTML></ui:msg></p>
+ class="{external.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>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiExternal.css b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiExternal.css
new file mode 100644
index 0000000..92056f6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiExternal.css
@@ -0,0 +1,9 @@
+.prettyText {
+ color: green;
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+.tmText {
+ color: purple;
+ font-family: serif;
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiExternalResources.java
similarity index 73%
rename from user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java
rename to user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiExternalResources.java
index 4af5810..c142496 100644
--- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiExternalResources.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -20,23 +20,20 @@
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.
+ * Sample external resources used by {@link WidgetBasedUi}, to test
+ * injection of such things.
*/
-public interface WidgetBasedUiResources extends ClientBundle {
+public interface WidgetBasedUiExternalResources extends ClientBundle {
/**
* Sample CssResource.
*/
public interface MyCss extends CssResource {
- String menuBar();
-
String prettyText();
String tmText();
}
- @Source("WidgetBasedUi.css")
+ @Source("WidgetBasedUiExternal.css")
@Strict
MyCss style();
}
diff --git a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
index 8831922..f4b717e 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
@@ -16,7 +16,7 @@
package com.google.gwt.uibinder;
import com.google.gwt.junit.tools.GWTTestSuite;
-import com.google.gwt.uibinder.client.UiBinderTest;
+import com.google.gwt.uibinder.sample.client.UiBinderTest;
import junit.framework.Test;
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
similarity index 75%
rename from user/test/com/google/gwt/uibinder/client/UiBinderTest.java
rename to user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
index 95e5b5d..90bf5a4 100644
--- a/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.gwt.uibinder.client;
+package com.google.gwt.uibinder.sample.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
@@ -24,13 +24,6 @@
import com.google.gwt.junit.DoNotRunWith;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.uibinder.sample.client.ArbitraryPojo;
-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.FooLabel;
-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;
@@ -59,10 +52,10 @@
super.gwtSetUp();
RootPanel.get().clear();
domUi = new DomBasedUi("Cherished User");
- Document.get().getBody().appendChild(domUi.getRoot());
+ Document.get().getBody().appendChild(domUi.root);
widgetUi = new WidgetBasedUi();
- root = widgetUi.getRoot();
+ root = widgetUi.root;
RootPanel.get().add(widgetUi);
}
@@ -73,7 +66,7 @@
}
public void testAccessToNonStandardElement() {
- Element elm = widgetUi.getNonStandardElement();
+ Element elm = widgetUi.nonStandardElement;
assertEquals("I", elm.getTagName());
}
@@ -86,13 +79,13 @@
}
public void testBundle() {
- assertEquals(getCenter(), widgetUi.getBundledLabel().getParent());
+ assertEquals(getCenter(), widgetUi.bundledLabel.getParent());
assertEquals(new FakeBundle().helloText(),
- widgetUi.getBundledLabel().getText());
- WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ widgetUi.bundledLabel.getText());
+ WidgetBasedUiExternalResources resources = GWT.create(WidgetBasedUiExternalResources.class);
assertEquals("bundledLabel should have styleName",
resources.style().prettyText(),
- widgetUi.getBundledLabel().getStyleName());
+ widgetUi.bundledLabel.getStyleName());
Element pretty = DOM.getElementById("prettyPara");
assertEquals(resources.style().prettyText(), pretty.getClassName());
@@ -100,7 +93,7 @@
ArbitraryPojo pojo = new ArbitraryPojo();
FooLabel foo = new FooLabel();
foo.setPojo(pojo);
- assertEquals(foo.getText(), widgetUi.getTheFoo().getText());
+ assertEquals(foo.getText(), widgetUi.theFoo.getText());
}
public void testCenter() {
@@ -114,30 +107,30 @@
assertTrue(html.contains("Button with"));
assertTrue(html.contains("Of course"));
- assertEquals(center, widgetUi.getMyButton().getParent());
+ assertEquals(center, widgetUi.myButton.getParent());
}
public void testComputedAttributeInPlaceholderedElement() {
- WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ WidgetBasedUiExternalResources resources = GWT.create(WidgetBasedUiExternalResources.class);
assertEquals(resources.style().prettyText(),
- widgetUi.getSpanInMsg().getClassName());
+ widgetUi.spanInMsg.getClassName());
}
public void testComputedStyleInAPlaceholder() {
- WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ WidgetBasedUiExternalResources resources = GWT.create(WidgetBasedUiExternalResources.class);
assertEquals(resources.style().tmText(),
- widgetUi.getTmElement().getClassName());
+ widgetUi.tmElement.getClassName());
}
public void testDomAccessAndComputedAttributeOnPlaceholderedElement() {
- WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+ WidgetBasedUiExternalResources resources = GWT.create(WidgetBasedUiExternalResources.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();
+ DivElement sideBar = widgetUi.sideBar;
assertTrue("sideBar should start: \"This could\"",
sideBar.getInnerText().startsWith("This could"));
assertTrue("sideBar should end: \"example:\"",
@@ -146,7 +139,7 @@
}
public void testDomAccessInHtmlPanel() {
- SpanElement messageInMain = widgetUi.getMessageInMain();
+ SpanElement messageInMain = widgetUi.messageInMain;
String text = messageInMain.getInnerText().trim();
assertTrue("sideBar should start: \"This is the main area\"",
text.startsWith("This is the main area"));
@@ -155,28 +148,28 @@
public void testDomAttributeMessageWithFunnyChars() {
ParagraphElement p =
- widgetUi.getFunnyCharsMessageDomAttributeParagraph();
+ widgetUi.funnyCharsMessageDomAttributeParagraph;
String t = p.getAttribute("title");
assertEquals("funny characters \" ' ' & < > > { }", t);
}
public void testDomAttributeNoMessageWithFunnyChars() {
- ParagraphElement p = widgetUi.getFunnyCharsDomAttributeParagraph();
+ ParagraphElement p = widgetUi.funnyCharsDomAttributeParagraph;
String t = p.getAttribute("title");
assertEquals("funny characters \" ' ' & < > > { }", t);
}
public void testDomTextMessageWithFunnyChars() {
- String t = widgetUi.getFunnyCharsMessageParagraph().getInnerText();
+ String t = widgetUi.funnyCharsMessageParagraph.getInnerText();
assertEquals("They might show up in body text that has been marked for "
+ "translation: funny characters \" \" ' ' & < > > { }",
t);
}
public void suppressedForSafari3Fail_testDomTextNoMessageWithFunnyChars() {
- ParagraphElement p = widgetUi.getFunnyCharsParagraph();
+ ParagraphElement p = widgetUi.funnyCharsParagraph;
// WebKit does \n replace thing, so let's do it everywhere
- String t = p.getInnerHTML().replace("\n", " ").toLowerCase();
+ 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 "
@@ -187,41 +180,40 @@
}
public void testFieldAttribute() {
- assertEquals(getCenter(), widgetUi.getGwtFieldLabel().getParent());
+ assertEquals(getCenter(), widgetUi.gwtFieldLabel.getParent());
}
public void testFieldInPlaceholderedElement() {
- assertEquals("named portions", widgetUi.getSpanInMsg().getInnerText());
+ assertEquals("named portions", widgetUi.spanInMsg.getInnerText());
}
public void testMenuAttributes() {
- WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
- assertEquals(widgetUi.getDropdownMenuBar().getStyleName(),
- resources.style().menuBar());
+ assertEquals(widgetUi.dropdownMenuBar.getStyleName(),
+ widgetUi.myStyle.menuBar());
}
public void testMenuItems() {
// Has a legacy MenuItemHTML in its midst
assertEquals("The pig's in a hurry",
- widgetUi.getMenuItemLegacy().getElement().getInnerText());
+ widgetUi.menuItemLegacy.getElement().getInnerText());
assertTrue("Style should include \"moppy\"",
- widgetUi.getMenuItemMop().getStyleName().contains("moppy"));
+ widgetUi.menuItemMop.getStyleName().contains("moppy"));
}
public void testMessageTrimming() {
assertEquals("Title area, specified largely in HTML.",
- widgetUi.getTrimmedMessage().getInnerHTML());
+ widgetUi.trimmedMessage.getInnerHTML());
assertEquals("Tommy can you hear me? Can you field me near you?",
- widgetUi.getGwtFieldLabel().getText());
+ widgetUi.gwtFieldLabel.getText());
}
-
+
public void testMinimalDom() {
assertEquals("Expect no wrapper div around root", widgetUi.getElement(),
root.getElement());
}
public void testNamedElementInAPlaceholder() {
- assertEquals("TM", widgetUi.getTmElement().getInnerText());
+ assertEquals("TM", widgetUi.tmElement.getInnerText());
}
public void testNestedBundle() {
@@ -229,12 +221,12 @@
GWT.create(DomBasedUi.Resources.class);
assertEquals(resources.style().bodyColor()
+ " " + resources.style().bodyFont() ,
- domUi.getRoot().getClassName());
+ domUi.root.getClassName());
}
public void suppressedForIEfail_testNonXmlEntities() {
// This fragment includes both translated and non-translated strings
- ParagraphElement mainParagraph = widgetUi.getMain();
+ ParagraphElement mainParagraph = widgetUi.main;
final String innerHTML = mainParagraph.getInnerHTML().trim();
assertTrue(innerHTML.contains(" \u261E \u2022 XHTML \u2022 \u261C"));
assertTrue(innerHTML.startsWith("\u261E <span>"));
@@ -250,8 +242,8 @@
@DoNotRunWith(Platform.Htmlunit)
public void testRadioButton() {
- RadioButton able = widgetUi.getMyRadioAble();
- RadioButton baker = widgetUi.getMyRadioBaker();
+ RadioButton able = widgetUi.myRadioAble;
+ RadioButton baker = widgetUi.myRadioBaker;
assertTrue("able should be checked", able.getValue());
assertFalse("baker should not be checked", baker.getValue());
assertEquals("radios", able.getName());
@@ -259,9 +251,9 @@
}
public void testStackPanel() {
- StackPanel p = widgetUi.getMyStackPanel();
+ StackPanel p = widgetUi.myStackPanel;
assertNotNull("Panel exists", p);
- Widget w = widgetUi.getMyStackPanelItem();
+ Widget w = widgetUi.myStackPanelItem;
assertNotNull("Widget exists", w);
boolean containsWidget = false;
for (int i = 0; i < p.getWidgetCount(); i++) {
@@ -273,9 +265,9 @@
}
public void testDisclosurePanel() {
- DisclosurePanel p = widgetUi.getMyDisclosurePanel();
+ DisclosurePanel p = widgetUi.myDisclosurePanel;
assertNotNull("Panel exists", p);
- Widget w = widgetUi.getMyDisclosurePanelItem();
+ Widget w = widgetUi.myDisclosurePanelItem;
assertNotNull("Widget exists", w);
assertEquals("Panel contains widget", w, p.getContent());
}
@@ -283,7 +275,7 @@
public void testStringAttributeIgnoresStaticSetter() {
// Assumes setPopupText() is overloaded such that there is a static
// setPopupText(Foo, String) method.
- ClickyLink clicky = widgetUi.getCustomLinkWidget();
+ ClickyLink clicky = widgetUi.customLinkWidget;
assertEquals("overloaded setter should have been called",
"That tickles!", clicky.getPopupText());
}
@@ -301,32 +293,32 @@
}
public void testWidgetAttributeMessageWithFunnyChars() {
- ClickyLink clicky = widgetUi.getFunnyCharsMessageAttributeWidget();
+ ClickyLink clicky = widgetUi.funnyCharsMessageAttributeWidget;
String t = clicky.getPopupText();
assertEquals("funny characters \" ' ' & < > > { }", t);
}
public void testWidgetAttributeNoMessageWithFunnyChars() {
- ClickyLink clicky = widgetUi.getFunnyCharsAttributeWidget();
+ ClickyLink clicky = widgetUi.funnyCharsAttributeWidget;
String t = clicky.getPopupText();
assertEquals("funny characters \" ' ' & < > > { }", t);
}
-
+
public void suppressForIEfail_testBizarrelyElementedWidgets() {
- assertInOrder(widgetUi.getWidgetCrazyTable().getInnerHTML().toLowerCase(),
+ assertInOrder(widgetUi.widgetCrazyTable.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(),
+ assertInOrder(widgetUi.widgetCrazyOrderedList.getInnerHTML(),
"<li>similar</li>", "<li>things</li>");
- assertInOrder(widgetUi.getWidgetCrazyDefinitionList().getInnerHTML(),
+ assertInOrder(widgetUi.widgetCrazyDefinitionList.getInnerHTML(),
"<dt>Being</dt>", "<dd>done</dd>", "<dd>with</dd>", "<dd>lists</dd>");
}
public void testCustomHtmlPanelTag() {
- assertInOrder(widgetUi.getCustomTagHtmlPanel().getElement().getInnerHTML(),
+ assertInOrder(widgetUi.customTagHtmlPanel.getElement().getInnerHTML(),
"<td>Even HTMLPanel gets in on the game</td>",
"<td>Lately, anyway.</td>");
}
@@ -340,7 +332,7 @@
body = body.toLowerCase();
int lastIndex = 0;
String lastExpected = "";
-
+
for (String next : expected) {
next = next.toLowerCase();
int index = body.indexOf(next);