Exporting a map of obfuscated CSS names to full class names into a build
artifact CSV file. This will allow CSS names to be found in the DOM for
frontend testing.

Review by: unnurg@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10675 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index 6baee52..eaf02ed 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
@@ -74,6 +75,10 @@
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.Serializable;
 import java.net.URL;
 import java.util.ArrayList;
@@ -437,10 +442,13 @@
     JClassType cssResourceSubtype = method.getReturnType().isInterface();
     assert cssResourceSubtype != null;
     CssStylesheet stylesheet = stylesheetMap.get(method);
-       
+    
     // Optimize the stylesheet, recording the class selector obfuscations
     Map<JMethod, String> actualReplacements = optimize(logger, context, method);
     
+    outputCssMapArtifact(logger, context, actualReplacements,
+        cssResourceSubtype.getQualifiedSourceName());
+
     outputAdditionalArtifacts(logger, context, method, actualReplacements,
         cssResourceSubtype, stylesheet);
     
@@ -577,12 +585,56 @@
    * Output additional artifacts. Does nothing in this baseclass, but is a hook
    * for subclasses to do so.
    */
-  protected void outputAdditionalArtifacts(TreeLogger logger, 
-      ResourceContext context, JMethod method, 
+  protected void outputAdditionalArtifacts(TreeLogger logger,
+      ResourceContext context, JMethod method,
       Map<JMethod, String> actualReplacements, JClassType cssResourceSubtype,
       CssStylesheet stylesheet) throws UnableToCompleteException {
   }
 
+  /**
+   * Builds a CSV file mapping obfuscated CSS class names to their qualified source name and
+   * outputs it as a private build artifact.
+   */
+  protected void outputCssMapArtifact(TreeLogger logger, ResourceContext context,
+      Map<JMethod, String> actualReplacements, String outputFileName) {
+    String mappingFileName = "cssResource/" + outputFileName + ".cssmap";
+
+    OutputStream os = null;
+    try {
+      os = context.getGeneratorContext().tryCreateResource(logger, mappingFileName);
+    } catch (UnableToCompleteException e) {
+      logger.log(TreeLogger.ERROR, "Could not create resource: " + mappingFileName);
+      return;
+    }
+
+    if (os == null) {
+      logger.log(TreeLogger.ERROR, "Created resource is null: " + mappingFileName);
+      return;
+    }
+
+    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
+    try {
+      for (Map.Entry<JMethod, String> replacement : actualReplacements.entrySet()) {
+        String qualifiedName = replacement.getKey().getEnclosingType().getQualifiedSourceName();
+        String baseName = replacement.getKey().getName();
+        writer.write(qualifiedName.replaceAll("[.$]", "-") + "-" + baseName);
+        writer.write(",");
+        writer.write(replacement.getValue());
+        writer.newLine();
+      }
+      writer.flush();
+      writer.close();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Error writing artifact: " + mappingFileName);
+    }
+
+    try {
+      context.getGeneratorContext().commitResource(logger, os).setVisibility(Visibility.Private);
+    } catch (UnableToCompleteException e) {
+      logger.log(TreeLogger.ERROR, "Error trying to commit artifact: " + mappingFileName);
+    }
+  }
+
   protected void writeGetName(JMethod method, SourceWriter sw) {
     sw.println("public String getName() {");
     sw.indent();
diff --git a/user/test/com/google/gwt/resources/ResourcesSuite.java b/user/test/com/google/gwt/resources/ResourcesSuite.java
index b101b12..5310191 100644
--- a/user/test/com/google/gwt/resources/ResourcesSuite.java
+++ b/user/test/com/google/gwt/resources/ResourcesSuite.java
@@ -33,6 +33,7 @@
 import com.google.gwt.resources.css.UnknownAtRuleTest;
 import com.google.gwt.resources.ext.ResourceGeneratorUtilTest;
 import com.google.gwt.resources.rg.CssClassNamesTestCase;
+import com.google.gwt.resources.rg.CssOutputTestCase;
 
 import junit.framework.Test;
 
@@ -46,6 +47,7 @@
     suite.addTestSuite(CssClassNamesTestCase.class);
     suite.addTestSuite(CssExternalTest.class);
     suite.addTestSuite(CssNodeClonerTest.class);
+    suite.addTestSuite(CssOutputTestCase.class);
     suite.addTestSuite(CssReorderTest.class);
     suite.addTestSuite(CSSResourceTest.class);
     suite.addTestSuite(CssRtlTest.class);
diff --git a/user/test/com/google/gwt/resources/rg/CssOutputTestCase.java b/user/test/com/google/gwt/resources/rg/CssOutputTestCase.java
new file mode 100644
index 0000000..aa55f0d
--- /dev/null
+++ b/user/test/com/google/gwt/resources/rg/CssOutputTestCase.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2011 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.rg;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.GeneratedResource;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.resources.ext.ResourceContext;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Tests output functions.
+ */
+public class CssOutputTestCase extends TestCase {
+
+  public void testOutputCssMapArtifact() throws UnableToCompleteException {
+    UnitTestTreeLogger testLogger = new UnitTestTreeLogger.Builder().createLogger();
+    ResourceContext mockResourceContext = EasyMock.createMock(ResourceContext.class);
+    Map<JMethod, String> testMap = new HashMap<JMethod, String>();
+    OutputStream mockOutputStream = EasyMock.createMock(OutputStream.class);
+    GeneratorContext mockGeneratorContext = EasyMock.createMock(GeneratorContext.class);
+    GeneratedResource mockGeneratedResource = EasyMock.createMock(GeneratedResource.class);
+
+    EasyMock.expect(mockResourceContext.getGeneratorContext()).andReturn(mockGeneratorContext);
+    EasyMock.expectLastCall().times(2);
+    EasyMock.expect(mockGeneratorContext.tryCreateResource(
+        testLogger, "cssResource/test-file-name.cssmap")).andReturn(mockOutputStream);
+    EasyMock.expect(mockGeneratorContext.commitResource(testLogger, mockOutputStream)).andReturn(
+        mockGeneratedResource);
+    EasyMock.replay(mockResourceContext);
+    EasyMock.replay(mockGeneratorContext);
+
+    CssResourceGenerator crg = new CssResourceGenerator();
+    crg.outputCssMapArtifact(testLogger, mockResourceContext, testMap, "test-file-name");
+
+    testLogger.assertCorrectLogEntries();
+    EasyMock.verify(mockResourceContext);
+    EasyMock.verify(mockGeneratorContext);
+  }
+
+  public void testOutputCssMapArtifactThrowOnTryCreateResource() throws UnableToCompleteException {
+    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+    builder.expectError("Could not create resource: cssResource/test-file2.cssmap", null);
+    UnitTestTreeLogger testLogger =  builder.createLogger();
+    ResourceContext mockResourceContext = EasyMock.createMock(ResourceContext.class);
+    Map<JMethod, String> testMap = new HashMap<JMethod, String>();
+    OutputStream mockOutputStream = EasyMock.createMock(OutputStream.class);
+    GeneratorContext mockGeneratorContext = EasyMock.createMock(GeneratorContext.class);
+    GeneratedResource mockGeneratedResource = EasyMock.createMock(GeneratedResource.class);
+
+    EasyMock.expect(mockResourceContext.getGeneratorContext()).andReturn(mockGeneratorContext);
+    EasyMock.expect(mockGeneratorContext.tryCreateResource(
+        testLogger, "cssResource/test-file2.cssmap")).andThrow(new UnableToCompleteException());
+    EasyMock.replay(mockResourceContext);
+    EasyMock.replay(mockGeneratorContext);
+
+    CssResourceGenerator crg = new CssResourceGenerator();
+    crg.outputCssMapArtifact(testLogger, mockResourceContext, testMap, "test-file2");
+
+    testLogger.assertCorrectLogEntries();
+    EasyMock.verify(mockResourceContext);
+    EasyMock.verify(mockGeneratorContext);
+  }
+
+  public void testOutputCssMapArtifactReturnNullOutputString() throws UnableToCompleteException {
+    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+    builder.expectError("Created resource is null: cssResource/test-file3.cssmap", null);
+    UnitTestTreeLogger testLogger =  builder.createLogger();
+    ResourceContext mockResourceContext = EasyMock.createMock(ResourceContext.class);
+    Map<JMethod, String> testMap = new HashMap<JMethod, String>();
+    OutputStream mockOutputStream = EasyMock.createMock(OutputStream.class);
+    GeneratorContext mockGeneratorContext = EasyMock.createMock(GeneratorContext.class);
+    GeneratedResource mockGeneratedResource = EasyMock.createMock(GeneratedResource.class);
+
+    EasyMock.expect(mockResourceContext.getGeneratorContext()).andReturn(mockGeneratorContext);
+    EasyMock.expect(mockGeneratorContext.tryCreateResource(
+        testLogger, "cssResource/test-file3.cssmap")).andReturn(null);
+    EasyMock.replay(mockResourceContext);
+    EasyMock.replay(mockGeneratorContext);
+
+    CssResourceGenerator crg = new CssResourceGenerator();
+    crg.outputCssMapArtifact(testLogger, mockResourceContext, testMap, "test-file3");
+
+    testLogger.assertCorrectLogEntries();
+    EasyMock.verify(mockResourceContext);
+    EasyMock.verify(mockGeneratorContext);
+  }
+
+  public void testOutputCssMapArtifactThrowOnCommitResource() throws UnableToCompleteException {
+    UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+    builder.expectError("Error trying to commit artifact: cssResource/test-file4.cssmap", null);
+    UnitTestTreeLogger testLogger =  builder.createLogger();
+    ResourceContext mockResourceContext = EasyMock.createMock(ResourceContext.class);
+    Map<JMethod, String> testMap = new HashMap<JMethod, String>();
+    OutputStream mockOutputStream = EasyMock.createMock(OutputStream.class);
+    GeneratorContext mockGeneratorContext = EasyMock.createMock(GeneratorContext.class);
+    GeneratedResource mockGeneratedResource = EasyMock.createMock(GeneratedResource.class);
+
+    EasyMock.expect(mockResourceContext.getGeneratorContext()).andReturn(mockGeneratorContext);
+    EasyMock.expectLastCall().times(2);
+    EasyMock.expect(mockGeneratorContext.tryCreateResource(
+        testLogger, "cssResource/test-file4.cssmap")).andReturn(mockOutputStream);
+    EasyMock.expect(mockGeneratorContext.commitResource(testLogger, mockOutputStream)).andThrow(
+        new UnableToCompleteException());
+    EasyMock.replay(mockResourceContext);
+    EasyMock.replay(mockGeneratorContext);
+
+    CssResourceGenerator crg = new CssResourceGenerator();
+    crg.outputCssMapArtifact(testLogger, mockResourceContext, testMap, "test-file4");
+
+    testLogger.assertCorrectLogEntries();
+    EasyMock.verify(mockResourceContext);
+    EasyMock.verify(mockGeneratorContext);
+  }
+
+  public void testOutputCssMapArtifactWithTestData() throws UnableToCompleteException {
+    UnitTestTreeLogger testLogger = new UnitTestTreeLogger.Builder().createLogger();
+    ResourceContext mockResourceContext = EasyMock.createMock(ResourceContext.class);
+    JMethod mockJMethod1 = EasyMock.createMock(JMethod.class);
+    JMethod mockJMethod2 = EasyMock.createMock(JMethod.class);
+    JMethod mockJMethod3 = EasyMock.createMock(JMethod.class);
+    JClassType mockJClassType1 = EasyMock.createMock(JClassType.class);
+    JClassType mockJClassType2 = EasyMock.createMock(JClassType.class);
+    JClassType mockJClassType3 = EasyMock.createMock(JClassType.class);
+    Map<JMethod, String> testMap = new LinkedHashMap<JMethod, String>();
+    testMap.put(mockJMethod1, "TESTCSSNAME1");
+    testMap.put(mockJMethod2, "TESTCSSNAME2");
+    testMap.put(mockJMethod3, "TESTCSSNAME3");
+    ByteArrayOutputStream testOutputStream = new ByteArrayOutputStream();
+    GeneratorContext mockGeneratorContext = EasyMock.createMock(GeneratorContext.class);
+    GeneratedResource mockGeneratedResource = EasyMock.createMock(GeneratedResource.class);
+
+    EasyMock.expect(mockResourceContext.getGeneratorContext()).andReturn(mockGeneratorContext);
+    EasyMock.expectLastCall().times(2);
+    EasyMock.expect(mockGeneratorContext.tryCreateResource(
+        testLogger, "cssResource/test-file5.cssmap")).andReturn(testOutputStream);
+    EasyMock.expect(mockJMethod1.getEnclosingType()).andReturn(mockJClassType1);
+    EasyMock.expect(mockJClassType1.getQualifiedSourceName()).andReturn("test.class.type.1");
+    EasyMock.expect(mockJMethod1.getName()).andReturn("basename1");
+    EasyMock.expect(mockJMethod2.getEnclosingType()).andReturn(mockJClassType2);
+    EasyMock.expect(mockJClassType2.getQualifiedSourceName()).andReturn("test.class.type.2");
+    EasyMock.expect(mockJMethod2.getName()).andReturn("basename2");
+    EasyMock.expect(mockJMethod3.getEnclosingType()).andReturn(mockJClassType3);
+    EasyMock.expect(mockJClassType3.getQualifiedSourceName()).andReturn("test.class.type.3");
+    EasyMock.expect(mockJMethod3.getName()).andReturn("basename3");
+    EasyMock.expect(mockGeneratorContext.commitResource(testLogger, testOutputStream)).andReturn(
+        mockGeneratedResource);
+    EasyMock.replay(mockResourceContext);
+    EasyMock.replay(mockGeneratorContext);
+    EasyMock.replay(mockJMethod1);
+    EasyMock.replay(mockJMethod2);
+    EasyMock.replay(mockJMethod3);
+    EasyMock.replay(mockJClassType1);
+    EasyMock.replay(mockJClassType2);
+    EasyMock.replay(mockJClassType3);
+
+    CssResourceGenerator crg = new CssResourceGenerator();
+    crg.outputCssMapArtifact(testLogger, mockResourceContext, testMap, "test-file5");
+    String expectedOutput = "test-class-type-1-basename1,TESTCSSNAME1\n" +
+        "test-class-type-2-basename2,TESTCSSNAME2\n" +
+        "test-class-type-3-basename3,TESTCSSNAME3\n";
+    assertEquals(expectedOutput, testOutputStream.toString());
+
+    testLogger.assertCorrectLogEntries();
+    EasyMock.verify(mockResourceContext);
+    EasyMock.verify(mockGeneratorContext);
+    EasyMock.verify(mockJMethod1);
+    EasyMock.verify(mockJMethod2);
+    EasyMock.verify(mockJMethod3);
+    EasyMock.verify(mockJClassType1);
+    EasyMock.verify(mockJClassType2);
+    EasyMock.verify(mockJClassType3);
+  }
+
+}