ReflectionsTest.java

package org.reflections;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsEqual;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.reflections.scanners.FieldAnnotationsScanner;
import org.reflections.scanners.MemberUsageScanner;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.MethodParameterNamesScanner;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.Scanners;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.reflections.util.NameHelper;

import java.io.File;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.reflections.TestModel.*;

@SuppressWarnings("unchecked")
public class ReflectionsTest implements NameHelper {
    private static final FilterBuilder TestModelFilter = new FilterBuilder()
        .includePattern("org\\.reflections\\.TestModel\\$.*")
        .includePattern("org\\.reflections\\.UsageTestModel\\$.*");

    static Reflections reflections;

    @BeforeAll
    public static void init() {
        //noinspection deprecation
        reflections = new Reflections(new ConfigurationBuilder()
                .setUrls(Collections.singletonList(ClasspathHelper.forClass(TestModel.class)))
                .filterInputsBy(TestModelFilter)
                .setScanners(
                    new SubTypesScanner(),
                    new TypeAnnotationsScanner(),
                    new MethodAnnotationsScanner(),
                    new FieldAnnotationsScanner(),
                    Scanners.ConstructorsAnnotated,
                    Scanners.MethodsParameter,
                    Scanners.MethodsSignature,
                    Scanners.MethodsReturn,
                    Scanners.ConstructorsParameter,
                    Scanners.ConstructorsSignature,
                    new ResourcesScanner(),
                    new MethodParameterNamesScanner(),
                    new MemberUsageScanner()));
    }

    @Test
    public void testSubTypesOf() {
        assertThat(reflections.getSubTypesOf(I1.class), are(I2.class, C1.class, C2.class, C3.class, C5.class));
        assertThat(reflections.getSubTypesOf(C1.class), are(C2.class, C3.class, C5.class));

        assertFalse(reflections.getAllTypes().isEmpty(), "getAllTypes should not be empty when Reflections is configured with SubTypesScanner(false)");
    }

    @Test
    public void testTypesAnnotatedWith() {
        assertThat(reflections.getTypesAnnotatedWith(MAI1.class, true), are(AI1.class));
        assertThat(reflections.getTypesAnnotatedWith(MAI1.class, true), annotatedWith(MAI1.class));

        assertThat(reflections.getTypesAnnotatedWith(AI2.class, true), are(I2.class));
        assertThat(reflections.getTypesAnnotatedWith(AI2.class, true), annotatedWith(AI2.class));

        assertThat(reflections.getTypesAnnotatedWith(AC1.class, true), are(C1.class, C2.class, C3.class, C5.class));
        assertThat(reflections.getTypesAnnotatedWith(AC1.class, true), annotatedWith(AC1.class));

        assertThat(reflections.getTypesAnnotatedWith(AC1n.class, true), are(C1.class));
        assertThat(reflections.getTypesAnnotatedWith(AC1n.class, true), annotatedWith(AC1n.class));
        assertThat(reflections.getTypesAnnotatedWith(MAI1.class), are(AI1.class, I1.class, I2.class, C1.class, C2.class, C3.class, C5.class));
        assertThat(reflections.getTypesAnnotatedWith(AI1.class), are(I1.class, I2.class, C1.class, C2.class, C3.class, C5.class));
        assertThat(reflections.getTypesAnnotatedWith(AI2.class), are(I2.class, C1.class, C2.class, C3.class, C5.class));

        assertThat(reflections.getTypesAnnotatedWith(AM1.class), isEmpty);

        //annotation member value matching
        AC2 ac2 = new AC2() {
            public String value() {return "ac2";}
            public Class<? extends Annotation> annotationType() {return AC2.class;}};

        assertThat(reflections.getTypesAnnotatedWith(ac2), are(C3.class, C5.class, I3.class, C6.class, AC3.class, C7.class));

        assertThat(reflections.getTypesAnnotatedWith(ac2, true), are(C3.class, I3.class, AC3.class));
    }

    @Test
    public void testMethodsAnnotatedWith() throws NoSuchMethodException {
        assertThat(reflections.getMethodsAnnotatedWith(AM1.class),
                are(C4.class.getDeclaredMethod("m1"),
                    C4.class.getDeclaredMethod("m1", int.class, String[].class),
                    C4.class.getDeclaredMethod("m1", int[][].class, String[][].class),
                    C4.class.getDeclaredMethod("m3")));

        AM1 am1 = new AM1() {
            public String value() {return "1";}
            public Class<? extends Annotation> annotationType() {return AM1.class;}
        };
        assertThat(reflections.getMethodsAnnotatedWith(am1),
                are(C4.class.getDeclaredMethod("m1"),
                    C4.class.getDeclaredMethod("m1", int.class, String[].class),
                    C4.class.getDeclaredMethod("m1", int[][].class, String[][].class)));
    }

    @Test
    public void testConstructorsAnnotatedWith() throws NoSuchMethodException {
        assertThat(reflections.getConstructorsAnnotatedWith(AM1.class),
                are(C4.class.getDeclaredConstructor(String.class)));

        AM1 am1 = new AM1() {
            public String value() {return "1";}
            public Class<? extends Annotation> annotationType() {return AM1.class;}
        };
        assertThat(reflections.getConstructorsAnnotatedWith(am1),
                are(C4.class.getDeclaredConstructor(String.class)));
    }

    @Test
    public void testFieldsAnnotatedWith() throws NoSuchFieldException {
        assertThat(reflections.getFieldsAnnotatedWith(AF1.class),
                are(C4.class.getDeclaredField("f1"),
                    C4.class.getDeclaredField("f2")
                    ));

        assertThat(reflections.getFieldsAnnotatedWith(new AF1() {
                        public String value() {return "2";}
                        public Class<? extends Annotation> annotationType() {return AF1.class;}}),
                are(C4.class.getDeclaredField("f2")));
    }

    @Test
    public void testMethodParameter() throws NoSuchMethodException {
            assertThat(reflections.getMethodsWithParameter(String.class),
                    are(C4.class.getDeclaredMethod("m4", String.class), UsageTestModel.C1.class.getDeclaredMethod("method", String.class)));

            assertThat(reflections.getMethodsWithSignature(),
                    are(C4.class.getDeclaredMethod("m1"), C4.class.getDeclaredMethod("m3"),
                            AC2.class.getMethod("value"), AF1.class.getMethod("value"), AM1.class.getMethod("value"),
                            UsageTestModel.C1.class.getDeclaredMethod("method"), UsageTestModel.C2.class.getDeclaredMethod("method"),
                            UsageTestModel.C2.class.getDeclaredMethod("zero")));
            assertThat(reflections.getMethodsWithSignature(int[][].class, String[][].class),
                    are(C4.class.getDeclaredMethod("m1", int[][].class, String[][].class)));

            assertThat(reflections.getMethodsReturn(int.class),
                    are(C4.class.getDeclaredMethod("add", int.class, int.class)));

            assertThat(reflections.getMethodsReturn(String.class),
                    are(C4.class.getDeclaredMethod("m3"), C4.class.getDeclaredMethod("m4", String.class),
                            AC2.class.getMethod("value"), AF1.class.getMethod("value"), AM1.class.getMethod("value")));

            assertThat(reflections.getMethodsReturn(void.class),
                    are(C4.class.getDeclaredMethod("m1"), C4.class.getDeclaredMethod("m1", int.class, String[].class),
                            C4.class.getDeclaredMethod("m1", int[][].class, String[][].class), UsageTestModel.C1.class.getDeclaredMethod("method"),
                            UsageTestModel.C1.class.getDeclaredMethod("method", String.class), UsageTestModel.C2.class.getDeclaredMethod("method")));

            assertThat(reflections.getMethodsWithParameter(AM1.class),
                    are(C4.class.getDeclaredMethod("m4", String.class)));

            assertThat(reflections.getMethodsWithParameter(AM2.class),
                    are(C4.class.getDeclaredMethod("m4", String.class),
                            C4.class.getDeclaredMethod("m1", int.class, String[].class)));
    }

    @Test
    public void testConstructorParameter() throws NoSuchMethodException {
        assertThat(reflections.getConstructorsWithParameter(String.class),
                are(C4.class.getDeclaredConstructor(String.class)));

        assertThat(reflections.getConstructorsWithSignature(),
                are(C1.class.getDeclaredConstructor(), C2.class.getDeclaredConstructor(), C3.class.getDeclaredConstructor(),
                        C4.class.getDeclaredConstructor(), C5.class.getDeclaredConstructor(), C6.class.getDeclaredConstructor(),
                        C7.class.getDeclaredConstructor(), UsageTestModel.C1.class.getDeclaredConstructor(), UsageTestModel.C2.class.getDeclaredConstructor()));

        assertThat(reflections.getConstructorsWithParameter(AM1.class),
                are(C4.class.getDeclaredConstructor(String.class)));
    }

    @Test
    public void testResourcesScanner() {
        Predicate<String> filter = new FilterBuilder().includePattern(".*\\.xml").excludePattern(".*testModel-reflections\\.xml");
        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .filterInputsBy(filter)
                .setScanners(Scanners.Resources)
                .setUrls(Collections.singletonList(ClasspathHelper.forClass(TestModel.class))));

        Collection<String> resolved = reflections.getResources(Pattern.compile(".*resource1-reflections\\.xml"));
        assertThat(resolved, are("META-INF/reflections/resource1-reflections.xml"));

        Collection<String> resources = reflections.getResources(".*");
        assertThat(resources, are("META-INF/reflections/resource1-reflections.xml", "META-INF/reflections/inner/resource2-reflections.xml"));
    }

    @Test
    public void testMethodParameterNames() throws NoSuchMethodException {
        assertEquals(reflections.getMemberParameterNames(C4.class.getDeclaredMethod("m3")),
                Collections.emptyList());

        assertEquals(reflections.getMemberParameterNames(C4.class.getDeclaredMethod("m4", String.class)),
                Collections.singletonList("string"));

        assertEquals(reflections.getMemberParameterNames(C4.class.getDeclaredMethod("add", int.class, int.class)),
                Arrays.asList("i1", "i2"));

        assertEquals(reflections.getMemberParameterNames(C4.class.getDeclaredConstructor(String.class)),
                Collections.singletonList("f1"));
    }

    @Test
    public void testMemberUsageScanner() throws NoSuchFieldException, NoSuchMethodException {
        //field usage
        assertThat(reflections.getMemberUsage(UsageTestModel.C1.class.getDeclaredField("c2")),
                are(UsageTestModel.C1.class.getDeclaredConstructor(),
                        UsageTestModel.C1.class.getDeclaredConstructor(UsageTestModel.C2.class),
                        UsageTestModel.C1.class.getDeclaredMethod("method"),
                        UsageTestModel.C1.class.getDeclaredMethod("method", String.class)));

        //method usage
        assertThat(reflections.getMemberUsage(UsageTestModel.C1.class.getDeclaredMethod("method")),
                are(UsageTestModel.C2.class.getDeclaredMethod("method")));

        assertThat(reflections.getMemberUsage(UsageTestModel.C1.class.getDeclaredMethod("method", String.class)),
                are(UsageTestModel.C2.class.getDeclaredMethod("method")));

        //constructor usage
        assertThat(reflections.getMemberUsage(UsageTestModel.C1.class.getDeclaredConstructor()),
                are(UsageTestModel.C2.class.getDeclaredConstructor(),
                        UsageTestModel.C2.class.getDeclaredMethod("method")));

        assertThat(reflections.getMemberUsage(UsageTestModel.C1.class.getDeclaredConstructor(UsageTestModel.C2.class)),
                are(UsageTestModel.C2.class.getDeclaredMethod("method")));

        // synthetic method for lambda expression
        assertThat(reflections.getMemberUsage(UsageTestModel.C2.class.getDeclaredMethod("zero")),
                are(reflections.forMember("org.reflections.UsageTestModel$C2.lambda$useLambda$0(org.reflections.UsageTestModel$C2)"),
                        reflections.forMember("org.reflections.UsageTestModel$C2$1.applyAsDouble(org.reflections.UsageTestModel$C2)")));
    }

    @Test
    public void testScannerNotConfigured() throws NoSuchMethodException {
        Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setUrls(Collections.singletonList(ClasspathHelper.forClass(TestModel.class)))
            .filterInputsBy(TestModelFilter.includePackage("org\\.reflections\\.UsageTestModel\\$.*"))
            .setScanners());

        assertTrue(reflections.getSubTypesOf(C1.class).isEmpty());
        assertTrue(reflections.getTypesAnnotatedWith(AC1.class).isEmpty());
        assertTrue(reflections.getMethodsAnnotatedWith(AC1.class).isEmpty());
        assertTrue(reflections.getMethodsWithSignature().isEmpty());
        assertTrue(reflections.getMethodsWithParameter(String.class).isEmpty());
        assertTrue(reflections.getMethodsReturn(String.class).isEmpty());
        assertTrue(reflections.getConstructorsAnnotatedWith(AM1.class).isEmpty());
        assertTrue(reflections.getConstructorsWithSignature().isEmpty());
        assertTrue(reflections.getConstructorsWithParameter(String.class).isEmpty());
        assertTrue(reflections.getFieldsAnnotatedWith(AF1.class).isEmpty());
        assertTrue(reflections.getResources(".*").isEmpty());
        assertTrue(reflections.getMemberParameterNames(C4.class.getDeclaredMethod("m4", String.class)).isEmpty());
        assertTrue(reflections.getMemberUsage(UsageTestModel.C1.class.getDeclaredConstructor()).isEmpty());
        assertTrue(reflections.getAllTypes().isEmpty());
    }

    //
    public static String getUserDir() {
        File file = new File(System.getProperty("user.dir"));
        //a hack to fix user.dir issue(?) in surfire
        if (Arrays.asList(file.list()).contains("reflections")) {
            file = new File(file, "reflections");
        }
        return file.getAbsolutePath();
    }

    private final BaseMatcher<Collection<Class<?>>> isEmpty = new BaseMatcher<Collection<Class<?>>>() {
        public boolean matches(Object o) {
            return ((Collection<?>) o).isEmpty();
        }

        public void describeTo(Description description) {
            description.appendText("empty collection");
        }
    };

    private abstract static class Match<T> extends BaseMatcher<T> {
        public void describeTo(Description description) { }
    }

    public static <T> Matcher<Collection<? super T>> are(final T... ts) {
        final Collection<?> c1 = Arrays.asList(ts);
        return new Match<Collection<? super T>>() {
            public boolean matches(Object o) {
                Collection<?> c2 = (Collection<?>) o;
                return c1.containsAll(c2) && c2.containsAll(c1);
            }

            @Override
            public void describeTo(Description description) {
                description.appendText(Arrays.toString(ts));
            }
        };
    }

    @SafeVarargs
    public static <T> Matcher<Collection<T>> equalTo(T... operand) {
        return IsEqual.equalTo(new HashSet<>(Arrays.asList(operand)));
    }

    private Matcher<Collection<Class<?>>> annotatedWith(final Class<? extends Annotation> annotation) {
        return new Match<Collection<Class<?>>>() {
            public boolean matches(Object o) {
                for (Class<?> c : (Iterable<Class<?>>) o) {
                    List<Class<? extends Annotation>> annotationTypes = Stream.of(c.getAnnotations()).map(Annotation::annotationType).collect(Collectors.toList());
                    if (!annotationTypes.contains(annotation)) return false;
                }
                return true;
            }
        };
    }
}