Introduces MinimalRebuildCache management.
Adds a memory and disk cache management class for MinimalRebuildCache
instances.
Change-Id: I7d3bce27b22d150991ca009f454015a3d3d75a8a
Review-Link: https://gwt-review.googlesource.com/#/c/10261/
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
index 6e76884..124ce48 100644
--- a/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCache.java
@@ -318,10 +318,10 @@
}
/*
- * Filter for just those stale types that are actually reachable. Since if they're not
- * reachable we don't want to artificially traverse them and unnecessarily reveal dependency
- * problems. And if they have become reachable, since they're missing JS, they will already be
- * fully traversed when seen in Unify.
+ * Filter for just those stale types that are actually reachable. Since if they're not reachable
+ * we don't want to artificially traverse them and unnecessarily reveal dependency problems. And
+ * if they have become reachable, since they're missing JS, they will already be fully traversed
+ * when seen in Unify.
*/
copyCollection(filterUnreachableTypeNames(staleTypeNames), staleTypeNames);
@@ -443,6 +443,50 @@
copyCollection(that.staleTypeNames, this.staleTypeNames);
}
+ @VisibleForTesting
+ boolean hasSameContent(MinimalRebuildCache that) {
+ // Ignoring processedStaleTypeNames since it is transient.
+ return
+ this.immediateTypeRelations.hasSameContent(that.immediateTypeRelations) && Objects.equal(
+ this.compilationUnitTypeNameByNestedTypeName,
+ that.compilationUnitTypeNameByNestedTypeName)
+ && Objects.equal(this.contentHashByGeneratedTypeName, that.contentHashByGeneratedTypeName)
+ && Objects.equal(this.deletedCompilationUnitNames, that.deletedCompilationUnitNames)
+ && Objects.equal(this.deletedDiskSourcePaths, that.deletedDiskSourcePaths)
+ && Objects.equal(this.deletedResourcePaths, that.deletedResourcePaths)
+ && Objects.equal(this.dualJsoImplInterfaceNames, that.dualJsoImplInterfaceNames)
+ && Objects.equal(this.generatedArtifacts, that.generatedArtifacts) && Objects.equal(
+ this.generatedCompilationUnitNamesByReboundTypeNames,
+ that.generatedCompilationUnitNamesByReboundTypeNames)
+ && this.intTypeMapper.hasSameContent(that.intTypeMapper)
+ && Objects.equal(this.jsByTypeName, that.jsByTypeName)
+ && Objects.equal(this.jsoStatusChangedTypeNames, that.jsoStatusChangedTypeNames)
+ && Objects.equal(this.jsoTypeNames, that.jsoTypeNames)
+ && Objects.equal(this.lastLinkedJsBytes, that.lastLinkedJsBytes)
+ && Objects.equal(this.lastModifiedByDiskSourcePath, that.lastModifiedByDiskSourcePath)
+ && Objects.equal(this.lastModifiedByResourcePath, that.lastModifiedByResourcePath)
+ && Objects.equal(this.lastReachableTypeNames, that.lastReachableTypeNames)
+ && Objects.equal(this.modifiedCompilationUnitNames, that.modifiedCompilationUnitNames)
+ && Objects.equal(this.modifiedDiskSourcePaths, that.modifiedDiskSourcePaths)
+ && Objects.equal(this.modifiedResourcePaths, that.modifiedResourcePaths)
+ && Objects.equal(this.nestedTypeNamesByUnitTypeName, that.nestedTypeNamesByUnitTypeName)
+ && this.persistentPrettyNamerState.hasSameContent(that.persistentPrettyNamerState)
+ && Objects.equal(this.preambleTypeNames, that.preambleTypeNames) && Objects.equal(
+ this.rebinderTypeNamesByReboundTypeName, that.rebinderTypeNamesByReboundTypeName)
+ && Objects.equal(this.reboundTypeNamesByGeneratedCompilationUnitNames,
+ that.reboundTypeNamesByGeneratedCompilationUnitNames) && Objects.equal(
+ this.reboundTypeNamesByInputResource, that.reboundTypeNamesByInputResource)
+ && Objects.equal(this.referencedTypeNamesByTypeName, that.referencedTypeNamesByTypeName)
+ && Objects.equal(this.rootTypeNames, that.rootTypeNames)
+ && Objects.equal(this.singleJsoImplInterfaceNames, that.singleJsoImplInterfaceNames)
+ && Objects.equal(this.sourceCompilationUnitNames, that.sourceCompilationUnitNames)
+ && Objects.equal(this.sourceMapsByTypeName, that.sourceMapsByTypeName)
+ && Objects.equal(this.staleTypeNames, that.staleTypeNames)
+ && Objects.equal(this.statementRangesByTypeName, that.statementRangesByTypeName)
+ && Objects.equal(this.typeNamesByReferencingTypeName,
+ that.typeNamesByReferencingTypeName);
+ }
+
/**
* Return the set of provided typeNames with unreachable types filtered out.
*/
@@ -458,10 +502,6 @@
return immediateTypeRelations;
}
- public IntTypeMapper getTypeMapper() {
- return intTypeMapper;
- }
-
public String getJs(String typeName) {
return jsByTypeName.get(typeName);
}
@@ -505,6 +545,10 @@
return statementRangesByTypeName.get(typeName);
}
+ public IntTypeMapper getTypeMapper() {
+ return intTypeMapper;
+ }
+
public boolean hasJs(String typeName) {
return jsByTypeName.containsKey(typeName);
}
diff --git a/dev/core/src/com/google/gwt/dev/MinimalRebuildCacheManager.java b/dev/core/src/com/google/gwt/dev/MinimalRebuildCacheManager.java
new file mode 100644
index 0000000..5149769
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/MinimalRebuildCacheManager.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2014 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.util.CompilerVersion;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.cache.Cache;
+import com.google.gwt.thirdparty.guava.common.cache.CacheBuilder;
+import com.google.gwt.thirdparty.guava.common.util.concurrent.Futures;
+import com.google.gwt.thirdparty.guava.common.util.concurrent.MoreExecutors;
+import com.google.gwt.util.tools.Utility;
+import com.google.gwt.util.tools.shared.Md5Utils;
+import com.google.gwt.util.tools.shared.StringUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages caching of MinimalRebuildCache instances.
+ * <p>
+ * Changes are immediately performed in memory and are asynchronously persisted to disk in original
+ * request order.
+ */
+public class MinimalRebuildCacheManager {
+
+ private static final int MEMORY_CACHE_COUNT_LIMIT = 3;
+ private static final String REBUILD_CACHE_PREFIX = "gwt-rebuildCache";
+
+ private final ExecutorService executorService =
+ MoreExecutors.getExitingExecutorService((ThreadPoolExecutor) Executors.newFixedThreadPool(1));
+ private final TreeLogger logger;
+ private final File minimalRebuildCacheDir;
+ private final Cache<String, MinimalRebuildCache> minimalRebuildCachesByName =
+ CacheBuilder.newBuilder().maximumSize(MEMORY_CACHE_COUNT_LIMIT).build();
+
+ public MinimalRebuildCacheManager(TreeLogger logger, File baseCacheDir) {
+ this.logger = logger;
+ if (baseCacheDir != null) {
+ minimalRebuildCacheDir = new File(baseCacheDir, REBUILD_CACHE_PREFIX);
+ minimalRebuildCacheDir.mkdir();
+ } else {
+ minimalRebuildCacheDir = null;
+ }
+ }
+
+ /**
+ * Synchronously delete all in memory caches managed here and all on disk in the managed folder.
+ */
+ public synchronized void deleteCaches() {
+ syncDeleteMemoryCaches();
+ if (haveCacheDir()) {
+ Futures.getUnchecked(enqueueAsyncDeleteDiskCaches());
+ }
+ }
+
+ /**
+ * Synchronously return the MinimalRebuildCache specific to the given module and binding
+ * properties.
+ * <p>
+ * If no cache is found in memory then it will be synchronously loaded from disk.
+ * <p>
+ * If it is still not found a new empty cache will be returned.
+ */
+ public synchronized MinimalRebuildCache getCache(String moduleName,
+ Map<String, String> bindingProperties) {
+ String cacheName = computeMinimalRebuildCacheName(moduleName, bindingProperties);
+
+ MinimalRebuildCache minimalRebuildCache = minimalRebuildCachesByName.getIfPresent(cacheName);
+
+ // If there's no cache already in memory, try to load a cache from disk.
+ if (minimalRebuildCache == null && haveCacheDir()) {
+ // Might return null.
+ minimalRebuildCache = syncReadDiskCache(moduleName, bindingProperties);
+ if (minimalRebuildCache != null) {
+ minimalRebuildCachesByName.put(cacheName, minimalRebuildCache);
+ }
+ }
+
+ // If there's still no cache loaded, just create a blank one.
+ if (minimalRebuildCache == null) {
+ minimalRebuildCache = new MinimalRebuildCache();
+ minimalRebuildCachesByName.put(cacheName, minimalRebuildCache);
+ return minimalRebuildCache;
+ }
+
+ // Return a copy.
+ MinimalRebuildCache mutableMinimalRebuildCache = new MinimalRebuildCache();
+ mutableMinimalRebuildCache.copyFrom(minimalRebuildCache);
+ return mutableMinimalRebuildCache;
+ }
+
+ /**
+ * Stores a MinimalRebuildCache specific to the given module and binding properties.
+ * <p>
+ * A copy of the cache will be lazily persisted to disk as well.
+ */
+ public synchronized void putCache(String moduleName, Map<String, String> bindingProperties,
+ MinimalRebuildCache knownGoodMinimalRebuildCache) {
+ syncPutMemoryCache(moduleName, bindingProperties, knownGoodMinimalRebuildCache);
+ if (haveCacheDir()) {
+ enqueueAsyncWriteDiskCache(moduleName, bindingProperties, knownGoodMinimalRebuildCache);
+ }
+ }
+
+ /**
+ * Enqueue to asynchronously delete all on disk caches in the managed cache folder.
+ */
+ @VisibleForTesting
+ synchronized Future<Void> enqueueAsyncDeleteDiskCaches() {
+ return executorService.submit(new Callable<Void>() {
+ @Override
+ public Void call() {
+ for (File cacheFile : minimalRebuildCacheDir.listFiles()) {
+ if (!cacheFile.delete()) {
+ logger.log(TreeLogger.WARN, "Couldn't delete " + cacheFile);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Enqueue to asynchronously find, read and return the MinimalRebuildCache unique to this module
+ * and binding properties combination in the managed cache folder.
+ */
+ @VisibleForTesting
+ synchronized Future<MinimalRebuildCache> enqueueAsyncReadDiskCache(final String moduleName,
+ final Map<String, String> bindingProperties) {
+ return executorService.submit(new Callable<MinimalRebuildCache>() {
+ @Override
+ public MinimalRebuildCache call() {
+ // Find the cache file unique to this module, binding properties and working directory.
+ File minimalRebuildCacheFile =
+ computeMinimalRebuildCacheFile(moduleName, bindingProperties);
+
+ // If the file exists.
+ if (minimalRebuildCacheFile.exists()) {
+ ObjectInputStream objectInputStream = null;
+ // Try to read it.
+ try {
+ objectInputStream = new ObjectInputStream(
+ new BufferedInputStream(new FileInputStream(minimalRebuildCacheFile)));
+ return (MinimalRebuildCache) objectInputStream.readObject();
+ } catch (IOException e) {
+ logger.log(TreeLogger.WARN,
+ "Unable to read the rebuild cache in " + minimalRebuildCacheFile + ".");
+ Utility.close(objectInputStream);
+ minimalRebuildCacheFile.delete();
+ } catch (ClassNotFoundException e) {
+ logger.log(TreeLogger.WARN,
+ "Unable to read the rebuild cache in " + minimalRebuildCacheFile + ".");
+ Utility.close(objectInputStream);
+ minimalRebuildCacheFile.delete();
+ } finally {
+ Utility.close(objectInputStream);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Enqueue to asynchronously write the provided MinimalRebuildCache to disk.
+ * <p>
+ * Persisted caches are uniquely named based on the compiler version, current module name, binding
+ * properties and the location where the JVM was launched.
+ * <p>
+ * Care is taken to completely and successfully write a new cache (to a different location on
+ * disk) before replacing the old cache (at the regular location on disk).
+ * <p>
+ * Write requests will occur in the order requested and will queue up if requests are made faster
+ * than they can be completed.
+ */
+ @VisibleForTesting
+ synchronized Future<Void> enqueueAsyncWriteDiskCache(final String moduleName,
+ final Map<String, String> bindingProperties, final MinimalRebuildCache minimalRebuildCache) {
+ return executorService.submit(new Callable<Void>() {
+ @Override
+ public Void call() {
+ File oldMinimalRebuildCacheFile =
+ computeMinimalRebuildCacheFile(moduleName, bindingProperties);
+ File newMinimalRebuildCacheFile =
+ new File(oldMinimalRebuildCacheFile.getAbsoluteFile() + ".new");
+
+ // Ensure the cache folder exists.
+ oldMinimalRebuildCacheFile.getParentFile().mkdirs();
+
+ // Write the new cache to disk.
+ ObjectOutputStream objectOutputStream = null;
+ try {
+ objectOutputStream = new ObjectOutputStream(
+ new BufferedOutputStream(new FileOutputStream(newMinimalRebuildCacheFile)));
+ objectOutputStream.writeObject(minimalRebuildCache);
+ Utility.close(objectOutputStream);
+
+ // Replace the old cache file with the new one.
+ oldMinimalRebuildCacheFile.delete();
+ newMinimalRebuildCacheFile.renameTo(oldMinimalRebuildCacheFile);
+ } catch (IOException e) {
+ logger.log(TreeLogger.WARN,
+ "Unable to update the cache in " + oldMinimalRebuildCacheFile + ".");
+ newMinimalRebuildCacheFile.delete();
+ } finally {
+ if (objectOutputStream != null) {
+ Utility.close(objectOutputStream);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ /**
+ * For testing only. Stops accepting any new tasks and waits for current tasks to complete.
+ */
+ @VisibleForTesting
+ boolean shutdown() throws InterruptedException {
+ executorService.shutdown();
+ return executorService.awaitTermination(30, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Find, read and return the MinimalRebuildCache unique to this module, binding properties and
+ * working directory.
+ */
+ @VisibleForTesting
+ synchronized MinimalRebuildCache syncReadDiskCache(String moduleName,
+ Map<String, String> bindingProperties) {
+ return Futures.getUnchecked(enqueueAsyncReadDiskCache(moduleName, bindingProperties));
+ }
+
+ private File computeMinimalRebuildCacheFile(String moduleName,
+ Map<String, String> bindingProperties) {
+ return new File(minimalRebuildCacheDir,
+ computeMinimalRebuildCacheName(moduleName, bindingProperties));
+ }
+
+ private String computeMinimalRebuildCacheName(String moduleName,
+ Map<String, String> bindingProperties) {
+ String currentWorkingDirectory = System.getProperty("user.dir");
+ String compilerVersionHash = CompilerVersion.getHash();
+ String bindingPropertiesString = bindingProperties.toString();
+
+ String consistentHash = StringUtils.toHexString(Md5Utils.getMd5Digest((
+ compilerVersionHash + moduleName + currentWorkingDirectory + bindingPropertiesString)
+ .getBytes()));
+ return REBUILD_CACHE_PREFIX + "-" + consistentHash;
+ }
+
+ private boolean haveCacheDir() {
+ return minimalRebuildCacheDir != null && minimalRebuildCacheDir.isDirectory();
+ }
+
+ private void syncDeleteMemoryCaches() {
+ minimalRebuildCachesByName.invalidateAll();
+ }
+
+ private void syncPutMemoryCache(String moduleName, Map<String, String> bindingProperties,
+ MinimalRebuildCache knownGoodMinimalRebuildCache) {
+ String cacheName = computeMinimalRebuildCacheName(moduleName, bindingProperties);
+ minimalRebuildCachesByName.put(cacheName, knownGoodMinimalRebuildCache);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java b/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java
index 4603ae6..6af0d24 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JsSourceMap.java
@@ -18,21 +18,26 @@
import com.google.gwt.core.ext.linker.impl.JsSourceMapExtractor;
import com.google.gwt.core.ext.soyc.Range;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
/**
* An unmodifiable container of Ranges that map from JavaScript to the Java it came from.
*/
-public class JsSourceMap {
+public class JsSourceMap implements Serializable {
- private final int bytes;
- private final int lines;
+ private int bytes;
+ private int lines;
/**
* Maps JS ranges to Java ranges. The mapping is sparse thus the need for separately tracking
* total bytes and lines.
*/
- private final List<Range> ranges;
+ private List<Range> ranges;
public JsSourceMap(List<Range> ranges, int bytes, int lines) {
this.ranges = ranges;
@@ -48,15 +53,50 @@
return bytes;
}
- public List<Range> getRanges() {
- return ranges;
- }
-
public int getLines() {
return lines;
}
+ public List<Range> getRanges() {
+ return ranges;
+ }
+
public int size() {
return ranges.size();
}
+
+ private void readObject(ObjectInputStream inStream) throws IOException, ClassNotFoundException {
+ bytes = inStream.readInt();
+ lines = inStream.readInt();
+
+ int rangeCount = inStream.readInt();
+ ranges = new ArrayList<Range>(rangeCount);
+ for (int i = 0; i < rangeCount; i++) {
+ int start = inStream.readInt();
+ int end = inStream.readInt();
+ int startLine = inStream.readInt();
+ int startColumn = inStream.readInt();
+ int endLine = inStream.readInt();
+ int endColumn = inStream.readInt();
+ SourceInfo sourceInfo = (SourceInfo) inStream.readObject();
+
+ ranges.add(new Range(start, end, startLine, startColumn, endLine, endColumn, sourceInfo));
+ }
+ }
+
+ private void writeObject(ObjectOutputStream outStream) throws IOException {
+ outStream.writeInt(bytes);
+ outStream.writeInt(lines);
+
+ outStream.writeInt(ranges.size());
+ for (Range range : ranges) {
+ outStream.writeInt(range.getStart());
+ outStream.writeInt(range.getEnd());
+ outStream.writeInt(range.getStartLine());
+ outStream.writeInt(range.getStartColumn());
+ outStream.writeInt(range.getEndLine());
+ outStream.writeInt(range.getEndColumn());
+ outStream.writeObject(range.getSourceInfo());
+ }
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
index 7cc1d9b..a1f049c 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -20,6 +20,7 @@
import com.google.gwt.dev.util.arg.OptionJsInteropMode;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
import com.google.gwt.thirdparty.guava.common.base.Strings;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
@@ -89,6 +90,15 @@
}
@VisibleForTesting
+ public boolean hasSameContent(ImmediateTypeRelations that) {
+ return Objects.equal(this.immediateImplementedInterfacesByClass,
+ that.immediateImplementedInterfacesByClass)
+ && Objects.equal(this.immediateSuperclassesByClass, that.immediateSuperclassesByClass)
+ && Objects.equal(this.immediateSuperInterfacesByInterface,
+ that.immediateSuperInterfacesByInterface);
+ }
+
+ @VisibleForTesting
public Map<String, String> getImmediateSuperclassesByClass() {
return immediateSuperclassesByClass;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java
index ca941cb..77e8f56 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRuntimeTypeReferences.java
@@ -26,12 +26,15 @@
import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultiset;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multiset;
import com.google.gwt.thirdparty.guava.common.collect.Multisets;
+import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -63,7 +66,7 @@
/**
* Sequentially creates int type ids for types.
*/
- public static class IntTypeMapper implements TypeMapper<Integer> {
+ public static class IntTypeMapper implements Serializable, TypeMapper<Integer> {
// NOTE: DO NOT STORE ANY AST REFERENCE. Objects of this type persist across compiles.
private final Map<String, Integer> typeIdByTypeName = Maps.newHashMap();
@@ -81,6 +84,12 @@
this.typeIdByTypeName.putAll(from.typeIdByTypeName);
}
+ @VisibleForTesting
+ public boolean hasSameContent(IntTypeMapper that) {
+ return Objects.equal(this.typeIdByTypeName, that.typeIdByTypeName)
+ && Objects.equal(this.nextAvailableId, that.nextAvailableId);
+ }
+
@Override
public Integer get(JType type) {
return typeIdByTypeName.get(type.getName());
diff --git a/dev/core/src/com/google/gwt/dev/js/JsPersistentPrettyNamer.java b/dev/core/src/com/google/gwt/dev/js/JsPersistentPrettyNamer.java
index f045f11..fb27f6b 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsPersistentPrettyNamer.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsPersistentPrettyNamer.java
@@ -18,11 +18,13 @@
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsScope;
import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
import com.google.gwt.thirdparty.guava.common.collect.HashMultiset;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multiset;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
+import java.io.Serializable;
import java.util.Map;
import java.util.Set;
@@ -35,12 +37,10 @@
/**
* Encapsulates the complete state of this namer so that state can be persisted and reused.
*/
- public static class PersistentPrettyNamerState {
+ public static class PersistentPrettyNamerState implements Serializable {
private Multiset<String> shortIdentCollisionCounts = HashMultiset.create();
-
private Map<String, String> prettyIdentByOriginalIdent = Maps.newHashMap();
-
private Set<String> usedPrettyIdents = Sets.newHashSet();
public void copyFrom(PersistentPrettyNamerState that) {
@@ -52,6 +52,13 @@
this.prettyIdentByOriginalIdent.putAll(that.prettyIdentByOriginalIdent);
this.usedPrettyIdents.addAll(that.usedPrettyIdents);
}
+
+ @VisibleForTesting
+ public boolean hasSameContent(PersistentPrettyNamerState that) {
+ return Objects.equal(this.shortIdentCollisionCounts, that.shortIdentCollisionCounts)
+ && Objects.equal(this.prettyIdentByOriginalIdent, that.prettyIdentByOriginalIdent)
+ && Objects.equal(this.usedPrettyIdents, that.usedPrettyIdents);
+ }
}
@VisibleForTesting
diff --git a/dev/core/src/com/google/gwt/dev/util/CompilerVersion.java b/dev/core/src/com/google/gwt/dev/util/CompilerVersion.java
new file mode 100644
index 0000000..0397cf3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/CompilerVersion.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 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.util;
+
+/**
+ * Utility for uniquely identifying the current compiler version.
+ */
+public class CompilerVersion {
+
+ /**
+ * Calculates and returns a hash to uniquely identify the current compiler version if possible.
+ */
+ public static synchronized String getHash() {
+ return "version-unknown";
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java
new file mode 100644
index 0000000..84ab280
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/MinimalRebuildCacheManagerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.ast.JTypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+import com.google.gwt.thirdparty.guava.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Tests for {@link MinimalRebuildCacheManager}.
+ */
+public class MinimalRebuildCacheManagerTest extends TestCase {
+
+ public void testNoSuchCache() {
+ MinimalRebuildCacheManager minimalRebuildCacheManager =
+ new MinimalRebuildCacheManager(TreeLogger.NULL, Files.createTempDir());
+
+ // Make sure we start with a blank slate.
+ minimalRebuildCacheManager.deleteCaches();
+
+ // Construct and empty cache and also ask the manager to get a cache which does not exist.
+ MinimalRebuildCache emptyCache = new MinimalRebuildCache();
+ MinimalRebuildCache noSuchCache = minimalRebuildCacheManager.getCache("com.google.FooModule",
+ Maps.<String, String> newHashMap());
+
+ // Show that the manager created a new empty cache for the request of a cache that does not
+ // exist.
+ assertFalse(emptyCache == noSuchCache);
+ assertTrue(emptyCache.hasSameContent(noSuchCache));
+ }
+
+ public void testReload() throws InterruptedException {
+ File cacheDir = Files.createTempDir();
+
+ String moduleName = "com.google.FooModule";
+ MinimalRebuildCacheManager minimalRebuildCacheManager =
+ new MinimalRebuildCacheManager(TreeLogger.NULL, cacheDir);
+ Map<String, String> bindingProperites = Maps.<String, String> newHashMap();
+
+ // Make sure we start with a blank slate.
+ minimalRebuildCacheManager.deleteCaches();
+
+ MinimalRebuildCache startingCache =
+ minimalRebuildCacheManager.getCache(moduleName, bindingProperites);
+
+ // Record and compute a bunch of random data.
+ Map<String, Long> currentModifiedBySourcePath = new ImmutableMap.Builder<String, Long>().put(
+ "Foo.java", 0L).put("Bar.java", 0L).put("Baz.java", 0L).build();
+ startingCache.recordDiskSourceResources(currentModifiedBySourcePath);
+ startingCache.recordNestedTypeName("Foo", "Foo");
+ startingCache.setJsForType(TreeLogger.NULL, "Foo", "Some Js for Foo");
+ startingCache.addTypeReference("Bar", "Foo");
+ startingCache.getImmediateTypeRelations().getImmediateSuperclassesByClass().put("Baz", "Foo");
+ startingCache.addTypeReference("Foo", "Foo$Inner");
+ Map<String, Long> laterModifiedBySourcePath = new ImmutableMap.Builder<String, Long>().put(
+ "Foo.java", 9999L).put("Bar.java", 0L).put("Baz.java", 0L).build();
+ startingCache.recordDiskSourceResources(laterModifiedBySourcePath);
+ startingCache.setRootTypeNames(Sets.newHashSet("Foo", "Bar", "Baz"));
+ startingCache.computeReachableTypeNames();
+ startingCache.computeAndClearStaleTypesCache(TreeLogger.NULL,
+ new JTypeOracle(null, startingCache, true));
+
+ // Save and reload the cache.
+ minimalRebuildCacheManager.putCache(moduleName, bindingProperites, startingCache);
+
+ // Shutdown the cache manager and make sure it was successful.
+ assertTrue(minimalRebuildCacheManager.shutdown());
+
+ // Start a new cache manager in the same folder.
+ MinimalRebuildCacheManager reloadedMinimalRebuildCacheManager =
+ new MinimalRebuildCacheManager(TreeLogger.NULL, cacheDir);
+
+ // Reread the previously saved cache.
+ MinimalRebuildCache reloadedCache =
+ reloadedMinimalRebuildCacheManager.syncReadDiskCache(moduleName, bindingProperites);
+
+ // Show that the reread cache is a different instance.
+ assertFalse(startingCache == reloadedCache);
+ // Show that the reread cache contains the same data as the original.
+ assertTrue(startingCache.hasSameContent(reloadedCache));
+ }
+}