Allows CSS @def declarations to be retrieved as a String in a CssResource implementation. Detection was added for colliding class names and @def names, but you can dis-ambiguate them with the @ClassName() annotation. Patch by: zundel Review by: bobv git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5664 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/client/CssResource.java b/user/src/com/google/gwt/resources/client/CssResource.java index e566e53..dc3a446 100644 --- a/user/src/com/google/gwt/resources/client/CssResource.java +++ b/user/src/com/google/gwt/resources/client/CssResource.java
@@ -32,9 +32,10 @@ * <li>{@code String someClassName();} will allow the css class * <code>.someClassName</code> to be obfuscated at runtime. The function will * return the obfuscated class name.</li> - * <li>{@code <primitive numeric type> someDefName();} will allow access to the - * values defined by {@literal @def} rules within the CSS file. The defined - * value must be a raw number, a CSS length, or a percentage value. + * <li>{@code <primitive numeric type or String> someDefName();} will allow + * access to the values defined by {@literal @def} rules within the CSS file. + * The defined value must be a raw number, a CSS length, or a percentage value + * if it is to be returned as a numeric type. * </ul> * * <p>
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java index 2a4a7e5..dff332a 100644 --- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -115,6 +115,7 @@ */ private final Map<String, Map<JMethod, String>> classReplacementsWithPrefix; private final Pattern classSelectorPattern = Pattern.compile("\\.([^ :>+#.]*)"); + private final Set<String> cssDefs = new HashSet<String>(); private final Set<String> externalClasses; private final TreeLogger logger; private final Set<JMethod> missingClasses; @@ -137,6 +138,11 @@ } @Override + public void endVisit(CssDef x, Context ctx) { + cssDefs.add(x.getKey()); + } + + @Override public void endVisit(CssSelector x, Context ctx) { String sel = x.getSelector(); @@ -193,6 +199,19 @@ @Override public void endVisit(CssStylesheet x, Context ctx) { boolean stop = false; + + // Skip names corresponding to @def entries. They too can be declared as + // String accessors. + List<JMethod> toRemove = new ArrayList<JMethod>(); + for (JMethod method : missingClasses) { + if (cssDefs.contains(method.getName())) { + toRemove.add(method); + } + } + for (JMethod method : toRemove) { + missingClasses.remove(method); + } + if (!missingClasses.isEmpty()) { stop = true; TreeLogger errorLogger = logger.branch(TreeLogger.INFO, @@ -231,6 +250,15 @@ } } + static class DefsCollector extends CssVisitor { + private final Set<String> defs = new HashSet<String>(); + + @Override + public void endVisit(CssDef x, Context ctx) { + defs.add(x.getKey()); + } + } + /** * Collects all {@code @external} declarations in the stylesheet. */ @@ -1212,8 +1240,8 @@ private boolean prettyOutput; private Map<JClassType, Map<JMethod, String>> replacementsByClassAndMethod; private Map<JMethod, String> replacementsForSharedMethods; - private Map<JMethod, CssStylesheet> stylesheetMap; private JClassType stringType; + private Map<JMethod, CssStylesheet> stylesheetMap; @Override public String createAssignment(TreeLogger logger, ResourceContext context, @@ -1701,34 +1729,39 @@ NumberValue numberValue = def.getValues().get(0).isNumberValue(); - if (numberValue == null) { - logger.log(TreeLogger.ERROR, "The define named " + name - + " does not define a numeric value"); - throw new UnableToCompleteException(); + String returnExpr = ""; + JClassType classReturnType = toImplement.getReturnType().isClass(); + if (classReturnType != null + && "java.lang.String".equals(classReturnType.getQualifiedSourceName())) { + returnExpr = "\"" + Generator.escape(def.getValues().get(0).toString()) + + "\""; + } else { + JPrimitiveType returnType = toImplement.getReturnType().isPrimitive(); + if (returnType == null) { + logger.log(TreeLogger.ERROR, toImplement.getName() + + ": Return type must be primitive type or String for " + + "@def accessors"); + throw new UnableToCompleteException(); + } + if (returnType == JPrimitiveType.INT || returnType == JPrimitiveType.LONG) { + returnExpr = "" + Math.round(numberValue.getValue()); + } else if (returnType == JPrimitiveType.FLOAT) { + returnExpr = numberValue.getValue() + "F"; + } else if (returnType == JPrimitiveType.DOUBLE) { + returnExpr = "" + numberValue.getValue(); + } else { + logger.log(TreeLogger.ERROR, returnType.getQualifiedSourceName() + + " is not a valid primitive return type for @def accessors"); + throw new UnableToCompleteException(); + } } - - JPrimitiveType returnType = toImplement.getReturnType().isPrimitive(); - assert returnType != null; - sw.print(toImplement.getReadableDeclaration(false, false, false, false, true)); sw.println(" {"); sw.indent(); - if (returnType == JPrimitiveType.INT || returnType == JPrimitiveType.LONG) { - sw.println("return " + Math.round(numberValue.getValue()) + ";"); - } else if (returnType == JPrimitiveType.FLOAT) { - sw.println("return " + numberValue.getValue() + "F;"); - } else if (returnType == JPrimitiveType.DOUBLE) { - sw.println("return " + numberValue.getValue() + ";"); - } else { - logger.log(TreeLogger.ERROR, returnType.getQualifiedSourceName() - + " is not a valid return type for @def accessors"); - throw new UnableToCompleteException(); - } + sw.println("return " + returnExpr + ";"); sw.outdent(); sw.println("}"); - - numberValue.getValue(); } /** @@ -1738,20 +1771,31 @@ CssStylesheet sheet, JMethod[] methods, Map<JMethod, String> obfuscatedClassNames) throws UnableToCompleteException { + + // Get list of @defs + DefsCollector collector = new DefsCollector(); + collector.accept(sheet); + for (JMethod toImplement : methods) { String name = toImplement.getName(); if ("getName".equals(name) || "getText".equals(name)) { continue; } - if (toImplement.getReturnType().equals(stringType) - && toImplement.getParameters().length == 0) { - writeClassAssignment(sw, toImplement, obfuscatedClassNames); + // Bomb out if there is a collision between @def and a style name + if (collector.defs.contains(name) + && obfuscatedClassNames.containsKey(toImplement)) { + logger.log(TreeLogger.ERROR, "@def shadows CSS class name: " + name + + ". Fix by renaming the @def name or the CSS class name."); + throw new UnableToCompleteException(); + } - } else if (toImplement.getReturnType().isPrimitive() != null + if (collector.defs.contains(toImplement.getName()) && toImplement.getParameters().length == 0) { writeDefAssignment(logger, sw, toImplement, sheet); - + } else if (toImplement.getReturnType().equals(stringType) + && toImplement.getParameters().length == 0) { + writeClassAssignment(sw, toImplement, obfuscatedClassNames); } else { logger.log(TreeLogger.ERROR, "Don't know how to implement method " + toImplement.getName());
diff --git a/user/test/com/google/gwt/resources/client/CSSResourceTest.java b/user/test/com/google/gwt/resources/client/CSSResourceTest.java index a07e86c..6cc3eca 100644 --- a/user/test/com/google/gwt/resources/client/CSSResourceTest.java +++ b/user/test/com/google/gwt/resources/client/CSSResourceTest.java
@@ -35,10 +35,19 @@ } interface CssWithDefines extends CssResource { + String colorString(); + double lengthFloat(); int lengthInt(); + String lengthString(); + + int overrideInt(); + + @ClassName("overrideInt") + String overrideIntClass(); + double percentFloat(); int percentInt(); @@ -258,6 +267,14 @@ assertEquals(50, defines.percentInt()); assertEquals(50.5, defines.percentFloat()); + + assertEquals("100px", defines.lengthString()); + assertEquals("#f00", defines.colorString()); + + assertEquals(10, defines.overrideInt()); + assertNotNull(defines.overrideIntClass()); + assertFalse("10px".equals(defines.overrideIntClass())); + assertFalse("10".equals(defines.overrideIntClass())); } public void testMultipleBundles() {
diff --git a/user/test/com/google/gwt/resources/client/deftest.css b/user/test/com/google/gwt/resources/client/deftest.css index 0db8a81..f168b53 100644 --- a/user/test/com/google/gwt/resources/client/deftest.css +++ b/user/test/com/google/gwt/resources/client/deftest.css
@@ -22,4 +22,24 @@ @def percentFloat 50.5%; @def lengthInt 50px; - @def lengthFloat 1.5px; \ No newline at end of file + @def lengthFloat 1.5px; + + @def lengthString 100px; + @def colorString #f00; + + /* Uncomment this, and you should get an error about a @def shadowing a name */ + /* + .colorString { + background-color: #f00; + } + */ + + /* Shouldn't get an error here because the method to access the style name + * is annotated with @ClassName() + */ + @def overrideInt 10px; + .overrideInt { + width: 10px; + } + + \ No newline at end of file