Clear persistent unit cache when relevant options change.

Change-Id: Id74918bc73a0c223ec4401fb5586d06bb5697718
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
index 5a3e769..dcf2b13 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
@@ -68,7 +68,9 @@
       try {
         File baseCacheDir =
             DiskCachingUtil.computePreferredCacheDir(options.getModuleNames(), logger);
-        UnitCache unitCache = UnitCacheSingleton.get(logger, null, baseCacheDir);
+        UnitCache unitCache = UnitCacheSingleton.get(logger, null, baseCacheDir,
+            new CompilerOptionsImpl(options));
+
         MinimalRebuildCacheManager minimalRebuildCacheManager =
             createMinimalRebuildCacheManager(logger, options, baseCacheDir);
 
@@ -141,7 +143,8 @@
     TreeLogger startupLogger = topLogger.branch(Type.INFO, "Super Dev Mode starting up");
     File baseCacheDir =
         DiskCachingUtil.computePreferredCacheDir(options.getModuleNames(), startupLogger);
-    UnitCache unitCache = UnitCacheSingleton.get(startupLogger, null, baseCacheDir);
+    UnitCache unitCache = UnitCacheSingleton.get(
+        startupLogger, null, baseCacheDir, new CompilerOptionsImpl(options));
     MinimalRebuildCacheManager minimalRebuildCacheManager =
         createMinimalRebuildCacheManager(topLogger, options, baseCacheDir);
     OutboxTable outboxTable =
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
index 9e28b32..8921246 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CompilerOptionsImpl.java
@@ -49,6 +49,10 @@
   private final boolean closureFormattedOutput;
   private final JsOutputOption output;
 
+  CompilerOptionsImpl(Options options) {
+    this(null, null, options);
+  }
+
   CompilerOptionsImpl(CompileDir compileDir, String moduleName, Options options) {
     this.compileDir = compileDir;
     this.incremental = options.isIncrementalCompileEnabled();
diff --git a/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java b/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
index c63e949..03abc05 100644
--- a/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
+++ b/dev/codeserver/javatests/com/google/gwt/dev/codeserver/RecompilerTest.java
@@ -159,6 +159,7 @@
 
   public void testIncrementalRecompile_compileErrorDoesntCorruptMinimalRebuildCache()
       throws UnableToCompleteException, IOException, InterruptedException {
+    String moduleName = "com.foo.SimpleModule";
     PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
     logger.setMaxDetail(TreeLogger.ERROR);
 
@@ -166,7 +167,7 @@
     // Setup options to perform a per-file compile and compile the given module.
     Options options = new Options();
     options.parseArgs(new String[] {
-        "-incremental", "-src", sourcePath.getAbsolutePath(), "com.foo.SimpleModule"});
+        "-incremental", "-src", sourcePath.getAbsolutePath(), moduleName});
 
     // Prepare the basic resources in the test application.
     List<MockResource> originalResources = Lists.newArrayList(simpleModuleResource,
@@ -175,11 +176,12 @@
     writeResourcesTo(originalResources, sourcePath);
 
     File baseCacheDir = Files.createTempDir();
-    UnitCache unitCache = UnitCacheSingleton.get(logger, null, baseCacheDir);
+    UnitCache unitCache = UnitCacheSingleton.get(
+        logger, null, baseCacheDir, new CompilerOptionsImpl(options));
     MinimalRebuildCacheManager minimalRebuildCacheManager =
         new MinimalRebuildCacheManager(logger, baseCacheDir, ImmutableMap.<String, String>of());
     Recompiler recompiler = new Recompiler(OutboxDir.create(Files.createTempDir(), logger), null,
-        "com.foo.SimpleModule", options, unitCache, minimalRebuildCacheManager);
+        moduleName, options, unitCache, minimalRebuildCacheManager);
     Outbox outbox = new Outbox("Transactional Cache", recompiler, options, logger);
     OutboxTable outboxTable = new OutboxTable();
     outboxTable.addOutbox(outbox);
@@ -205,6 +207,7 @@
 
   public void testIncrementalRecompile_modulePropertyEditsWork() throws UnableToCompleteException,
       IOException, InterruptedException {
+    String moduleName = "com.foo.PropertyModule";
     PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
     logger.setMaxDetail(TreeLogger.ERROR);
 
@@ -212,7 +215,7 @@
     // Setup options to perform a per-file compile and compile the given module.
     Options options = new Options();
     options.parseArgs(new String[] {
-        "-incremental", "-src", sourcePath.getAbsolutePath(), "com.foo.PropertyModule"});
+        "-incremental", "-src", sourcePath.getAbsolutePath(), moduleName});
 
     // Prepare the basic resources in the test application.
     List<MockResource> originalResources = Lists.newArrayList(propertyIsFooModuleResource,
@@ -221,11 +224,12 @@
     writeResourcesTo(originalResources, sourcePath);
 
     File baseCacheDir = Files.createTempDir();
-    UnitCache unitCache = UnitCacheSingleton.get(logger, null, baseCacheDir);
+    UnitCache unitCache = UnitCacheSingleton.get(
+        logger, null, baseCacheDir, new CompilerOptionsImpl(options));
     MinimalRebuildCacheManager minimalRebuildCacheManager =
         new MinimalRebuildCacheManager(logger, baseCacheDir, ImmutableMap.<String, String>of());
     Recompiler recompiler = new Recompiler(OutboxDir.create(Files.createTempDir(), logger), null,
-        "com.foo.PropertyModule", options, unitCache, minimalRebuildCacheManager);
+        moduleName, options, unitCache, minimalRebuildCacheManager);
     Outbox outbox = new Outbox("Transactional Cache", recompiler, options, logger);
     OutboxTable outboxTable = new OutboxTable();
     outboxTable.addOutbox(outbox);
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
index 9496222..3e4f0a7 100644
--- a/dev/core/src/com/google/gwt/dev/Compiler.java
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -87,7 +87,7 @@
     }
     // TODO: returns the same UnitCache even if the passed directory changes. Make this less
     // surprising.
-    return UnitCacheSingleton.get(logger, null, persistentUnitCacheDir);
+    return UnitCacheSingleton.get(logger, null, persistentUnitCacheDir, options);
   }
 
   public static void main(String[] args) {
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index 3618977..b7cc46d 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -784,7 +784,7 @@
     topLogger = ui.getTopLogger();
 
     compilerContext = compilerContextBuilder.unitCache(
-        UnitCacheSingleton.get(getTopLogger(), null, persistentCacheDir)).build();
+        UnitCacheSingleton.get(getTopLogger(), null, persistentCacheDir, options)).build();
 
     // Set done callback
     ui.setCallback(DoneEvent.getType(), this);
diff --git a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
index e18160e..8b6d378 100644
--- a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
+++ b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCache.java
@@ -99,8 +99,11 @@
 
   private Semaphore cleanupInProgress = new Semaphore(1);
   private AtomicInteger newUnitsSinceLastCleanup = new AtomicInteger();
+  private final String relevantOptionsHash;
 
-  PersistentUnitCache(final TreeLogger logger, File parentDir) throws UnableToCompleteException {
+  PersistentUnitCache(final TreeLogger logger, File parentDir, String relevantOptionsHash)
+      throws UnableToCompleteException {
+    this.relevantOptionsHash = relevantOptionsHash;
     this.backgroundService = new BackgroundService(logger, parentDir, this);
   }
 
@@ -272,7 +275,8 @@
     BackgroundService(TreeLogger logger, File parentDir, final PersistentUnitCache cacheToLoad)
         throws UnableToCompleteException {
       this.logger = logger;
-      this.cacheDir = new PersistentUnitCacheDir(logger, parentDir);
+      this.cacheDir =
+          new PersistentUnitCacheDir(logger, parentDir, cacheToLoad.relevantOptionsHash);
       this.cacheToLoad = cacheToLoad;
 
       start();
diff --git a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCacheDir.java b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCacheDir.java
index 8b04f19..4c2e648 100644
--- a/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCacheDir.java
+++ b/dev/core/src/com/google/gwt/dev/javac/PersistentUnitCacheDir.java
@@ -50,10 +50,11 @@
   private static final String CACHE_FILE_PREFIX = "gwt-unitCache-";
 
   static final String CURRENT_VERSION_CACHE_FILE_PREFIX =
-      CACHE_FILE_PREFIX + CompilerVersion.getHash() + "-";
+      CACHE_FILE_PREFIX + CompilerVersion.getHash();
 
   private final TreeLogger logger;
   private final File dir;
+  private final String filePrefix;
 
   // Non-null when a a cache file is open for writing. (Always true in normal operation.)
   private OpenFile openFile;
@@ -62,8 +63,10 @@
    * Finds the child directory where the cache files will be stored and opens a new cache
    * file for appending.
    */
-  PersistentUnitCacheDir(TreeLogger logger, File parentDir) throws UnableToCompleteException {
+  PersistentUnitCacheDir(TreeLogger logger, File parentDir, String cacheFilePrefix)
+      throws UnableToCompleteException {
     this.logger = logger;
+    this.filePrefix = CURRENT_VERSION_CACHE_FILE_PREFIX + "-" + cacheFilePrefix + "-";
 
     /*
      * We must canonicalize the path here, otherwise we might set cacheDirectory
@@ -96,7 +99,7 @@
 
     logger.log(TreeLogger.TRACE, "Persistent unit cache dir set to: " + dir.getAbsolutePath());
 
-    openFile = new OpenFile(logger, createEmptyCacheFile(logger, dir));
+    openFile = new OpenFile(logger, createEmptyCacheFile(logger, dir, filePrefix));
   }
 
   /**
@@ -110,7 +113,7 @@
    * Returns the number of files written to the cache directory and closed.
    */
   synchronized int getClosedCacheFileCount() {
-    return selectClosedFiles(listFiles(CURRENT_VERSION_CACHE_FILE_PREFIX)).size();
+    return selectClosedFiles(listFiles(filePrefix)).size();
   }
 
   /**
@@ -124,7 +127,7 @@
           + getPath());
     }
     try {
-      List<File> files = selectClosedFiles(listFiles(CURRENT_VERSION_CACHE_FILE_PREFIX));
+      List<File> files = selectClosedFiles(listFiles(filePrefix));
       for (File cacheFile : files) {
         loadOrDeleteCacheFile(cacheFile, destination);
       }
@@ -162,7 +165,7 @@
       openFile.close(logger);
       openFile = null;
     }
-    openFile = new OpenFile(logger, createEmptyCacheFile(logger, dir));
+    openFile = new OpenFile(logger, createEmptyCacheFile(logger, dir, filePrefix));
   }
 
   /**
@@ -311,14 +314,13 @@
   /**
    * Creates a new, empty file with a name based on the current system time.
    */
-  private static File createEmptyCacheFile(TreeLogger logger, File dir)
+  private static File createEmptyCacheFile(TreeLogger logger, File dir, String filePrefix)
       throws UnableToCompleteException {
     File newFile = null;
     long timestamp = System.currentTimeMillis();
     try {
       do {
-        newFile = new File(dir, CURRENT_VERSION_CACHE_FILE_PREFIX +
-            String.format("%016X", timestamp++));
+        newFile = new File(dir, filePrefix + String.format("%016X", timestamp++));
       } while (!newFile.createNewFile());
     } catch (IOException ex) {
       logger.log(TreeLogger.WARN, "Can't create new cache log file "
diff --git a/dev/core/src/com/google/gwt/dev/javac/UnitCacheSingleton.java b/dev/core/src/com/google/gwt/dev/javac/UnitCacheSingleton.java
index bce16c7..4d01f3e 100644
--- a/dev/core/src/com/google/gwt/dev/javac/UnitCacheSingleton.java
+++ b/dev/core/src/com/google/gwt/dev/javac/UnitCacheSingleton.java
@@ -18,6 +18,11 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 
+import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.util.tools.shared.Md5Utils;
+import com.google.gwt.util.tools.shared.StringUtils;
+
 import java.io.File;
 
 /**
@@ -47,6 +52,15 @@
     instance.clear();
   }
 
+  public static String getRelevantOptionsHash(JJSOptions options) {
+    return StringUtils.toHexString(
+        Md5Utils.getMd5Digest(
+            Joiner.on('-').join(
+                options.getJsInteropExportFilter(),
+                options.shouldGenerateJsInteropExports()
+        ).getBytes()));
+  }
+
   /**
    * If the cache is enabled, instantiates the cache and begins loading units
    * into memory in a background thread. If the cache is not enabled, it clears
@@ -57,8 +71,9 @@
    * <p>
    * The specified cache dir parameter is optional.
    */
-  public static synchronized UnitCache get(TreeLogger logger, File specifiedCacheDir) {
-    return get(logger, specifiedCacheDir, null);
+  public static synchronized UnitCache get(
+      TreeLogger logger, File specifiedCacheDir, JJSOptions options) {
+    return get(logger, specifiedCacheDir, null, options);
   }
 
   /**
@@ -72,7 +87,7 @@
    * Both specified and fallback cache dir parameters are optional.
    */
   public static synchronized UnitCache get(TreeLogger logger, File specifiedCacheDir,
-      File fallbackCacheDir) {
+      File fallbackCacheDir, JJSOptions options) {
     assert logger != null;
     if (instance == null) {
       String propertyCachePath = System.getProperty(GWT_PERSISTENTUNITCACHEDIR);
@@ -95,7 +110,8 @@
 
         if (actualCacheDir != null) {
           try {
-            return instance = new PersistentUnitCache(logger, actualCacheDir);
+            return instance =
+                new PersistentUnitCache(logger, actualCacheDir, getRelevantOptionsHash(options));
           } catch (UnableToCompleteException ignored) {
           }
         }
diff --git a/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java b/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java
index 59f3e81..f68460e 100644
--- a/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/PersistentUnitCacheTest.java
@@ -24,7 +24,6 @@
 import junit.framework.TestCase;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
@@ -54,6 +53,8 @@
 
   private TreeLogger logger;
   private File lastParentDir = null;
+  private String hash1 = "HASH1";
+  private String hash2 = "HASH2";
 
   @Override
   protected void setUp() throws Exception {
@@ -90,7 +91,7 @@
     assertTrue(fileInTheWay.exists());
     fileInTheWay.deleteOnExit();
     try {
-      new PersistentUnitCache(logger, fileInTheWay);
+      new PersistentUnitCache(logger, fileInTheWay, hash1);
       fail("Expected an exception to be thrown");
     } catch (UnableToCompleteException expected) {
     }
@@ -115,7 +116,7 @@
     assertTrue(baseDir.exists());
     assertTrue(baseDir.delete());
     File parentDir = lastParentDir = new File(baseDir, "sHoUlDnOtExi57");
-    new PersistentUnitCache(logger, parentDir);
+    new PersistentUnitCache(logger, parentDir, hash1);
     assertTrue(parentDir.isDirectory());
   }
 
@@ -125,7 +126,7 @@
     File parentDir = lastParentDir = File.createTempFile("persistentCacheTest", "");
     File unitCacheDir = mkCacheDir(parentDir);
 
-    PersistentUnitCache cache = new PersistentUnitCache(logger, parentDir);
+    PersistentUnitCache cache = new PersistentUnitCache(logger, parentDir, hash1);
 
     MockCompilationUnit foo1 = new MockCompilationUnit("com.example.Foo", "Foo: source1");
     cache.add(foo1);
@@ -172,7 +173,7 @@
 
     // Fire up the cache again. It should be pre-populated.
     // Search by type name
-    cache = new PersistentUnitCache(logger, parentDir);
+    cache = new PersistentUnitCache(logger, parentDir, hash1);
     result = cache.find("com/example/Foo.java");
     assertNotNull(result);
     assertEquals("com.example.Foo", result.getTypeName());
@@ -204,7 +205,7 @@
     assertNumCacheFiles(unitCacheDir, 1);
 
     // Fire up the cache again. (Creates a second file in the background.)
-    cache = new PersistentUnitCache(logger, parentDir);
+    cache = new PersistentUnitCache(logger, parentDir, hash1);
 
     // keep making more files
     MockCompilationUnit lastUnit = null;
@@ -221,7 +222,7 @@
     }
 
     // One last check, we should load the last unit added to the cache.
-    cache = new PersistentUnitCache(logger, parentDir);
+    cache = new PersistentUnitCache(logger, parentDir, hash1);
     result = cache.find(lastUnit.getContentId());
     assertNotNull(result);
     assertEquals("com.example.Foo", result.getTypeName());
@@ -262,13 +263,55 @@
     assertNumCacheFiles(unitCacheDir, 1);
 
     // Fire up the cache on this one coalesced file.
-    cache = new PersistentUnitCache(logger, parentDir);
+    cache = new PersistentUnitCache(logger, parentDir, hash1);
 
     // Verify that we can still find the content that was coalesced.
     assertNotNull(cache.find("com/example/Foo.java"));
     assertNotNull(cache.find("com/example/Bar.java"));
     assertNotNull(cache.find("com/example/Baz.java"));
     assertNotNull(cache.find("com/example/Qux.java"));
+
+    cache.shutdown();
+
+    // There should be a single file in the cache dir.
+    assertNumCacheFiles(unitCacheDir, 1);
+
+    cache = new PersistentUnitCache(logger, parentDir, hash2);
+    // A different hash implies a new cache.
+    assertNull(cache.find("com/example/Foo.java"));
+    assertNull(cache.find("com/example/Bar.java"));
+    assertNull(cache.find("com/example/Baz.java"));
+    assertNull(cache.find("com/example/Qux.java"));
+
+    cache.internalAdd(
+        new MockCompilationUnit("com.example.Hash2", "Foo Source")).get();
+
+    assertNotNull(cache.find("com/example/Hash2.java"));
+
+    cache.shutdown();
+
+    // There should be a single file in the cache dir.
+    assertNumCacheFiles(unitCacheDir, 2);
+
+    cache = new PersistentUnitCache(logger, parentDir, hash1);
+    // Verify that we can still find the content with the original hash.
+    assertNotNull(cache.find("com/example/Foo.java"));
+    assertNotNull(cache.find("com/example/Bar.java"));
+    assertNotNull(cache.find("com/example/Baz.java"));
+    assertNotNull(cache.find("com/example/Qux.java"));
+    assertNull(cache.find("com/example/Hash2.java"));
+
+    cache.shutdown();
+
+    cache = new PersistentUnitCache(logger, parentDir, hash2);
+    // A different hash implies a new cache.
+    assertNull(cache.find("com/example/Foo.java"));
+    assertNull(cache.find("com/example/Bar.java"));
+    assertNull(cache.find("com/example/Baz.java"));
+    assertNull(cache.find("com/example/Qux.java"));
+    assertNotNull(cache.find("com/example/Hash2.java"));
+
+    cache.shutdown();
   }
 
   private void assertNumCacheFiles(File unitCacheDir, int expected) {
@@ -290,7 +333,7 @@
   }
 
   private void checkInvalidObjectInCache(Object toSerialize) throws IOException,
-      FileNotFoundException, UnableToCompleteException, InterruptedException, ExecutionException {
+      UnableToCompleteException, InterruptedException, ExecutionException {
     File parentDir = lastParentDir = File.createTempFile("PersistentUnitTest-CNF", "");
     File unitCacheDir = mkCacheDir(parentDir);
 
@@ -299,14 +342,14 @@
      * object in it.
      */
     File errorFile = new File(unitCacheDir,
-        PersistentUnitCacheDir.CURRENT_VERSION_CACHE_FILE_PREFIX + "12345");
+        PersistentUnitCacheDir.CURRENT_VERSION_CACHE_FILE_PREFIX + "-"  + hash1 + "-" + "12345");
     ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(errorFile));
     os.writeObject(toSerialize);
     os.close();
 
     assertNumCacheFiles(unitCacheDir, 1);
 
-    PersistentUnitCache cache = new PersistentUnitCache(logger, parentDir);
+    PersistentUnitCache cache = new PersistentUnitCache(logger, parentDir, hash1);
     cache.cleanup(logger);
     cache.shutdown();