8029674: (reflect) getMethods returns default methods that are not members of the class
authorjfranck
Tue, 10 Jun 2014 14:40:20 +0200
changeset 24867 c3514175b6cd
parent 24866 fcd7bd73d928
child 24868 89d9bd9eba96
8029674: (reflect) getMethods returns default methods that are not members of the class Summary: Filter out methods that have a more specific default method from result Reviewed-by: darcy, dlsmith
jdk/src/share/classes/java/lang/Class.java
jdk/test/java/lang/reflect/DefaultMethodMembers/FilterNotMostSpecific.java
--- a/jdk/src/share/classes/java/lang/Class.java	Wed Jun 11 16:33:23 2014 +0800
+++ b/jdk/src/share/classes/java/lang/Class.java	Tue Jun 10 14:40:20 2014 +0200
@@ -2696,12 +2696,26 @@
     }
 
     static class MethodArray {
+        // Don't add or remove methods except by add() or remove() calls.
         private Method[] methods;
         private int length;
+        private int defaults;
 
         MethodArray() {
-            methods = new Method[20];
+            this(20);
+        }
+
+        MethodArray(int initialSize) {
+            if (initialSize < 2)
+                throw new IllegalArgumentException("Size should be 2 or more");
+
+            methods = new Method[initialSize];
             length = 0;
+            defaults = 0;
+        }
+
+        boolean hasDefaults() {
+            return defaults != 0;
         }
 
         void add(Method m) {
@@ -2709,6 +2723,9 @@
                 methods = Arrays.copyOf(methods, 2 * methods.length);
             }
             methods[length++] = m;
+
+            if (m != null && m.isDefault())
+                defaults++;
         }
 
         void addAll(Method[] ma) {
@@ -2742,7 +2759,10 @@
             }
         }
 
-        void addAllNonStatic(Method[] methods) {
+        /* Add Methods declared in an interface to this MethodArray.
+         * Static methods declared in interfaces are not inherited.
+         */
+        void addInterfaceMethods(Method[] methods) {
             for (Method candidate : methods) {
                 if (!Modifier.isStatic(candidate.getModifiers())) {
                     add(candidate);
@@ -2758,19 +2778,35 @@
             return methods[i];
         }
 
-        void removeByNameAndSignature(Method toRemove) {
+        Method getFirst() {
+            for (Method m : methods)
+                if (m != null)
+                    return m;
+            return null;
+        }
+
+        void removeByNameAndDescriptor(Method toRemove) {
             for (int i = 0; i < length; i++) {
                 Method m = methods[i];
-                if (m != null &&
-                    m.getReturnType() == toRemove.getReturnType() &&
-                    m.getName() == toRemove.getName() &&
-                    arrayContentsEq(m.getParameterTypes(),
-                                    toRemove.getParameterTypes())) {
-                    methods[i] = null;
+                if (m != null && matchesNameAndDescriptor(m, toRemove)) {
+                    remove(i);
                 }
             }
         }
 
+        private void remove(int i) {
+            if (methods[i] != null && methods[i].isDefault())
+                defaults--;
+            methods[i] = null;
+        }
+
+        private boolean matchesNameAndDescriptor(Method m1, Method m2) {
+            return m1.getReturnType() == m2.getReturnType() &&
+                   m1.getName() == m2.getName() && // name is guaranteed to be interned
+                   arrayContentsEq(m1.getParameterTypes(),
+                           m2.getParameterTypes());
+        }
+
         void compactAndTrim() {
             int newPos = 0;
             // Get rid of null slots
@@ -2788,9 +2824,48 @@
             }
         }
 
+        /* Removes all Methods from this MethodArray that have a more specific
+         * default Method in this MethodArray.
+         *
+         * Users of MethodArray are responsible for pruning Methods that have
+         * a more specific <em>concrete</em> Method.
+         */
+        void removeLessSpecifics() {
+            if (!hasDefaults())
+                return;
+
+            for (int i = 0; i < length; i++) {
+                Method m = get(i);
+                if  (m == null || !m.isDefault())
+                    continue;
+
+                for (int j  = 0; j < length; j++) {
+                    if (i == j)
+                        continue;
+
+                    Method candidate = get(j);
+                    if (candidate == null)
+                        continue;
+
+                    if (!matchesNameAndDescriptor(m, candidate))
+                        continue;
+
+                    if (hasMoreSpecificClass(m, candidate))
+                        remove(j);
+                }
+            }
+        }
+
         Method[] getArray() {
             return methods;
         }
+
+        // Returns true if m1 is more specific than m2
+        static boolean hasMoreSpecificClass(Method m1, Method m2) {
+            Class<?> m1Class = m1.getDeclaringClass();
+            Class<?> m2Class = m2.getDeclaringClass();
+            return m1Class != m2Class && m2Class.isAssignableFrom(m1Class);
+        }
     }
 
 
@@ -2819,7 +2894,7 @@
         // the end.
         MethodArray inheritedMethods = new MethodArray();
         for (Class<?> i : getInterfaces()) {
-            inheritedMethods.addAllNonStatic(i.privateGetPublicMethods());
+            inheritedMethods.addInterfaceMethods(i.privateGetPublicMethods());
         }
         if (!isInterface()) {
             Class<?> c = getSuperclass();
@@ -2830,8 +2905,10 @@
                 // interface methods
                 for (int i = 0; i < supers.length(); i++) {
                     Method m = supers.get(i);
-                    if (m != null && !Modifier.isAbstract(m.getModifiers())) {
-                        inheritedMethods.removeByNameAndSignature(m);
+                    if (m != null &&
+                            !Modifier.isAbstract(m.getModifiers()) &&
+                            !m.isDefault()) {
+                        inheritedMethods.removeByNameAndDescriptor(m);
                     }
                 }
                 // Insert superclass's inherited methods before
@@ -2844,9 +2921,10 @@
         // Filter out all local methods from inherited ones
         for (int i = 0; i < methods.length(); i++) {
             Method m = methods.get(i);
-            inheritedMethods.removeByNameAndSignature(m);
+            inheritedMethods.removeByNameAndDescriptor(m);
         }
         methods.addAllIfNotPresent(inheritedMethods);
+        methods.removeLessSpecifics();
         methods.compactAndTrim();
         res = methods.getArray();
         if (rd != null) {
@@ -2919,8 +2997,21 @@
         return (res == null ? res : getReflectionFactory().copyMethod(res));
     }
 
-
     private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
+        MethodArray interfaceCandidates = new MethodArray(2);
+        Method res =  privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
+        if (res != null)
+            return res;
+
+        // Not found on class or superclass directly
+        interfaceCandidates.removeLessSpecifics();
+        return interfaceCandidates.getFirst(); // may be null
+    }
+
+    private Method privateGetMethodRecursive(String name,
+            Class<?>[] parameterTypes,
+            boolean includeStaticMethods,
+            MethodArray allInterfaceCandidates) {
         // Note: the intent is that the search algorithm this routine
         // uses be equivalent to the ordering imposed by
         // privateGetPublicMethods(). It fetches only the declared
@@ -2928,6 +3019,14 @@
         // number of Method objects which have to be created for the
         // common case where the method being requested is declared in
         // the class which is being queried.
+        //
+        // Due to default methods, unless a method is found on a superclass,
+        // methods declared in any superinterface needs to be considered.
+        // Collect all candidates declared in superinterfaces in {@code
+        // allInterfaceCandidates} and select the most specific if no match on
+        // a superclass is found.
+
+        // Must _not_ return root methods
         Method res;
         // Search declared public methods
         if ((res = searchMethods(privateGetDeclaredMethods(true),
@@ -2949,7 +3048,7 @@
         Class<?>[] interfaces = getInterfaces();
         for (Class<?> c : interfaces)
             if ((res = c.getMethod0(name, parameterTypes, false)) != null)
-                return res;
+                allInterfaceCandidates.add(res);
         // Not found
         return null;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/reflect/DefaultMethodMembers/FilterNotMostSpecific.java	Tue Jun 10 14:40:20 2014 +0200
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8029674
+ * @summary Verify that the right interface methods are returned by
+ *          Class.getMethod() and Class.getMethods()
+ * @run testng FilterNotMostSpecific
+ */
+
+import java.lang.reflect.*;
+import java.lang.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+public class FilterNotMostSpecific {
+
+    @Test(dataProvider="getCases")
+    public void testGetMethod(Class<?> iface) {
+        boolean match = false;
+        MethodDesc[] expectedMethods = iface.getAnnotationsByType(MethodDesc.class);
+
+        for (MethodDesc expected : expectedMethods) {
+            if (expected.isGetMethodReturn()) {
+                try {
+                    Method m = iface.getMethod(expected.name());
+                    if (!assertMatch(expected, m))
+                        fail(failMsg(expected, m, iface));
+                    else
+                        match = true;
+                } catch (NoSuchMethodException e) {
+                    fail("expected: " + toMethodString(expected), e);
+                }
+            }
+        }
+        assert(match);
+    }
+
+    @Test(dataProvider="getCases")
+    public void testGetMethods(Class<?> iface) {
+        List<Method> foundMethods = filterObjectMethods(iface.getMethods());
+        MethodDesc[] expectedMethods = iface.getAnnotationsByType(MethodDesc.class);
+        Set<Method> used = new HashSet<>();
+
+        for (MethodDesc expected : expectedMethods) {
+            boolean found = false;
+
+            for (Method m : foundMethods) {
+                if (used.contains(m))
+                    continue;
+
+                if(expected.name().equals(m.getName()) &&
+                    expected.declaringClass() ==m.getDeclaringClass()) {
+
+                    found = true;
+                    assertMatch(expected, m);
+                    used.add(m);
+                    break;
+                }
+            }
+            if (! found)
+                fail("On: "+ iface +"\nDid not find " + toMethodString(expected) + " among " + foundMethods);
+        }
+        assertEquals(foundMethods.size(), expectedMethods.length,
+                "\non: " + iface +
+                "\nexpected: " + toMethodStrings(expectedMethods) +
+                "\nfound: " + foundMethods + "\n");
+    }
+
+    private boolean assertMatch(MethodDesc expected, Method m) {
+        if (!expected.name().equals(m.getName()))
+            return false;
+        if (expected.declaringClass() != m.getDeclaringClass())
+            return false;
+
+        if (expected.kind() == MethodKind.ABSTRACT)
+            assertTrue(Modifier.isAbstract(m.getModifiers()), m + " should be ABSTRACT");
+        else if (expected.kind() == MethodKind.CONCRETE)
+            assertTrue(!Modifier.isAbstract(m.getModifiers()) && !m.isDefault(), m + " should be CONCRETE");
+        else if (expected.kind() == MethodKind.DEFAULT)
+            assertTrue(m.isDefault(), m + " should be DEFAULT");
+
+        return true;
+    }
+
+    private String failMsg(MethodDesc expected, Method m, Class<?> iface) {
+        return "\nOn interface: " + iface +
+            "\nexpected: " + toMethodString(expected) +
+            "\nfound: " + m;
+    }
+
+    private static List<Method> filterObjectMethods(Method[] in) {
+        return Arrays.stream(in).
+            filter(m -> (m.getDeclaringClass() != java.lang.Object.class)).
+            collect(Collectors.toList());
+    }
+
+    private String toMethodString(MethodDesc m) {
+        return m.declaringClass().getSimpleName().toString() + "." +
+            m.name() + "()";
+    }
+
+    private List<String> toMethodStrings(MethodDesc[] m) {
+        return Arrays.stream(m).
+            map(this::toMethodString)
+            .collect(Collectors.toList());
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Repeatable(MethodDescs.class)
+    public @interface MethodDesc {
+        String name();
+        Class<?> declaringClass();
+        MethodKind kind() default MethodKind.ABSTRACT;
+        boolean isGetMethodReturn() default false;
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface MethodDescs {
+        MethodDesc[] value();
+    }
+
+    public static enum MethodKind {
+        ABSTRACT,
+        CONCRETE,
+        DEFAULT,
+    }
+    // base interfaces
+    interface I { void nonDefault(); }
+    interface J extends I { void nonDefault(); }
+
+    interface Jprim extends I {}
+    interface Jbis extends Jprim { void nonDefault(); }
+
+    // interesting cases
+
+    @MethodDesc(name="nonDefault", declaringClass=Jbis.class,
+            isGetMethodReturn=true)
+    interface P1 extends Jbis {}
+
+    @MethodDesc(name="nonDefault", declaringClass=Jbis.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=I.class)
+    interface P2 extends Jbis, Jprim {}
+
+    @MethodDesc(name="nonDefault", declaringClass=Jbis.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=I.class)
+    interface P3 extends Jbis, Jprim, I {}
+
+    @MethodDesc(name="nonDefault", declaringClass=I.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=J.class)
+    interface P4 extends I, J {}
+
+    @MethodDesc(name="nonDefault", declaringClass=J.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=I.class)
+    interface P5 extends J, I {}
+
+    @MethodDesc(name="nonDefault", declaringClass=J.class,
+            isGetMethodReturn=true)
+    interface K1 extends J {}
+
+    @MethodDesc(name="nonDefault", declaringClass=K1M.class,
+            isGetMethodReturn=true)
+    interface K1M extends J { void nonDefault(); }
+
+    @MethodDesc(name="nonDefault", declaringClass=I.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=J.class)
+    interface K2 extends I, J {}
+
+    @MethodDesc(name="nonDefault", declaringClass=J.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=I.class)
+    interface K2O extends J, I {}
+
+    @MethodDesc(name="nonDefault", declaringClass=K2M.class,
+            isGetMethodReturn=true)
+    interface K2M extends J, I { void nonDefault(); }
+
+    // base interfaces default methods
+    interface L { default void isDefault() {} void nonDefault(); }
+    interface M extends L { default void isDefault() {} void nonDefault(); }
+
+    // test cases default methods
+
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=M.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface N1 extends M {}
+
+    @MethodDesc(name="isDefault", declaringClass=N1D.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    interface N1D extends M { default void isDefault() {}}
+
+    @MethodDesc(name="nonDefault", declaringClass=N1N.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=M.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface N1N extends M { void nonDefault(); }
+
+    @MethodDesc(name="isDefault", declaringClass=N1DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N1DN.class,
+            isGetMethodReturn=true)
+    interface N1DN extends M { default void isDefault() {} void nonDefault(); }
+
+    @MethodDesc(name="isDefault", declaringClass=M.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    interface N2 extends M, L {}
+
+    @MethodDesc(name="isDefault", declaringClass=M.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    interface N22 extends L, M {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2D.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    interface N2D extends M, L { default void isDefault() {}}
+
+    @MethodDesc(name="isDefault", declaringClass=M.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2N.class,
+            isGetMethodReturn=true)
+    interface N2N extends M, L { void nonDefault(); }
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class,
+            isGetMethodReturn=true)
+    interface N2DN extends M, L { default void isDefault() {} void nonDefault(); }
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface O1 extends L, M, N2DN {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface O2 extends M, N2DN, L {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class,
+            isGetMethodReturn=true)
+    interface O3 extends N2DN, L, M {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    abstract class C1 implements L, M, N2DN {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    abstract class C2 implements M, N2DN, L {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class,
+            isGetMethodReturn=true)
+    abstract class C3 implements N2DN, L, M {}
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=C4.class,
+            kind=MethodKind.CONCRETE, isGetMethodReturn=true)
+    class C4 implements L, M, N2DN { public void nonDefault() {} }
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=C5.class,
+            kind=MethodKind.CONCRETE, isGetMethodReturn=true)
+    class C5 implements M, N2DN, L { public void nonDefault() {} }
+
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=C6.class,
+            kind=MethodKind.CONCRETE, isGetMethodReturn=true)
+    class C6 implements N2DN, L, M { public void nonDefault() {} }
+
+    // reabstraction
+
+    @MethodDesc(name="isDefault", declaringClass=R1.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface R1 extends L, M, N2DN { void isDefault(); }
+
+    @MethodDesc(name="isDefault", declaringClass=R2.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface R2 extends M, N2DN, L { void isDefault(); }
+
+    @MethodDesc(name="isDefault", declaringClass=R3.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class,
+            isGetMethodReturn=true)
+    interface R3 extends N2DN, L, M { void isDefault(); }
+
+    // this one is strange but logical, getMethod finds N2DN first, which is
+    // default but not the most specific
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=R1.class)
+    @MethodDesc(name="nonDefault", declaringClass=L.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface R4 extends L, M, N2DN, R1 {}
+
+    // this one is strange but logical, getMethod finds N2DN first, which is
+    // default but not the most specific
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=R2.class)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface R5 extends M, N2DN, R2, L {}
+
+    // this one is strange but logical, getMethod finds N2DN first, which is
+    // default but not the most specific
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=R3.class)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class,
+            isGetMethodReturn=true)
+    interface R6 extends N2DN, R3, L, M {}
+
+    // the following three finds the "right" one
+    @MethodDesc(name="isDefault", declaringClass=R1.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="nonDefault", declaringClass=L.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface R7 extends L, M, R1, N2DN {}
+
+    @MethodDesc(name="isDefault", declaringClass=R2.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class)
+    interface R8 extends M, R2, N2DN, L {}
+
+    @MethodDesc(name="isDefault", declaringClass=R3.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="isDefault", declaringClass=N2DN.class,
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="nonDefault", declaringClass=L.class)
+    @MethodDesc(name="nonDefault", declaringClass=M.class)
+    @MethodDesc(name="nonDefault", declaringClass=N2DN.class,
+            isGetMethodReturn=true)
+    interface R9 extends R3, N2DN, L, M {}
+
+    // More reabstraction
+    interface Z1 { void z(); }
+    interface Z2 extends Z1 { default void z() {} }
+
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            isGetMethodReturn=true, kind=MethodKind.DEFAULT)
+    interface Z31 extends Z1, Z2 {}
+
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface Z32 extends Z2, Z1 {}
+
+    interface Z3 extends Z2, Z1 { void z(); }
+
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="z", declaringClass=Z3.class)
+    interface Z41 extends Z1, Z2, Z3 { }
+
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="z", declaringClass=Z3.class)
+    interface Z42 extends Z2, Z3, Z1 { }
+
+    @MethodDesc(name="z", declaringClass=Z3.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT)
+    interface Z43 extends Z3, Z1, Z2 { }
+
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="z", declaringClass=Z3.class)
+    abstract class ZC41 implements Z1, Z2, Z3 { }
+
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="z", declaringClass=Z3.class)
+    abstract class ZC42 implements Z2, Z3, Z1 { }
+
+    @MethodDesc(name="z", declaringClass=Z3.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="z", declaringClass=Z2.class,
+            kind=MethodKind.DEFAULT)
+    abstract class ZC43 implements Z3, Z1, Z2 { }
+
+    // More reabstraction + concretization
+    interface X1 { default void x() {} }
+    interface X2 extends X1 { void x(); }
+
+    @MethodDesc(name="x", declaringClass=X1.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    @MethodDesc(name="x", declaringClass=X2.class)
+    interface X31 extends X1, X2 {}
+
+    @MethodDesc(name="x", declaringClass=X2.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="x", declaringClass=X1.class,
+            kind=MethodKind.DEFAULT)
+    interface X32 extends X2, X1 {}
+
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface X3 extends X2, X1 { default void x() {} }
+
+    // order shouldn't matter here
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface X41 extends X1, X2, X3 { }
+
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface X42 extends X2, X3, X1 { }
+
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    interface X43 extends X3, X1, X2 { }
+
+    // order shouldn't matter here
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    abstract class XC41 implements X1, X2, X3 { }
+
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    abstract class XC42 implements X2, X3, X1 { }
+
+    @MethodDesc(name="x", declaringClass=X3.class,
+            kind=MethodKind.DEFAULT, isGetMethodReturn=true)
+    abstract class XC43 implements X3, X1, X2 { }
+
+    interface K extends I, J { void nonDefault(); }
+
+    @MethodDesc(name="nonDefault", declaringClass=I.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=J.class)
+    @MethodDesc(name="nonDefault", declaringClass=K.class)
+    abstract class ZZ1 implements I, J, K {}
+
+    @MethodDesc(name="nonDefault", declaringClass=I.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=J.class)
+    @MethodDesc(name="nonDefault", declaringClass=K.class)
+    abstract class ZZ2 extends ZZ1 implements K, I, J {}
+
+    @MethodDesc(name="nonDefault", declaringClass=I.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="nonDefault", declaringClass=J.class)
+    @MethodDesc(name="nonDefault", declaringClass=K.class)
+    abstract class ZZ3 extends ZZ2 implements J, K, I {}
+
+    // bridges
+    interface B1A { Object m(); }
+    interface B1B extends B1A { Map m(); }
+
+    @MethodDesc(name="m", declaringClass=B1C.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="m", declaringClass=B1C.class,
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="m", declaringClass=B1C.class,
+            kind=MethodKind.DEFAULT)
+    interface B1C extends B1B { HashMap m(); }
+
+    @MethodDesc(name="m", declaringClass=B2.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="m", declaringClass=B2.class,
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="m", declaringClass=B2.class,
+            kind=MethodKind.DEFAULT)
+    interface B2 extends B1C { HashMap m(); }
+
+    @MethodDesc(name="m", declaringClass=B2.class, //HahsMap
+            isGetMethodReturn=true)
+    @MethodDesc(name="m", declaringClass=B2.class, //Map
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="m", declaringClass=B2.class, //Object
+            kind=MethodKind.DEFAULT)
+    interface B3A extends B2, B1A {}
+
+    // this one is funny since HashMap isn't a bridge thus not a default
+    @MethodDesc(name="m", declaringClass=B2.class, //HashMap
+            isGetMethodReturn=true)
+    @MethodDesc(name="m", declaringClass=B2.class, //Map
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="m", declaringClass=B2.class, //Object
+            kind=MethodKind.DEFAULT)
+    @MethodDesc(name="m", declaringClass=B1C.class) //HashMap
+    interface B3B extends B2, B1C {}
+
+    // same name different params type
+    interface A1 { void m(); void m(int i); void m(int i, int j); }
+    interface A2A extends A1 { void m(); void m(int i); void m(int i, int j); }
+    interface A2B extends A1 { void m(); void m(int i); default void m(int i, int j) {} }
+
+    @MethodDesc(name="m", declaringClass=A1.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="m", declaringClass=A1.class)
+    @MethodDesc(name="m", declaringClass=A1.class)
+    @MethodDesc(name="m", declaringClass=A2A.class)
+    @MethodDesc(name="m", declaringClass=A2A.class)
+    @MethodDesc(name="m", declaringClass=A2A.class)
+    interface A3A extends A1, A2A {}
+
+    @MethodDesc(name="m", declaringClass=A1.class,
+            isGetMethodReturn=true)
+    @MethodDesc(name="m", declaringClass=A1.class)
+    @MethodDesc(name="m", declaringClass=A2B.class)
+    @MethodDesc(name="m", declaringClass=A2B.class)
+    @MethodDesc(name="m", declaringClass=A2B.class,
+            kind=MethodKind.DEFAULT)
+    interface A3B extends A1, A2B {}
+
+    @DataProvider
+    public Object[][] getCases() { return CASES; }
+    public static final Class<?>[][] CASES =  {
+        { K1.class },
+        { K1M.class },
+        { K2.class },
+        { K2O.class },
+        { K2M.class },
+
+        { N1.class },
+        { N1D.class },
+        { N1N.class },
+        { N1DN.class },
+
+        { N2.class },
+        { N22.class },
+        { N2D.class },
+        { N2N.class },
+        { N2DN.class },
+
+        { P1.class },
+        { P2.class },
+        { P3.class },
+        { P4.class },
+        { P5.class },
+
+        { O1.class },
+        { O2.class },
+        { O3.class },
+
+        { C1.class },
+        { C2.class },
+        { C3.class },
+
+        { C4.class },
+        { C5.class },
+        { C6.class },
+
+        { R1.class },
+        { R2.class },
+        { R3.class },
+
+        { R4.class },
+        { R5.class },
+        { R6.class },
+
+        { R7.class },
+        { R8.class },
+        { R9.class },
+
+        { Z31.class },
+        { Z32.class },
+
+        { Z41.class },
+        { Z42.class },
+        { Z43.class },
+
+        { ZC41.class },
+        { ZC42.class },
+        { ZC43.class },
+
+        { ZZ1.class },
+        { ZZ2.class },
+        { ZZ3.class },
+
+        { X3.class },
+        { X31.class },
+        { X32.class },
+
+        { X41.class },
+        { X42.class },
+        { X43.class },
+
+        { XC41.class },
+        { XC42.class },
+        { XC43.class },
+
+        { B1C.class },
+        { B2.class },
+        { B3A.class },
+        { B3B.class },
+
+        { A3A.class },
+        { A3B.class },
+    };
+}