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;