Revision: 6606
Author: [email protected]
Date: Tue Nov  3 10:06:29 2009
Log: Stabilize the output from ClientBundle and MhtmlResourceContext.

Patch by: bobv
Review by: jgw
http://code.google.com/p/google-web-toolkit/source/detail?r=6606

Modified:
   
/trunk/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
   
/trunk/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
   
/trunk/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java

=======================================
---  
/trunk/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
       
Wed Oct 28 07:06:41 2009
+++  
/trunk/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
       
Tue Nov  3 10:06:29 2009
@@ -42,12 +42,12 @@
  import com.google.gwt.user.rebind.SourceWriter;

  import java.io.PrintWriter;
-import java.io.StringWriter;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
@@ -58,13 +58,15 @@
  public abstract class AbstractClientBundleGenerator extends Generator {

    /**
-   * An implementation of FieldAccumulator that immediately composes its  
output
-   * into a StringWriter.
+   * An implementation of ClientBundleFields.
     */
-  private static class FieldsImpl implements ClientBundleFields {
+  protected static class FieldsImpl implements ClientBundleFields {
      private final NameFactory factory = new NameFactory();
-    private final StringWriter buffer = new StringWriter();
-    private final PrintWriter pw = new PrintWriter(buffer);
+    /**
+     * It is necessary to maintain order in case one field refers to  
another.
+     */
+    private final Map<String, String> fieldsToDeclarations = new  
LinkedHashMap<String, String>();
+    private final Map<String, String> fieldsToInitializers = new  
HashMap<String, String>();

      public void addName(String name) {
        factory.addName(name);
@@ -82,33 +84,56 @@

        String ident = factory.createName(name);

-      pw.print("private ");
+      StringBuilder sb = new StringBuilder();
+      sb.append("private ");

        if (isStatic) {
-        pw.print("static ");
+        sb.append("static ");
        }

        if (isFinal) {
-        pw.print("final ");
+        sb.append("final ");
        }

-      pw.print(type.getQualifiedSourceName());
-      pw.print(" ");
-      pw.print(ident);
+      sb.append(type.getQualifiedSourceName());
+      sb.append(" ");
+      sb.append(ident);
+
+      fieldsToDeclarations.put(ident, sb.toString());

        if (initializer != null) {
-        pw.print(" = ");
-        pw.print(initializer);
-      }
-
-      pw.println(";");
+        fieldsToInitializers.put(ident, initializer);
+      }

        return ident;
      }

-    public String toString() {
-      pw.flush();
-      return buffer.getBuffer().toString();
+    /**
+     * This can be called to reset the initializer expression on an
+     * already-defined field.
+     *
+     * @param ident an identifier previously returned by {...@link #define}
+     * @param initializer a Java expression that will be used to  
initialize the
+     *          field
+     */
+    public void setInitializer(String ident, String initializer) {
+      assert fieldsToDeclarations.containsKey(ident) : ident + " not  
defined";
+      fieldsToInitializers.put(ident, initializer);
+    }
+
+    private String getCode() {
+      StringBuilder sb = new StringBuilder();
+      for (Map.Entry<String, String> entry :  
fieldsToDeclarations.entrySet()) {
+        String ident = entry.getKey();
+        sb.append(entry.getValue());
+
+        String initializer = fieldsToInitializers.get(ident);
+        if (initializer != null) {
+          sb.append(" = ").append(initializer);
+        }
+        sb.append(";\n");
+      }
+      return sb.toString();
      }
    }

@@ -194,6 +219,9 @@

      // If an implementation already exists, we don't need to do any work
      if (out != null) {
+      // There is actual work to do
+      doCreateBundleForPermutation(logger, generatorContext, fields,
+          generatedSimpleSourceName);
        // Begin writing the generated source.
        ClassSourceFileComposerFactory f = new  
ClassSourceFileComposerFactory(
            packageName, generatedSimpleSourceName);
@@ -218,7 +246,7 @@
            fields);

        // Print the accumulated field definitions
-      sw.println(fields.toString());
+      sw.println(fields.getCode());

        /*
         * The map-accessor methods use JSNI and need a fully-qualified class
@@ -231,7 +259,7 @@
      }

      finish(logger, resourceContext, generators.keySet());
-    doFinish();
+    doFinish(logger);

      // Return the name of the concrete class
      return createdClassName;
@@ -246,30 +274,34 @@
        TreeLogger logger, GeneratorContext context, JClassType  
resourceBundleType)
        throws UnableToCompleteException;

-  // FIXME - document params
    /**
     * Provides a hook for subtypes to add additional fields or requirements  
to
     * the bundle.
     *
-   * @param logger
-   * @param contect
-   * @param context
-   * @param fields
-   * @param requirements
-   *
     * @throws UnableToCompleteException if an error occurs.
     */
    protected void doAddFieldsAndRequirements(TreeLogger logger,
-      GeneratorContext context, ClientBundleFields fields,
+      GeneratorContext context, FieldsImpl fields,
        ClientBundleRequirements requirements) throws  
UnableToCompleteException {
    }
+
+  /**
+   * This method is called after the ClientBundleRequirements have been
+   * evaluated and a new ClientBundle implementation is being created.
+   *
+   * @throws UnableToCompleteException if an error occurs.
+   */
+  protected void doCreateBundleForPermutation(TreeLogger logger,
+      GeneratorContext generatorContext, FieldsImpl fields,
+      String generatedSimpleSourceName) throws UnableToCompleteException {
+  }

    /**
     * Provides a hook for finalizing generated resources.
-   *
+   *
     * @throws UnableToCompleteException if an error occurs.
     */
-  protected void doFinish() throws UnableToCompleteException {
+  protected void doFinish(TreeLogger logger) throws  
UnableToCompleteException {
    }

    /**
@@ -354,13 +386,14 @@

    /**
     * Given a ClientBundle subtype, compute which ResourceGenerators should
-   * implement which methods.
+   * implement which methods. The data returned from this method should be
+   * stable across identical modules.
     */
    private Map<Class<? extends ResourceGenerator>, List<JMethod>>  
createTaskList(
        TreeLogger logger, TypeOracle typeOracle, JClassType sourceType)
        throws UnableToCompleteException {

-    Map<Class<? extends ResourceGenerator>, List<JMethod>> toReturn = new  
HashMap<Class<? extends ResourceGenerator>, List<JMethod>>();
+    Map<Class<? extends ResourceGenerator>, List<JMethod>> toReturn = new  
LinkedHashMap<Class<? extends ResourceGenerator>, List<JMethod>>();

      JClassType bundleType =  
typeOracle.findType(ClientBundle.class.getName());
      assert bundleType != null;
@@ -494,33 +527,6 @@

      return toReturn.toString();
    }
-
-  private Map<ResourceGenerator, List<JMethod>> initAndPrepare(
-      TreeLogger logger,
-      Map<Class<? extends ResourceGenerator>, List<JMethod>> taskList,
-      AbstractResourceContext resourceContext,
-      ClientBundleRequirements requirements) throws  
UnableToCompleteException {
-    // Try to provide as many errors as possible before failing.
-    boolean success = true;
-    Map<ResourceGenerator, List<JMethod>> toReturn = new  
IdentityHashMap<ResourceGenerator, List<JMethod>>();
-
-    // Run the ResourceGenerators to generate implementations of the  
methods
-    for (Map.Entry<Class<? extends ResourceGenerator>, List<JMethod>>  
entry : taskList.entrySet()) {
-
-      ResourceGenerator rg = instantiateResourceGenerator(logger,
-          entry.getKey());
-      toReturn.put(rg, entry.getValue());
-
-      success &= initAndPrepare(logger, resourceContext, rg,  
entry.getValue(),
-          requirements);
-    }
-
-    if (!success) {
-      throw new UnableToCompleteException();
-    }
-
-    return toReturn;
-  }

    private boolean initAndPrepare(TreeLogger logger,
        AbstractResourceContext resourceContext, ResourceGenerator rg,
@@ -549,6 +555,33 @@

      return !fail;
    }
+
+  private Map<ResourceGenerator, List<JMethod>> initAndPrepare(
+      TreeLogger logger,
+      Map<Class<? extends ResourceGenerator>, List<JMethod>> taskList,
+      AbstractResourceContext resourceContext,
+      ClientBundleRequirements requirements) throws  
UnableToCompleteException {
+    // Try to provide as many errors as possible before failing.
+    boolean success = true;
+    Map<ResourceGenerator, List<JMethod>> toReturn = new  
IdentityHashMap<ResourceGenerator, List<JMethod>>();
+
+    // Run the ResourceGenerators to generate implementations of the  
methods
+    for (Map.Entry<Class<? extends ResourceGenerator>, List<JMethod>>  
entry : taskList.entrySet()) {
+
+      ResourceGenerator rg = instantiateResourceGenerator(logger,
+          entry.getKey());
+      toReturn.put(rg, entry.getValue());
+
+      success &= initAndPrepare(logger, resourceContext, rg,  
entry.getValue(),
+          requirements);
+    }
+
+    if (!success) {
+      throw new UnableToCompleteException();
+    }
+
+    return toReturn;
+  }

    /**
     * Utility method to construct a ResourceGenerator that logs errors.
=======================================
---  
/trunk/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
  
Wed Oct 28 12:36:51 2009
+++  
/trunk/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
  
Tue Nov  3 10:06:29 2009
@@ -22,7 +22,6 @@
  import com.google.gwt.core.ext.typeinfo.JType;
  import com.google.gwt.core.ext.typeinfo.TypeOracleException;
  import com.google.gwt.dev.util.Util;
-import com.google.gwt.resources.ext.ClientBundleFields;
  import com.google.gwt.resources.ext.ClientBundleRequirements;

  /**
@@ -31,10 +30,9 @@
  public class MhtmlClientBundleGenerator extends  
AbstractClientBundleGenerator {

    private static final String BUNDLE_EXTENSION = ".cache.txt";
-  private static int counter = 0;
-
+
+  private String bundleBaseIdent;
    private MhtmlResourceContext resourceContext;
-  private String partialPath;

    @Override
    protected AbstractResourceContext createResourceContext(TreeLogger  
logger,
@@ -42,25 +40,12 @@
      resourceContext = new MhtmlResourceContext(logger, context,
          resourceBundleType);

-    /*
-     * We use a counter to ensure that the generated resources have unique
-     * names. Previously we used the system time, but it sometimes led to  
non-
-     * unique names if subsequent calls happened within the same  
millisecond.
-     *
-     * TODO: figure out how to make the filename stable based on actual  
content.
-     */
-    counter++;
-    partialPath =  
Util.computeStrongName(Util.getBytes(resourceBundleType.getQualifiedSourceName()
-        + counter))
-        + BUNDLE_EXTENSION;
-    resourceContext.setPartialPath(partialPath);
-
      return resourceContext;
    }

    @Override
    protected void doAddFieldsAndRequirements(TreeLogger logger,
-      GeneratorContext generatorContext, ClientBundleFields fields,
+      GeneratorContext generatorContext, FieldsImpl fields,
        ClientBundleRequirements requirements) throws  
UnableToCompleteException {
      JType booleanType;
      JType stringType;
@@ -78,14 +63,23 @@
      resourceContext.setIsHttpsIdent(isHttpsIdent);

      // "mhtml:" + GWT.getModuleBaseURL() + "partialPath!cid:"
-    String bundleBaseIdent = fields.define(stringType, "bundleBase",
-        "\"mhtml:\" + GWT.getModuleBaseURL() + \"" + partialPath  
+ "!cid:\"",
-        true, true);
+    bundleBaseIdent = fields.define(stringType, "bundleBase", null, true,  
true);
      resourceContext.setBundleBaseIdent(bundleBaseIdent);
    }

    @Override
-  protected void doFinish() throws UnableToCompleteException {
+  protected void doCreateBundleForPermutation(TreeLogger logger,
+      GeneratorContext generatorContext, FieldsImpl fields,
+      String generatedSimpleSourceName) throws UnableToCompleteException {
+    String partialPath =  
Util.computeStrongName(Util.getBytes(generatedSimpleSourceName))
+        + BUNDLE_EXTENSION;
+    resourceContext.setPartialPath(partialPath);
+    fields.setInitializer(bundleBaseIdent,
+        "\"mhtml:\" + GWT.getModuleBaseURL() + \"" + partialPath  
+ "!cid:\"");
+  }
+
+  @Override
+  protected void doFinish(TreeLogger logger) throws  
UnableToCompleteException {
      resourceContext.finish();
    }
  }
=======================================
---  
/trunk/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
        
Wed Oct 28 09:10:53 2009
+++  
/trunk/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
        
Tue Nov  3 10:06:29 2009
@@ -21,6 +21,8 @@
  import com.google.gwt.core.ext.typeinfo.JClassType;
  import com.google.gwt.dev.util.Util;

+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
  import java.io.OutputStream;
  import java.io.PrintWriter;
  import java.util.HashMap;
@@ -51,7 +53,7 @@
     * Output is lazily initialized in the case that all deployed resources  
are
     * large.
     */
-  private OutputStream out;
+  private ByteArrayOutputStream out;
    private String partialPath;
    private PrintWriter pw;

@@ -70,7 +72,6 @@
        return toReturn;
      }

-    assert partialPath != null : "partialPath";
      assert isHttpsIdent != null : "isHttpsIdent";
      assert bundleBaseIdent != null : "bundleBaseIdent";

@@ -90,7 +91,7 @@
      }

      if (out == null) {
-      out = getContext().tryCreateResource(getLogger(), partialPath);
+      out = new ByteArrayOutputStream();
        pw = new PrintWriter(out);
        pw.println("Content-Type: multipart/related; boundary=\"" + BOUNDARY
            + "\"");
@@ -121,8 +122,19 @@

    public void finish() throws UnableToCompleteException {
      if (out != null) {
+      TreeLogger logger = getLogger().branch(TreeLogger.DEBUG,
+          "Writing container to disk");
        pw.close();
-      getContext().commitResource(getLogger(), out);
+
+      assert partialPath != null : "partialPath";
+
+      OutputStream realOut = getContext().tryCreateResource(logger,  
partialPath);
+      try {
+        realOut.write(out.toByteArray());
+      } catch (IOException e) {
+        logger.log(TreeLogger.ERROR, "Unable to write container file", e);
+      }
+      getContext().commitResource(logger, realOut);
      }
    }


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

Reply via email to