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)); + } +}