This change attempts to fix some compile instabilities. In particular, there are
these issues:

1) Source origin information can vary based on the directory in which something
is checked-out into and built, even if it's identical
2) CompilationUnitArchive was serializing an array of CompiledClass in a non-stable way.
3) CompilationUnitArchive was serializing a list of jsniMethods in a non-stable way.
This version of the patch performs the stabilization sort only when objects are
serialized.

Review by: johnlenz@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10802 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
index 14ecc1a..a06a750 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CachedCompilationUnit.java
@@ -17,10 +17,17 @@
 
 import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
 import com.google.gwt.dev.util.DiskCacheToken;
+import com.google.gwt.dev.util.Util;
 
 import org.eclipse.jdt.core.compiler.CategorizedProblem;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -29,13 +36,13 @@
 public class CachedCompilationUnit extends CompilationUnit {
   private final DiskCacheToken astToken;
   private final long astVersion;
-  private final Collection<CompiledClass> compiledClasses;
+  private transient Collection<CompiledClass> compiledClasses;
   private final ContentId contentId;
   private final Dependencies dependencies;
   private final boolean isError;
   private final boolean isGenerated;
   private final boolean isSuperSource;
-  private final List<JsniMethod> jsniMethods;
+  private transient List<JsniMethod> jsniMethods;
   private final long lastModified;
   private final MethodArgNamesLookup methodArgNamesLookup;
   private final CategorizedProblem[] problems;
@@ -57,6 +64,7 @@
     this.contentId = unit.getContentId();
     this.dependencies = unit.getDependencies();
     this.resourcePath = unit.getResourcePath();
+    this.resourceLocation = Util.stripJarPathPrefix(resourceLocation);
     this.jsniMethods = unit.getJsniMethods();
     this.methodArgNamesLookup = unit.getMethodArgs();
     this.typeName = unit.getTypeName();
@@ -69,7 +77,6 @@
 
     // Override these fields
     this.lastModified = lastModified;
-    this.resourceLocation = resourceLocation;
   }
 
   /**
@@ -88,8 +95,8 @@
     this.compiledClasses = CompiledClass.copyForUnit(unit.getCompiledClasses(), this);
     this.contentId = unit.getContentId();
     this.dependencies = unit.getDependencies();
-    this.resourceLocation = unit.getResourceLocation();
     this.resourcePath = unit.getResourcePath();
+    this.resourceLocation = Util.stripJarPathPrefix(unit.getResourceLocation());
     this.jsniMethods = unit.getJsniMethods();
     this.lastModified = unit.getLastModified();
     this.methodArgNamesLookup = unit.getMethodArgs();
@@ -190,4 +197,39 @@
   long getTypesSerializedVersion() {
     return astVersion;
   }
+
+  @SuppressWarnings("unchecked")
+  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+    ois.defaultReadObject();
+    this.compiledClasses = (Collection<CompiledClass>) ois.readObject();
+    this.jsniMethods = (List<JsniMethod>) ois.readObject();
+  }
+
+  private <T> Collection<T> sort(Collection<T> collection, Comparator<T> comparator) {
+    if (collection == null) {
+      return null;
+    }
+
+    // copy because the source may be unmodifiable or singleton
+    ArrayList<T> copy = new ArrayList<T>(collection);
+
+    Collections.sort(copy, comparator);
+    return copy;
+  }
+  
+  private void writeObject(ObjectOutputStream oos) throws IOException {
+    oos.defaultWriteObject();
+    oos.writeObject(sort(this.compiledClasses, new Comparator<CompiledClass>() {
+      @Override
+      public int compare(CompiledClass o1, CompiledClass o2) {
+        return o1.getSourceName().compareTo(o2.getSourceName());
+      }
+    }));
+    oos.writeObject(sort(this.jsniMethods, new Comparator<JsniMethod>() {
+      @Override
+      public int compare(JsniMethod o1, JsniMethod o2) {
+         return o1.name().compareTo(o2.name());
+      }
+    }));
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
index f253eba..6740b7a 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
@@ -68,7 +68,6 @@
       }
       copyCc.enclosingClass = newRef;
     }
-
     return Collections.unmodifiableCollection(copy);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
index b19eb9a..4a879c0 100644
--- a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
@@ -182,7 +182,7 @@
 
     @Override
     public String optionalFileLocation() {
-      return file.exists() ? file.getAbsolutePath() : null;
+      return file.exists() ? Util.stripJarPathPrefix(file.getAbsolutePath()) : null;
     }
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
index 3e42270..8d102a5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
@@ -18,6 +18,7 @@
 import com.google.gwt.dev.jjs.Correlation.Axis;
 import com.google.gwt.dev.jjs.CorrelationFactory.DummyCorrelationFactory;
 import com.google.gwt.dev.util.StringInterner;
+import com.google.gwt.dev.util.Util;
 
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -117,7 +118,7 @@
   private final int startLine;
 
   private SourceOrigin(String location, int startLine) {
-    this.fileName = StringInterner.get().intern(location);
+    this.fileName = StringInterner.get().intern(Util.stripJarPathPrefix(location));
     this.startLine = startLine;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 0bbd4fe..89ccbed 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -910,6 +910,21 @@
   }
 
   /**
+   * Remove leading file:jar:...!/ prefix from source paths for source located in jars.
+   * @param absolutePath an absolute JAR file URL path
+   * @return the location of the file within the JAR
+   */
+  public static String stripJarPathPrefix(String absolutePath) {
+    if (absolutePath != null) {
+      int bang = absolutePath.lastIndexOf('!');
+      if (bang != -1) {
+        return absolutePath.substring(bang + 2);
+      }
+    }
+    return absolutePath;
+  }
+
+  /**
    * Get a large byte buffer local to this thread. Currently this is set to a
    * 16k buffer, which is small enough to fit into the L2 cache on modern
    * processors. The contents of the returned buffer are undefined. Calling
@@ -1480,5 +1495,4 @@
    */
   private Util() {
   }
-
 }