Fix sorting of place types in generated getTokenAndPrefix

The use of a TreeMap with a Comparator that falls back to type name for
unrelated types can lead to related types ending up in different branches,
and the generated getToken returning the wrong token (for a supertype,
when a more specific types exists).

Sorting by "flattened supertype hierarchy size" works here because:
 * Place is a class (rather than an interface),
 * the compared types are guaranteed to extend the Place type, and
 * the input to the getTokenAndPrefix is a Place too.

Original idea by larsaaslin@gmail.com.

Bug: issue 8086
Change-Id: I9295c9546839aa4cf87041c3f508acd575ffccfb
diff --git a/user/src/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparator.java b/user/src/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparator.java
index 2a993cd..f51165e 100644
--- a/user/src/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparator.java
+++ b/user/src/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparator.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 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
@@ -16,6 +16,7 @@
 package com.google.gwt.place.rebind;
 
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.thirdparty.guava.common.collect.ComparisonChain;
 
 import java.util.Comparator;
 
@@ -23,18 +24,15 @@
  * Sorts types from most derived to least derived, falling back to alphabetical
  * sorting.
  */
-public class MostToLeastDerivedPlaceTypeComparator implements
-    Comparator<JClassType> {
+class MostToLeastDerivedPlaceTypeComparator implements Comparator<JClassType> {
   public int compare(JClassType o1, JClassType o2) {
     if (o1.equals(o2)) {
       return 0;
     }
-    if (o1.isAssignableFrom(o2)) {
-      return 1;
-    }
-    if (o1.isAssignableTo(o2)) {
-      return -1;
-    }
-    return o1.getQualifiedSourceName().compareTo(o2.getQualifiedSourceName());
+    return ComparisonChain.start()
+        // We want the longest first, so we put o2 before o1 in the comparison chain.
+        .compare(o2.getFlattenedSupertypeHierarchy().size(), o1.getFlattenedSupertypeHierarchy().size())
+        .compare(o1.getQualifiedSourceName(), o2.getQualifiedSourceName())
+        .result();
   }
-}
\ No newline at end of file
+}
diff --git a/user/test/com/google/gwt/place/impl/PlaceHistoryMapperGeneratorTest.java b/user/test/com/google/gwt/place/impl/PlaceHistoryMapperGeneratorTest.java
index 245ab8a..eb1b557 100644
--- a/user/test/com/google/gwt/place/impl/PlaceHistoryMapperGeneratorTest.java
+++ b/user/test/com/google/gwt/place/impl/PlaceHistoryMapperGeneratorTest.java
@@ -49,6 +49,11 @@
       PlaceHistoryMapperWithFactory<TokenizerFactory> {
   };
 
+  // Note: tokenizer order here is important for the test!
+  @WithTokenizers({Tokenizer2.class, Place1.Tokenizer.class, Tokenizer3.class})
+  interface SortOrder extends PlaceHistoryMapper {
+  }
+
   /**
    * The goal is only to test that the generator doesn't fail (but doesn't
    * generate anything either).
@@ -116,6 +121,16 @@
     assertNull(subject.getPlace(null));
   }
 
+  /**
+   * See https://code.google.com/p/google-web-toolkit/issues/detail?id=8036
+   */
+  public void testSortOrder() {
+    PlaceHistoryMapper subject = GWT.create(SortOrder.class);
+    assertEquals(Place1.Tokenizer.PREFIX + ":" + place1.content, subject.getToken(place1));
+    assertEquals("Place2:" + place2.content, subject.getToken(place2));
+    assertEquals("Place3:" + place3.content, subject.getToken(place3));
+  }
+
   // CHECKSTYLE_OFF
   private void doTest(AbstractPlaceHistoryMapper<?> subject,
       TokenizerFactory factory) {
diff --git a/user/test/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparatorTest.java b/user/test/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparatorTest.java
index 6de702d..11a2e8c 100644
--- a/user/test/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparatorTest.java
+++ b/user/test/com/google/gwt/place/rebind/MostToLeastDerivedPlaceTypeComparatorTest.java
@@ -37,8 +37,10 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -138,14 +140,37 @@
   }
 
   public void testFallbackToClassName() {
-    // Array sorted from least derived to most derived. In each pair of adjacent
-    // values, neither place extends the other.
-    JClassType[] places = {place1, place2, place3, place4, place5};
-    for (int i = 0; i < places.length - 1; i++) {
-      assertEquals(-1, (int) Math.signum(comparator.compare(places[i],
-          places[i + 1])));
-      assertEquals(1, (int) Math.signum(comparator.compare(places[i + 1],
-          places[i])));
+    JClassType[][] places = {
+        {place3, place4}, // place3 and place4 both extend directly from place1
+        {place1, place2}, // place1 and place2 both extend directly from place
+    };
+    for (JClassType[] pair : places) {
+      assertEquals(-1, (int) Math.signum(comparator.compare(pair[0], pair[1])));
+      assertEquals(1, (int) Math.signum(comparator.compare(pair[1], pair[0])));
     }
   }
+
+  public void testCollectionSort() {
+    List<JClassType> actual = Arrays.asList(place, place1, place3, place5);
+    Collections.sort(actual, comparator);
+    assertEquals(Arrays.asList(place5, place3, place1, place), actual);
+
+    actual = Arrays.asList(place5, place3, place1, place);
+    Collections.sort(actual, comparator);
+    assertEquals(Arrays.asList(place5, place3, place1, place), actual);
+
+    actual = Arrays.asList(place, place1, place2, place3, place4, place5);
+    Collections.sort(actual, comparator);
+    assertEquals(Arrays.asList(place5, place3, place4, place1, place2, place), actual);
+
+    actual = Arrays.asList(place5, place4, place3, place2, place1, place);
+    Collections.sort(actual, comparator);
+    assertEquals(Arrays.asList(place5, place3, place4, place1, place2, place), actual);
+
+    // This is equivalent to the test-case from issue 8036
+    // https://code.google.com/p/google-web-toolkit/issues/detail?id=8036
+    actual = Arrays.asList(place2, place1, place3);
+    Collections.sort(actual, comparator);
+    assertEquals(Arrays.asList(place3, place1, place2), actual);
+  }
 }