UiBinder now understands CssResource's imported scopes, which is more than I can say for myself most of the time. Reviewed by jgw git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6363 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 deleted file mode 100644 index db42c19..0000000 --- a/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java +++ /dev/null
@@ -1,50 +0,0 @@ -/* - * 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/rebind/BundleWriter.java b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java index 4ba4f5d..1db5074 100644 --- a/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java +++ b/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
@@ -21,6 +21,7 @@ import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.DataResource; import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.resources.client.CssResource.Import; import com.google.gwt.resources.client.ImageResource.ImageOptions; import com.google.gwt.resources.client.ImageResource.RepeatStyle; import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle; @@ -28,6 +29,8 @@ import com.google.gwt.uibinder.rebind.model.ImplicitDataResource; import com.google.gwt.uibinder.rebind.model.ImplicitImageResource; +import java.util.Set; + /** * Writes source implementing an {@link ImplicitClientBundle}. */ @@ -43,6 +46,7 @@ private final JClassType imageOptionType; private final JClassType imageResourceType; private final JClassType repeatStyleType; + private final JClassType importAnnotationType; public BundleWriter(ImplicitClientBundle bundleClass, PrintWriterManager writerManager, TypeOracle oracle, @@ -58,6 +62,7 @@ imageOptionType = oracle.findType(ImageOptions.class.getCanonicalName()); imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); repeatStyleType = oracle.findType(RepeatStyle.class.getCanonicalName()); + importAnnotationType = oracle.findType(Import.class.getCanonicalName()); } public void write() throws UnableToCompleteException { @@ -77,20 +82,22 @@ } // Imports - writer.write("import %s;", imageResourceType.getQualifiedSourceName()); - writer.write("import %s;", imageOptionType.getQualifiedSourceName()); writer.write("import %s;", clientBundleType.getQualifiedSourceName()); writer.write("import %s;", dataResourceType.getQualifiedSourceName()); + writer.write("import %s;", imageResourceType.getQualifiedSourceName()); + writer.write("import %s;", imageOptionType.getQualifiedSourceName()); + writer.write("import %s;", importAnnotationType.getQualifiedSourceName()); writer.newline(); // Open interface writer.write("public interface %s extends ClientBundle {", bundleClass.getClassName()); writer.indent(); - + // Write css methods for (ImplicitCssResource css : bundleClass.getCssMethods()) { writer.write("@Source(\"%s\")", css.getSource()); + writeCssImports(css); writer.write("%s %s();", css.getClassName(), css.getName()); writer.newline(); } @@ -109,6 +116,26 @@ writer.write("}"); } + private void writeCssImports(ImplicitCssResource css) { + Set<JClassType> importTypes = css.getImports(); + int numImports = importTypes.size(); + if (numImports > 0) { + if (numImports == 1) { + writer.write("@Import(%s.class)", + importTypes.iterator().next().getQualifiedSourceName()); + } else { + StringBuffer b = new StringBuffer(); + for (JClassType importType : importTypes) { + if (b.length() > 0) { + b.append(", "); + } + b.append(importType.getQualifiedSourceName()).append(".class"); + } + writer.write("@Import({%s})", b); + } + } + } + private void writeImageMethods() { for (ImplicitImageResource image : bundleClass.getImageMethods()) { if (null != image.getSource()) {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java index 0c2e7bd..d6dca10 100644 --- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java +++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -30,43 +30,14 @@ import com.google.gwt.uibinder.rebind.model.ImplicitImageResource; import com.google.gwt.uibinder.rebind.model.OwnerField; +import java.util.LinkedHashSet; + /** * Parses the root UiBinder element, and kicks of the parsing of the rest of the * document. */ public class UiBinderParser { - private static final String FLIP_RTL_ATTRIBUTE = "flipRtl"; - private static final String FIELD_ATTRIBUTE = "field"; - private static final String SOURCE_ATTRIBUTE = "src"; - private static final String REPEAT_STYLE_ATTRIBUTE = "repeatStyle"; - - // TODO(rjrjr) Make all the ElementParsers receive their dependencies via - // 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 ImplicitClientBundle bundleClass; - private final JClassType cssResourceType; - private final JClassType imageResourceType; - private final JClassType dataResourceType; - - public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter, - FieldManager fieldManager, TypeOracle oracle, - ImplicitClientBundle bundleClass) { - this.writer = writer; - this.oracle = oracle; - this.messagesWriter = messagesWriter; - this.fieldManager = fieldManager; - this.bundleClass = bundleClass; - this.cssResourceType = oracle.findType(CssResource.class.getCanonicalName()); - this.imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); - this.dataResourceType = oracle.findType(DataResource.class.getCanonicalName()); - } - private enum Resource { data { void create(UiBinderParser parser, XMLElement elem) @@ -96,14 +67,44 @@ abstract void create(UiBinderParser parser, XMLElement elem) throws UnableToCompleteException; } + private static final String FLIP_RTL_ATTRIBUTE = "flipRtl"; + private static final String FIELD_ATTRIBUTE = "field"; + private static final String SOURCE_ATTRIBUTE = "src"; + private static final String REPEAT_STYLE_ATTRIBUTE = "repeatStyle"; + + // TODO(rjrjr) Make all the ElementParsers receive their dependencies via + // constructor like this one does, and make this an ElementParser. I want + // guice!!! + + private static final String IMPORT_ATTRIBUTE = "import"; + private final UiBinderWriter writer; + private final TypeOracle oracle; + private final MessagesWriter messagesWriter; + private final FieldManager fieldManager; + private final ImplicitClientBundle bundleClass; + private final JClassType cssResourceType; + private final JClassType imageResourceType; + + private final JClassType dataResourceType; + + public UiBinderParser(UiBinderWriter writer, MessagesWriter messagesWriter, + FieldManager fieldManager, TypeOracle oracle, + ImplicitClientBundle bundleClass) { + this.writer = writer; + this.oracle = oracle; + this.messagesWriter = messagesWriter; + this.fieldManager = fieldManager; + this.bundleClass = bundleClass; + this.cssResourceType = oracle.findType(CssResource.class.getCanonicalName()); + this.imageResourceType = oracle.findType(ImageResource.class.getCanonicalName()); + this.dataResourceType = oracle.findType(DataResource.class.getCanonicalName()); + } /** * Parses the root UiBinder element, and kicks off the parsing of the rest of * the document. */ public String parse(XMLElement elem) throws UnableToCompleteException { - // TODO(rjrjr) Clearly need to break these find* methods out into their own - // parsers, an so need a registration scheme for uibinder-specific parsers findResources(elem); messagesWriter.findMessagesConfig(elem); XMLElement uiRoot = elem.consumeSingleChildElement(); @@ -117,17 +118,7 @@ return cssResourceType; } - JClassType publicType = oracle.findType(typeName); - if (publicType == null) { - writer.die("In %s, no such type %s", elem, typeName); - } - - if (!publicType.isAssignableTo(cssResourceType)) { - writer.die("In %s, type %s does not extend %s", elem, - publicType.getQualifiedSourceName(), - cssResourceType.getQualifiedSourceName()); - } - return publicType; + return findCssResourceType(elem, typeName); } private JClassType consumeTypeAttribute(XMLElement elem) @@ -243,8 +234,17 @@ String name = elem.consumeAttribute(FIELD_ATTRIBUTE, "style"); JClassType publicType = consumeCssResourceType(elem); + String importTypeNames = elem.consumeAttribute(IMPORT_ATTRIBUTE, null); + LinkedHashSet<JClassType> importTypes = new LinkedHashSet<JClassType>(); + if (importTypeNames != null) { + String[] typeNames = importTypeNames.split("\\s+"); + for (String type : typeNames) { + importTypes.add(findCssResourceType(elem, type)); + } + } + ImplicitCssResource cssMethod = bundleClass.createCssResource(name, source, - publicType, body); + publicType, body, importTypes); FieldWriter field = fieldManager.registerFieldOfGeneratedType( cssMethod.getPackageName(), cssMethod.getClassName(), @@ -253,6 +253,21 @@ cssMethod.getName())); } + private JClassType findCssResourceType(XMLElement elem, String typeName) + throws UnableToCompleteException { + JClassType publicType = oracle.findType(typeName); + if (publicType == null) { + writer.die("In %s, no such type %s", elem, typeName); + } + + if (!publicType.isAssignableTo(cssResourceType)) { + writer.die("In %s, type %s does not extend %s", elem, + publicType.getQualifiedSourceName(), + cssResourceType.getQualifiedSourceName()); + } + return publicType; + } + private void findResources(XMLElement binderElement) throws UnableToCompleteException { binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java index 7d8acb6..63340e3 100644 --- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java +++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -1191,7 +1191,7 @@ } private void writeClassOpen(IndentedWriter w) { - w.write("public class %s extends AbstractUiBinder<%s, %s> implements %s {", + w.write("public class %s implements UiBinder<%s, %s>, %s {", implClassName, uiRootType.getName(), uiOwnerType.getName(), baseClass.getName()); w.indent(); @@ -1199,7 +1199,7 @@ private void writeCssInjectors(IndentedWriter w) { for (ImplicitCssResource css : bundleClass.getCssMethods()) { - w.write("ensureCssInjected(%s.%s());", bundleClass.getFieldName(), + w.write("%s.%s().ensureInjected();", bundleClass.getFieldName(), css.getName()); } w.newline(); @@ -1242,7 +1242,7 @@ private void writeImports(IndentedWriter w) { w.write("import com.google.gwt.core.client.GWT;"); - w.write("import com.google.gwt.uibinder.client.AbstractUiBinder;"); + w.write("import com.google.gwt.uibinder.client.UiBinder;"); w.write("import com.google.gwt.uibinder.client.UiBinderUtil;"); w.write("import %s.%s;", uiRootType.getPackage().getName(), uiRootType.getName());
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java index 6ec02e0..8f0a906 100644 --- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java +++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
@@ -30,7 +30,7 @@ // LinkedHashSets for consistent order across recompiles private final LinkedHashSet<ImplicitCssResource> cssMethods = new LinkedHashSet<ImplicitCssResource>(); private final LinkedHashSet<ImplicitImageResource> imageMethods = new LinkedHashSet<ImplicitImageResource>(); - private final LinkedHashSet<ImplicitDataResource> dataMethods = new LinkedHashSet<ImplicitDataResource>(); + private final LinkedHashSet<ImplicitDataResource> dataMethods = new LinkedHashSet<ImplicitDataResource>(); private final String packageName; private final String className; private final String fieldName; @@ -60,12 +60,14 @@ * @param extendedInterface the public interface implemented by this * CssResource, or null * @param body the inline css text + * @param importTypes for the {@literal @}Import annotation, if any. LinkedHashSet + * to enforce deterministic order across recompiles * @return */ public ImplicitCssResource createCssResource(String name, String source, - JClassType extendedInterface, String body) { + JClassType extendedInterface, String body, LinkedHashSet<JClassType> importTypes) { ImplicitCssResource css = new ImplicitCssResource(packageName, cssBaseName - + name, name, source, extendedInterface, body, logger); + + name, name, source, extendedInterface, body, logger, importTypes); cssMethods.add(css); return css; }
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java index b0c6b30..b5c5bb6 100644 --- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java +++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
@@ -29,6 +29,8 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -42,17 +44,19 @@ private final JClassType extendedInterface; private final String body; private final MortalLogger logger; + private final Set<JClassType> imports; private File generatedFile; ImplicitCssResource(String packageName, String className, String name, String source, JClassType extendedInterface, String body, - MortalLogger logger) { + MortalLogger logger, HashSet<JClassType> importTypes) { this.packageName = packageName; this.className = className; this.name = name; this.extendedInterface = extendedInterface; this.body = body; this.logger = logger; + this.imports = Collections.unmodifiableSet(importTypes); if (body.length() > 0) { assert "".equals(source); // Enforced for real by the parser @@ -96,7 +100,7 @@ } CssStylesheet sheet = GenerateCssAst.exec(logger.getTreeLogger(), urls); - return ExtractClassNamesVisitor.exec(sheet); + return ExtractClassNamesVisitor.exec(sheet, imports.toArray(new JClassType[imports.size()])); } /** @@ -106,6 +110,10 @@ return extendedInterface; } + public Set<JClassType> getImports() { + return imports; + } + /** * @return the name of this resource. This is both its method name in the * owning {@link ImplicitClientBundle} and its ui:field name
diff --git a/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.css b/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.css new file mode 100644 index 0000000..eb1702e --- /dev/null +++ b/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.css
@@ -0,0 +1,3 @@ +.body { + background-color: pink; +}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.java b/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.java new file mode 100644 index 0000000..1e1f90a --- /dev/null +++ b/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.java
@@ -0,0 +1,83 @@ +/* + * 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.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.ImportedWithPrefix; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.HasText; +import com.google.gwt.user.client.ui.Widget; + +/** + * Odd widget demonstrates UiBinder's integration with CssResource's obscure but + * crucial imported scopes feature. + */ +public class CssImportScopeSample extends Widget implements HasText { + interface Binder extends UiBinder<DivElement, CssImportScopeSample> { + } + private static final Binder binder = GWT.create(Binder.class); + + interface Bundle extends ClientBundle { + @Source("CssImportScopeSample.css") + InnerStyle innerStyle(); + + @Source("CssImportScopeSample.css") + OuterStyle style(); + } + + @ImportedWithPrefix("inner") + interface InnerStyle extends Style { + } + + @ImportedWithPrefix("outer") + interface OuterStyle extends Style { + } + + interface Style extends CssResource { + String body(); + } + + @UiField(provided = true) + Bundle bundle = GWT.create(Bundle.class); + @UiField + Element inner; + + @UiField + Element outer; + + CssImportScopeSample() { + bundle.style().ensureInjected(); + bundle.innerStyle().ensureInjected(); + setElement(binder.createAndBindUi(this)); + } + + public String getText() { + return outer.getInnerText(); + } + + public void setText(String text) { + outer.setInnerText(text); + } + + public void setWrappedText(String text) { + inner.setInnerText(text); + } +}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.ui.xml new file mode 100644 index 0000000..3c56a2c --- /dev/null +++ b/user/src/com/google/gwt/uibinder/sample/client/CssImportScopeSample.ui.xml
@@ -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 --> +<!-- 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' > + <ui:with field='bundle' type='com.google.gwt.uibinder.sample.client.CssImportScopeSample.Bundle' /> + + <ui:style import='com.google.gwt.uibinder.sample.client.CssImportScopeSample.OuterStyle + com.google.gwt.uibinder.sample.client.CssImportScopeSample.InnerStyle'> + .outer-body .inner-body { width: 100px; background-color: red; } + </ui:style> + + <div class='{bundle.style.body}'> + <span ui:field='outer'/> + <div ui:field='inner' class='{bundle.innerStyle.body}'>Inner!</div> + </div> +</ui:UiBinder>
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 4126a0e..2a5e0f1 100644 --- a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java +++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -58,7 +58,7 @@ public interface Style extends CssResource { String menuBar(); } - + interface Binder extends UiBinder<Widget, WidgetBasedUi> { } private static final Binder binder = GWT.create(Binder.class); @@ -121,6 +121,7 @@ @UiField Image babyWidget; @UiField ParagraphElement simpleSpriteParagraph; @UiField DataResource heartCursorResource; + @UiField CssImportScopeSample cssImportScopeSample; public WidgetBasedUi() { this.bundledLabel = new Label();
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 f13963f..169888e 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
@@ -348,6 +348,9 @@ (This one too, but even more so.) </span> </p> + <demo:CssImportScopeSample ui:field='cssImportScopeSample' wrappedText='Please use it.'> + And this one relies on CssResource's imported scopes feature + </demo:CssImportScopeSample> <h2>Evolving</h2> <p> Things change. This label uses the new ui:field attribute to declare
diff --git a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java index 0d2749f..e52cc00 100644 --- a/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java +++ b/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java
@@ -25,6 +25,7 @@ import com.google.gwt.junit.Platform; import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.DataResource; import com.google.gwt.resources.client.CssResource.NotStrict; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.DisclosurePanel; @@ -356,6 +357,11 @@ assertNotNull(widgetUi.heartCursorResource.getUrl()); } + @DoNotRunWith(Platform.Htmlunit) + public void testCssImportedScopes() { + assertEquals(100, widgetUi.cssImportScopeSample.inner.getOffsetWidth()); + } + public void testSpritedElement() { assertEquals(widgetUi.prettyImage.getWidth(), widgetUi.simpleSpriteParagraph.getOffsetWidth());