Revision: 6932
Author: rj...@google.com
Date: Mon Nov 16 15:06:51 2009
Log: Fixes bad code gen when css class names in a ui:style block have  
dashes in them,
by adopting a convention that {foo.bar-baz} == {foo.barBaz}.

Addresses issue 4052, issue 4053

Review by jasonparekh
http://code.google.com/p/google-web-toolkit/source/detail?r=6932

Added:
   
/trunk/user/src/com/google/gwt/uibinder/attributeparsers/CssNameConverter.java
   
/trunk/user/test/com/google/gwt/uibinder/attributeparsers/CssNameConverterTest.java
Modified:
   
/trunk/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java
  /trunk/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java
  /trunk/user/src/com/google/gwt/uibinder/rebind/CssResourceWriter.java
  /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
  /trunk/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
   
/trunk/user/test/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverterTest.java
  /trunk/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml

=======================================
--- /dev/null
+++  
/trunk/user/src/com/google/gwt/uibinder/attributeparsers/CssNameConverter.java  
 
Mon Nov 16 15:06:51 2009
@@ -0,0 +1,81 @@
+/*
+ * 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.attributeparsers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Converts css class names to a form safe to use as a Java identifier.
+ */
+public class CssNameConverter {
+  /**
+   * Thrown by {...@link CssNameConverter#convertSet(Set)} on name collision.
+   */
+  public static class Failure extends Exception {
+    Failure(String message, Object... params) {
+       super(String.format(message, params));
+    }
+  }
+
+  /**
+   * @param className a css class name
+   * @return the same name in a form safe to use as a Java identifier
+   */
+  public String convertName(String className) {
+    String[] bits = className.split("\\-");
+    StringBuilder b = new StringBuilder();
+    for (String bit : bits) {
+      if (b.length() == 0) {
+        b.append(bit);
+      } else {
+        b.append(bit.substring(0, 1).toUpperCase());
+        if (bit.length() > 1) {
+          b.append(bit.substring(1));
+        }
+      }
+    }
+    String converted = b.toString();
+    return converted;
+  }
+
+  /**
+   * @param classNames css class names to convert
+   * @return map of the same class names in a form safe for use as Java
+   *         identifiers, with the order of the input set preserved
+   * @throws Failure on collisions due to conversions
+   */
+  public Map<String, String> convertSet(Set<String> classNames)
+      throws Failure {
+    Map<String, String> rawToConverted = new LinkedHashMap<String,  
String>();
+    Map<String, String> convertedToRaw = new LinkedHashMap<String,  
String>();
+    for (String className : classNames) {
+      String converted = convertName(className);
+      String already = convertedToRaw.get(converted);
+      if (already != null) {
+        throw new Failure("CSS class name collision: \"%s\" and \"%s\"",  
already,
+            className);
+      }
+      if (!converted.equals(className)) {
+        convertedToRaw.put(converted, className);
+      }
+      rawToConverted.put(className, converted);
+    }
+    return rawToConverted;
+  }
+
+}
=======================================
--- /dev/null
+++  
/trunk/user/test/com/google/gwt/uibinder/attributeparsers/CssNameConverterTest.java
      
Mon Nov 16 15:06:51 2009
@@ -0,0 +1,110 @@
+/*
+ * 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.attributeparsers;
+
+import com.google.gwt.dev.util.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Eponymous test class.
+ */
+public class CssNameConverterTest extends TestCase {
+  static class Pair {
+    final String before;
+    final String after;
+
+    Pair(String before, String after) {
+      this.before = before;
+      this.after = after;
+    }
+  }
+
+  private final CssNameConverter converter = new CssNameConverter();
+
+  public void testCollision() {
+    Set<String> in = Sets.create("charlie-delta", "baker", "charlieDelta",
+        "echo");
+
+    try {
+      converter.convertSet(in);
+    } catch (CssNameConverter.Failure e) {
+      assertContains(e.getMessage(), "charlie-delta");
+      assertContains(e.getMessage(), "charlieDelta");
+    }
+  }
+
+  public void testNameConversion() {
+    Pair[] beforeAndAfter = {
+        new Pair("able", "able"), new Pair("-baker-", "baker"),
+        new Pair("charlie-delta", "charlieDelta"), new  
Pair("echo", "echo"),
+        new Pair("foxtrot-Tango", "foxtrotTango")};
+
+    for (Pair pair : beforeAndAfter) {
+      assertEquals(pair.after, converter.convertName(pair.before));
+    }
+  }
+
+  public void testNoOp() throws CssNameConverter.Failure {
+    Set<String> in = Sets.create("able", "baker", "charlieDelta", "echo");
+    Map<String, String> out = converter.convertSet(in);
+
+    for (Map.Entry<String, String> entry : out.entrySet()) {
+      String key = entry.getKey();
+      assertTrue(in.remove(key));
+      assertEquals(key, entry.getValue());
+    }
+    assertTrue(in.isEmpty());
+  }
+
+  public void testReverseCollision() {
+    Set<String> in = Sets.create("charlieDelta", "baker", "charlie-delta",
+        "echo");
+
+    try {
+      converter.convertSet(in);
+    } catch (CssNameConverter.Failure e) {
+      assertContains(e.getMessage(), "charlie-delta");
+      assertContains(e.getMessage(), "charlieDelta");
+    }
+  }
+
+  public void testSomeOp() throws CssNameConverter.Failure {
+    Set<String> in =  
Sets.create("able", "-baker-", "charlie-delta", "echo",
+        "foxtrot-Tango");
+    Pair[] expected = {
+        new Pair("able", "able"), new Pair("-baker-", "baker"),
+        new Pair("charlie-delta", "charlieDelta"), new  
Pair("echo", "echo"),
+        new Pair("foxtrot-Tango", "foxtrotTango")};
+
+    Map<String, String> out = converter.convertSet(in);
+
+    for (Pair pair : expected) {
+      String convert = out.remove(pair.before);
+      assertEquals(pair.after, convert);
+    }
+
+    assertTrue(out.isEmpty());
+  }
+
+  private void assertContains(String string, String fragment) {
+    assertTrue(String.format("%s should contain %s", string, fragment),
+        string.contains(fragment));
+  }
+}
=======================================
---  
/trunk/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java
    
Wed Nov 11 22:08:47 2009
+++  
/trunk/user/src/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverter.java
    
Mon Nov 16 15:06:51 2009
@@ -34,6 +34,10 @@
   * A field reference starts with '{' and is followed immediately by a  
character
   * that can legally start a java identifier&mdash;that is a letter, $, or
   * underscore. Braces not followed by such a character are left in place.
+ * <p>
+ * For convenience when dealing with generated CssResources, field  
segments with
+ * dashes are converted to camel case. That is, {able.baker-charlie} is  
the same
+ * as {able.bakerCharlie}
   * <p>
   * Opening braces may be escape by doubling them. That is, "{{foo}" will
   * converted to "{foo}", with no field reference detected.
@@ -45,32 +49,6 @@
    @SuppressWarnings("serial")
    public static class IllegalFieldReferenceException extends  
RuntimeException {
    }
-
-  /**
-   * Used by {...@link #hasFieldReferences}. Passthrough implementation that  
notes
-   * when handleReference has been called.
-   */
-  private static final class Telltale implements
-      FieldReferenceConverter.Delegate {
-    boolean hasComputed = false;
-
-    public JType getType() {
-      return null;
-    }
-
-    public String handleFragment(String fragment) {
-      return fragment;
-    }
-
-    public String handleReference(String reference) {
-      hasComputed = true;
-      return reference;
-    }
-
-    public boolean hasComputed() {
-      return hasComputed;
-    }
-  }

    /**
     * Responsible for the bits around and between the field references. May  
throw
@@ -103,10 +81,36 @@
          throws IllegalFieldReferenceException;
    }

-  private static final Pattern BRACES = Pattern.compile("[{]([^}]*)[}]");
-
+  /**
+   * Used by {...@link #hasFieldReferences}. Passthrough implementation that  
notes
+   * when handleReference has been called.
+   */
+  private static final class Telltale implements
+      FieldReferenceConverter.Delegate {
+    boolean hasComputed = false;
+
+    public JType getType() {
+      return null;
+    }
+
+    public String handleFragment(String fragment) {
+      return fragment;
+    }
+
+    public String handleReference(String reference) {
+      hasComputed = true;
+      return reference;
+    }
+
+    public boolean hasComputed() {
+      return hasComputed;
+    }
+  }
+
+  private static final Pattern BRACES = Pattern.compile("[{]([^}]*)[}]");
    private static final Pattern LEGAL_FIRST_CHAR =  
Pattern.compile("^[$_a-zA-Z].*");

+
    /**
     * @return true if the given string holds one or more field references
     */
@@ -116,6 +120,7 @@
      return telltale.hasComputed();
    }

+  private final CssNameConverter cssConverter = new CssNameConverter();
    private final FieldManager fieldManager;

    /**
@@ -162,6 +167,7 @@
      String[] segments = value.split("[.]");

      for (String segment : segments) {
+      segment = cssConverter.convertName(segment);
        if (b.length() == 0) {
          b.append(segment); // field name
        } else {
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java    Wed  
Oct 28 09:10:53 2009
+++ /trunk/user/src/com/google/gwt/uibinder/rebind/BundleWriter.java    Mon  
Nov 16 15:06:51 2009
@@ -40,7 +40,8 @@
    private final ImplicitClientBundle bundleClass;
    private final IndentedWriter writer;
    private final PrintWriterManager writerManager;
-  private final TypeOracle oracle;
+  private final TypeOracle types;
+  private final MortalLogger logger;

    private final JClassType clientBundleType;
    private final JClassType dataResourceType;
@@ -50,27 +51,28 @@
    private final JClassType importAnnotationType;

    public BundleWriter(ImplicitClientBundle bundleClass,
-      PrintWriterManager writerManager, TypeOracle oracle,
-      PrintWriterManager printWriterProvider) {
+      PrintWriterManager writerManager, TypeOracle types, MortalLogger  
logger) {
      this.bundleClass = bundleClass;
      this.writer = new IndentedWriter(
          writerManager.makePrintWriterFor(bundleClass.getClassName()));
      this.writerManager = writerManager;
-    this.oracle = oracle;
-
-    clientBundleType = oracle.findType(ClientBundle.class.getName());
-    dataResourceType =  
oracle.findType(DataResource.class.getCanonicalName());
-    imageOptionType =  
oracle.findType(ImageOptions.class.getCanonicalName());
-    imageResourceType =  
oracle.findType(ImageResource.class.getCanonicalName());
-    repeatStyleType =  
oracle.findType(RepeatStyle.class.getCanonicalName());
-    importAnnotationType =   
oracle.findType(Import.class.getCanonicalName());
+    this.types = types;
+    this.logger = logger;
+
+    clientBundleType = types.findType(ClientBundle.class.getName());
+    dataResourceType =  
types.findType(DataResource.class.getCanonicalName());
+    imageOptionType =  
types.findType(ImageOptions.class.getCanonicalName());
+    imageResourceType =  
types.findType(ImageResource.class.getCanonicalName());
+    repeatStyleType = types.findType(RepeatStyle.class.getCanonicalName());
+    importAnnotationType = types.findType(Import.class.getCanonicalName());
    }

    public void write() throws UnableToCompleteException {
      writeBundleClass();
      for (ImplicitCssResource css : bundleClass.getCssMethods()) {
-      new CssResourceWriter(css, oracle,
-          writerManager.makePrintWriterFor(css.getClassName())).write();
+      new CssResourceWriter(css, types,
+          writerManager.makePrintWriterFor(css.getClassName()),
+          logger).write();
      }
    }

@@ -94,7 +96,7 @@
      writer.write("public interface %s extends ClientBundle {",
          bundleClass.getClassName());
      writer.indent();
-
+
      // Write css methods
      for (ImplicitCssResource css : bundleClass.getCssMethods()) {
        writeCssSource(css);
@@ -102,7 +104,7 @@
        writer.write("%s %s();", css.getClassName(), css.getName());
        writer.newline();
      }
-
+
      // Write data methods
      for (ImplicitDataResource data : bundleClass.getDataMethods()) {
        writer.write("@Source(\"%s\")", data.getSource());
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/CssResourceWriter.java       
 
Wed Oct 28 09:10:53 2009
+++ /trunk/user/src/com/google/gwt/uibinder/rebind/CssResourceWriter.java       
 
Mon Nov 16 15:06:51 2009
@@ -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
@@ -21,30 +21,33 @@
  import com.google.gwt.core.ext.typeinfo.JType;
  import com.google.gwt.core.ext.typeinfo.TypeOracle;
  import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.attributeparsers.CssNameConverter;
  import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;

  import java.io.PrintWriter;
+import java.util.Map;
  import java.util.Set;

  /**
   * Writes the source to implement an {...@link ImplicitCssResource} interface.
   */
  public class CssResourceWriter {
-  /**
-   *
-   */
    private static final JType[] NO_PARAMS = new JType[0];
    private final ImplicitCssResource css;
    private final IndentedWriter writer;
    private final JClassType cssResourceType;
    private final JClassType stringType;
-
-  public CssResourceWriter(ImplicitCssResource css, TypeOracle oracle,
-      PrintWriter writer) {
+  private final CssNameConverter nameConverter;
+  private final MortalLogger logger;
+
+  public CssResourceWriter(ImplicitCssResource css, TypeOracle types,
+      PrintWriter writer, MortalLogger logger) {
      this.css = css;
      this.writer = new IndentedWriter(writer);
-    this.cssResourceType = oracle.findType(CssResource.class.getName());
-    this.stringType = oracle.findType(String.class.getName());
+    this.cssResourceType = types.findType(CssResource.class.getName());
+    this.stringType = types.findType(String.class.getName());
+    this.nameConverter = new CssNameConverter();
+    this.logger = logger;
    }

    public void write() throws UnableToCompleteException {
@@ -75,16 +78,35 @@
      writer.write("}");
    }

-  private void writeCssMethods(JClassType superType) throws  
UnableToCompleteException {
-    Set<String> classNames = css.getCssClassNames();
-
-    /*
-     * Only write names that we are not overriding from super, or else  
we'll
-     * re-obfuscate any @Shared ones
-     */
-    for (String className : classNames) {
-      JMethod method = superType.findMethod(className, NO_PARAMS);
-      if (method == null || !stringType.equals(method.getReturnType())) {
+  private boolean isOverride(String methodName, JClassType superType) {
+    JMethod method = superType.findMethod(methodName, NO_PARAMS);
+    if (method != null && stringType.equals(method.getReturnType())) {
+      return true;
+    }
+    return false;
+  }
+
+  private void writeCssMethods(JClassType superType)
+      throws UnableToCompleteException {
+    Set<String> rawClassNames = css.getCssClassNames();
+    Map<String, String> convertedClassNames = null;
+
+    try {
+      convertedClassNames = nameConverter.convertSet(rawClassNames);
+    } catch (CssNameConverter.Failure e) {
+      logger.die(e.getMessage());
+    }
+
+    for (Map.Entry<String, String> entry : convertedClassNames.entrySet())  
{
+      String className = entry.getValue();
+      /*
+       * Only write names that we are not overriding from super, or else  
we'll
+       * re-obfuscate any @Shared ones
+       */
+      if (!isOverride(className, superType)) {
+        if (!rawClassNames.contains(className)) {
+          writer.write("@ClassName(\"%s\")", entry.getKey());
+        }
          writer.write("String %s();", className);
        }
      }
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java       
 
Wed Nov 11 22:08:47 2009
+++ /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java       
 
Mon Nov 16 15:06:51 2009
@@ -130,7 +130,7 @@
      }

      ImplicitClientBundle bundleClass = uiBinderWriter.getBundleClass();
-    new BundleWriter(bundleClass, writerManager, oracle,  
writerManager).write();
+    new BundleWriter(bundleClass, writerManager, oracle, logger).write();

      writerManager.commit();
    }
=======================================
--- /trunk/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java      Tue Nov 
 
10 12:33:30 2009
+++ /trunk/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java      Mon Nov 
 
16 15:06:51 2009
@@ -15,6 +15,7 @@
   */
  package com.google.gwt.uibinder;

+import com.google.gwt.uibinder.attributeparsers.CssNameConverterTest;
  import  
com.google.gwt.uibinder.attributeparsers.FieldReferenceConverterTest;
  import com.google.gwt.uibinder.attributeparsers.IntAttributeParserTest;
  import com.google.gwt.uibinder.attributeparsers.StrictAttributeParserTest;
@@ -55,6 +56,7 @@
      suite.addTestSuite(OwnerFieldTest.class);

      // attributeparsers
+    suite.addTestSuite(CssNameConverterTest.class);
      suite.addTestSuite(IntAttributeParserTest.class);
      suite.addTestSuite(FieldReferenceConverterTest.class);
      suite.addTestSuite(StrictAttributeParserTest.class);
=======================================
---  
/trunk/user/test/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverterTest.java
       
Wed Nov 11 22:08:47 2009
+++  
/trunk/user/test/com/google/gwt/uibinder/attributeparsers/FieldReferenceConverterTest.java
       
Mon Nov 16 15:06:51 2009
@@ -52,6 +52,13 @@
      assertEquals(expected, converter.convert(before, frDelegate));
    }

+  public void testOne() {
+    String before = "{baker}";
+    String expected = "** & baker & **";
+
+    assertEquals(expected, converter.convert(before, frDelegate));
+  }
+
    public void testReplaceSimple() {
      String before = "able {baker} charlie";
      String expected = "*able * & baker & * charlie*";
@@ -59,13 +66,20 @@
      assertEquals(expected, converter.convert(before, frDelegate));
    }

+  public void testDashes() {
+    String before = "{foo-bar.baz-bangZoom.zip-zap}";
+    String expected = "** & fooBar.bazBangZoom().zipZap() & **";
+
+    assertEquals(expected, converter.convert(before, frDelegate));
+  }
+
    public void testReplaceSeveral() {
      String before = "{foo.bar.baz} baker {bang.zoom} delta {zap}";
      String expected = "** & foo.bar().baz() & * baker * & bang.zoom() & *  
delta * & zap & **";

      assertEquals(expected, converter.convert(before, frDelegate));
    }
-
+
    public void testEscaping() {
      String before = "Well {{Hi mom}!";
      String expected = "*Well {Hi mom}!*";
=======================================
---  
/trunk/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml       
 
Mon Nov 16 10:04:26 2009
+++  
/trunk/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml       
 
Mon Nov 16 15:06:51 2009
@@ -100,7 +100,7 @@
  <ui:style field='myTotallyPrivateStyle'>
    /* An inline style tied to no public type */

-  .superPrivateColor {
+  .super-private-color {
      background-color: Gold;
    }
  </ui:style>
@@ -422,7 +422,7 @@
        </p>
        <p ui:field='reallyPrivateStyleParagraph'  
class='{myOtherStyle.privateColor}'>
          And this style is defined right here inside the ui.xml file.
-        <span ui:field='totallyPrivateStyleSpan'  
class='{myTotallyPrivateStyle.superPrivateColor}'>
+        <span ui:field='totallyPrivateStyleSpan'  
class='{myTotallyPrivateStyle.super-private-color}'>
            (This one too, but even more so.)
          </span>
        </p>

-- 
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to