Stabilize the output from ClientBundle and MhtmlResourceContext.
Patch by: bobv
Review by: jgw
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6606 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
index 0b13be3..573216e 100644
--- a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
+++ b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
@@ -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);
+ fieldsToInitializers.put(ident, initializer);
}
- pw.println(";");
-
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 {
}
/**
- * Provides a hook for finalizing generated resources.
- *
+ * 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 doFinish() throws UnableToCompleteException {
+ 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(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;
@@ -495,33 +528,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,
List<JMethod> generatorMethods, ClientBundleRequirements requirements) {
@@ -550,6 +556,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.
*/
diff --git a/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
index 984b42d..1ea1f5a 100644
--- a/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
+++ b/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
@@ -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();
}
}
diff --git a/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
index 285a993..2bb175c 100644
--- a/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
@@ -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);
}
}