Add deterministic accessors for constructors

In inner classes, accessors for private constructor are
now generated in a deterministic way.
The visit order of inner classes methods is deterministic
in the InnerAccessorGenerator.
This version sorts with sourceinfo then the method signature.

Bug: 25729191
Change-Id: I0e25efe23d7e2a3bd303e5f71da4a89d07592537
diff --git a/jack-tests/tests/com/android/jack/inner/InnerTests.java b/jack-tests/tests/com/android/jack/inner/InnerTests.java
index 710f271..8634495 100644
--- a/jack-tests/tests/com/android/jack/inner/InnerTests.java
+++ b/jack-tests/tests/com/android/jack/inner/InnerTests.java
@@ -135,6 +135,10 @@
     AbstractTestTools.getTestRootDir("com.android.jack.inner.test026"),
     "com.android.jack.inner.test026.dx.Tests");
 
+  private RuntimeTestInfo TEST028 = new RuntimeTestInfo(
+      AbstractTestTools.getTestRootDir("com.android.jack.inner.test028"),
+      "com.android.jack.inner.test028.dx.Tests");
+
 
   @Test
   @Category(RuntimeRegressionTest.class)
@@ -297,6 +301,12 @@
   }
 
   @Test
+  @Category(RuntimeRegressionTest.class)
+  public void test028() throws Exception {
+    new RuntimeTestHelper(TEST028).compileAndRunTest();
+  }
+
+  @Test
   public void testCheckStructure20() throws Exception {
     //TODO: find out why debug info check fails
     checkStructure("test020");
@@ -341,5 +351,6 @@
     rtTestInfos.add(TEST023);
     rtTestInfos.add(TEST024);
     rtTestInfos.add(TEST026);
+    rtTestInfos.add(TEST028);
   }
 }
diff --git a/jack-tests/tests/com/android/jack/inner/test028/dx/Tests.java b/jack-tests/tests/com/android/jack/inner/test028/dx/Tests.java
new file mode 100644
index 0000000..e3e0421
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/inner/test028/dx/Tests.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.android.jack.inner.test028.dx;
+
+
+import com.android.jack.inner.test028.jack.Outer;
+
+import org.junit.Test;
+
+public class Tests {
+
+  @Test
+  public void test() {
+    Outer outer = new Outer();
+    outer.getInner();
+    outer.resetInner();
+  }
+}
diff --git a/jack-tests/tests/com/android/jack/inner/test028/jack/Outer.java b/jack-tests/tests/com/android/jack/inner/test028/jack/Outer.java
new file mode 100644
index 0000000..2db27c8
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/inner/test028/jack/Outer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.android.jack.inner.test028.jack;
+
+public class Outer {
+
+  private Inner inner = null;
+
+  public final static class Inner {
+
+      private Inner() {
+      }
+
+      private Inner(Inner i) {
+      }
+  }
+
+  public void resetInner() {
+      inner = new Inner();
+  }
+
+  public Inner getInner() {
+      return new Inner(inner);
+  }
+}
diff --git a/jack/src/com/android/jack/Jack.java b/jack/src/com/android/jack/Jack.java
index a618c26..58bb934 100644
--- a/jack/src/com/android/jack/Jack.java
+++ b/jack/src/com/android/jack/Jack.java
@@ -980,7 +980,6 @@
           methodPlan2.append(IncDecRemover.class);
           methodPlan2.append(CompoundAssignmentRemover.class);
           methodPlan2.append(ConcatRemover.class);
-          methodPlan2.append(InnerAccessorGenerator.class);
         }
       }
     }
@@ -988,6 +987,7 @@
     {
       SubPlanBuilder<JDefinedClassOrInterface> typePlan =
           planBuilder.appendSubPlan(ExcludeTypeFromLibAdapter.class);
+      typePlan.append(InnerAccessorGenerator.class);
       SubPlanBuilder<JMethod> methodPlan = typePlan.appendSubPlan(JMethodAdapter.class);
       methodPlan.append(SwitchEnumSupport.class);
     }
diff --git a/jack/src/com/android/jack/transformations/ast/inner/InnerAccessorGenerator.java b/jack/src/com/android/jack/transformations/ast/inner/InnerAccessorGenerator.java
index 16a27e2..c6aae70 100644
--- a/jack/src/com/android/jack/transformations/ast/inner/InnerAccessorGenerator.java
+++ b/jack/src/com/android/jack/transformations/ast/inner/InnerAccessorGenerator.java
@@ -16,11 +16,15 @@
 
 package com.android.jack.transformations.ast.inner;
 
+import com.google.common.collect.Ordering;
+
+import com.android.jack.Jack;
 import com.android.jack.Options;
 import com.android.jack.ir.SideEffectOperation;
 import com.android.jack.ir.ast.JAlloc;
 import com.android.jack.ir.ast.JAsgOperation;
 import com.android.jack.ir.ast.JBinaryOperation;
+import com.android.jack.ir.ast.JClassOrInterface;
 import com.android.jack.ir.ast.JConstructor;
 import com.android.jack.ir.ast.JDefinedClass;
 import com.android.jack.ir.ast.JDefinedClassOrInterface;
@@ -38,6 +42,7 @@
 import com.android.jack.ir.ast.JNullLiteral;
 import com.android.jack.ir.ast.JVisitor;
 import com.android.jack.ir.ast.MethodKind;
+import com.android.jack.ir.formatter.TypePackageAndMethodFormatter;
 import com.android.jack.ir.impl.ResolutionTargetMarker;
 import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.transformations.ast.NewInstanceRemoved;
@@ -53,6 +58,9 @@
 import com.android.sched.schedulable.Transform;
 import com.android.sched.util.config.ThreadConfig;
 
+import java.util.Comparator;
+
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 /**
@@ -70,27 +78,21 @@
     InnerAccessorSchedulingSeparator.SeparatorTag.class},
     remove = {ThreeAddressCodeForm.class, NewInstanceRemoved.class})
 @Constraint(no = {SideEffectOperation.class, JAlloc.class})
-public class InnerAccessorGenerator implements RunnableSchedulable<JMethod> {
+public class InnerAccessorGenerator implements RunnableSchedulable<JDefinedClassOrInterface> {
 
   @Nonnull
   static final String THIS_PARAM_NAME = NamingTools.getNonSourceConflictingName("this");
 
-  @Nonnull
-  private final Filter<JMethod> filter = ThreadConfig.get(Options.METHOD_FILTER);
-
-  private static class Visitor extends JVisitor {
+  private class Visitor extends JVisitor {
 
     @Nonnull
-    private final TransformationRequest tr;
+    protected final Filter<JMethod> filter = ThreadConfig.get(Options.METHOD_FILTER);
 
-    @Nonnull
-    private final JDefinedClassOrInterface currentType;
+    @CheckForNull
+    protected TransformationRequest tr;
 
-    public Visitor(@Nonnull TransformationRequest tr,
-        @Nonnull JDefinedClassOrInterface currentType) {
-      this.tr = tr;
-      this.currentType = currentType;
-    }
+    @CheckForNull
+    private JDefinedClassOrInterface currentType = null;
 
     /**
      * Determines where the accessor must be located in case of super invocation
@@ -102,6 +104,7 @@
         @Nonnull JDefinedClassOrInterface declaringType) {
 
       // if the instance is the super of an enclosing class, we have to retrieve it
+      assert currentType != null;
       JDefinedClass enclosing = (JDefinedClass) currentType;
       while (!isSuperClassOf((JDefinedClass) declaringType, enclosing)) {
         enclosing = (JDefinedClass) enclosing.getEnclosingType();
@@ -169,6 +172,8 @@
       assert field != null;
       JDefinedClassOrInterface accessorClass = getAccessorClass(field.getModifier(),
           field.getEnclosingType());
+      assert currentType != null;
+      assert tr != null;
       if (!accessorClass.isSameType(currentType)) {
         assert accessorClass.getSourceInfo().getFileSourceInfo()
             .equals(currentType.getSourceInfo().getFileSourceInfo());
@@ -244,6 +249,8 @@
               method.getEnclosingType());
         }
 
+        assert currentType != null;
+        assert tr != null;
         if (!accessorClass.isSameType(currentType)) {
           assert accessorClass.getSourceInfo().getFileSourceInfo()
             .equals(currentType.getSourceInfo().getFileSourceInfo());
@@ -297,19 +304,75 @@
       }
       return super.visit(x);
     }
+
+    @Override
+    public boolean visit(@Nonnull JDefinedClassOrInterface type) {
+      currentType = type;
+      tr = new TransformationRequest(type);
+      // Sort types and methods to make this visitor deterministic
+      for (JMethod method : methodOrdering.sortedCopy(type.getMethods())) {
+        if (!method.isNative() && !method.isAbstract()
+            && filter.accept(InnerAccessorGenerator.class, method)) {
+          this.accept(method);
+        }
+      }
+      assert tr != null;
+      tr.commit();
+
+      for (JClassOrInterface innerType : typeOrdering.sortedCopy(type.getMemberTypes())) {
+        if (innerType instanceof JDefinedClassOrInterface) {
+          visit((JDefinedClassOrInterface) innerType);
+        }
+      }
+      return false;
+    }
   }
 
+  @Nonnull
+  TypePackageAndMethodFormatter formatter = Jack.getLookupFormatter();
+
+  private int compareSourceInfo(@Nonnull JNode n1, @Nonnull JNode n2) {
+    return n1.getSourceInfo().getStartLine() - n2.getSourceInfo().getStartLine();
+  }
+
+  @Nonnull
+  private final Ordering<JMethod> methodOrdering = Ordering.from(new Comparator<JMethod>() {
+    @Override
+    public int compare(@Nonnull JMethod m1, @Nonnull JMethod m2) {
+      int compareSourceInfo = compareSourceInfo(m1, m2);
+      if (compareSourceInfo != 0) {
+        return compareSourceInfo;
+      }
+      return formatter.getName(m1).compareTo(formatter.getName(m2));
+    }
+  });
+
+  @Nonnull
+  private final Ordering<JClassOrInterface> typeOrdering =
+      Ordering.from(new Comparator<JClassOrInterface>() {
+        @Override
+        public int compare(@Nonnull JClassOrInterface t1, @Nonnull JClassOrInterface t2) {
+          int compareSourceInfo = compareSourceInfo((JNode) t1, (JNode) t2);
+          if (compareSourceInfo != 0) {
+            return compareSourceInfo;
+          }
+          return formatter.getName(t1).compareTo(formatter.getName(t2));
+        }
+      });
+
   @Override
-  public synchronized void run(@Nonnull JMethod method) throws Exception {
-    if (method.getEnclosingType().isExternal() || method.isNative() || method.isAbstract()
-        || !filter.accept(this.getClass(), method)) {
+  public synchronized void run(@Nonnull JDefinedClassOrInterface type) throws Exception {
+    // Start visit on outer types for a deterministic visit order.
+    if (type.getEnclosingType() != null) {
       return;
     }
 
-    TransformationRequest tr = new TransformationRequest(method);
-    Visitor visitor = new Visitor(tr, method.getEnclosingType());
-    visitor.accept(method);
-    tr.commit();
+    // No need to visit types without inner classes.
+    if (type.getMemberTypes().isEmpty()) {
+      return;
+    }
+
+    new Visitor().accept(type);
   }