Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get annotation from overridden method in Java?

I have the following situation

class Parent {
    @SomeAnnotation(someValue)
    public void someMethod(){...}
}

class Child extends Parent {
    @Override
    public void someMethod(){...}
}

And I need to get @SomeAnnotation when I have reference to method Child.someMethod.

Using Child.getSuperclass() I can get Parent.class. Besides, I found a solution here to get reference to MethodHandle of Parent.someMethod, so I have

MethodHandle parentMethodHandle = MethodHandles.lookup().findSpecial(
        Parent.class, childSomeMethod.getName(),
        MethodType.methodType(Void.class), Child.class);

However, I can't find a way to get reference to method Parent.someMethod from parentMethodHandle to getAnnotation() from it. Please, help.

like image 749
Pavel_K Avatar asked Mar 05 '18 07:03

Pavel_K


2 Answers

Here's a complete solution. I believe this should be correct for all cases, except in strange situations like class A extends B where A and B were loaded by different class loaders. This also might not work correctly with other JVM languages (like Scala) where methods can have the same erasure but not override one another (like void m(List<A> l) and void m(List<B> l), which would cause a compilation error in Java).

Doing this correctly turns out to be a bit complicated, because you have to make sure that two methods actually override each other, and there's no method that does that in Java SE at the moment. It's useful code to have on the site, anyway.

package mcve.reflect;

import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;

public final class MCVEReflect {
    private MCVEReflect() {}

    /**
     * Returns the 0th element of the list returned by
     * {@code getAnnotations}, or {@code null} if the
     * list would be empty.
     * 
     * @param  <A> the type of the annotation to find.
     * @param  m   the method to begin the search from.
     * @param  t   the type of the annotation to find.
     * @return the first annotation found of the specified type which
     *         is present on {@code m}, or present on any methods which
     *         {@code m} overrides.
     * @throws NullPointerException if any argument is {@code null}.
     * @see    MCVEReflect#getAnnotations(Method, Class)
     */
    public static <A extends Annotation> A getAnnotation(Method m, Class<A> t) {
        List<A> list = getAnnotations(m, t);
        return list.isEmpty() ? null : list.get(0);
    }

    /**
     * Let {@code D} be the class or interface which declares the method
     * {@code m}.
     * <p>
     * Returns a list of all of the annotations of the specified type
     * which are either present on {@code m}, or present on any methods
     * declared by a supertype of {@code D} which {@code m} overrides.
     * <p>
     * Annotations are listed in order of nearest proximity to {@code D},
     * that is, assuming {@code D extends E} and {@code E extends F}, then
     * the returned list would contain annotations in the order of
     * {@code [D, E, F]}. A bit more formally, if {@code Sn} is the nth
     * superclass of {@code D} (where {@code n} is an integer starting at 0),
     * then the index of the annotation present on {@code Sn.m} is {@code n+1},
     * assuming annotations are present on {@code m} for every class.
     * <p>
     * Annotations from methods declared by the superinterfaces of {@code D}
     * appear <em>last</em> in the list, in order of their declaration,
     * recursively. For example, if {@code class D implements X, Y} and
     * {@code interface X extends Z}, then annotations will appear in the
     * list in the order of {@code [D, X, Z, Y]}.
     * 
     * @param  <A> the type of the annotation to find.
     * @param  m   the method to begin the search from.
     * @param  t   the type of the annotation to find.
     * @return a list of all of the annotations of the specified type
     *         which are either present on {@code m}, or present on any
     *         methods which {@code m} overrides.
     * @throws NullPointerException if any argument is {@code null}.
     */
    public static <A extends Annotation> List<A> getAnnotations(Method m, Class<A> t) {
        List<A> list = new ArrayList<>();
        Collections.addAll(list, m.getAnnotationsByType(t));
        Class<?> decl = m.getDeclaringClass();

        for (Class<?> supr = decl; (supr = supr.getSuperclass()) != null;) {
            addAnnotations(list, m, t, supr);
        }
        for (Class<?> face : getAllInterfaces(decl)) {
            addAnnotations(list, m, t, face);
        }

        return list;
    }

    private static Set<Class<?>> getAllInterfaces(Class<?> c) {
        Set<Class<?>> set = new LinkedHashSet<>();
        do {
            addAllInterfaces(set, c);
        } while ((c = c.getSuperclass()) != null);
        return set;
    }
    private static void addAllInterfaces(Set<Class<?>> set, Class<?> c) {
        for (Class<?> i : c.getInterfaces()) {
            if (set.add(i)) {
                addAllInterfaces(set, i);
            }
        }
    }
    private static <A extends Annotation> void addAnnotations
            (List<A> list, Method m, Class<A> t, Class<?> decl) {
        try {
            Method n = decl.getDeclaredMethod(m.getName(), m.getParameterTypes());
            if (overrides(m, n)) {
                Collections.addAll(list, n.getAnnotationsByType(t));
            }
        } catch (NoSuchMethodException x) {
        }
    }

    /**
     * @param  a the method which may override {@code b}.
     * @param  b the method which may be overridden by {@code a}.
     * @return {@code true} if {@code a} probably overrides {@code b}
     *         and {@code false} otherwise.
     * @throws NullPointerException if any argument is {@code null}.
     */
    public static boolean overrides(Method a, Method b) {
        if (!a.getName().equals(b.getName()))
            return false;
        Class<?> classA = a.getDeclaringClass();
        Class<?> classB = b.getDeclaringClass();
        if (classA.equals(classB))
            return false;
        if (!classB.isAssignableFrom(classA))
            return false;
        int modsA = a.getModifiers();
        int modsB = b.getModifiers();
        if (Modifier.isPrivate(modsA) || Modifier.isPrivate(modsB))
            return false;
        if (Modifier.isStatic(modsA) || Modifier.isStatic(modsB))
            return false;
        if (Modifier.isFinal(modsB))
            return false;
        if (compareAccess(modsA, modsB) < 0)
            return false;
        if ((isPackageAccess(modsA) || isPackageAccess(modsB))
            && !Objects.equals(classA.getPackage(), classB.getPackage()))
            return false;
        if (!b.getReturnType().isAssignableFrom(a.getReturnType()))
            return false;
        Class<?>[] paramsA = a.getParameterTypes();
        Class<?>[] paramsB = b.getParameterTypes();
        if (paramsA.length != paramsB.length)
            return false;
        for (int i = 0; i < paramsA.length; ++i)
            if (!paramsA[i].equals(paramsB[i]))
                return false;
        return true;
    }

    public static boolean isPackageAccess(int mods) {
        return (mods & ACCESS_MODIFIERS) == 0;
    }

    private static final int ACCESS_MODIFIERS =
        Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
    private static final List<Integer> ACCESS_ORDER =
        Arrays.asList(Modifier.PRIVATE,
                      0,
                      Modifier.PROTECTED,
                      Modifier.PUBLIC);
    public static int compareAccess(int lhs, int rhs) {
        return Integer.compare(ACCESS_ORDER.indexOf(lhs & ACCESS_MODIFIERS),
                               ACCESS_ORDER.indexOf(rhs & ACCESS_MODIFIERS));
    }
}

Here's a JUnit 4 test as well:

package mcve.reflect;

import static mcve.reflect.MCVEReflect.*;

import java.lang.reflect.*;
import java.lang.annotation.*;
import java.util.*;
import static java.util.stream.Collectors.*;
import org.junit.*;
import static org.junit.Assert.*;

public class MCVEReflectTest {
    public MCVEReflectTest() {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @interface AnnoContainer {
        Anno[] value();
    }

    @Repeatable(AnnoContainer.class)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Anno {
        String value();
    }

    interface I {
        @Anno("I.m")
        void m();
    }
    interface J extends I {
        @Anno("J.m")
        @Override
        void m();
    }
    interface K {
        @Anno("K.m")
        void m();
    }

    @Test
    public void getAnnotationsTest() throws NoSuchMethodException {
        try {
            getAnnotations(null, Anno.class);
            fail();
        } catch (NullPointerException x) {
        }
        try {
            getAnnotations(List.class.getMethod("size"), null);
            fail();
        } catch (NullPointerException x) {
        }

        class A {
            @Anno("A.m")
            void m() {}
            @Anno("A.m(int)")
            void m(int a) {}
        }
        class B extends A implements K {
            @Anno("B.m 1")
            @Anno("B.m 2")
            @Override
            public void m() {}
        }
        class C extends B implements J {
            @Anno("C.m")
            @Override
            public void m() {}
        }

        System.out.println(Arrays.toString(B.class.getInterfaces()));

        List<Anno>   annos;
        List<String> expect;
        List<String> actual;

        annos  = getAnnotations(A.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("A.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos  = getAnnotations(B.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("B.m 1", "B.m 2", "A.m", "K.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos  = getAnnotations(C.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("C.m", "B.m 1", "B.m 2", "A.m", "J.m", "I.m", "K.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos  = getAnnotations(J.class.getDeclaredMethod("m"), Anno.class);
        expect = Arrays.asList("J.m", "I.m");
        actual = annos.stream().map(Anno::value).collect(toList());

        assertEquals(expect, actual);

        annos = getAnnotations(Object.class.getMethod("toString"), Anno.class);
        assertEquals(Collections.emptyList(), annos);
    }

    private boolean overrides(Method a, Method b) {
        return MCVEReflect.overrides(a, b);
    }

    private boolean overrides(Class<?> classA, String nameA,
                              Class<?> classB, String nameB)
            throws NoSuchMethodException {
        return MCVEReflect.overrides(classA.getDeclaredMethod(nameA),
                                     classB.getDeclaredMethod(nameB));
    }

    private boolean overrides(Class<?> classA, Class<?> classB, String name)
            throws NoSuchMethodException {
        return overrides(classA, name, classB, name);
    }

    @Test
    public void overridesTest() throws NoSuchMethodException {
        try {
            overrides(null, List.class.getMethod("size"));
            fail();
        } catch (NullPointerException x) {
        }
        try {
            overrides(List.class.getMethod("size"), null);
            fail();
        } catch (NullPointerException x) {
        }

        assertTrue("this is an override", overrides(ArrayList.class, "size",  List.class, "size"));
        assertFalse("same method",        overrides(List.class,      "size",  List.class, "size"));
        assertFalse("different methods",  overrides(ArrayList.class, "clear", List.class, "size"));

        class A { private void m() {} }
        class B extends A { private void m() {} }

        assertFalse("private methods", overrides(B.class, "m", A.class, "m"));

        class C { public void m() {} }
        class D { public void m() {} }

        assertFalse("no inheritance", overrides(D.class, "m", C.class, "m"));

        class E { public void m() {} }
        class F { public void m() {} }
        class G extends F { @Override public void m() {} }

        assertTrue("yes inheritance", overrides(G.class, "m", F.class, "m"));
        assertFalse("no inheritance", overrides(G.class, "m", E.class, "m"));

        class H {
            public void m(char a) {}
            public void m(long a) {}
        }
        class I extends H {
            @Override public void m(char a) {}
            @Override public void m(long a) {}
        }

        assertTrue("same parameters",
                   overrides(I.class.getDeclaredMethod("m", char.class),
                             H.class.getDeclaredMethod("m", char.class)));
        assertFalse("different parameters",
                    overrides(I.class.getDeclaredMethod("m", char.class),
                              H.class.getDeclaredMethod("m", long.class)));

        class SubHashMap extends HashMap<Object, Object> {
            void reinitialize() {
            }
        }

        String reinitialize = "reinitialize";
        // if this throws, find another method as a replacement
        Method m = HashMap.class.getDeclaredMethod(reinitialize);
        assertTrue(isPackageAccess(m.getModifiers()));
        assertFalse(Modifier.isFinal(m.getModifiers()));

        assertFalse("would override, except they are in different packages",
                    overrides(SubHashMap.class, HashMap.class, reinitialize));
    }

    @Test
    public void compareAccessTest() {
        assertEquals( 0, compareAccess(Modifier.PUBLIC,    Modifier.PUBLIC));
        assertEquals(+1, compareAccess(Modifier.PUBLIC,    Modifier.PROTECTED));
        assertEquals(+1, compareAccess(Modifier.PUBLIC,    0));
        assertEquals(+1, compareAccess(Modifier.PUBLIC,    Modifier.PRIVATE));
        assertEquals(-1, compareAccess(Modifier.PROTECTED, Modifier.PUBLIC));
        assertEquals( 0, compareAccess(Modifier.PROTECTED, Modifier.PROTECTED));
        assertEquals(+1, compareAccess(Modifier.PROTECTED, 0));
        assertEquals(+1, compareAccess(Modifier.PROTECTED, Modifier.PRIVATE));
        assertEquals(-1, compareAccess(0,                  Modifier.PUBLIC));
        assertEquals(-1, compareAccess(0,                  Modifier.PROTECTED));
        assertEquals( 0, compareAccess(0,                  0));
        assertEquals(+1, compareAccess(0,                  Modifier.PRIVATE));
        assertEquals(-1, compareAccess(Modifier.PRIVATE,   Modifier.PUBLIC));
        assertEquals(-1, compareAccess(Modifier.PRIVATE,   Modifier.PROTECTED));
        assertEquals(-1, compareAccess(Modifier.PRIVATE,   0));
        assertEquals( 0, compareAccess(Modifier.PRIVATE,   Modifier.PRIVATE));
        int notModsL = Modifier.PRIVATE | Modifier.PUBLIC;
        int notModsR = Modifier.PRIVATE | Modifier.PROTECTED;
        assertEquals("this might as well be undefined, but it's here for posterity",
                     0, compareAccess(notModsL, notModsR));
    }
}
like image 85
Radiodef Avatar answered Sep 17 '22 14:09

Radiodef


I think you have get super class method and test for annotation. Other than that java does not support to get super method annotations.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Inherited
public @interface SomeAnnotation {
}

@SomeAnnotation
public class Parent {

    @SomeAnnotation
    public void someMethod() {
        System.out.println("Parent");
    }
}

public class Child extends Parent {

    @Override
    public void someMethod() {
        super.someMethod();
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = Child.class;

        System.out.println("Child Method");
        Method method = clazz.getMethod("someMethod");
        Arrays.stream(method.getDeclaredAnnotations()).forEach(annotation -> System.out.println(annotation));

        System.out.println("Parent Method");
        Method superMethod = clazz.getSuperclass().getMethod("someMethod");
        Arrays.stream(superMethod.getDeclaredAnnotations()).forEach(annotation -> System.out.println(annotation));

        System.out.println("Class Level support inherited annotations. ");
        Arrays.stream(clazz.getAnnotations()).forEach(annotation -> System.out.println(annotation));
    }
}

Also you can do some generic way is access super class by dynamically. As

public void printAnnotationInMethod(String methodName, Class clazz) {
        try {
            Method method = clazz.getDeclaredMethod(methodName);
            Arrays.stream(method.getDeclaredAnnotations()).forEach(annotation -> System.out.println(annotation));
            if (null != clazz.getSuperclass()) {
                printAnnotationInMethod(methodName, clazz.getSuperclass());
            }
        } catch (NoSuchMethodException e) {
            //e.printStackTrace();
        } catch (SecurityException e) {
            //e.printStackTrace();
        }
    }

@Inherited in annotation support only for annotation which use TYPE. (classes level only)

For reference Java Inheritance annotation

like image 26
Chamly Idunil Avatar answered Sep 18 '22 14:09

Chamly Idunil