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