2

I have 1 interface, 1 abstract class and 1 concrete class. The annotation @Handler is used in two methods in the interface and abstract class:

public interface A<T> {
    @Handler
    void a(T data);
}

public abstract class B<V> implements A<Integer> {
    @Override
    void a(Integer data) {
        // implementation
    }

    @Handler
    public abstract void b(V data);
}

public class C extends B<String> {
    @Override
    void b(String data) {
        // implementation
    }
}

I'd like to retrieve all concrete methods that are decorated with @Handler from the concrete class C (including those from superclasses) along with their parameters, i.e.

B.a(java.lang.Integer)
C.b(java.lang.String)

I tried using Apache Commons Lang's MethodUtils.getMethodsListWithAnnotation(), but it could only find the abstract methods (where the annotations are actually placed), without parameters' info:

A.a(java.lang.Object) 
B.b(java.lang.Object)

Is it possible to retrieve the concrete methods along with their parameter types from the abstract ones?

Kent
  • 905
  • 10
  • 12

2 Answers2

3

Your problem is not about abstract vs concrete methods. You have annotated methods whose erasure differs from the erasure of the overriding methods. If it was just for invoking the methods, you could simply invoke the Method describing the abstract method and be sure that a concrete class will have a concrete overriding implementation for it, though it might be a bridge method delegating to an actual implementation method with a more specific erasure.

In other words, your actual question is about getting actual types for methods in the context of generic declarations.

This is not an easy task. Basically, if a method has references to type parameters, you have to substitute them with the actual types of the actual concrete class. There is no guaranty that this will work, i.e. the actual class doesn’t have to be a reifiable type, just like ArrayList is a concrete class implementing List, but not a reifiable class, so it’s impossible to get the actual element type of an ArrayList via Reflection at runtime. But you can still get the most specific implementation method of such a class.

public static Set<Method> getAnnotatedMethods(
              Class<?> actualClass, Class<? extends Annotation> a) {
    Set<Method> raw = getRawAnnotatedMethods(actualClass, a);
    if(raw.isEmpty()) return raw;
    Set<Method> resolved = new HashSet<>();
    for(Method m: raw) {
        if(m.getDeclaringClass()==actualClass) resolved.add(m);
        else {
            Method x = getMoreSpecific(actualClass, m);
            resolved.add(!x.isBridge()? x: resolveGeneric(x, m));
        }
    }
    return resolved;
}
private static Method resolveGeneric(Method x, Method m) {
    final Class<?> decl = m.getDeclaringClass();
    Map<Type,Type> translate = new HashMap<>();
    up: for(Class<?> c=x.getDeclaringClass(); c!=decl; ) {
        if(decl.isInterface()) {
            for(Type t: c.getGenericInterfaces()) {
                Class<?> e = erased(t);
                if(updateMap(decl, e, t, translate)) continue;
                c = e;
                continue up;
            }
        }
        Type t = c.getGenericSuperclass();
        c = erased(t);
        updateMap(decl, c, t, translate);
    }
    Class<?>[] raw = m.getParameterTypes();
    Type[] generic = m.getGenericParameterTypes();
    for(int ix = 0; ix<raw.length; ix++)
        raw[ix] = erased(translate.getOrDefault(generic[ix], raw[ix]));
    return getMoreSpecific(x.getDeclaringClass(), x, raw);
}
private static Method getMoreSpecific(Class<?> actual, Method inherited) {
    return getMoreSpecific(actual, inherited, inherited.getParameterTypes());
}
private static Method getMoreSpecific(
               Class<?> actual, Method inherited, Class<?>[] pTypes) {
    try {
        final String name = inherited.getName();
        if(inherited.getDeclaringClass().isInterface()
        || Modifier.isPublic(inherited.getModifiers())) {
            return actual.getMethod(name, pTypes);
        }
        for(;;) try {
            return actual.getDeclaredMethod(name, pTypes);
        }
        catch(NoSuchMethodException ex) {
            actual = actual.getSuperclass();
            if(actual == null) throw ex;
        }
    }
    catch(NoSuchMethodException ex) {
        throw new IllegalStateException(ex);
    }
}

private static boolean updateMap(Class<?> decl, Class<?> e, Type t, Map<Type, Type> m) {
    if (!decl.isAssignableFrom(e)) {
        return true;
    }
    if(t!=e) {
        TypeVariable<?>[] tp = e.getTypeParameters();
        if(tp.length>0) {
            Type[] arg = ((ParameterizedType)t).getActualTypeArguments();
            for(int ix=0; ix<arg.length; ix++)
                m.put(tp[ix], erased(m.getOrDefault(arg[ix], arg[ix])));
        }
    }
    return false;
}
private static Class<?> erased(Type t) {
    if(t instanceof Class<?>) return (Class<?>)t;
    if(t instanceof ParameterizedType)
        return (Class<?>)((ParameterizedType)t).getRawType();
    if(t instanceof TypeVariable<?>) return erased(((TypeVariable<?>)t).getBounds()[0]);
    if(t instanceof GenericArrayType) return Array.newInstance(
            erased(((GenericArrayType)t).getGenericComponentType()), 0).getClass();
    return erased(((WildcardType)t).getUpperBounds()[0]);
}
/**
 * You may replace this with the MethodUtils.getMethodsListWithAnnotation()
 * you were already using.
 */
private static Set<Method> getRawAnnotatedMethods(
               Class<?> c, Class<? extends Annotation> a) {
    Set<Method> s = new HashSet<>();
    for(; c!=null; c=c.getSuperclass()) {
        for(Method m: c.getDeclaredMethods()) {
            if(m.isAnnotationPresent(a)) s.add(m);
        }
        for(Class<?> ifC: c.getInterfaces()) {
            for(Method m: ifC.getMethods()) {
                if(m.isAnnotationPresent(a)) s.add(m);
            }
        }
    }
    return s;
}

The first thing, this method does, is to look up the method with identical raw parameter types with the most specific declaring class. If the method is declared by an interface or has been declared public, the implementing/overriding method(s) must be public as well, so a simple getMethod is sufficient. Otherwise, traversing the hierarchy using getDeclaredMethod is unavoidable.

Then, a simple check tells us whether we have to do more. If a method with more specific parameter and/or return types exists, the method we’ve just found must be a bridge method. If so, we have to follow the chain of possibly generic extends or implements relationships to get the actual types for the type parameters. Then, we have to substitute the method’s uses of those type parameters with the actual types. If not possible, e.g. if the method itself is generic or the actual type is not reifiable, all remaining type parameters are replaced with the erasure of their bounds.

Finally, the method with that erasure is looked up. As you can see, it’s quiet a complex process, so I can’t guaranty that this code handles all corner cases. But it’s capable of handling your use case.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • if you say `This is not an easy task` I'm afraid my brain will melt, this is indeed working with a more non-trivial example that I took the liberty running from our sample work code base... excellent answer as usual! – Eugene Nov 20 '17 at 20:53
  • This is simply awesome. Thank you for the excellent answer! It works perfectly for my use case, and I think I'll need some time to fully absorb your solution haha – Kent Nov 21 '17 at 06:53
  • @Eugene: unfortunately, the Reflection API makes this task harder than it needs to be. Just look at the `instanceof` sequence of my `erased` method, for example. A good API should not make such things necessary… – Holger Nov 21 '17 at 07:25
0

Sure, once you get the Method handles, you can check whether the method is abstract by using Modifier.isAbstract(method.getModifiers()).

If you want to retrieve classes that extend a given class (without knowing them in advance) that's a different matter entirely - you might want to look here: How do you find all subclasses of a given class in Java? for a library that helps you do that. Once you get those classes, you can check via standard methods (Class::getDeclaredMethod()) whether they override the relevant method and whether the override is concrete.

Piotr Wilkin
  • 3,446
  • 10
  • 18
  • Ah thanks for the insight. As you mention, I want to determine the concrete method given the abstract method and the derived (concrete) class. How to check if a concrete method overrides the given abstract method? Nevertheless, I don't think `getDeclaredMethod()` can find methods in superclass, which is `B.a()` in the above example. – Kent Nov 17 '17 at 11:18