Generator Result Caching implementation for ClientBundle Review at http://gwt-code-reviews.appspot.com/1236801 git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9704 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java index ad1f909..e207a5f 100644 --- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java +++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
@@ -19,4 +19,12 @@ * Type representing a Java class or interface type that a user would declare. */ public interface JRealClassType extends JClassType { + + /** + * EXPERIMENTAL and subject to change. Do not use this in production code. + * + * Generate a hash to be used as a signature for comparing versions of the + * structure of a type. + */ + String getTypeStrongHash(); }
diff --git a/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java index 3df184c..6593aba 100644 --- a/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java +++ b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
@@ -510,6 +510,11 @@ // Always add implicit modifiers on interfaces. resultType.addModifierBits(Shared.MOD_STATIC | Shared.MOD_ABSTRACT); } + + /* + * Add a reference to the byteCode + */ + resultType.addByteCode(typeData.byteCode); return resultType; }
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedClientDataMap.java b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedClientDataMap.java new file mode 100644 index 0000000..c7b0d3d --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedClientDataMap.java
@@ -0,0 +1,41 @@ +/* + * 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.dev.javac.rebind; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * A simple map class for storing cached rebind client data. It ensures that + * all stored data implement Serializable. + */ +public class CachedClientDataMap implements Serializable { + + private final Map<String, Serializable> dataMap; + + public CachedClientDataMap() { + dataMap = new HashMap<String, Serializable>(); + } + + public Object get(String key) { + return dataMap.get(key); + } + + public void put(String key, Object value) { + dataMap.put(key, (Serializable) value); + } +}
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java index fd20b0f..b18a3d9 100644 --- a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java +++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java
@@ -21,6 +21,7 @@ import com.google.gwt.core.ext.SelectionProperty; import com.google.gwt.core.ext.TreeLogger; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -29,7 +30,7 @@ * A container for saving lists of deferred-binding and configuration properties * to be compared subsequently with a PropertyOracle. */ -public class CachedPropertyInformation { +public class CachedPropertyInformation implements Serializable { private final List<SelectionProperty> selectionProperties; private final List<ConfigurationProperty> configProperties;
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java index cd8e3c8..dc74223 100644 --- a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java +++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java
@@ -18,6 +18,7 @@ import com.google.gwt.core.ext.linker.ArtifactSet; import com.google.gwt.dev.javac.GeneratedUnit; +import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -27,21 +28,21 @@ * cached and presented to subsequent rebind operations, providing the generator * information needed to decide whether full or partial re-generation is required. */ -public class CachedRebindResult { +public class CachedRebindResult implements Serializable { private final ArtifactSet artifacts; private final Map<String, GeneratedUnit> generatedUnitMap; private final String returnedTypeName; private final long timeGenerated; - private final Object clientData; + private final CachedClientDataMap clientDataMap; public CachedRebindResult(String resultTypeName, ArtifactSet artifacts, Map<String, GeneratedUnit> generatedUnitMap, - long timeGenerated, Object clientData) { + long timeGenerated, CachedClientDataMap clientDataMap) { this.returnedTypeName = resultTypeName; this.artifacts = new ArtifactSet(artifacts); this.generatedUnitMap = new HashMap<String, GeneratedUnit>(generatedUnitMap); this.timeGenerated = timeGenerated; - this.clientData = clientData; + this.clientDataMap = clientDataMap; } public CachedRebindResult(String resultTypeName, ArtifactSet artifacts, @@ -53,8 +54,16 @@ return artifacts; } - public Object getClientData() { - return clientData; + public Object getClientData(String key) { + if (clientDataMap == null) { + return null; + } else { + return clientDataMap.get(key); + } + } + + public CachedClientDataMap getClientDataMap() { + return clientDataMap; } public GeneratedUnit getGeneratedUnit(String typeName) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java index ae59a51..b1e7caa 100644 --- a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java +++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java
@@ -17,6 +17,7 @@ import com.google.gwt.dev.cfg.Rule; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -24,7 +25,7 @@ * A cache for storing {@link CachedRebindResult} entries. Entries are keyed * by rebind Rule and queryTypeName. */ -public class RebindCache { +public class RebindCache implements Serializable { private final Map<String, Map<String, CachedRebindResult>> rebindResults;
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java index 5ec7df0..377c679 100644 --- a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java +++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java
@@ -19,13 +19,12 @@ * A class for returning the result of a rebind operation. */ public class RebindResult { - private final RebindStatus resultStatus; private final String returnedTypeName; - private final Object clientData; - + private final CachedClientDataMap clientData; + public RebindResult(RebindStatus resultStatus, - String returnedType, Object clientData) { + String returnedType, CachedClientDataMap clientData) { this.resultStatus = resultStatus; this.returnedTypeName = returnedType; this.clientData = clientData; @@ -34,11 +33,19 @@ public RebindResult(RebindStatus resultStatus, String returnedType) { this(resultStatus, returnedType, null); } - - public Object getClientData() { + + public Object getClientData(String key) { + if (clientData == null) { + return null; + } else { + return clientData.get(key); + } + } + + public CachedClientDataMap getClientDataMap() { return clientData; } - + public RebindStatus getResultStatus() { return resultStatus; }
diff --git a/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java b/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java index 9be9b78..66d9ebe 100644 --- a/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java +++ b/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java
@@ -19,6 +19,7 @@ import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.dev.util.StringInterner; +import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.collect.IdentitySets; import com.google.gwt.dev.util.collect.Lists; @@ -51,6 +52,8 @@ private String lazyQualifiedBinaryName; private String lazyQualifiedName; + + private String lazyTypeStrongHash; private final Members members = new Members(this); @@ -63,6 +66,8 @@ private final TypeOracle oracle; private JClassType superclass; + + private byte[] byteCode; /** * Create a class type that reflects an actual type. @@ -72,7 +77,6 @@ * @param enclosingTypeName the fully qualified source name of the enclosing * class or null if a top-level class - setEnclosingType must be * called later with the proper enclosing type if this is non-null - * @param isLocalType * @param name * @param isInterface */ @@ -99,6 +103,10 @@ } oracle.addNewType(this); } + + public void addByteCode(byte[] byteCode) { + this.byteCode = byteCode; + } @Override public void addModifierBits(int bits) { @@ -268,6 +276,27 @@ public JClassType getSuperclass() { return superclass; } + + /** + * EXPERIMENTAL and subject to change. Do not use this in production code. + * + * Generate a hash to be used as a signature for comparing versions of the + * structure of a type. + * + * TODO(jbrosenberg): Note, this implementation is based on the entire byte + * code for a class, which is probably overkill, since we only need a hash + * based on the type's structure (but not all of its code). Need to come up + * with an efficient way to compute a hash of a type's structure. For now, + * using the raw bytes directly is quick relative to making multiple api calls + * into type oracle to determine the type's structure. + */ + public String getTypeStrongHash() { + if (lazyTypeStrongHash != null) { + return lazyTypeStrongHash; + } + lazyTypeStrongHash = Util.computeStrongName(byteCode); + return lazyTypeStrongHash; + } @Override public boolean isAbstract() {
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java index 502b221..ee7e9f8 100644 --- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java +++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -181,7 +181,7 @@ // use all new results, add a new cache entry cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(), genCtx.getArtifacts(), genCtx.getGeneratedUnitMap(), - System.currentTimeMillis(), newResult.getClientData()); + System.currentTimeMillis(), newResult.getClientDataMap()); rebindCachePut(rule, typeName, cachedResult); break; @@ -210,7 +210,7 @@ */ cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(), genCtx.getArtifacts(), genCtx.getGeneratedUnitMap(), - System.currentTimeMillis(), newResult.getClientData()); + System.currentTimeMillis(), newResult.getClientDataMap()); rebindCachePut(rule, typeName, cachedResult); break; } @@ -237,7 +237,7 @@ /** * Invalidates the given source type name, so the next rebind request will - * generate type again. + * generate the type again. */ public void invalidateRebind(String sourceTypeName) { typeNameBindingMap.remove(sourceTypeName);
diff --git a/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java b/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java index caea394..a54ade8 100644 --- a/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java +++ b/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java
@@ -16,6 +16,9 @@ package com.google.gwt.resources.ext; import com.google.gwt.core.ext.BadPropertyValueException; +import com.google.gwt.core.ext.typeinfo.JClassType; + +import java.net.URL; /** * Allows ResourceGenerators to indicate how their generated resources may be @@ -24,6 +27,18 @@ * method. */ public interface ClientBundleRequirements { + + /** + * Indicates that the ResourcePrototype implementation generated by a + * ResourceGenerator is sensitive to the values of the specified + * configuration property. + * + * @param propertyName the name of the configuration property + * @throws BadPropertyValueException + */ + void addConfigurationProperty(String propertyName) + throws BadPropertyValueException; + /** * Indicates that the ResourcePrototype implementation generated by a * ResourceGenerator is sensitive to the value of the specified @@ -32,10 +47,49 @@ * output. For example, some resource implementations may be sensitive to the * <code>user.agent</code> deferred-binding property, and would call this * method with the literal string <code>user.agent</code>. + * <p> + * If a deferred-binding property does not exist, an attempt is made to check + * whether a configuration property by the same name exists. * * @param propertyName the name of the deferred-binding property - * @throws BadPropertyValueException if <code>propertyName</code> is not a - * valid deferred-binding property. + * @throws BadPropertyValueException if <code>propertyName</code> is neither a + * valid deferred-binding property nor a valid configuration + * property. */ void addPermutationAxis(String propertyName) throws BadPropertyValueException; + + /** + * Indicates that the ResourcePrototype implementation generated by a + * ResourceGenerator is sensitive to a dependent resource. This method takes + * both an unresolved <param>partialPath</param> and a located + * <param>resolvedResourceUrl</param>, since the resolved location of a + * resource can change dynamically at run time. So, by calling this method, + * the requirement is being declared for both the resolution of the resource's + * URL, as well as its content. + * <p> + * The implementation for resolving a resource url from a partial path is + * contained in {@link ResourceGeneratorUtil}, and is based on an ordered set + * of 'locator' implementations, which are tried in sequence. Example + * 'locator' implementations include looking up a resource file by name, which + * usually amounts to a freshly generated temporary file (see + * {@link ResourceGeneratorUtil.addNamedFile}), or by using the partial path + * as a classpath resource used by a class loader, which can be affected by + * classpath shadowing. + * <p> + * The current resolution for a resource partial path can be checked via + * {@link ResourceGeneratorUtil.tryFindResource}. + * + * @param partialPath a partial path representing a dependent resource. + * @param resolvedResourceUrl a located resolved URL for a dependent resource. + */ + void addResolvedResource(String partialPath, URL resolvedResourceUrl); + + /** + * Indicates that the ResourcePrototype implementation generated by a + * ResourceGenerator is sensitive to structural changes to the given type, as + * well as any of it's super type hierarchy. + * + * @param type a type + */ + void addTypeHierarchy(JClassType type); }
diff --git a/user/src/com/google/gwt/resources/ext/ResourceContext.java b/user/src/com/google/gwt/resources/ext/ResourceContext.java index 7c5f2ff..afde4b2 100644 --- a/user/src/com/google/gwt/resources/ext/ResourceContext.java +++ b/user/src/com/google/gwt/resources/ext/ResourceContext.java
@@ -133,7 +133,14 @@ * {@link ResourceGenerator#prepare} methods. */ String getImplementationSimpleSourceName() throws IllegalStateException; - + + /** + * Returns a {@link ClientBundleRequirements} object, which can be used to + * track deferred-binding and configuration properties that are relevant to a + * resource context. + */ + ClientBundleRequirements getRequirements(); + /** * Store data in the ResourceContext. ResourceGenerators may reduce the amount * of recomputation performed by caching data the ResourceContext. This cache
diff --git a/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java b/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java index baed540..5e632a5 100644 --- a/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java +++ b/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
@@ -16,6 +16,7 @@ package com.google.gwt.resources.ext; import com.google.gwt.core.ext.BadPropertyValueException; +import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.SelectionProperty; import com.google.gwt.core.ext.TreeLogger; @@ -61,10 +62,10 @@ * A locator which will use files published via * {@link ResourceGeneratorUtil#addNamedFile(String, File)}. */ - private static class FileLocator implements Locator { - public static final FileLocator INSTANCE = new FileLocator(); + private static class NamedFileLocator implements Locator { + public static final NamedFileLocator INSTANCE = new NamedFileLocator(); - private FileLocator() { + private NamedFileLocator() { } public URL locate(String resourceName) { @@ -285,12 +286,7 @@ public static URL[] findResources(TreeLogger logger, ResourceContext context, JMethod method, String[] defaultSuffixes) throws UnableToCompleteException { - Locator[] locators = { - FileLocator.INSTANCE, - new ResourceOracleLocator( - context.getGeneratorContext().getResourcesOracle()), - new ClassLoaderLocator(Thread.currentThread().getContextClassLoader())}; - + Locator[] locators = getDefaultLocators(context.getGeneratorContext()); URL[] toReturn = findResources(logger, locators, context, method, defaultSuffixes); return toReturn; @@ -370,8 +366,47 @@ return currentMethod; } + + /** + * Try to find a resource with the given resourceName. It will use the default + * search order to locate the resource as is used by {@link #findResources}. + * + * @param logger + * @param context + * @param resourceName + * @return a URL for the resource, if found + */ + public static URL tryFindResource(TreeLogger logger, + GeneratorContext genContext, ResourceContext resourceContext, + String resourceName) { + String locale = getLocale(logger, genContext); + Locator[] locators = getDefaultLocators(genContext); + for (Locator locator : locators) { + URL toReturn = tryFindResource(locator, resourceContext, resourceName, + locale); + if (toReturn != null) { + return toReturn; + } + } + return null; + } /** + * Add the type dependency requirements for a method, to the context. + * + * @param context + * @param method + */ + private static void addTypeRequirementsForMethod(ResourceContext context, + JMethod method) { + ClientBundleRequirements reqs = context.getRequirements(); + if (reqs != null) { + reqs.addTypeHierarchy(method.getEnclosingType()); + reqs.addTypeHierarchy((JClassType) method.getReturnType()); + } + } + + /** * We want to warn the user about any annotations from ImageBundle or the old * incubator code. */ @@ -387,7 +422,7 @@ } } } - + /** * Main implementation of findResources. */ @@ -396,14 +431,7 @@ throws UnableToCompleteException { logger = logger.branch(TreeLogger.DEBUG, "Finding resources"); - String locale; - try { - PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle(); - SelectionProperty prop = oracle.getSelectionProperty(logger, "locale"); - locale = prop.getCurrentValue(); - } catch (BadPropertyValueException e) { - locale = null; - } + String locale = getLocale(logger, context.getGeneratorContext()); checkForDeprecatedAnnotations(logger, method); @@ -416,13 +444,13 @@ for (String extension : defaultSuffixes) { logger.log(TreeLogger.SPAM, "Trying default extension " + extension); for (Locator locator : locators) { - URL resourceUrl = tryFindResource(locator, - getPathRelativeToPackage( - method.getEnclosingType().getPackage(), method.getName() - + extension), locale); + URL resourceUrl = tryFindResource(locator, context, + getPathRelativeToPackage(method.getEnclosingType().getPackage(), + method.getName() + extension), locale); + // Take the first match if (resourceUrl != null) { - // Take the first match + addTypeRequirementsForMethod(context, method); return new URL[] {resourceUrl}; } } @@ -446,15 +474,16 @@ URL resourceURL = null; for (Locator locator : locators) { - resourceURL = tryFindResource(locator, getPathRelativeToPackage( - method.getEnclosingType().getPackage(), resource), locale); + resourceURL = tryFindResource(locator, context, + getPathRelativeToPackage(method.getEnclosingType().getPackage(), + resource), locale); /* * If we didn't find the resource relative to the package, assume it * is absolute. */ if (resourceURL == null) { - resourceURL = tryFindResource(locator, resource, locale); + resourceURL = tryFindResource(locator, context, resource, locale); } // If we have found a resource, take the first match @@ -478,9 +507,44 @@ throw new UnableToCompleteException(); } + addTypeRequirementsForMethod(context, method); return toReturn; } - + + /** + * Get default list of resource Locators, in the default order. + * + * @param context + * @return an ordered array of Locator[] + */ + private static Locator[] getDefaultLocators(GeneratorContext genContext) { + Locator[] locators = { + NamedFileLocator.INSTANCE, + new ResourceOracleLocator(genContext.getResourcesOracle()), + new ClassLoaderLocator(Thread.currentThread().getContextClassLoader())}; + + return locators; + } + + /** + * Get the current locale string. + * + * @param logger + * @param context + * @return the current locale + */ + private static String getLocale(TreeLogger logger, GeneratorContext genContext) { + String locale; + try { + PropertyOracle oracle = genContext.getPropertyOracle(); + SelectionProperty prop = oracle.getSelectionProperty(logger, "locale"); + locale = prop.getCurrentValue(); + } catch (BadPropertyValueException e) { + locale = null; + } + return locale; + } + /** * Converts a package relative path into an absolute path. * @@ -531,6 +595,31 @@ return toReturn; } + + /** + * Performs the locale lookup function for a given resource name. Will also + * add the located resource to the requirements object for the context. + * + * @param locator the Locator to use to load the resources + * @param context the ResourceContext + * @param resourceName the string name of the desired resource + * @param locale the locale of the current rebind permutation + * @return a URL by which the resource can be loaded, <code>null</code> if one + * cannot be found + */ + private static URL tryFindResource(Locator locator, ResourceContext context, + String resourceName, String locale) { + + URL toReturn = tryFindResource(locator, resourceName, locale); + if (toReturn != null && context != null) { + ClientBundleRequirements reqs = context.getRequirements(); + if (reqs != null) { + reqs.addResolvedResource(resourceName, toReturn); + } + } + + return toReturn; + } /** * Utility class.
diff --git a/user/src/com/google/gwt/resources/ext/SupportsGeneratorResultCaching.java b/user/src/com/google/gwt/resources/ext/SupportsGeneratorResultCaching.java new file mode 100644 index 0000000..430dfe4 --- /dev/null +++ b/user/src/com/google/gwt/resources/ext/SupportsGeneratorResultCaching.java
@@ -0,0 +1,23 @@ +/* + * 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.ext; + +/** + * A marker interface that a {@link ResourceGenerator} can implement to indicate + * it supports generator result caching. + */ +public interface SupportsGeneratorResultCaching { +}
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 94cc9bf..8be3874 100644 --- a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java +++ b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
@@ -17,8 +17,9 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.core.ext.BadPropertyValueException; -import com.google.gwt.core.ext.Generator; import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.GeneratorContextExt; +import com.google.gwt.core.ext.GeneratorExt; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.SelectionProperty; import com.google.gwt.core.ext.TreeLogger; @@ -27,9 +28,15 @@ import com.google.gwt.core.ext.typeinfo.JGenericType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JRealClassType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.dev.generator.NameFactory; +import com.google.gwt.dev.javac.rebind.CachedClientDataMap; +import com.google.gwt.dev.javac.rebind.CachedPropertyInformation; +import com.google.gwt.dev.javac.rebind.CachedRebindResult; +import com.google.gwt.dev.javac.rebind.RebindResult; +import com.google.gwt.dev.javac.rebind.RebindStatus; import com.google.gwt.dev.util.Util; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.ClientBundleWithLookup; @@ -39,12 +46,19 @@ import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGenerator; import com.google.gwt.resources.ext.ResourceGeneratorType; +import com.google.gwt.resources.ext.ResourceGeneratorUtil; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; import com.google.gwt.resources.rg.BundleResourceGenerator; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.rebind.StringSourceWriter; +import java.io.File; +import java.io.IOException; import java.io.PrintWriter; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -52,6 +66,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; /** @@ -97,7 +112,13 @@ * of an instance of the ClientBundle type so that resources can refer to one * another by simply emitting a call to <code>resource()</code>. */ -public abstract class AbstractClientBundleGenerator extends Generator { +public abstract class AbstractClientBundleGenerator extends GeneratorExt { + + private static final String FILE_PROTOCOL = "file"; + private static final String JAR_PROTOCOL = "jar"; + private static final String CACHED_PROPERTY_INFORMATION = "cpi"; + private static final String CACHED_RESOURCE_INFORMATION = "cri"; + private static final String CACHED_TYPE_INFORMATION = "cti"; /** * An implementation of ClientBundleFields. @@ -176,31 +197,145 @@ } private static class RequirementsImpl implements ClientBundleRequirements { - private final Set<String> axes = new HashSet<String>(); - private final PropertyOracle oracle; + private final Set<String> axes; + private boolean axesLocked = false; + private final boolean canBeCacheable; + private final Set<String> configProps; + private final PropertyOracle propertyOracle; + private final Map<String, URL> resolvedResources; + private final Set<JClassType> types; - public RequirementsImpl(PropertyOracle oracle) { - this.oracle = oracle; + public RequirementsImpl(PropertyOracle propertyOracle, boolean canBeCacheable) { + this.propertyOracle = propertyOracle; + this.canBeCacheable = canBeCacheable; + + // always need to track permuationAxes + axes = new HashSet<String>(); + + // only need to track these if generator caching is a possibility + if (canBeCacheable) { + configProps = new HashSet<String>(); + types = new HashSet<JClassType>(); + resolvedResources = new HashMap<String, URL>(); + } else { + configProps = null; + types = null; + resolvedResources = null; + } + } + + public void addConfigurationProperty(String propertyName) + throws BadPropertyValueException { + + if (!canBeCacheable) { + return; + } + // Ensure the property exists + propertyOracle.getConfigurationProperty(propertyName).getValues(); + configProps.add(propertyName); } public void addPermutationAxis(String propertyName) throws BadPropertyValueException { + + if (axes.contains(propertyName)) { + return; + } + + // Ensure adding of permutationAxes has not been locked + if (axesLocked) { + throw new IllegalStateException( + "addPermutationAxis failed, axes have been locked"); + } + // Ensure the property exists and add a permutation axis if the // property is a deferred binding property. try { - oracle.getSelectionProperty(TreeLogger.NULL, propertyName).getCurrentValue(); + propertyOracle.getSelectionProperty( + TreeLogger.NULL, propertyName).getCurrentValue(); axes.add(propertyName); } catch (BadPropertyValueException e) { - oracle.getConfigurationProperty(propertyName).getValues(); + addConfigurationProperty(propertyName); } } + + public void addResolvedResource(String partialPath, URL resolvedResourceUrl) { + if (!canBeCacheable) { + return; + } + resolvedResources.put(partialPath, resolvedResourceUrl); + } + + public void addTypeHierarchy(JClassType type) { + if (!canBeCacheable) { + return; + } + if (!types.add(type)) { + return; + } + Set<? extends JClassType> superTypes = type.getFlattenedSupertypeHierarchy(); + types.addAll(superTypes); + } + + public Collection<String> getConfigurationPropertyNames() { + if (!canBeCacheable) { + return null; + } + return configProps; + } + + public Collection<String> getPermutationAxes() { + return axes; + } + + public Map<String, URL> getResolvedResources() { + if (!canBeCacheable) { + return null; + } + return resolvedResources; + } + + public Map<String, String> getTypeSignatures() { + if (!canBeCacheable) { + return null; + } + Map<String, String> typeSignatures = new HashMap<String, String>(); + for (JClassType type : types) { + String typeName = type.getQualifiedSourceName(); + if (type instanceof JRealClassType) { + JRealClassType sourceRealType = (JRealClassType) type; + String typeSignature = sourceRealType.getTypeStrongHash(); + typeSignatures.put(typeName, typeSignature); + } else { + typeSignatures.put(typeName, ""); + } + } + return typeSignatures; + } + + /* + * No further permutation axes can be added after this is called + */ + public void lockPermutationAxes() { + axesLocked = true; + } } @Override - public final String generate(TreeLogger logger, - GeneratorContext generatorContext, String typeName) + public RebindResult generateIncrementally(TreeLogger logger, + GeneratorContextExt generatorContext, String typeName) throws UnableToCompleteException { + /* + * Do a series of checks to see if we can use a previously cached result, + * and if so, we can skip further execution and return immediately. + */ + if (checkPropertyCacheability(logger, generatorContext) + && checkSourceTypeCacheability(generatorContext) + && checkDependentResourceCacheability(logger, generatorContext, null)) { + return new RebindResult(RebindStatus.USE_ALL_CACHED, typeName); + } + // The TypeOracle knows about all types in the type system TypeOracle typeOracle = generatorContext.getTypeOracle(); @@ -227,16 +362,30 @@ logger, typeOracle, sourceType); /* + * Check the resource generators associated with our taskList, and see if + * they all support generator result caching. + */ + boolean canBeCacheable = checkResourceGeneratorCacheability( + generatorContext, taskList); + + /* * Additional objects that hold state during the generation process. */ AbstractResourceContext resourceContext = createResourceContext(logger, generatorContext, sourceType); FieldsImpl fields = new FieldsImpl(); RequirementsImpl requirements = new RequirementsImpl( - generatorContext.getPropertyOracle()); + generatorContext.getPropertyOracle(), canBeCacheable); + resourceContext.setRequirements(requirements); doAddFieldsAndRequirements(logger, generatorContext, fields, requirements); /* + * Add our source type (and it's supertypes) as a requirement. Note further + * types may be added during the processing of the taskList. + */ + requirements.addTypeHierarchy(sourceType); + + /* * Initialize the ResourceGenerators and prepare them for subsequent code * generation. */ @@ -309,8 +458,32 @@ finish(logger, resourceContext, generators.keySet()); doFinish(logger); - // Return the name of the concrete class - return createdClassName; + if (canBeCacheable) { + // remember the current set of required properties, and their values + CachedPropertyInformation cpi = new CachedPropertyInformation(logger, + generatorContext.getPropertyOracle(), + requirements.getPermutationAxes(), + requirements.getConfigurationPropertyNames()); + + // remember the type signatures for required source types + Map<String, String> cti = requirements.getTypeSignatures(); + + // remember the required resources + Map<String, URL> cri = requirements.getResolvedResources(); + + // create data map to be returned the next time the generator is run + CachedClientDataMap data = new CachedClientDataMap(); + data.put(CACHED_PROPERTY_INFORMATION, cpi); + data.put(CACHED_RESOURCE_INFORMATION, cri); + data.put(CACHED_TYPE_INFORMATION, cti); + + // Return a new cacheable result + return new RebindResult(RebindStatus.USE_ALL_NEW, createdClassName, data); + } else { + // If we can't be cacheable, don't return a cacheable result + return new RebindResult(RebindStatus.USE_ALL_NEW_WITH_NO_CACHING, + createdClassName); + } } /** @@ -365,6 +538,200 @@ } /** + * Check that the map of cached type signatures matches those from the current + * typeOracle. + */ + private boolean checkCachedTypeSignatures( + GeneratorContextExt generatorContext, Map<String, String> typeSignatures) { + + TypeOracle oracle = generatorContext.getTypeOracle(); + + for (String sourceTypeName : typeSignatures.keySet()) { + JClassType sourceType = oracle.findType(sourceTypeName); + if (sourceType == null || !(sourceType instanceof JRealClassType)) { + return false; + } + JRealClassType sourceRealType = (JRealClassType) sourceType; + + String signature = sourceRealType.getTypeStrongHash(); + if (!signature.equals(typeSignatures.get(sourceTypeName))) { + return false; + } + } + + return true; + } + + /** + * Check dependent resources for cacheability. + */ + private boolean checkDependentResourceCacheability(TreeLogger logger, + GeneratorContextExt genContext, ResourceContext resourceContext) { + + CachedRebindResult lastRebindResult = genContext.getCachedGeneratorResult(); + + if (lastRebindResult == null + || !genContext.isGeneratorResultCachingEnabled()) { + return false; + } + long lastTimeGenerated = lastRebindResult.getTimeGenerated(); + + // check that resource URL's haven't moved, and haven't been modified + @SuppressWarnings("unchecked") + Map<String, URL> cachedResolvedResources = (Map<String, URL>) + lastRebindResult.getClientData(CACHED_RESOURCE_INFORMATION); + + if (cachedResolvedResources == null) { + return false; + } + + for (Entry<String, URL> entry : cachedResolvedResources.entrySet()) { + String resourceName = entry.getKey(); + URL resolvedUrl = entry.getValue(); + URL currentUrl = ResourceGeneratorUtil.tryFindResource(logger, genContext, + resourceContext, resourceName); + if (currentUrl == null || resolvedUrl == null + || !resolvedUrl.toExternalForm().equals(currentUrl.toExternalForm())) { + return false; + } + + if (!checkDependentResourceUpToDate(lastTimeGenerated, resolvedUrl)) { + return false; + } + } + + return true; + } + + /** + * Checks whether a dependent resource referenced by the provided URL is + * up to date, based on the provided referenceTime. + */ + private boolean checkDependentResourceUpToDate(long referenceTime, URL url) { + try { + if (url.getProtocol().equals(JAR_PROTOCOL)) { + /* + * If this resource is contained inside a jar file, such as can + * happen if it's bundled in a 3rd-party library, we use the jar + * file itself to test whether it's up to date. We don't want + * to call JarURLConnection.getLastModified(), as this is much + * slower than using the jar File resource directly. + */ + JarURLConnection jarConn = (JarURLConnection) url.openConnection(); + url = jarConn.getJarFileURL(); + } + + long lastModified; + if (url.getProtocol().equals(FILE_PROTOCOL)) { + /* + * Need to handle possibly wonky syntax in a file URL resource. + * Modeled after suggestion in this blog entry: + * http://weblogs.java.net/blog/2007/04/25/how-convert-javaneturl-javaiofile + */ + File file; + try { + file = new File(url.toURI()); + } catch (URISyntaxException uriEx) { + file = new File(url.getPath()); + } + lastModified = file.lastModified(); + } else { + /* + * Don't attempt to handle any other protocol + */ + return false; + } + if (lastModified == 0L || + lastModified > referenceTime) { + return false; + } + } catch (IOException ioEx) { + return false; + } catch (RuntimeException ruEx) { + /* + * Return false for any RuntimeException (e.g. a SecurityException), and + * allow the cacheability check to fail, since we don't want to change the + * behavior that would be encountered if no cache is available. Force + * resource generators to utilize their own exception handling. + */ + return false; + } + return true; + } + + /* + * Check properties for cacheability + */ + private boolean checkPropertyCacheability(TreeLogger logger, + GeneratorContextExt genContext) { + + CachedRebindResult lastRebindResult = genContext.getCachedGeneratorResult(); + + if (lastRebindResult == null + || !genContext.isGeneratorResultCachingEnabled()) { + return false; + } + + /* + * Do a check of deferred-binding and configuration properties, comparing + * the cached values saved previously with the current properties. + */ + CachedPropertyInformation cpi = (CachedPropertyInformation) + lastRebindResult.getClientData(CACHED_PROPERTY_INFORMATION); + + return cpi != null && cpi.checkPropertiesWithPropertyOracle(logger, + genContext.getPropertyOracle()); + } + + /** + * Check cacheability for resource generators in taskList. + */ + private boolean checkResourceGeneratorCacheability( + GeneratorContextExt genContext, + Map<Class<? extends ResourceGenerator>, List<JMethod>> taskList) { + + if (!genContext.isGeneratorResultCachingEnabled()) { + return false; + } + + /* + * Loop through each of our ResouceGenerator classes, and check those that + * implement the SupportsGeneratorResultCaching interface. + */ + for (Class<? extends ResourceGenerator> rgClass : taskList.keySet()) { + if (!SupportsGeneratorResultCaching.class.isAssignableFrom(rgClass)) { + return false; + } + } + return true; + } + + /* + * Check source types for cacheability + */ + private boolean checkSourceTypeCacheability(GeneratorContextExt genContext) { + + CachedRebindResult lastRebindResult = genContext.getCachedGeneratorResult(); + + if (lastRebindResult == null + || !genContext.isGeneratorResultCachingEnabled()) { + return false; + } + + /* + * Do a check over the cached list of types that were previously flagged as + * required. Check that none of these types has undergone a version change + * since the previous cached result was generated. + */ + @SuppressWarnings("unchecked") + Map<String, String> cachedTypeSignatures = (Map<String, String>) + lastRebindResult.getClientData(CACHED_TYPE_INFORMATION); + + return cachedTypeSignatures != null + && checkCachedTypeSignatures(genContext, cachedTypeSignatures); + } + + /** * Create fields and assignments for a single ResourceGenerator. */ private boolean createFieldsAndAssignments(TreeLogger logger, @@ -567,19 +934,24 @@ /** * Given a user-defined type name, determine the type name for the generated * class based on accumulated requirements. - * - * @throws UnableToCompleteException if an error occurs. */ private String generateSimpleSourceName(TreeLogger logger, ResourceContext context, RequirementsImpl requirements) { StringBuilder toReturn = new StringBuilder( context.getClientBundleType().getName().replaceAll("[.$]", "_")); - Set<String> permutationAxes = new HashSet<String>(requirements.axes); - permutationAxes.add("locale"); try { + // always add the locale property + requirements.addPermutationAxis("locale"); + } catch (BadPropertyValueException e) { + } + + try { + // no further additions to the permutation axes allowed after this point + requirements.lockPermutationAxes(); + PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle(); - for (String property : permutationAxes) { + for (String property : requirements.getPermutationAxes()) { SelectionProperty prop = oracle.getSelectionProperty(logger, property); String value = prop.getCurrentValue(); toReturn.append("_" + value);
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java index 477c0ff..be8eea5 100644 --- a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java +++ b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
@@ -20,6 +20,7 @@ import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.dev.util.Util; +import com.google.gwt.resources.ext.ClientBundleRequirements; import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGenerator; import com.google.gwt.resources.ext.ResourceGeneratorUtil; @@ -46,8 +47,9 @@ private final TreeLogger logger; private final ClientBundleContext clientBundleCtx; - private String currentResourceGeneratorType; private final GeneratorContext context; + private String currentResourceGeneratorType; + private ClientBundleRequirements requirements = null; private final JClassType resourceBundleType; private String simpleSourceName; @@ -100,11 +102,19 @@ } return simpleSourceName; } + + public ClientBundleRequirements getRequirements() { + return requirements; + } public <T> boolean putCachedData(String key, T value) { key = currentResourceGeneratorType + ":" + key; return value != clientBundleCtx.putCachedData(key, value); } + + public void setRequirements(ClientBundleRequirements requirements) { + this.requirements = requirements; + } protected GeneratorContext getContext() { return context;
diff --git a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java index 5ff0c31..1788c72 100644 --- a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java +++ b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
@@ -49,6 +49,7 @@ String enableRenaming = null; try { ConfigurationProperty prop = propertyOracle.getConfigurationProperty(ENABLE_RENAMING); + getRequirements().addConfigurationProperty(ENABLE_RENAMING); enableRenaming = prop.getValues().get(0); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Bad value for " + ENABLE_RENAMING, e);
diff --git a/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java b/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java index 3ccc0fa..a7ae3e5 100644 --- a/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java
@@ -22,11 +22,13 @@ import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.resources.ext.AbstractResourceGenerator; import com.google.gwt.resources.ext.ResourceContext; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; /** * This is a special case of ResourceGenerator that handles nested bundles. */ -public final class BundleResourceGenerator extends AbstractResourceGenerator { +public final class BundleResourceGenerator extends AbstractResourceGenerator + implements SupportsGeneratorResultCaching { @Override public String createAssignment(TreeLogger logger, ResourceContext context,
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java index f1cca0f..0075f45 100644 --- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -68,6 +68,7 @@ import com.google.gwt.resources.ext.ClientBundleRequirements; import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGeneratorUtil; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.rebind.StringSourceWriter; @@ -88,7 +89,8 @@ /** * Provides implementations of CSSResources. */ -public final class CssResourceGenerator extends AbstractResourceGenerator { +public final class CssResourceGenerator extends AbstractResourceGenerator + implements SupportsGeneratorResultCaching { @SuppressWarnings("serial") static class JClassOrderComparator implements Comparator<JClassType>, @@ -125,8 +127,11 @@ private static final String KEY_CLASS_PREFIX = "prefix"; private static final String KEY_CLASS_COUNTER = "counter"; private static final String KEY_HAS_CACHED_DATA = "hasCachedData"; - private static final String KEY_SHARED_METHODS = "sharedMethods"; + private static final String KEY_MERGE_ENABLED = "CssResource.mergeEnabled"; + private static final String KEY_OBFUSCATION_PREFIX = "CssResource.obfuscationPrefix"; private static final String KEY_RESERVED_PREFIXES = "CssResource.reservedClassPrefixes"; + private static final String KEY_SHARED_METHODS = "sharedMethods"; + private static final String KEY_STYLE = "CssResource.style"; /** * This character must not appear in {@link #BASE32_CHARS}. @@ -451,6 +456,9 @@ '$', '.')); assert importType != null : "TypeOracle does not have type " + clazz.getName(); + + // add this import type as a requirement for this generator + context.getRequirements().addTypeHierarchy(importType); String prefix = getImportPrefix(importType); @@ -499,17 +507,27 @@ throws UnableToCompleteException { String classPrefix; try { - PropertyOracle propertyOracle = context.getGeneratorContext().getPropertyOracle(); - ConfigurationProperty styleProp = propertyOracle.getConfigurationProperty("CssResource.style"); + PropertyOracle propertyOracle = + context.getGeneratorContext().getPropertyOracle(); + ConfigurationProperty styleProp = + propertyOracle.getConfigurationProperty(KEY_STYLE); String style = styleProp.getValues().get(0); prettyOutput = style.equals("pretty"); - ConfigurationProperty mergeProp = propertyOracle.getConfigurationProperty("CssResource.mergeEnabled"); + ConfigurationProperty mergeProp = + propertyOracle.getConfigurationProperty(KEY_MERGE_ENABLED); String merge = mergeProp.getValues().get(0); enableMerge = merge.equals("true"); - ConfigurationProperty classPrefixProp = propertyOracle.getConfigurationProperty("CssResource.obfuscationPrefix"); + ConfigurationProperty classPrefixProp = + propertyOracle.getConfigurationProperty(KEY_OBFUSCATION_PREFIX); classPrefix = classPrefixProp.getValues().get(0); + + // add these configuration properties to our requirements + ClientBundleRequirements requirements = context.getRequirements(); + requirements.addConfigurationProperty(KEY_STYLE); + requirements.addConfigurationProperty(KEY_MERGE_ENABLED); + requirements.addConfigurationProperty(KEY_OBFUSCATION_PREFIX); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Unable to query module property", e); throw new UnableToCompleteException(); @@ -553,7 +571,7 @@ // Create the AST and do a quick scan for requirements CssStylesheet sheet = GenerateCssAst.exec(logger, resources); stylesheetMap.put(method, sheet); - (new RequirementsCollector(logger, requirements)).accept(sheet); + (new RequirementsCollector(logger, context.getRequirements())).accept(sheet); } /** @@ -773,8 +791,12 @@ ConfigurationProperty prop; TreeSet<String> reservedPrefixes = new TreeSet<String>(); try { - prop = context.getGeneratorContext().getPropertyOracle().getConfigurationProperty( - KEY_RESERVED_PREFIXES); + prop = context.getGeneratorContext().getPropertyOracle() + .getConfigurationProperty(KEY_RESERVED_PREFIXES); + + // add this configuration property to our requirements + context.getRequirements().addConfigurationProperty(KEY_RESERVED_PREFIXES); + for (String value : prop.getValues()) { value = value.trim(); if (value.length() == 0) {
diff --git a/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java b/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java index 8fda74f..416ca28 100644 --- a/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
@@ -24,6 +24,7 @@ import com.google.gwt.resources.ext.AbstractResourceGenerator; import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGeneratorUtil; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.rebind.StringSourceWriter; @@ -32,7 +33,8 @@ /** * Provides implementations of DataResource. */ -public final class DataResourceGenerator extends AbstractResourceGenerator { +public final class DataResourceGenerator extends AbstractResourceGenerator + implements SupportsGeneratorResultCaching { @Override public String createAssignment(TreeLogger logger, ResourceContext context, JMethod method) throws UnableToCompleteException {
diff --git a/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java index 928f989..04bc77b 100644 --- a/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
@@ -32,6 +32,7 @@ import com.google.gwt.resources.ext.ClientBundleRequirements; import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGeneratorUtil; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.rebind.StringSourceWriter; @@ -43,7 +44,7 @@ * Adds {@link ExternalTextResourcePrototype} objects to the bundle. */ public final class ExternalTextResourceGenerator extends - AbstractResourceGenerator { + AbstractResourceGenerator implements SupportsGeneratorResultCaching { /** * The name of a deferred binding property that determines whether or not this * generator will use JSONP to fetch the files. @@ -177,6 +178,9 @@ ConfigurationProperty prop = context.getGeneratorContext() .getPropertyOracle().getConfigurationProperty(USE_JSONP); useJsonpProp = prop.getValues().get(0); + + // add this configuration property to our requirements + context.getRequirements().addConfigurationProperty(USE_JSONP); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Bad value for " + USE_JSONP, e); return false;
diff --git a/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java b/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java index dd2e199..254aa43 100644 --- a/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java
@@ -24,11 +24,13 @@ import com.google.gwt.resources.client.GwtCreateResource.ClassType; import com.google.gwt.resources.ext.AbstractResourceGenerator; import com.google.gwt.resources.ext.ResourceContext; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; /** * Provides implementations of GwtCreateResource. */ -public final class GwtCreateResourceGenerator extends AbstractResourceGenerator { +public final class GwtCreateResourceGenerator extends AbstractResourceGenerator + implements SupportsGeneratorResultCaching { @Override public String createAssignment(TreeLogger logger, ResourceContext context,
diff --git a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java index ee835c3..8707e98 100644 --- a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
@@ -32,6 +32,7 @@ import com.google.gwt.resources.ext.ClientBundleRequirements; import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGeneratorUtil; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; import com.google.gwt.resources.rg.ImageBundleBuilder.Arranger; import com.google.gwt.resources.rg.ImageBundleBuilder.ImageRect; import com.google.gwt.user.rebind.SourceWriter; @@ -48,7 +49,8 @@ /** * Builds an image strip for all ImageResources defined within an ClientBundle. */ -public final class ImageResourceGenerator extends AbstractResourceGenerator { +public final class ImageResourceGenerator extends AbstractResourceGenerator + implements SupportsGeneratorResultCaching { /** * Represents a file that contains multiple image regions. */ @@ -602,6 +604,10 @@ try { String locale = context.getGeneratorContext().getPropertyOracle().getSelectionProperty( TreeLogger.NULL, "locale").getCurrentValue(); + + // add the locale selection property as a permuation axis for our requirements + context.getRequirements().addPermutationAxis("locale"); + sb.append(locale); } catch (BadPropertyValueException e) { // OK, locale isn't defined
diff --git a/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java b/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java index e40ccd6..95c1d4e 100644 --- a/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java +++ b/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
@@ -24,6 +24,7 @@ import com.google.gwt.resources.ext.AbstractResourceGenerator; import com.google.gwt.resources.ext.ResourceContext; import com.google.gwt.resources.ext.ResourceGeneratorUtil; +import com.google.gwt.resources.ext.SupportsGeneratorResultCaching; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.rebind.StringSourceWriter; @@ -32,7 +33,8 @@ /** * Provides implementations of TextResource. */ -public final class TextResourceGenerator extends AbstractResourceGenerator { +public final class TextResourceGenerator extends AbstractResourceGenerator + implements SupportsGeneratorResultCaching { /** * Java compiler has a limit of 2^16 bytes for encoding string constants in a * class file. Since the max size of a character is 4 bytes, we'll limit the
diff --git a/user/test/com/google/gwt/resources/rg/CssTestCase.java b/user/test/com/google/gwt/resources/rg/CssTestCase.java index a4c07ae..6b46164 100644 --- a/user/test/com/google/gwt/resources/rg/CssTestCase.java +++ b/user/test/com/google/gwt/resources/rg/CssTestCase.java
@@ -24,6 +24,7 @@ import com.google.gwt.resources.css.ast.CssStylesheet; import com.google.gwt.resources.css.ast.CssVisitor; import com.google.gwt.resources.css.ast.HasNodes; +import com.google.gwt.resources.ext.ClientBundleRequirements; import com.google.gwt.resources.ext.ResourceContext; import junit.framework.TestCase; @@ -110,6 +111,10 @@ throws IllegalStateException { return null; } + + public ClientBundleRequirements getRequirements() { + return null; + } public <T> boolean putCachedData(String key, T value) { return false;