Revision: 6607 Author: [email protected] Date: Tue Nov 3 10:24:07 2009 Log: Merge tr...@6606 to stabilize ClientBundle and MhtmlResourceContext generator output. $ svn merge --ignore-ancestry -c 6606 http://google-web-toolkit.googlecode.com/svn/trunk/ .
http://code.google.com/p/google-web-toolkit/source/detail?r=6607 Modified: /releases/2.0/branch-info.txt /releases/2.0/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java /releases/2.0/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java /releases/2.0/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java ======================================= --- /releases/2.0/branch-info.txt Tue Nov 3 09:00:31 2009 +++ /releases/2.0/branch-info.txt Tue Nov 3 10:24:07 2009 @@ -209,3 +209,7 @@ Fix deRPC with ciruclar references through CustomFieldSerializers svn merge --ignore-ancestry -c 6604 http://google-web-toolkit.googlecode.com/svn/trunk/ . +tr...@6606 was merged into this branch + Make ClientBundle and MhtmlResourceContext generators emit stable output. + svn merge --ignore-ancestry -c 6606 http://google-web-toolkit.googlecode.com/svn/trunk/ . + ======================================= --- /releases/2.0/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java Wed Oct 28 07:24:14 2009 +++ /releases/2.0/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java Tue Nov 3 10:24:07 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; @@ -249,16 +277,31 @@ /** * Provides a hook for subtypes to add additional fields or requirements to * the bundle. + * + * @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 { } /** @@ -343,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; @@ -482,33 +526,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, @@ -537,6 +554,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. ======================================= --- /releases/2.0/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java Mon Nov 2 12:44:54 2009 +++ /releases/2.0/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java Tue Nov 3 10:24:07 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(); } } ======================================= --- /releases/2.0/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java Mon Nov 2 12:44:54 2009 +++ /releases/2.0/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java Tue Nov 3 10:24:07 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 -~----------~----~----~----~------~----~------~--~---
