Add CssResource interface generator.
Patch by: bobv
Review by: rjrjr
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6344 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/doc/build.xml b/doc/build.xml
index 5ce1b14..fb7ac64 100644
--- a/doc/build.xml
+++ b/doc/build.xml
@@ -10,7 +10,7 @@
<property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-${build.host.platform}.jar" />
<property name="USER_PKGS"
- value="com.google.gwt.animation.client;com.google.gwt.benchmarks.client;com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.soyc;com.google.gwt.core.ext.linker;com.google.gwt.core.ext.typeinfo;com.google.gwt.debug.client;com.google.gwt.dom.client;com.google.gwt.event.dom.client;com.google.gwt.event.logical.shared;com.google.gwt.event.shared;com.google.gwt.http.client;com.google.gwt.i18n.client;com.google.gwt.i18n.client.constants;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.datepicker.client;com.google.gwt.user.server.rpc;com.google.gwt.xml.client"/>
+ value="com.google.gwt.animation.client;com.google.gwt.benchmarks.client;com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.soyc;com.google.gwt.core.ext.linker;com.google.gwt.core.ext.typeinfo;com.google.gwt.debug.client;com.google.gwt.dom.client;com.google.gwt.event.dom.client;com.google.gwt.event.logical.shared;com.google.gwt.event.shared;com.google.gwt.http.client;com.google.gwt.i18n.client;com.google.gwt.i18n.client.constants;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.resources.client;com.google.gwt.resources.ext;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.datepicker.client;com.google.gwt.user.server.rpc;com.google.gwt.xml.client"/>
<property name="LANG_PKGS" value="java.lang;java.lang.annotation;java.util;java.io;java.sql" />
<!-- Individual classes to include when we don't want to
diff --git a/user/src/com/google/gwt/resources/client/CssResource.java b/user/src/com/google/gwt/resources/client/CssResource.java
index a35af8f..6f4baec 100644
--- a/user/src/com/google/gwt/resources/client/CssResource.java
+++ b/user/src/com/google/gwt/resources/client/CssResource.java
@@ -136,6 +136,10 @@
*
* If a <code>String foo()</code> method were defined in <code>MyCss</code>, it
* would return the string value "<code>foo</code>".
+ * <p>
+ * The utility tool <code>com.google.gwt.resources.css.InterfaceGenerator</code>
+ * can be used to automatically generate a Java interface from a
+ * CssResource-compatible CSS file.
*
* @see <a href="http://code.google.com/p/google-web-toolkit/wiki/CssResource"
* >CssResource design doc</a>
diff --git a/user/src/com/google/gwt/resources/css/GenerateCssAst.java b/user/src/com/google/gwt/resources/css/GenerateCssAst.java
index 8749f3e..f9b37e4 100644
--- a/user/src/com/google/gwt/resources/css/GenerateCssAst.java
+++ b/user/src/com/google/gwt/resources/css/GenerateCssAst.java
@@ -614,9 +614,11 @@
private static final String VALUE_FUNCTION_NAME = "value";
/**
- * Create a CssStylesheet from the contents of a URL.
+ * Create a CssStylesheet from the contents of one or more URLs. If multiple
+ * URLs are provided, the generated stylesheet will be created as though the
+ * contents of the URLs had been concatenated.
*/
- public static CssStylesheet exec(TreeLogger logger, URL[] stylesheets)
+ public static CssStylesheet exec(TreeLogger logger, URL... stylesheets)
throws UnableToCompleteException {
Parser p = new Parser();
Errors errors = new Errors(logger);
@@ -652,8 +654,8 @@
* Expresses an rgb function as a hex expression.
*
* @param colors a sequence of LexicalUnits, assumed to be
- * <code>(VAL COMMA VAL COMMA VAL)</code>
- * where VAL can be an INT or a PERCENT (which is then converted to INT)
+ * <code>(VAL COMMA VAL COMMA VAL)</code> where VAL can be an INT or
+ * a PERCENT (which is then converted to INT)
* @return the minimal hex expression for the RGB color values
*/
private static Value colorValue(LexicalUnit colors) {
@@ -745,11 +747,11 @@
/**
* Return an integer value from 0-255 for a component of an RGB color.
*
- * @param color typed value from the CSS parser, which may be an INTEGER or
- * a PERCENTAGE
+ * @param color typed value from the CSS parser, which may be an INTEGER or a
+ * PERCENTAGE
* @return integer value from 0-255
* @throws IllegalArgumentException if the color is not an INTEGER or
- * PERCENTAGE value
+ * PERCENTAGE value
*/
private static int getRgbComponentValue(LexicalUnit color) {
switch (color.getLexicalUnitType()) {
diff --git a/user/src/com/google/gwt/resources/css/InterfaceGenerator.java b/user/src/com/google/gwt/resources/css/InterfaceGenerator.java
new file mode 100644
index 0000000..ad0e63c
--- /dev/null
+++ b/user/src/com/google/gwt/resources/css/InterfaceGenerator.java
@@ -0,0 +1,262 @@
+/*
+ * 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.resources.css;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.ClassName;
+import com.google.gwt.resources.css.ast.CssStylesheet;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.rebind.StringSourceWriter;
+import com.google.gwt.util.tools.ArgHandlerFile;
+import com.google.gwt.util.tools.ArgHandlerFlag;
+import com.google.gwt.util.tools.ArgHandlerString;
+import com.google.gwt.util.tools.ToolBase;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * A utility class for creating a Java interface declaration for a given CSS
+ * file.
+ */
+public class InterfaceGenerator extends ToolBase {
+
+ private static final Comparator<String> CSS_CLASS_COMPARATOR = new Comparator<String>() {
+ public int compare(String o1, String o2) {
+ return o1.compareToIgnoreCase(o2);
+ }
+ };
+
+ private static final TreeLogger.Type LOG_LEVEL = TreeLogger.WARN;
+
+ public static void main(String[] args) {
+ (new InterfaceGenerator()).execImpl(args);
+ }
+
+ private String interfaceName;
+ private File inputFile;
+ private TreeLogger logger;
+ private boolean standaloneFile;
+
+ private InterfaceGenerator() {
+ // -standalone
+ registerHandler(new ArgHandlerFlag() {
+
+ @Override
+ public String getPurpose() {
+ return "Add package and import statements to generated interface";
+ }
+
+ @Override
+ public String getTag() {
+ return "-standalone";
+ }
+
+ @Override
+ public boolean setFlag() {
+ standaloneFile = true;
+ logger.log(TreeLogger.DEBUG, "Creating standalone file");
+ return true;
+ }
+ });
+
+ // -typeName some.package.MyCssResource
+ registerHandler(new ArgHandlerString() {
+
+ @Override
+ public String getPurpose() {
+ return "The name of the generated CssResource subtype";
+ }
+
+ @Override
+ public String getTag() {
+ return "-typeName";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"some.package.MyCssResource"};
+ }
+
+ @Override
+ public boolean isRequired() {
+ return true;
+ }
+
+ @Override
+ public boolean setString(String str) {
+ if (str.length() == 0) {
+ return false;
+ }
+ if (!Character.isJavaIdentifierStart(str.charAt(0))) {
+ return false;
+ }
+ for (int i = 1, j = str.length(); i < j; i++) {
+ char c = str.charAt(i);
+ if (!(Character.isJavaIdentifierPart(c) || c == '.')) {
+ return false;
+ }
+ }
+ interfaceName = str;
+ logger.log(TreeLogger.DEBUG, "interfaceName = " + interfaceName);
+ return true;
+ }
+ });
+
+ // -css in.css
+ registerHandler(new ArgHandlerFile() {
+
+ @Override
+ public String getPurpose() {
+ return "The input CSS file to process";
+ }
+
+ @Override
+ public String getTag() {
+ return "-css";
+ }
+
+ @Override
+ public boolean isRequired() {
+ return true;
+ }
+
+ @Override
+ public void setFile(File file) {
+ inputFile = file;
+ logger.log(TreeLogger.DEBUG, "inputFile = " + file.getAbsolutePath());
+ }
+ });
+ }
+
+ @Override
+ protected String getDescription() {
+ return "Create a CssResource interface based on a CSS file";
+ }
+
+ private void execImpl(String[] args) {
+ // Set up logging
+ PrintWriter logWriter = new PrintWriter(System.err);
+ logger = new PrintWriterTreeLogger(logWriter);
+ ((PrintWriterTreeLogger) logger).setMaxDetail(LOG_LEVEL);
+
+ // Process args or die
+ if (!processArgs(args)) {
+ System.exit(-1);
+ }
+
+ boolean error = false;
+ try {
+ System.out.println(process());
+ } catch (MalformedURLException e) {
+ logger.log(TreeLogger.ERROR, "Unable to load CSS", e);
+ error = true;
+ } catch (UnableToCompleteException e) {
+ logger.log(TreeLogger.ERROR, "Unable to process CSS", e);
+ error = true;
+ } finally {
+ // Make sure the logs are emitted
+ logWriter.flush();
+ }
+
+ System.exit(error ? -1 : 0);
+ }
+
+ /**
+ * Munge a CSS class name into a Java identifier.
+ */
+ private String methodName(String className) {
+ StringBuilder sb = new StringBuilder();
+ char c = className.charAt(0);
+ boolean nextUpCase = false;
+
+ if (Character.isJavaIdentifierStart(c)) {
+ sb.append(Character.toLowerCase(c));
+ }
+
+ for (int i = 1, j = className.length(); i < j; i++) {
+ c = className.charAt(i);
+ if (!Character.isJavaIdentifierPart(c)) {
+ nextUpCase = true;
+ continue;
+ }
+
+ if (nextUpCase) {
+ nextUpCase = false;
+ c = Character.toUpperCase(c);
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ private String process() throws MalformedURLException,
+ UnableToCompleteException {
+ // Create AST
+ CssStylesheet sheet = GenerateCssAst.exec(logger, inputFile.toURI().toURL());
+
+ // Sort all class names
+ Set<String> classNames = new TreeSet<String>(CSS_CLASS_COMPARATOR);
+ classNames.addAll(ExtractClassNamesVisitor.exec(sheet));
+
+ // Deduplicate method names
+ Set<String> methodNames = new HashSet<String>();
+
+ // Build the interface
+ SourceWriter sw = new StringSourceWriter();
+
+ int lastDot = interfaceName.lastIndexOf('.');
+ if (standaloneFile) {
+ sw.println("// DO NOT EDIT");
+ sw.println("// Automatically generated by "
+ + InterfaceGenerator.class.getName());
+ sw.println("package " + interfaceName.substring(0, lastDot) + ";");
+ sw.println("import " + CssResource.class.getCanonicalName() + ";");
+ sw.println("import " + ClassName.class.getCanonicalName() + ";");
+ }
+
+ sw.println("interface " + interfaceName.substring(lastDot + 1)
+ + " extends CssResource {");
+ sw.indent();
+ for (String className : classNames) {
+ String methodName = methodName(className);
+
+ while (!methodNames.add(methodName)) {
+ // Unusual, handles foo-bar and foo--bar
+ methodName += "_";
+ }
+
+ sw.println();
+ if (!methodName.equals(className)) {
+ sw.println("@ClassName(\"" + Generator.escape(className) + "\")");
+ }
+ sw.println("String " + methodName + "();");
+ }
+ sw.outdent();
+ sw.println("}");
+
+ return sw.toString();
+ }
+}
diff --git a/user/src/com/google/gwt/resources/css/Minify.java b/user/src/com/google/gwt/resources/css/Minify.java
index 5523312..b7fc260 100644
--- a/user/src/com/google/gwt/resources/css/Minify.java
+++ b/user/src/com/google/gwt/resources/css/Minify.java
@@ -117,7 +117,7 @@
}
try {
- CssStylesheet sheet = GenerateCssAst.exec(logger, new URL[] {source});
+ CssStylesheet sheet = GenerateCssAst.exec(logger, source);
TextOutput out = new DefaultTextOutput(!pretty);
(new CssGenerationVisitor(out)).accept(sheet);
System.out.println(out.toString());
diff --git a/user/src/com/google/gwt/resources/css/ast/CssSelector.java b/user/src/com/google/gwt/resources/css/ast/CssSelector.java
index b6067ce..44ebf53 100644
--- a/user/src/com/google/gwt/resources/css/ast/CssSelector.java
+++ b/user/src/com/google/gwt/resources/css/ast/CssSelector.java
@@ -21,7 +21,7 @@
* An opaque view of a selector.
*/
public class CssSelector extends CssNode {
- public static final Pattern CLASS_SELECTOR_PATTERN = Pattern.compile("\\.([^ :>+#.]*)");
+ public static final Pattern CLASS_SELECTOR_PATTERN = Pattern.compile("\\.([^ \\[:>+#.]+)");
/*
* TODO: Evaluate whether or not we need to have a type hierarchy of
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 b940167..522e044 100644
--- a/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
+++ b/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
@@ -1,12 +1,12 @@
/*
* Copyright 2009 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -78,7 +78,7 @@
/**
* @return the set of CSS classnames in the underlying .css files
- *
+ *
* @throws UnableToCompleteException if the user has called for a .css file we
* can't find.
*/
@@ -89,7 +89,7 @@
urls = getExternalCss();
} else {
try {
- urls = new URL[] {getGeneratedFile().toURL()};
+ urls = new URL[] {getGeneratedFile().toURI().toURL()};
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
diff --git a/user/test/com/google/gwt/resources/client/CSSResourceTest.java b/user/test/com/google/gwt/resources/client/CSSResourceTest.java
index bdf8ee9..e1297ff 100644
--- a/user/test/com/google/gwt/resources/client/CSSResourceTest.java
+++ b/user/test/com/google/gwt/resources/client/CSSResourceTest.java
@@ -19,14 +19,12 @@
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.resources.client.CssResource.Import;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
-import com.google.gwt.resources.client.CssResource.NotStrict;
import com.google.gwt.resources.client.CssResource.Shared;
/**
* Contains various full-stack tests of the CssResource system.
*/
public class CSSResourceTest extends GWTTestCase {
-
interface ChildResources extends Resources {
ChildResources INSTANCE = GWT.create(ChildResources.class);
@@ -64,6 +62,63 @@
int rawInt();
}
+ /**
+ * Regenerate this interface by running InterfaceGenerator over test.css.
+ */
+ interface FullTestCss extends CssResource {
+ String affectedBySprite();
+
+ String blahA();
+
+ String css3Color();
+
+ String externalA();
+
+ String externalB();
+
+ String externalC();
+
+ String extraSpriteClass();
+
+ @ClassName("FAIL")
+ String fAIL();
+
+ @ClassName("may-combine")
+ String mayCombine();
+
+ @ClassName("may-combine2")
+ String mayCombine2();
+
+ @ClassName("may-merge")
+ String mayMerge();
+
+ @ClassName("may-not-combine")
+ String mayNotCombine();
+
+ @ClassName("may-not-combine2")
+ String mayNotCombine2();
+
+ @ClassName("may-not-merge")
+ String mayNotMerge();
+
+ @ClassName("may-not-merge-or-combine-because-of-this")
+ String mayNotMergeOrCombineBecauseOfThis();
+
+ String multiClassA();
+
+ String multiClassB();
+
+ String replacement();
+
+ @ClassName("replacement-not-java-ident")
+ String replacementNotJavaIdent();
+
+ String spriteClass();
+
+ @ClassName("this-does-not-matter")
+ String thisDoesNotMatter();
+ }
+
interface HasDescendants extends CssResource {
String foo();
}
@@ -116,8 +171,7 @@
MyCssResourceB b();
@Source("test.css")
- @NotStrict
- MyCssResourceWithSprite css();
+ FullTestCss css();
@Source("32x32.png")
DataResource dataMethod();
@@ -186,7 +240,7 @@
}
public void testCss() {
- MyCssResourceWithSprite css = Resources.INSTANCE.css();
+ FullTestCss css = Resources.INSTANCE.css();
String text = css.getText();
report(text);
@@ -202,7 +256,7 @@
assertFalse("replacement".equals(css.replacement()));
assertTrue(text.contains("." + css.replacement()));
assertTrue(text.contains("." + css.replacement() + ":after"));
- assertTrue(text.contains("." + css.nameOverride()));
+ assertTrue(text.contains("." + css.replacementNotJavaIdent()));
// Make sure renaming for multi-class selectors (.foo.bar) works
assertFalse("multiClassA".equals(css.multiClassA()));
@@ -218,8 +272,8 @@
// Check interestingly-named idents
assertTrue(text.contains("\\-some-wacky-extension"));
- assertTrue(text.contains(".ns\\:tag"));
- assertTrue(text.contains(".ns\\:tag:pseudo"));
+ assertTrue(text.contains("ns\\:tag"));
+ assertTrue(text.contains("ns\\:tag:pseudo"));
// Check escaped string values
assertTrue(text.contains("\"Hello\\\\\\\" world\""));
@@ -247,12 +301,14 @@
assertTrue(text.indexOf("static:PASSED") < text.indexOf("runtime:PASSED"));
assertTrue(text.indexOf("before:merge") != -1);
assertTrue(text.indexOf("before:merge") < text.indexOf("after:merge"));
- assertTrue(text.indexOf(".may-combine,.may-combine2") != -1);
+ assertTrue(text.indexOf("." + css.mayCombine() + ",." + css.mayCombine2()) != -1);
assertTrue(text.indexOf("merge:merge") != -1);
- assertTrue(text.indexOf("merge:merge") < text.indexOf("may-not-combine"));
+ assertTrue(text.indexOf("merge:merge") < text.indexOf("."
+ + css.mayNotCombine()));
assertTrue(text.indexOf("may-not-combine") < text.indexOf("prevent:true"));
assertTrue(text.indexOf("prevent:true") < text.indexOf("prevent-merge:true"));
- assertTrue(text.indexOf("prevent:true") < text.indexOf("may-not-combine2"));
+ assertTrue(text.indexOf("prevent:true") < text.indexOf("."
+ + css.mayNotCombine2()));
// Check commonly-used CSS3 constructs
assertTrue(text.contains("background-color:rgba(0,0,0,0.5);"));
diff --git a/user/test/com/google/gwt/resources/client/test.css b/user/test/com/google/gwt/resources/client/test.css
index 76059d4..e33311d 100644
--- a/user/test/com/google/gwt/resources/client/test.css
+++ b/user/test/com/google/gwt/resources/client/test.css
@@ -112,11 +112,11 @@
top: literal("expression(document.compatMode==\"CSS1Compat\" ? documentElement.scrollTop:document.body.scrollTop \\ 2)");
}
-.ns\:tag {
+ns\:tag {
border: red;
}
-.ns\:tag:pseudo {
+ns\:tag:pseudo {
content : "blah";
}
diff --git a/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java b/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java
index 517de81..7467a60 100644
--- a/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java
+++ b/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java
@@ -24,7 +24,6 @@
import com.google.gwt.resources.css.ast.CssStylesheet;
import com.google.gwt.resources.rg.CssTestCase;
-import java.net.URL;
import java.util.List;
/**
@@ -34,8 +33,8 @@
public void testClone() throws UnableToCompleteException {
CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL,
- new URL[] {getClass().getClassLoader().getResource(
- "com/google/gwt/resources/client/test.css")});
+ getClass().getClassLoader().getResource(
+ "com/google/gwt/resources/client/test.css"));
CssStylesheet cloned = CssNodeCloner.clone(CssStylesheet.class, sheet);
@@ -45,8 +44,8 @@
public void testCloneList() throws UnableToCompleteException {
CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL,
- new URL[] {getClass().getClassLoader().getResource(
- "com/google/gwt/resources/client/test.css")});
+ getClass().getClassLoader().getResource(
+ "com/google/gwt/resources/client/test.css"));
List<CssNode> cloned = CssNodeCloner.clone(CssNode.class, sheet.getNodes());
diff --git a/user/test/com/google/gwt/resources/rg/CssTestCase.java b/user/test/com/google/gwt/resources/rg/CssTestCase.java
index 33cbc0b..2974160 100644
--- a/user/test/com/google/gwt/resources/rg/CssTestCase.java
+++ b/user/test/com/google/gwt/resources/rg/CssTestCase.java
@@ -144,8 +144,8 @@
CssStylesheet testSheet = null;
try {
- expectedSheet = GenerateCssAst.exec(logger, new URL[] {expected});
- testSheet = GenerateCssAst.exec(logger, new URL[] {test});
+ expectedSheet = GenerateCssAst.exec(logger, expected);
+ testSheet = GenerateCssAst.exec(logger, test);
} catch (UnableToCompleteException e) {
fail("Unable to parse stylesheet");
}