diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..04ceb6e8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml new file mode 100644 index 00000000..1290983d --- /dev/null +++ b/.github/workflows/maven-ci.yml @@ -0,0 +1,42 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + fail-fast: false + matrix: + java: [ '8', '9', '11', '13', '15', '17'] + os: [ ubuntu-latest ] + name: Java ${{ matrix.Java }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: ${{ matrix.java }} + + - name: Show version + run: mvn -version + + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-m2 + restore-keys: ${{ runner.os }}-m2 + + - name: Test with Maven + run: mvn -B clean package ${{ matrix.jdk }} diff --git a/README.md b/README.md index 8b068cd1..9aa03ebd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -*Released `org.reflections:reflections:0.10`* +*Released `org.reflections:reflections:0.10.1`* *Reflections library has ~4 million downloads per month from Maven Central, and is being used by thousands of [projects](https://github.com/ronmamo/reflections/network/dependents) and [libraries](https://mvnrepository.com/artifact/org.reflections/reflections/usages). Thank you for your continuous support! And apologize for the issues. We're looking for community collaborators to assist in reviewing pull requests and issues, please reach out.* -# Java runtime metadata analysis, in the spirit of [Scannotations](http://bill.burkecentral.com/2008/01/14/scanning-java-annotations-at-runtime/) +# Java runtime metadata analysis [![Build Status](https://travis-ci.org/ronmamo/reflections.svg?branch=master)](https://travis-ci.org/ronmamo/reflections) @@ -16,6 +16,8 @@ Using Reflections you can query for example: * Resources found in classpath And more... +*Reflections was written in the spirit of [Scannotations](http://bill.burkecentral.com/2008/01/14/scanning-java-annotations-at-runtime/) library* + ## Usage Add Reflections dependency to your project: ```xml @@ -23,11 +25,11 @@ Add Reflections dependency to your project: org.reflections reflections - 0.10 + 0.10.1 # Gradle -implementation 'org.reflections:reflections:0.10' +implementation 'org.reflections:reflections:0.10.1' ``` Create Reflections instance and use the query functions: @@ -41,7 +43,7 @@ Set> annotated = reflections.get(TypesAnnotated.with(SomeAnnotation.class).asClass()); ``` -*Note that there are some breaking changes with Reflections 0.10, along with performance improvements and more functional API. Migration is encouraged and should be easy though.* +*Note that there are some breaking changes with Reflections 0.10+, along with performance improvements and more functional API. Migration is encouraged and should be easy though.* ### Scan Creating Reflections instance requires providing scanning configuration: @@ -263,7 +265,9 @@ Although less common, it can be useful for accessing types and members in a stro Can be used for finding usages between packages, layers, modules, types etc. ### Contribute -Pull requests are welcomed!! +Pull requests are welcomed!! +Here are some issues labeled with [please contribute :heart:](https://github.com/ronmamo/reflections/issues?q=is%3Aissue+is%3Aopen+label%3A%22please+contribute+%E2%9D%A4%EF%B8%8F%22+label%3A%22good+first+issue%22) +*We're looking for community collaborators to assist in reviewing pull requests and issues, please reach out.* Dual licenced with Apache 2 and [WTFPL](http://www.wtfpl.net/), just do what the fuck you want to. diff --git a/pom.xml b/pom.xml index b3ffb28e..6ddf566d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.reflections reflections - 0.11-SNAPSHOT + 0.10.1-SNAPSHOT jar Reflections @@ -232,6 +232,9 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + org.reflections + diff --git a/src/main/java/org/reflections/ReflectionUtils.java b/src/main/java/org/reflections/ReflectionUtils.java index ee052f15..418c6394 100644 --- a/src/main/java/org/reflections/ReflectionUtils.java +++ b/src/main/java/org/reflections/ReflectionUtils.java @@ -23,11 +23,13 @@ import static java.util.stream.Collectors.toList; /** - * utils for querying java reflection meta types {@link #SuperTypes}, {@link #Annotations}, {@link #AnnotationTypes}, {@link #Methods}, {@link #Constructors}, {@link #Fields}. - *
{@code Set> supertypes = get(SuperTypes.of(type))
+ * utils for querying java reflection meta types
+ * 

see {@link #SuperTypes}, {@link #Annotations}, {@link #AnnotationTypes}, {@link #Methods}, {@link #Constructors} and {@link #Fields}. + *

{@code
+ * Set> supertypes = get(SuperTypes.of(type))
  * Set annotations = get(Annotations.of(type))
  * }
- *

generally, apply {@link #get(QueryFunction)} on {@link QueryFunction} created by {@link UtilQueryBuilder}, and optionally use the functional methods in QueryFunction + *

generally, apply {@link #get(QueryFunction)} on {@link QueryFunction} created by {@link UtilQueryBuilder}, and optionally use the functional methods in QueryFunction. *

{@code get(Methods.of(type)
  *   .filter(withPublic().and(withPrefix("get")).and(withParameterCount(0)))
  *   .as(Method.class)
diff --git a/src/main/java/org/reflections/Reflections.java b/src/main/java/org/reflections/Reflections.java
index 3a297077..71d39d07 100644
--- a/src/main/java/org/reflections/Reflections.java
+++ b/src/main/java/org/reflections/Reflections.java
@@ -64,7 +64,8 @@
  * 
  *
  * 

Create Reflections instance, preferably using {@link ConfigurationBuilder}: - *

{@code Reflections reflections = new Reflections(
+ * 
{@code
+ * Reflections reflections = new Reflections(
  *   new ConfigurationBuilder()
  *     .forPackage("com.my.project"));
  *
@@ -87,7 +88,8 @@
  * 

Classloader can optionally be used for resolving runtime classes from names. * *

Query using {@link Reflections#get(QueryFunction)}, such as: - *
{@code Set> modules = reflections.get(SubTypes.of(Module.class).asClass());
+ * 
{@code
+ * Set> modules = reflections.get(SubTypes.of(Module.class).asClass());
  * Set> singletons = reflections.get(TypesAnnotated.with(Singleton.class).asClass());
  * Set properties   = reflections.get(Resources.with(".*\\.properties"));
  * Set requests     = reflections.get(MethodsAnnotated.with(RequestMapping.class).as(Method.class));
diff --git a/src/main/java/org/reflections/scanners/Scanners.java b/src/main/java/org/reflections/scanners/Scanners.java
index b47d88aa..37b0eb9e 100644
--- a/src/main/java/org/reflections/scanners/Scanners.java
+++ b/src/main/java/org/reflections/scanners/Scanners.java
@@ -27,10 +27,13 @@
  *   
  • {@link #SubTypes}
  • *
  • {@link #TypesAnnotated}
  • *
  • {@link #MethodsAnnotated}
  • + *
  • {@link #ConstructorsAnnotated}
  • *
  • {@link #FieldsAnnotated}
  • *
  • {@link #Resources}
  • *
  • {@link #MethodsParameter}
  • + *
  • {@link #ConstructorsParameter}
  • *
  • {@link #MethodsSignature}
  • + *
  • {@link #ConstructorsSignature}
  • *
  • {@link #MethodsReturn}
  • * * note that scanners must be configured in {@link org.reflections.Configuration} in order to be queried diff --git a/src/main/java/org/reflections/util/JavassistHelper.java b/src/main/java/org/reflections/util/JavassistHelper.java index e3a71e3a..ac04bfc1 100644 --- a/src/main/java/org/reflections/util/JavassistHelper.java +++ b/src/main/java/org/reflections/util/JavassistHelper.java @@ -11,7 +11,6 @@ import javassist.bytecode.annotation.Annotation; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -22,12 +21,6 @@ public class JavassistHelper { /** setting this static to false will result in returning only {@link java.lang.annotation.RetentionPolicy#RUNTIME} visible annotation */ public static boolean includeInvisibleTag = true; - public static List getAnnotations(Function function) { - List list = getAnnotations((AnnotationsAttribute) function.apply(AnnotationsAttribute.visibleTag)); - if (includeInvisibleTag) list.addAll(getAnnotations((AnnotationsAttribute) function.apply(AnnotationsAttribute.invisibleTag))); - return list; - } - public static String fieldName(ClassFile classFile, FieldInfo object) { return String.format("%s.%s", classFile.getName(), object.getName()); } @@ -70,22 +63,39 @@ public static String getReturnType(MethodInfo method) { return Descriptor.toString(descriptor); } - public static List> getParametersAnnotations(MethodInfo method) { - List> list = getAnnotations((ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag)); - if (includeInvisibleTag) list.addAll(getAnnotations((ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag))); - return list; - } + public static List getAnnotations(Function function) { + Function> names = function + .andThen(attribute -> attribute != null ? ((AnnotationsAttribute) attribute).getAnnotations() : null) + .andThen(JavassistHelper::annotationNames); - private static List> getAnnotations(ParameterAnnotationsAttribute attribute) { - return mapList(attribute, ParameterAnnotationsAttribute::getAnnotations, aa -> mapList(aa, a -> a, Annotation::getTypeName)); + List result = new ArrayList<>(names.apply(AnnotationsAttribute.visibleTag)); + if (includeInvisibleTag) result.addAll(names.apply(AnnotationsAttribute.invisibleTag)); + return result; } - private static List getAnnotations(AnnotationsAttribute attribute) { - return mapList(attribute, AnnotationsAttribute::getAnnotations, Annotation::getTypeName); + public static List> getParametersAnnotations(MethodInfo method) { + Function>> names = ((Function) method::getAttribute) + .andThen(attribute -> attribute != null ? ((ParameterAnnotationsAttribute) attribute).getAnnotations() : null) + .andThen((Annotation[][] aa) -> aa != null ? Stream.of(aa).map(JavassistHelper::annotationNames).collect(Collectors.toList()) : Collections.emptyList()); + + List> visibleAnnotations = names.apply(ParameterAnnotationsAttribute.visibleTag); + if (!includeInvisibleTag) return new ArrayList<>(visibleAnnotations); + + List> invisibleAnnotations = names.apply(ParameterAnnotationsAttribute.invisibleTag); + if (invisibleAnnotations.isEmpty()) return new ArrayList<>(visibleAnnotations); + + // horror + List> result = new ArrayList<>(); + for (int i = 0; i < Math.max(visibleAnnotations.size(), invisibleAnnotations.size()); i++) { + List concat = new ArrayList<>(); + if (i < visibleAnnotations.size()) concat.addAll(visibleAnnotations.get(i)); + if (i < invisibleAnnotations.size()) concat.addAll(invisibleAnnotations.get(i)); + result.add(concat); + } + return result; } - // todo inline & simplify - private static List mapList(T t, Function f1, Function f2) { - return t != null ? Arrays.stream(f1.apply(t)).map(f2).collect(Collectors.toList()) : Collections.emptyList(); + private static List annotationNames(Annotation[] annotations) { + return annotations != null ? Stream.of(annotations).map(Annotation::getTypeName).collect(Collectors.toList()) : Collections.emptyList(); } } diff --git a/src/main/java/org/reflections/util/ReflectionUtilsPredicates.java b/src/main/java/org/reflections/util/ReflectionUtilsPredicates.java index cc83aee8..f5603b3f 100644 --- a/src/main/java/org/reflections/util/ReflectionUtilsPredicates.java +++ b/src/main/java/org/reflections/util/ReflectionUtilsPredicates.java @@ -34,6 +34,13 @@ public static Predicate withPrefix(final String prefix) { return input -> input != null && input.getName().startsWith(prefix); } + /** + * where annotated element name startsWith given {@code prefix} + */ + public static Predicate withNamePrefix(final String prefix) { + return input -> toName(input).startsWith(prefix); + } + /** * where member's {@code toString} matches given {@code regex} *
     get(Methods.of(someClass).filter(withPattern("public void .*"))) 
    @@ -200,6 +207,14 @@ public static boolean isAssignable(Class[] childClasses, Class[] parentClasses) } // + private static String toName(Object input) { + return input == null ? "" : + input.getClass().equals(Class.class) ? ((Class) input).getName() : + input instanceof Member ? ((Member) input).getName() : + input instanceof Annotation ? ((Annotation) input).annotationType().getName() : + input.toString(); + } + private static Class[] parameterTypes(Member member) { return member != null ? member.getClass() == Method.class ? ((Method) member).getParameterTypes() : diff --git a/src/test/java/org/reflections/MyTestModelStore.java b/src/test/java/org/reflections/MyTestModelStore.java index 3ea7b834..1c7126df 100644 --- a/src/test/java/org/reflections/MyTestModelStore.java +++ b/src/test/java/org/reflections/MyTestModelStore.java @@ -36,6 +36,8 @@ interface methods { interface value {} } } + interface TestModel$AM2 { + } interface TestModel$C1 { interface annotations { interface org_reflections_TestModel$AC1 {} diff --git a/src/test/java/org/reflections/ReflectionUtilsQueryTest.java b/src/test/java/org/reflections/ReflectionUtilsQueryTest.java index 33e3f47c..7c8708d6 100644 --- a/src/test/java/org/reflections/ReflectionUtilsQueryTest.java +++ b/src/test/java/org/reflections/ReflectionUtilsQueryTest.java @@ -124,11 +124,9 @@ public void testMembers() throws NoSuchMethodException, NoSuchFieldException { @Test public void nestedQuery() { Set> annotations = - get(Annotations.of( + get(AnnotationTypes.of( Methods.of(C4.class)) - .map(Annotation::annotationType) - .filter(a -> !a.getName().startsWith("java.")) - .as()); + .filter(withNamePrefix("org.reflections"))); assertThat(annotations, equalTo(AM1.class)); @@ -198,6 +196,7 @@ public void annotationToMap() { Set> valueMaps = get(Annotations.of( Methods.of(CombinedTestModel.Impl.class)) + .filter(withNamePrefix("org.reflections")) .map(ReflectionUtils::toMap)); // todo proper assert diff --git a/src/test/java/org/reflections/ReflectionUtilsTest.java b/src/test/java/org/reflections/ReflectionUtilsTest.java index 944887a3..ab964dde 100644 --- a/src/test/java/org/reflections/ReflectionUtilsTest.java +++ b/src/test/java/org/reflections/ReflectionUtilsTest.java @@ -6,6 +6,10 @@ import org.reflections.scanners.Scanners; import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -14,6 +18,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -21,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.reflections.ReflectionUtils.*; import static org.reflections.ReflectionsTest.are; +import static org.reflections.ReflectionsTest.equalTo; @SuppressWarnings("unchecked") public class ReflectionUtilsTest { @@ -48,17 +54,11 @@ public void getAllTest() { assertThat(getAllConstructors(TestModel.C4.class, withParametersCount(0)), names(TestModel.C4.class.getName())); - assertEquals(toStringSorted(getAllAnnotations(TestModel.C3.class)), - "[@java.lang.annotation.Documented(), " + - "@java.lang.annotation.Inherited(), " + - "@java.lang.annotation.Retention(value=RUNTIME), " + - "@java.lang.annotation.Target(value=ANNOTATION_TYPE), " + - "@org.reflections.TestModel$AC1(), " + - "@org.reflections.TestModel$AC1n(), " + - "@org.reflections.TestModel$AC2(value=ac2), " + - "@org.reflections.TestModel$AI1(), " + - "@org.reflections.TestModel$AI2(), " + - "@org.reflections.TestModel$MAI1()]"); + Set allAnnotations = getAllAnnotations(TestModel.C3.class); + assertThat(allAnnotations.stream().map(Annotation::annotationType).collect(Collectors.toSet()), + equalTo(Documented.class, Inherited.class, Retention.class, Target.class, + TestModel.MAI1.class, TestModel.AI1.class, TestModel.AI2.class, + TestModel.AC1.class, TestModel.AC1n.class, TestModel.AC2.class)); Method m4 = getMethods(TestModel.C4.class, withName("m4")).iterator().next(); assertEquals(m4.getName(), "m4"); diff --git a/src/test/java/org/reflections/ReflectionsQueryTest.java b/src/test/java/org/reflections/ReflectionsQueryTest.java index d3e340f6..4a69f0fe 100644 --- a/src/test/java/org/reflections/ReflectionsQueryTest.java +++ b/src/test/java/org/reflections/ReflectionsQueryTest.java @@ -92,6 +92,10 @@ public void testTypesAnnotatedWithMemberMatching() { reflections.get(TypesAnnotated.with(AC2.class).asClass()), equalTo(C2.class, C3.class, I3.class, AC3.class, C7.class)); + assertThat("direct types annotated with annotation with Retention(CLASS)", + reflections.get(TypesAnnotated.get(AC3.class).asClass()), + equalTo(C7.class)); + assertThat("transitive subtypes of types annotated with annotation", reflections.get(SubTypes.of(TypesAnnotated.with(AC2.class)).asClass()), equalTo(C2.class, C3.class, I3.class, AC3.class, C7.class, C5.class, C6.class)); @@ -221,7 +225,8 @@ public void testMethods() throws NoSuchMethodException { assertThat("methods with return type void", reflections.get(MethodsReturn.of(void.class)), - equalToNames(C4.class.getDeclaredMethod("m1"), + equalToNames( + C4.class.getDeclaredMethod("m1"), C4.class.getDeclaredMethod("m1", int.class, String[].class), C4.class.getDeclaredMethod("m1", int[][].class, String[][].class))); @@ -241,6 +246,12 @@ public Class annotationType() { assertThat("methods with parameter annotation filter by member matching", reflections.get(MethodsParameter.with(AM1.class).as(Method.class).filter(withAnyParameterAnnotation(am1))), equalTo(C4.class.getDeclaredMethod("m4", String.class))); + + assertThat("methods with parameter annotation visible/invisible", + reflections.get(MethodsParameter.with(AM2.class)), + equalToNames( + C4.class.getDeclaredMethod("m4", String.class), + C4.class.getDeclaredMethod("m1", int.class, String[].class))); } @Test @@ -308,7 +319,7 @@ public void testGetAll() { equalTo("java.lang.Object", "java.lang.annotation.Annotation", "org.reflections.TestModel$MAI1", "org.reflections.TestModel$AI1", "org.reflections.TestModel$AI2", "org.reflections.TestModel$I1", "org.reflections.TestModel$I2", "org.reflections.TestModel$I3", - "org.reflections.TestModel$AF1", "org.reflections.TestModel$AM1", + "org.reflections.TestModel$AF1", "org.reflections.TestModel$AM1", "org.reflections.TestModel$AM2", "org.reflections.TestModel$AC1", "org.reflections.TestModel$AC1n", "org.reflections.TestModel$AC2", "org.reflections.TestModel$AC3", "org.reflections.TestModel$C1", "org.reflections.TestModel$C2", "org.reflections.TestModel$C3", "org.reflections.TestModel$C4", "org.reflections.TestModel$C5", "org.reflections.TestModel$C6", "org.reflections.TestModel$C7")); diff --git a/src/test/java/org/reflections/ReflectionsTest.java b/src/test/java/org/reflections/ReflectionsTest.java index 6f188203..f497cd44 100644 --- a/src/test/java/org/reflections/ReflectionsTest.java +++ b/src/test/java/org/reflections/ReflectionsTest.java @@ -158,6 +158,10 @@ public void testMethodParameter() throws NoSuchMethodException { 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 diff --git a/src/test/java/org/reflections/TestModel.java b/src/test/java/org/reflections/TestModel.java index 06ba7284..68b7b9e7 100644 --- a/src/test/java/org/reflections/TestModel.java +++ b/src/test/java/org/reflections/TestModel.java @@ -3,7 +3,7 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.RetentionPolicy.*; @SuppressWarnings({"ALL"}) public interface TestModel { @@ -26,6 +26,7 @@ public interface TestModel { public @Retention(RUNTIME) @interface AM1 { public abstract String value(); } + public @interface AM2 {} public @Retention(RUNTIME) @interface AF1 { public abstract String value(); } @@ -38,10 +39,10 @@ public C4() { } @AM1("1") public C4(@AM1("1") String f1) { this.f1 = f1; } @AM1("1") protected void m1() {} - @AM1("1") public void m1(int integer, String... strings) {} + @AM1("1") public void m1(int integer, @AM2 String... strings) {} @AM1("1") public void m1(int[][] integer, String[][] strings) {} @AM1("2") public String m3() {return null;} - public String m4(@AM1("2") String string) {return null;} + public String m4(@AM1("2") @AM2 String string) {return null;} public C3 c2toC3(C2 c2) {return null;} public int add(int i1, int i2) { return i1+i2; } } @@ -50,7 +51,6 @@ public class C5 extends C3 {} public @AC2("ac2") interface I3 {} public class C6 implements I3 {} - public @Retention(RUNTIME) @AC2("ac2") @interface AC3 { } + public @AC2("ac2") @interface AC3 { } // not @Retention(RUNTIME) public @AC3 class C7 {} - } diff --git a/src/test/java/org/reflections/VfsTest.java b/src/test/java/org/reflections/VfsTest.java index ea898c68..817ac829 100644 --- a/src/test/java/org/reflections/VfsTest.java +++ b/src/test/java/org/reflections/VfsTest.java @@ -107,7 +107,7 @@ public void vfsFromDirWithJarInName() throws MalformedURLException { try { Vfs.Dir dir = Vfs.fromURL(new URL(format("file:{0}", dirWithJarInName))); - assertEquals(dirWithJarInName, dir.getPath()); + assertEquals(dirWithJarInName.replace("\\", "/"), dir.getPath()); assertEquals(SystemDir.class, dir.getClass()); } finally { newDir.delete(); @@ -121,4 +121,4 @@ private void testVfsDir(Vfs.Dir dir) { } assertFalse(files.isEmpty()); } -} \ No newline at end of file +}