From 5ff7a1d366e8656dfa0f2f476991b558b037e919 Mon Sep 17 00:00:00 2001 From: ronmamo Date: Mon, 25 Oct 2021 21:18:04 +0700 Subject: [PATCH] ref-0.10.2 (#356) * fix npe on empty Resources store * ReflectionUtils.Methods to use getDeclaredMethods * add ReflectionUtils.Resources * add JdkTests * fragile/flaky * fix scanner index for deprecated scanners :( * deprecated scanners javadocs * JdkTests cleanup * Bump maven-javadoc-plugin from 3.1.1 to 3.3.1 (#352) * Detect annotations during expand super types step (#354) * Add subtypes to SubTypes store during expand super types step * Detect annotations during expandSuperType step Co-authored-by: Julian Rubin * npe fix * ConfigurationBuilder.setParallel fluent * readme * arranged ReflectionsExpandSupertypesTest * Correct the wrong match (#334) * Correct the wrong matc * Fetch upstream Co-authored-by: ileler * arranged VfsTest * minors Co-authored-by: ronma Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Julian Rubin Co-authored-by: Julian Rubin Co-authored-by: Kerwin Bryant Co-authored-by: ileler --- README.md | 148 +++++----- pom.xml | 2 +- .../java/org/reflections/Configuration.java | 2 +- .../java/org/reflections/ReflectionUtils.java | 9 +- .../java/org/reflections/Reflections.java | 35 ++- .../reflections/scanners/AbstractScanner.java | 9 +- .../scanners/FieldAnnotationsScanner.java | 5 +- .../scanners/MethodAnnotationsScanner.java | 6 +- .../scanners/MethodParameterScanner.java | 22 +- .../scanners/ResourcesScanner.java | 5 +- .../org/reflections/scanners/Scanners.java | 3 +- .../reflections/scanners/SubTypesScanner.java | 11 +- .../scanners/TypeAnnotationsScanner.java | 5 +- .../util/ConfigurationBuilder.java | 5 +- .../reflections/util/UtilQueryBuilder.java | 2 +- src/main/java/org/reflections/vfs/Vfs.java | 18 +- src/test/java/org/reflections/JdkTests.java | 258 ++++++++++++++++++ src/test/java/org/reflections/MoreTests.java | 8 +- .../reflections/ReflectionUtilsQueryTest.java | 13 +- .../org/reflections/ReflectionUtilsTest.java | 5 +- .../ReflectionsExpandSupertypesTest.java | 100 +++++-- .../java/org/reflections/ReflectionsTest.java | 57 ++-- src/test/java/org/reflections/VfsTest.java | 34 +++ src/test/resources/jarWithBootLibJar.jar | Bin 0 -> 942 bytes 24 files changed, 594 insertions(+), 168 deletions(-) create mode 100644 src/test/java/org/reflections/JdkTests.java create mode 100644 src/test/resources/jarWithBootLibJar.jar diff --git a/README.md b/README.md index 9aa03ebd..6fd33bd0 100644 --- a/README.md +++ b/README.md @@ -40,50 +40,61 @@ Set> subTypes = reflections.get(SubTypes.of(SomeType.class).asClass()); Set> annotated = - reflections.get(TypesAnnotated.with(SomeAnnotation.class).asClass()); + reflections.get(SubTypes.of(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.* +Or using previous 0.9.x APIs, for example: + +```java +Set> subTypes = + reflections.getSubTypesOf(SomeType.class); + +Set> annotated = + reflections.getTypesAnnotatedWith(SomeAnnotation.class); +``` + +*Note that there are some breaking changes with Reflections 0.10+, along with performance improvements and more functional API, see below.* ### Scan -Creating Reflections instance requires providing scanning configuration: +Creating Reflections instance requires [ConfigurationBuilder](https://ronmamo.github.io/reflections/org/reflections/util/ConfigurationBuilder.html), typically configured with packages and [Scanners](https://ronmamo.github.io/reflections/org/reflections/scanners/Scanners.html) to use: ```java -// scan for: -// urls in classpath that contain 'com.my.project' package -// filter types starting with 'com.my.project' -// use the default scanners SubTypes and TypesAnnotated +// typical usage: scan package with the default scanners SubTypes, TypesAnnotated Reflections reflections = new Reflections( new ConfigurationBuilder() .forPackage("com.my.project") .filterInputsBy(new FilterBuilder().includePackage("com.my.project"))); -// or similarly +// or similarly using the convenient constructor Reflections reflections = new Reflections("com.my.project"); +``` -// another example +Other examples: +```java +import static org.reflections.scanners.Scanners.*; + +// scan package with specific scanners Reflections reflections = new Reflections( new ConfigurationBuilder() - .addUrls(ClasspathHelper.forPackage("com.my.project")) // same as forPackage - .setScanners(Scanners.values()) // all standard scanners - .filterInputsBy(new FilterBuilder() // optionally include/exclude packages - .includePackage("com.my.project") - .excludePackage("com.my.project.exclude"))); -``` + .forPackage("com.my.project") + .filterInputsBy(new FilterBuilder().includePackage("com.my.project").excludePackage("com.my.project.exclude")) + .setScanners(TypesAnnotated, MethodsAnnotated, MethodsReturn)); -*See more in [ConfigurationBuilder](https://ronmamo.github.io/reflections/org/reflections/util/ConfigurationBuilder.html).* +// scan package with all standard scanners +Reflections reflections = new Reflections("com.my.project", Scanners.values()); +``` Note that: -* **Scanners must be configured in order to be queried, otherwise an empty result is returned.** -If not specified, default scanners are `SubTypes` and `TypesAnnotated`. For all standard [Scanners](https://ronmamo.github.io/reflections/org/reflections/scanners/Scanners.html) use `Scanners.values()` [(src)](src/main/java/org/reflections/scanners/Scanners.java). -* **All relevant URLs should be configured.** +* **Scanner must be configured in order to be queried, otherwise an empty result is returned** +If not specified, default scanners will be used SubTypes, TypesAnnotated. +For all standard scanners use `Scanners.values()`. See more scanners in the source [package](https://ronmamo.github.io/reflections/org/reflections/scanners). +* **All relevant URLs should be configured** +Consider `.filterInputsBy()` in case too many classes are scanned. If required, Reflections will [expand super types](https://ronmamo.github.io/reflections/org/reflections/Reflections.html#expandSuperTypes(java.util.Map)) in order to get the transitive closure metadata without scanning large 3rd party urls. -Consider adding inputs filter in case too many classes are scanned. * Classloader can optionally be used for resolving runtime classes from names. ### Query Once Reflections was instantiated and scanning was successful, it can be used for querying the indexed metadata. -Standard [Scanners](https://ronmamo.github.io/reflections/org/reflections/scanners/Scanners.html) are provided for query using `reflections.get()`, for example: ```java import static org.reflections.scanners.Scanners.*; @@ -92,18 +103,14 @@ import static org.reflections.scanners.Scanners.*; Set> modules = reflections.get(SubTypes.of(Module.class).asClass()); -// TypesAnnotated +// TypesAnnotated (*1) Set> singletons = reflections.get(TypesAnnotated.with(Singleton.class).asClass()); -// MethodAnnotated +// MethodsAnnotated Set resources = reflections.get(MethodsAnnotated.with(GetMapping.class).as(Method.class)); -// ConstructorsAnnotated -Set injectables = - reflections.get(ConstructorsAnnotated.with(Inject.class).as(Constructor.class)); - // FieldsAnnotated Set ids = reflections.get(FieldsAnnotated.with(Id.class).as(Field.class)); @@ -113,7 +120,7 @@ Set properties = reflections.get(Resources.with(".*\\.properties")); ``` -Member scanners: +More scanners: ```java // MethodsReturn @@ -128,51 +135,52 @@ Set someMethods = Set pathParam = reflections.get(MethodsParameter.of(PathParam.class).as(Method.class)); +// ConstructorsAnnotated +Set injectables = + reflections.get(ConstructorsAnnotated.with(Inject.class).as(Constructor.class)); + // ConstructorsSignature Set someConstructors = reflections.get(ConstructorsSignature.of(String.class).as(Constructor.class)); -// ConstructorsParameter -Set pathParam = - reflections.get(ConstructorsParameter.of(PathParam.class).as(Constructor.class)); +// MethodParameterNamesScanner +List parameterNames = + reflections.getMemberParameterNames(member); + +// MemberUsageScanner +Set usages = + reflections.getMemberUsages(member) ``` *See more examples in [ReflectionsQueryTest](src/test/java/org/reflections/ReflectionsQueryTest.java).* -Scanner queries return `Set` by default, if not using `as() / asClass()` mappers: -```java -Set moduleNames = - reflections.get(SubTypes.of(Module.class)); - -Set singleNames = - reflections.get(TypesAnnotated.with(Singleton.class)); -``` -Note that previous 0.9.x API is still supported, for example: -```java -Set> modules = - reflections.getSubTypesOf(Module.class); +*Note that previous 0.9.x APIs are still supported* -Set> singletons = - reflections.getTypesAnnotatedWith(Singleton.class); -```
- Compare Scanners and previous 0.9.x API - -| Scanners | previous 0.9.x API | -| -------- | ------------------ | -| `get(SubType.of(T))` | getSubTypesOf(T) | -| `get(TypesAnnotated.with(A))` | getTypesAnnotatedWith(A) | -| `get(MethodsAnnotated.with(A))` | getMethodsAnnotatedWith(A) | -| `get(ConstructorsAnnotated.with(A))` | getConstructorsAnnotatedWith(A) | -| `get(FieldsAnnotated.with(A))` | getFieldsAnnotatedWith(A) | -| `get(Resources.with(regex))` | getResources(regex) | -| `get(MethodsParameter.with(P))` | getMethodsWithParameter(P) | -| `get(MethodsSignature.of(P, ...))` | getMethodsWithSignature(P, ...) | -| `get(MethodsReturn.of(T))` | getMethodsReturn(T) | -| `get(ConstructorsParameter.with(P))` | getConstructorsWithParameter(P) | -| `get(ConstructorsSignature.of(P, ...))` | getConstructorsWithSignature(P, ...) | + Compare Scanners and previous 0.9.x API (*) + +| Scanners | previous 0.9.x API | previous Scanner | +| -------- | ------------------ | ------ | +| `get(SubType.of(T))` | getSubTypesOf(T) | ~~SubTypesScanner~~ | +| `get(SubTypes.of(`
    `TypesAnnotated.with(A)))` | getTypesAnnotatedWith(A) *(1)*| ~~TypeAnnotationsScanner~~ | +| `get(MethodsAnnotated.with(A))` | getMethodsAnnotatedWith(A) | ~~MethodAnnotationsScanner~~ | +| `get(ConstructorsAnnotated.with(A))` | getConstructorsAnnotatedWith(A) *(2)*| ~~MethodAnnotationsScanner~~ | +| `get(FieldsAnnotated.with(A))` | getFieldsAnnotatedWith(A) | ~~FieldAnnotationsScanner~~ | +| `get(Resources.with(regex))` | getResources(regex) | ~~ResourcesScanner~~ | +| `get(MethodsParameter.with(P))` | getMethodsWithParameter(P) *(3)*
~~getMethodsWithAnyParamAnnotated(P)~~| ~~MethodParameterScanner~~
*obsolete* | +| `get(MethodsSignature.of(P, ...))` | getMethodsWithSignature(P, ...) *(3)
~~getMethodsMatchParams(P, ...)~~*| " | +| `get(MethodsReturn.of(T))` | getMethodsReturn(T) *(3)*| " | +| `get(ConstructorsParameter.with(P))` | getConstructorsWithParameter(P) *(3)
~~getConstructorsWithAnyParamAnnotated(P)~~*| " | +| `get(ConstructorsSignature.of(P, ...))` | getConstructorsWithSignature(P, ...) *(3)
~~getConstructorsMatchParams(P, ...)~~*| " | *Note: `asClass()` and `as()` mappings were omitted* + +*(1): The equivalent of `getTypesAnnotatedWith(A)` is `get(SubTypes.of(TypesAnnotated.with(A)))`, including SubTypes* + +*(2): MethodsAnnotatedScanner does not include constructor annotation scanning, use instead Scanners.ConstructorsAnnotated* + +*(3): MethodParameterScanner is obsolete, use instead as required: +Scanners.MethodsParameter, Scanners.MethodsSignature, Scanners.MethodsReturn, Scanners.ConstructorsParameter, Scanners.ConstructorsSignature*
## ReflectionUtils @@ -187,13 +195,15 @@ Set> superTypes = get(SuperTypes.of(T)); Set fields = get(Fields.of(T)); Set constructors = get(Constructors.of(T)); Set methods = get(Methods.of(T)); +Set resources = get(Resources.with(T)); + Set annotations = get(Annotations.of(T)); -Set> annotationTypes = get(AnnotationTypess.of(T)); +Set> annotationTypes = get(AnnotationTypes.of(T)); ``` *Previous ReflectionUtils 0.9.x API is still supported though marked for removal, more info in the javadocs.* -## QueryBuilder and QueryFunction +## Query API Each Scanner and ReflectionUtils function implements [QueryBuilder](https://ronmamo.github.io/reflections/org/reflections/util/QueryBuilder.html), and supports: * `get()` - function returns direct values * `with()` or `of()` - function returns all transitive values @@ -206,21 +216,19 @@ Next, each function implements [QueryFunction](https://ronmamo.github.io/reflect and provides fluent functional interface for composing `filter()`, `map()`, `flatMap()`, `as()` and more, such that: ```java +// filter, as/map QueryFunction getters = Methods.of(C1.class) .filter(withModifier(Modifier.PUBLIC)) .filter(withPrefix("get").and(withParametersCount(0))) .as(Method.class); -``` -Query functions can be composed, for example: -```java -// compose Scanner and ReflectionUtils functions +// compose Scanners and ReflectionUtils functions QueryFunction methods = SubTypes.of(type).asClass() // <-- classpath scanned metadata .flatMap(Methods::of); // <-- java reflection api -// compose function of function +// function of function QueryFunction> queryAnnotations = Annotations.of(Methods.of(C4.class)) .map(Annotation::annotationType); @@ -256,11 +264,11 @@ Check the [tests](src/test/java/org/reflections) folder for more examples and AP ### What else? - **Integrating with build lifecycle** -It is sometime useful to save the scanned metadata into xml/json as part of the build lifecycle for generating resources, +It is sometime useful to `Reflections.save()` the scanned metadata into xml/json as part of the build lifecycle for generating resources, and then collect it on bootstrap with `Reflections.collect()` and avoid scanning. *See [reflections-maven](https://github.com/ronmamo/reflections-maven/) for example*. - [JavaCodeSerializer](https://ronmamo.github.io/reflections/org/reflections/serializers/JavaCodeSerializer.html) - scanned metadata can be persisted into a generated Java source code. Although less common, it can be useful for accessing types and members in a strongly typed manner. *(see [example](src/test/java/org/reflections/MyTestModelStore.java))* -- [AnnotationMergeCollector](https://ronmamo.github.io/reflections/org/reflections/util/AnnotationMergeCollector.html) - can be used to merge similar annotations, for example for finding effective REST controller endpoints. *(see [test](src/test/java/org/reflections/ReflectionUtilsQueryTest.java#L216))* +- [AnnotationMergeCollector](https://ronmamo.github.io/reflections/org/reflections/util/AnnotationMergeCollector.html) - can be used to merge similar annotations. *(see [test](src/test/java/org/reflections/ReflectionUtilsQueryTest.java#L216))* - `MemberUsageScanner` - experimental scanner allow querying for member usages `getMemberUsages()` of packages/types/elements in the classpath. Can be used for finding usages between packages, layers, modules, types etc. diff --git a/pom.xml b/pom.xml index 795050cf..98931d65 100644 --- a/pom.xml +++ b/pom.xml @@ -152,7 +152,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.3.1 attach-javadocs diff --git a/src/main/java/org/reflections/Configuration.java b/src/main/java/org/reflections/Configuration.java index 1a0b4d39..37bf66fc 100644 --- a/src/main/java/org/reflections/Configuration.java +++ b/src/main/java/org/reflections/Configuration.java @@ -28,6 +28,6 @@ public interface Configuration { ClassLoader[] getClassLoaders(); /** if true (default), expand super types after scanning, for super types that were not scanned. - *

see {@link org.reflections.Reflections#expandSuperTypes(Map)}*/ + *

see {@link Reflections#expandSuperTypes(Map, Map)}*/ boolean shouldExpandSuperTypes(); } diff --git a/src/main/java/org/reflections/ReflectionUtils.java b/src/main/java/org/reflections/ReflectionUtils.java index 418c6394..64102d08 100644 --- a/src/main/java/org/reflections/ReflectionUtils.java +++ b/src/main/java/org/reflections/ReflectionUtils.java @@ -1,5 +1,6 @@ package org.reflections; +import org.reflections.util.ClasspathHelper; import org.reflections.util.QueryFunction; import org.reflections.util.ReflectionUtilsPredicates; import org.reflections.util.UtilQueryBuilder; @@ -10,8 +11,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.net.URL; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -137,7 +140,7 @@ public QueryFunction> of(AnnotatedElement ele /** query methods

{@code get(Methods.of(type)) -> Set}
*/ public static final UtilQueryBuilder, Method> Methods = - element -> ctx -> Arrays.stream(element.getMethods()).filter(notObjectMethod).collect(Collectors.toCollection(LinkedHashSet::new)); + element -> ctx -> Arrays.stream(element.getDeclaredMethods()).filter(notObjectMethod).collect(Collectors.toCollection(LinkedHashSet::new)); /** query constructors
{@code get(Constructors.of(type)) -> Set }
*/ public static final UtilQueryBuilder, Constructor> Constructors = @@ -147,6 +150,10 @@ public QueryFunction> of(AnnotatedElement ele public static final UtilQueryBuilder, Field> Fields = element -> ctx -> Arrays.stream(element.getDeclaredFields()).collect(Collectors.toCollection(LinkedHashSet::new)); + /** query url resources using {@link ClassLoader#getResources(java.lang.String)}
{@code get(Resources.with(name)) -> Set }
*/ + public static final UtilQueryBuilder Resources = + element -> ctx -> new HashSet<>(ClasspathHelper.forResource(element)); + public static UtilQueryBuilder extendType() { return element -> { if (element instanceof Class && !((Class) element).isAnnotation()) { diff --git a/src/main/java/org/reflections/Reflections.java b/src/main/java/org/reflections/Reflections.java index 71d39d07..6454497f 100644 --- a/src/main/java/org/reflections/Reflections.java +++ b/src/main/java/org/reflections/Reflections.java @@ -81,7 +81,7 @@ * } * *

All relevant URLs should be configured. - *
If required, Reflections will {@link #expandSuperTypes(Map)} in order to get the transitive closure metadata without scanning large 3rd party urls. + *
If required, Reflections will {@link #expandSuperTypes(Map, Map)} in order to get the transitive closure metadata without scanning large 3rd party urls. *

{@link Scanners} must be configured in order to be queried, otherwise an empty result is returned. *
Default scanners are {@code SubTypes} and {@code TypesAnnotated}. * For all standard scanners use {@code Scanners.values()}. @@ -125,7 +125,7 @@ public Reflections(Configuration configuration) { this.configuration = configuration; Map>> storeMap = scan(); if (configuration.shouldExpandSuperTypes()) { - expandSuperTypes(storeMap.get(SubTypes.index())); + expandSuperTypes(storeMap.get(SubTypes.index()), storeMap.get(TypesAnnotated.index())); } store = new Store(storeMap); } @@ -309,7 +309,7 @@ public Reflections merge(Reflections reflections) { /** * expand super types after scanning, for super types that were not scanned. - *
this is helpful in finding the transitive closure without scanning all 3rd party dependencies. + *
this is helpful for finding the transitive closure without scanning all 3rd party dependencies. *

* for example, for classes A,B,C where A supertype of B, B supertype of C (A -> B -> C): *
    @@ -317,25 +317,36 @@ public Reflections merge(Reflections reflections) { *
  • if expanding supertypes, B will be expanded with A (A->B in store) - then getSubTypes(A) will return C
  • *
*/ - public void expandSuperTypes(Map> map) { - if (map == null || map.isEmpty()) return; - Set keys = new LinkedHashSet<>(map.keySet()); - keys.removeAll(map.values().stream().flatMap(Collection::stream).collect(Collectors.toSet())); + public void expandSuperTypes(Map> subTypesStore, Map> typesAnnotatedStore) { + if (subTypesStore == null || subTypesStore.isEmpty()) return; + Set keys = new LinkedHashSet<>(subTypesStore.keySet()); + keys.removeAll(subTypesStore.values().stream().flatMap(Collection::stream).collect(Collectors.toSet())); keys.remove("java.lang.Object"); for (String key : keys) { Class type = forClass(key, loaders()); if (type != null) { - expandSupertypes(map, key, type); + expandSupertypes(subTypesStore, typesAnnotatedStore, key, type); } } } - private void expandSupertypes(Map> map, String key, Class type) { + private void expandSupertypes(Map> subTypesStore, + Map> typesAnnotatedStore, String key, Class type) { + Set typeAnnotations = ReflectionUtils.getAnnotations(type); + if (typesAnnotatedStore != null && !typeAnnotations.isEmpty()) { + String typeName = type.getName(); + for (Annotation typeAnnotation : typeAnnotations) { + String annotationName = typeAnnotation.annotationType().getName(); + typesAnnotatedStore.computeIfAbsent(annotationName, s -> new HashSet<>()).add(typeName); + } + } for (Class supertype : ReflectionUtils.getSuperTypes(type)) { String supertypeName = supertype.getName(); - if (!map.containsKey(supertypeName)) { - map.computeIfAbsent(supertypeName, s -> new HashSet<>()).add(key); - expandSupertypes(map, supertypeName, supertype); + if (subTypesStore.containsKey(supertypeName)) { + subTypesStore.get(supertypeName).add(key); + } else { + subTypesStore.computeIfAbsent(supertypeName, s -> new HashSet<>()).add(key); + expandSupertypes(subTypesStore, typesAnnotatedStore, supertypeName, supertype); } } } diff --git a/src/main/java/org/reflections/scanners/AbstractScanner.java b/src/main/java/org/reflections/scanners/AbstractScanner.java index f35b85a9..53252c84 100644 --- a/src/main/java/org/reflections/scanners/AbstractScanner.java +++ b/src/main/java/org/reflections/scanners/AbstractScanner.java @@ -6,13 +6,18 @@ import java.util.Map; @Deprecated -public class AbstractScanner implements Scanner { +class AbstractScanner implements Scanner { protected final Scanner scanner; - public AbstractScanner(Scanner scanner) { + AbstractScanner(Scanner scanner) { this.scanner = scanner; } + @Override + public String index() { + return scanner.index(); + } + @Override public List> scan(final ClassFile cls) { return scanner.scan(cls); diff --git a/src/main/java/org/reflections/scanners/FieldAnnotationsScanner.java b/src/main/java/org/reflections/scanners/FieldAnnotationsScanner.java index 11c836af..2545db59 100644 --- a/src/main/java/org/reflections/scanners/FieldAnnotationsScanner.java +++ b/src/main/java/org/reflections/scanners/FieldAnnotationsScanner.java @@ -1,11 +1,12 @@ package org.reflections.scanners; /** scan field annotations. - * {@code Deprecated}, use {@link Scanners#FieldsAnnotated} instead - * */ + * {@code Deprecated}, use {@link Scanners#FieldsAnnotated} instead */ @Deprecated public class FieldAnnotationsScanner extends AbstractScanner { + /** {@code Deprecated}, use {@link Scanners#FieldsAnnotated} instead */ + @Deprecated public FieldAnnotationsScanner() { super(Scanners.FieldsAnnotated); } diff --git a/src/main/java/org/reflections/scanners/MethodAnnotationsScanner.java b/src/main/java/org/reflections/scanners/MethodAnnotationsScanner.java index dddb9320..7c15083e 100644 --- a/src/main/java/org/reflections/scanners/MethodAnnotationsScanner.java +++ b/src/main/java/org/reflections/scanners/MethodAnnotationsScanner.java @@ -1,11 +1,13 @@ package org.reflections.scanners; /** scan method annotations. - * {@code Deprecated}, use {@link Scanners#MethodsAnnotated} instead - * */ + *

breaking change: does not include constructor annotations, use {@link Scanners#ConstructorsAnnotated} instead + *

{@code Deprecated}, use {@link Scanners#MethodsAnnotated} and {@link Scanners#ConstructorsAnnotated} instead */ @Deprecated public class MethodAnnotationsScanner extends AbstractScanner { + /** {@code Deprecated}, use {@link Scanners#MethodsAnnotated} and {@link Scanners#ConstructorsAnnotated} instead */ + @Deprecated public MethodAnnotationsScanner() { super(Scanners.MethodsAnnotated); } diff --git a/src/main/java/org/reflections/scanners/MethodParameterScanner.java b/src/main/java/org/reflections/scanners/MethodParameterScanner.java index b47579b4..ae073f05 100644 --- a/src/main/java/org/reflections/scanners/MethodParameterScanner.java +++ b/src/main/java/org/reflections/scanners/MethodParameterScanner.java @@ -1,11 +1,29 @@ package org.reflections.scanners; -/** scan methods/constructors and indexes parameters, return type and parameter annotations. - * {@code Deprecated}, use {@link Scanners#MethodsParameter} instead +/** Not supported since 0.10, will be removed. + *

{@code Deprecated}, use instead: + *
    + *
  • {@link Scanners#MethodsParameter}
  • + *
  • {@link Scanners#MethodsSignature}
  • + *
  • {@link Scanners#MethodsReturn}
  • + *
  • {@link Scanners#ConstructorsParameter}
  • + *
  • {@link Scanners#ConstructorsSignature}
  • + *
* */ @Deprecated public class MethodParameterScanner extends AbstractScanner { + /** Not supported since 0.10, will be removed. + *

{@code Deprecated}, use instead: + *
    + *
  • {@link Scanners#MethodsParameter}
  • + *
  • {@link Scanners#MethodsSignature}
  • + *
  • {@link Scanners#MethodsReturn}
  • + *
  • {@link Scanners#ConstructorsParameter}
  • + *
  • {@link Scanners#ConstructorsSignature}
  • + *
+ */ + @Deprecated public MethodParameterScanner() { super(Scanners.MethodsParameter); } diff --git a/src/main/java/org/reflections/scanners/ResourcesScanner.java b/src/main/java/org/reflections/scanners/ResourcesScanner.java index 8e20c305..1c8eb461 100644 --- a/src/main/java/org/reflections/scanners/ResourcesScanner.java +++ b/src/main/java/org/reflections/scanners/ResourcesScanner.java @@ -2,11 +2,12 @@ /** collects all resources that are not classes in a collection *

key: value - {web.xml: WEB-INF/web.xml}

- * {@code Deprecated}, use {@link Scanners#Resources} instead - * */ + * {@code Deprecated}, use {@link Scanners#Resources} instead */ @Deprecated public class ResourcesScanner extends AbstractScanner { + /** {@code Deprecated}, use {@link Scanners#Resources} instead */ + @Deprecated public ResourcesScanner() { super(Scanners.Resources); } diff --git a/src/main/java/org/reflections/scanners/Scanners.java b/src/main/java/org/reflections/scanners/Scanners.java index 37b0eb9e..8e2e02fc 100644 --- a/src/main/java/org/reflections/scanners/Scanners.java +++ b/src/main/java/org/reflections/scanners/Scanners.java @@ -115,7 +115,8 @@ public void scan(ClassFile classFile, List> entries) { @Override public QueryFunction with(String pattern) { - return store -> store.get(index()).entrySet().stream().filter(entry -> entry.getKey().matches(pattern)) + return store -> store.getOrDefault(index(), Collections.emptyMap()) + .entrySet().stream().filter(entry -> entry.getKey().matches(pattern)) .flatMap(entry -> entry.getValue().stream()).collect(Collectors.toCollection(LinkedHashSet::new)); } }, diff --git a/src/main/java/org/reflections/scanners/SubTypesScanner.java b/src/main/java/org/reflections/scanners/SubTypesScanner.java index 4dcb4c43..35b93cbc 100644 --- a/src/main/java/org/reflections/scanners/SubTypesScanner.java +++ b/src/main/java/org/reflections/scanners/SubTypesScanner.java @@ -6,17 +6,20 @@ import java.util.Map; /** scan superclass and interfaces of a class, allowing a reverse lookup for subtypes. - * {@code Deprecated}, use {@link Scanners#SubTypes} instead - * */ + * {@code Deprecated}, use {@link Scanners#SubTypes} instead */ @Deprecated public class SubTypesScanner extends AbstractScanner { - /** create new SubTypesScanner. will exclude direct Object subtypes */ + /** create new SubTypesScanner. will exclude direct Object subtypes + * {@code Deprecated}, use {@link Scanners#SubTypes} instead */ + @Deprecated public SubTypesScanner() { super(Scanners.SubTypes); } - /** create new SubTypesScanner. include direct {@link Object} subtypes in results. */ + /** create new SubTypesScanner. include direct {@link Object} subtypes in results. + * {@code Deprecated}, use {@link Scanners#SubTypes} instead */ + @Deprecated public SubTypesScanner(boolean excludeObjectClass) { super(excludeObjectClass ? Scanners.SubTypes : Scanners.SubTypes.filterResultsBy(s -> true)); } diff --git a/src/main/java/org/reflections/scanners/TypeAnnotationsScanner.java b/src/main/java/org/reflections/scanners/TypeAnnotationsScanner.java index 7777c872..37bc1cad 100644 --- a/src/main/java/org/reflections/scanners/TypeAnnotationsScanner.java +++ b/src/main/java/org/reflections/scanners/TypeAnnotationsScanner.java @@ -1,11 +1,12 @@ package org.reflections.scanners; /** scan class annotations, where @Retention(RetentionPolicy.RUNTIME). - * {@code Deprecated}, use {@link Scanners#TypesAnnotated} instead - **/ + * {@code Deprecated}, use {@link Scanners#TypesAnnotated} instead */ @Deprecated public class TypeAnnotationsScanner extends AbstractScanner { + /** {@code Deprecated}, use {@link Scanners#TypesAnnotated} instead */ + @Deprecated public TypeAnnotationsScanner() { super(Scanners.TypesAnnotated); } diff --git a/src/main/java/org/reflections/util/ConfigurationBuilder.java b/src/main/java/org/reflections/util/ConfigurationBuilder.java index aff76c4f..2b455a28 100644 --- a/src/main/java/org/reflections/util/ConfigurationBuilder.java +++ b/src/main/java/org/reflections/util/ConfigurationBuilder.java @@ -199,8 +199,9 @@ public boolean isParallel() { } /** if true, scan urls in parallel. */ - public void setParallel(boolean parallel) { + public ConfigurationBuilder setParallel(boolean parallel) { isParallel = parallel; + return this; } @Override @@ -230,7 +231,7 @@ public boolean shouldExpandSuperTypes() { } /** if set to true, Reflections will expand super types after scanning. - *

see {@link org.reflections.Reflections#expandSuperTypes(Map)} */ + *

see {@link org.reflections.Reflections#expandSuperTypes(Map, Map)} */ public ConfigurationBuilder setExpandSuperTypes(boolean expandSuperTypes) { this.expandSuperTypes = expandSuperTypes; return this; diff --git a/src/main/java/org/reflections/util/UtilQueryBuilder.java b/src/main/java/org/reflections/util/UtilQueryBuilder.java index 91d974e2..7befd734 100644 --- a/src/main/java/org/reflections/util/UtilQueryBuilder.java +++ b/src/main/java/org/reflections/util/UtilQueryBuilder.java @@ -13,7 +13,7 @@ *

{@code UtilQueryBuilder builder =
  *   element -> store -> element.getDeclaredAnnotations()} 
*/ -public interface UtilQueryBuilder extends NameHelper { +public interface UtilQueryBuilder { /** get direct values of given element */ QueryFunction get(F element); diff --git a/src/main/java/org/reflections/vfs/Vfs.java b/src/main/java/org/reflections/vfs/Vfs.java index a64732a4..a153ec17 100644 --- a/src/main/java/org/reflections/vfs/Vfs.java +++ b/src/main/java/org/reflections/vfs/Vfs.java @@ -186,20 +186,24 @@ public static java.io.File getFile(URL url) { return null; } - + private static boolean hasJarFileInPath(URL url) { - return url.toExternalForm().matches(".*\\.jar(!.*|$)"); - } + return url.toExternalForm().matches(".*\\.jar(!.*|$)"); + } + + private static boolean hasInnerJarFileInPath(URL url) { + return url.toExternalForm().matches(".+\\.jar!/.+"); + } /** default url types used by {@link org.reflections.vfs.Vfs#fromURL(java.net.URL)} *

*

jarFile - creates a {@link org.reflections.vfs.ZipDir} over jar file - *

jarUrl - creates a {@link org.reflections.vfs.ZipDir} over a jar url (contains ".jar!/" in it's name), using Java's {@link JarURLConnection} + *

jarUrl - creates a {@link org.reflections.vfs.ZipDir} over a jar url, using Java's {@link JarURLConnection} *

directory - creates a {@link org.reflections.vfs.SystemDir} over a file system directory *

jboss vfs - for protocols vfs, using jboss vfs (should be provided in classpath) *

jboss vfsfile - creates a {@link UrlTypeVFS} for protocols vfszip and vfsfile. *

bundle - for bundle protocol, using eclipse FileLocator (should be provided in classpath) - *

jarInputStream - creates a {@link JarInputDir} over jar files, using Java's JarInputStream + *

jarInputStream - creates a {@link JarInputDir} over jar files (contains ".jar!/" in it's name), using Java's JarInputStream * */ public enum DefaultUrlTypes implements UrlType { jarFile { @@ -214,7 +218,7 @@ public Dir createDir(final URL url) throws Exception { jarUrl { public boolean matches(URL url) { - return "jar".equals(url.getProtocol()) || "zip".equals(url.getProtocol()) || "wsjar".equals(url.getProtocol()); + return ("jar".equals(url.getProtocol()) || "zip".equals(url.getProtocol()) || "wsjar".equals(url.getProtocol())) && !hasInnerJarFileInPath(url); } public Dir createDir(URL url) throws Exception { @@ -252,7 +256,7 @@ public boolean matches(URL url) { } public Vfs.Dir createDir(URL url) throws Exception { - return JbossDir.createDir(url); + return JbossDir.createDir(url); } }, diff --git a/src/test/java/org/reflections/JdkTests.java b/src/test/java/org/reflections/JdkTests.java new file mode 100644 index 00000000..630c5361 --- /dev/null +++ b/src/test/java/org/reflections/JdkTests.java @@ -0,0 +1,258 @@ +package org.reflections; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.reflections.scanners.Scanner; +import org.reflections.scanners.Scanners; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.UtilQueryBuilder; +import org.reflections.vfs.Vfs; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.reflections.ReflectionUtils.get; + +/** + * test reflection symmetry between jrt scanned metadata (Scanners) and java reflection accessibility (ReflectionUtils functions). + *

except for known differences per jdk version, these pairs should access similar metadata: + * SubTypes/SuperTypes, TypesAnnotated/AnnotatedTypes, MethodsAnnotated/AnnotatedTypes, Resources etc... + *

tested with AdoptOpenJDK + */ +@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument"}) +public class JdkTests { + + private static Reflections reflections; + + @BeforeAll + static void init() { + if (!Vfs.getDefaultUrlTypes().get(0).getClass().equals(JrtUrlType.class)) { + Vfs.addDefaultURLTypes(new JrtUrlType()); + } + URL urls = ClasspathHelper.forClass(Object.class); + measure("before"); + + reflections = new Reflections( + new ConfigurationBuilder() + .addUrls(urls) + .setScanners(Scanners.values())); + + measure("scan"); + } + + @AfterAll + static void cleanup() { + if (Vfs.getDefaultUrlTypes().get(0).getClass().equals(JrtUrlType.class)) { + Vfs.getDefaultUrlTypes().remove(0); + } + reflections.getStore().clear(); + measure("cleanup"); + } + + @Test + public void checkSubTypes() { + Map> diff = reflect( + Scanners.SubTypes, + ReflectionUtils.SuperTypes, + Class.class); + + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkTypesAnnotated() { + Map> diff = reflect( + Scanners.TypesAnnotated, + ReflectionUtils.AnnotationTypes, + Class.class); + + Arrays.asList("jdk.internal.PreviewFeature", // jdk 15 + "jdk.internal.javac.PreviewFeature") // jdk 17 + .forEach(diff::remove); + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkMethodsAnnotated() { + Map> diff = reflect( + Scanners.MethodsAnnotated, + ReflectionUtils.AnnotationTypes, + Method.class); + + // todo fix differences @A2 such as - @A1 public @A2 result method(...) + Arrays.asList("com.sun.istack.internal.NotNull", // jdk 8 + "com.sun.istack.internal.Nullable", + "sun.reflect.CallerSensitive", + "java.lang.invoke.LambdaForm$Hidden", + "jdk.internal.reflect.CallerSensitive", // jdk 11, 13, 15 + "jdk.internal.PreviewFeature") // jdk 15 + .forEach(diff::remove); + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkConstructorsAnnotated() { + Map> diff = reflect( + Scanners.ConstructorsAnnotated, + ReflectionUtils.AnnotationTypes, + Constructor.class); + + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkFieldsAnnotated() { + Map> diff = reflect( + Scanners.FieldsAnnotated, + ReflectionUtils.AnnotationTypes, + Field.class); + + Arrays.asList("com.sun.istack.internal.NotNull", // jdk 8 + "com.sun.istack.internal.Nullable", + "jdk.internal.PreviewFeature", // jdk 15 + "jdk.internal.vm.annotation.Stable") // jdk 17 + .forEach(diff::remove); + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkResources() { + Set diff = new HashSet<>(); + Map> mmap = reflections.getStore().get(Scanners.Resources.index()); + mmap.values().forEach(resources -> + resources.forEach(resource -> { + Set urls = get(ReflectionUtils.Resources.get(resource)); +// if (urls == null || urls.isEmpty()) diff.add(resource); + for (URL url : urls) { + try { if (!Files.exists(JrtUrlType.getJrtRealPath(url))) diff.add(resource); } + catch (Exception e) { diff.add(resource); } + } + })); + System.out.println(Scanners.Resources.index() + ": " + mmap.values().stream().mapToInt(Set::size).sum() + ", missing: " + diff.size()); + + Arrays.asList("META-INF/MANIFEST.MF") // jdk 8 + .forEach(diff::remove); + assertEquals(diff, Collections.emptySet()); + } + + @Test + public void checkMethodsSignature() { +// Map> diffMethodSignature = +// findDiff(reflections, Scanners.MethodsSignature, ReflectionUtils.MethodSignature, Field.class); +// assertEquals(diffMethodSignature, Collections.emptyMap()); } + } + + private Map> reflect( + Scanner scanner, UtilQueryBuilder utilQueryBuilder, Class resultType) { + Map> mmap = reflections.getStore().get(scanner.index()); + Map> missing = new HashMap<>(); + mmap.forEach((key, strings) -> + strings.forEach(string -> { + //noinspection unchecked + F element = (F) reflections.forName(string, resultType); + if (element == null || !reflections.toNames(get(utilQueryBuilder.get(element))).contains(key)) { + missing.computeIfAbsent(key, k -> new HashSet<>()).add(string); + } + })); + System.out.println(scanner.index() + ": " + mmap.values().stream().mapToInt(Set::size).sum() + ", missing: " + missing.values().stream().mapToInt(Set::size).sum()); + return missing; + } + + private static void measure(String s) { + System.out.printf("-> %s %s ", s, mb(mem())); + gc(); + System.out.printf("(gc -> %s)%n", mb(mem())); + } + + private static void gc() { + for (int i = 0; i < 3; i++) { + Runtime.getRuntime().gc(); + System.runFinalization(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { /*java sucks*/ } + } + } + + private static long mem() { + return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + } + + private static String mb(long mem2) { + return (mem2 / 1024 / 1024) + "mb"; + } + + public static class JrtUrlType implements Vfs.UrlType { + @Override + public boolean matches(URL url) throws Exception { + return url.getProtocol().equals("jrt"); + } + + @Override + public Vfs.Dir createDir(URL url) throws Exception { + final Path realPath = getJrtRealPath(url); + return new Vfs.Dir() { + @Override + public String getPath() { + return url.getPath(); + } + + @Override + public Iterable getFiles() { + return () -> { + try { + return Files.walk(realPath) + .filter(Files::isRegularFile) + .map(p -> (Vfs.File) new Vfs.File() { + @Override + public String getName() { + return p.toString(); + } + + @Override + public String getRelativePath() { + return p.startsWith(realPath) ? p.toString().substring(realPath.toString().length()) : p.toString(); + } + + @Override + public InputStream openInputStream() throws IOException { + return Files.newInputStream(p); + } + }) + .iterator(); + } catch (Exception e) { + throw new ReflectionsException(e); + } + }; + } + }; + } + + /** + * jdk 11 workaround for {@code Paths.get().toRealPath()} + */ + public static Path getJrtRealPath(URL url) throws IOException { + // jdk 11 workaround + return FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules", url.getPath()) + .toRealPath(); + } + } +} diff --git a/src/test/java/org/reflections/MoreTests.java b/src/test/java/org/reflections/MoreTests.java index 16dbbf77..733fcd47 100644 --- a/src/test/java/org/reflections/MoreTests.java +++ b/src/test/java/org/reflections/MoreTests.java @@ -21,7 +21,8 @@ import static org.reflections.ReflectionUtils.Annotations; import static org.reflections.ReflectionUtils.SuperTypes; import static org.reflections.ReflectionUtilsTest.toStringSorted; -import static org.reflections.ReflectionsTest.are; +import static org.reflections.ReflectionsTest.equalTo; +import static org.reflections.scanners.Scanners.Resources; import static org.reflections.scanners.Scanners.SubTypes; public class MoreTests { @@ -30,7 +31,7 @@ public class MoreTests { public void test_cyclic_annotation() { Reflections reflections = new Reflections(MoreTestsModel.class); assertThat(reflections.getTypesAnnotatedWith(CyclicAnnotation.class), - are(CyclicAnnotation.class)); + equalTo(CyclicAnnotation.class)); } @Test @@ -38,11 +39,12 @@ public void no_exception_when_configured_scanner_store_is_empty() { Reflections reflections = new Reflections( new ConfigurationBuilder() .setUrls(ClasspathHelper.forClass(TestModel.class)) - .setScanners(Scanners.Resources)); + .setScanners()); assertNull(reflections.getStore().get(SubTypes.index())); assertTrue(reflections.getSubTypesOf(TestModel.C1.class).isEmpty()); assertTrue(reflections.get(SubTypes.of(TestModel.C1.class)).isEmpty()); + assertTrue(reflections.get(Resources.with(".*")).isEmpty()); } @Test diff --git a/src/test/java/org/reflections/ReflectionUtilsQueryTest.java b/src/test/java/org/reflections/ReflectionUtilsQueryTest.java index 7c8708d6..0a9ef351 100644 --- a/src/test/java/org/reflections/ReflectionUtilsQueryTest.java +++ b/src/test/java/org/reflections/ReflectionUtilsQueryTest.java @@ -185,8 +185,7 @@ public void flatMapQuery() throws NoSuchMethodException { equalTo( CombinedTestModel.Post.class.getDeclaredMethod("value"), CombinedTestModel.Requests.class.getDeclaredMethod("value"), - CombinedTestModel.Get.class.getDeclaredMethod("value"), - Annotation.class.getDeclaredMethod("annotationType"))); + CombinedTestModel.Get.class.getDeclaredMethod("value"))); assertEquals(query, query1); } @@ -203,11 +202,11 @@ public void annotationToMap() { Set collect = valueMaps.stream().map(Object::toString).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); assertThat(collect, equalTo( - "{annotationType=interface org.reflections.CombinedTestModel$Get, value=/get}", - "{annotationType=interface org.reflections.CombinedTestModel$Post, value=/post}", - "{annotationType=interface org.reflections.CombinedTestModel$Requests, value=[" + - "{method=PUT, annotationType=interface org.reflections.CombinedTestModel$Request, value=/another}, " + - "{method=PATCH, annotationType=interface org.reflections.CombinedTestModel$Request, value=/another}]}" + "{value=/get}", + "{value=/post}", + "{value=[" + + "{method=PUT, value=/another}, " + + "{method=PATCH, value=/another}]}" )); } diff --git a/src/test/java/org/reflections/ReflectionUtilsTest.java b/src/test/java/org/reflections/ReflectionUtilsTest.java index ab964dde..1470a2dc 100644 --- a/src/test/java/org/reflections/ReflectionUtilsTest.java +++ b/src/test/java/org/reflections/ReflectionUtilsTest.java @@ -18,14 +18,12 @@ 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; import static org.hamcrest.MatcherAssert.assertThat; 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") @@ -33,7 +31,8 @@ public class ReflectionUtilsTest { @Test public void getAllTest() { - assertThat(getAllSuperTypes(TestModel.C3.class, withAnnotation(TestModel.AI1.class)), are(TestModel.I1.class)); + assertThat(getAllSuperTypes(TestModel.C3.class, withAnnotation(TestModel.AI1.class)), + equalTo(TestModel.I1.class)); Set allMethods = getAllMethods(TestModel.C4.class, withModifier(Modifier.PUBLIC), withReturnType(void.class)); Set allMethods1 = getAllMethods(TestModel.C4.class, withPattern("public.*.void .*")); diff --git a/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java b/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java index 3123ac65..a59d2756 100644 --- a/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java +++ b/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java @@ -1,51 +1,97 @@ package org.reflections; import org.junit.jupiter.api.Test; -import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; -import java.util.Collection; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.reflections.ReflectionsExpandSupertypesTest.ExpandTestModel.NotScanned; +import static org.reflections.ReflectionsExpandSupertypesTest.ExpandTestModel.Scanned; +import static org.reflections.ReflectionsQueryTest.equalTo; +import static org.reflections.scanners.Scanners.SubTypes; public class ReflectionsExpandSupertypesTest { private final FilterBuilder inputsFilter = new FilterBuilder() - .includePattern("org\\.reflections\\.ReflectionsExpandSupertypesTest\\$TestModel\\$ScannedScope\\$.*"); + .includePattern("org\\.reflections\\.ReflectionsExpandSupertypesTest\\$ExpandTestModel\\$Scanned\\$.*"); @SuppressWarnings("unused") - public interface TestModel { - interface A { } // outside of scanned scope - interface B extends A { } // outside of scanned scope, but immediate supertype + public interface ExpandTestModel { + interface NotScanned { + @Retention(RetentionPolicy.RUNTIME) + @interface MetaAnnotation { } // outside of scanned scope - interface ScannedScope { - interface C extends B { } - interface D extends B { } + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @MetaAnnotation + @interface TestAnnotation { } // outside of scanned scope, but immediate annotation + + interface BaseInterface { } // outside of scanned scope + + @TestAnnotation + class BaseClass implements BaseInterface { } // outside of scanned scope, but immediate supertype + } + + interface Scanned { + class ChildrenClass extends NotScanned.BaseClass { } } } @Test public void testExpandSupertypes() { - Reflections refExpand = new Reflections(new ConfigurationBuilder(). - setUrls(ClasspathHelper.forClass(TestModel.ScannedScope.C.class)). - filterInputsBy(inputsFilter)); - assertTrue(refExpand.getConfiguration().shouldExpandSuperTypes()); - Collection> subTypesOf = refExpand.getSubTypesOf(TestModel.A.class); - assertTrue(subTypesOf.contains(TestModel.B.class), "expanded"); - assertTrue(subTypesOf.containsAll(refExpand.getSubTypesOf(TestModel.B.class)), "transitivity"); + ConfigurationBuilder configuration = new ConfigurationBuilder() + .forPackage("org.reflections") + .filterInputsBy(inputsFilter); + + Reflections reflections = new Reflections(configuration); + assertThat(reflections.get(SubTypes.of(NotScanned.BaseInterface.class).asClass()), + equalTo( + NotScanned.BaseClass.class, + Scanned.ChildrenClass.class)); + + Reflections refNoExpand = new Reflections(configuration.setExpandSuperTypes(false)); + assertThat(refNoExpand.get(SubTypes.of(NotScanned.BaseInterface.class).asClass()), + equalTo()); } @Test - public void testNotExpandSupertypes() { - Reflections refDontExpand = new Reflections( - new ConfigurationBuilder() - .forPackage("org.reflections") - .filterInputsBy(inputsFilter). - setExpandSuperTypes(false)); - assertFalse(refDontExpand.getConfiguration().shouldExpandSuperTypes()); - Collection> subTypesOf1 = refDontExpand.getSubTypesOf(TestModel.A.class); - assertFalse(subTypesOf1.contains(TestModel.B.class)); + void testDetectInheritedAnnotations() { + ConfigurationBuilder configuration = new ConfigurationBuilder() + .forPackage("org.reflections") + .filterInputsBy(inputsFilter); + + Reflections reflections = new Reflections(configuration); + assertThat(reflections.getTypesAnnotatedWith(NotScanned.TestAnnotation.class), + equalTo( + NotScanned.BaseClass.class, + Scanned.ChildrenClass.class)); + + Reflections refNoExpand = new Reflections(configuration.setExpandSuperTypes(false)); + assertThat(refNoExpand.getTypesAnnotatedWith(NotScanned.TestAnnotation.class), + equalTo()); + } + + @Test + void testExpandMetaAnnotations() { + ConfigurationBuilder configuration = new ConfigurationBuilder() + .forPackage("org.reflections") + .filterInputsBy(inputsFilter); + + Reflections reflections = new Reflections(configuration); + assertThat(reflections.getTypesAnnotatedWith(NotScanned.MetaAnnotation.class), + equalTo()); +// todo fix, support expansion of meta annotations outside of scanned scope +// equalTo( +// NotScanned.TestAnnotation.class, +// NotScanned.BaseClass.class, +// Scanned.ChildrenClass.class)); + + Reflections refNoExpand = new Reflections(configuration.setExpandSuperTypes(false)); + assertThat(refNoExpand.getTypesAnnotatedWith(NotScanned.MetaAnnotation.class), + equalTo()); } } diff --git a/src/test/java/org/reflections/ReflectionsTest.java b/src/test/java/org/reflections/ReflectionsTest.java index f497cd44..dbfb3dbb 100644 --- a/src/test/java/org/reflections/ReflectionsTest.java +++ b/src/test/java/org/reflections/ReflectionsTest.java @@ -6,9 +6,14 @@ 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; @@ -16,16 +21,15 @@ import java.io.File; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Objects; 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.*; @@ -41,13 +45,24 @@ public class ReflectionsTest implements NameHelper { @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()) - .addScanners(Scanners.values())); + new MemberUsageScanner())); } @Test @@ -234,8 +249,26 @@ public void testMemberUsageScanner() throws NoSuchFieldException, NoSuchMethodEx } @Test - public void testScannerNotConfigured() { - assertTrue(new Reflections(TestModel.class, TestModelFilter).getMethodsAnnotatedWith(AC1.class).isEmpty()); + 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()); } // @@ -282,23 +315,15 @@ public static Matcher> equalTo(T... operand) { return IsEqual.equalTo(new HashSet<>(Arrays.asList(operand))); } - @SafeVarargs - public final Matcher> equalToNames(T... operand) { - return IsEqual.equalTo(new HashSet<>(toNames(operand))); - } - private Matcher>> annotatedWith(final Class annotation) { return new Match>>() { public boolean matches(Object o) { for (Class c : (Iterable>) o) { - if (!annotationTypes(Arrays.asList(c.getAnnotations())).contains(annotation)) return false; + List> annotationTypes = Stream.of(c.getAnnotations()).map(Annotation::annotationType).collect(Collectors.toList()); + if (!annotationTypes.contains(annotation)) return false; } return true; } }; } - - private List> annotationTypes(Collection annotations) { - return annotations.stream().filter(Objects::nonNull).map(Annotation::annotationType).collect(Collectors.toList()); - } } diff --git a/src/test/java/org/reflections/VfsTest.java b/src/test/java/org/reflections/VfsTest.java index 817ac829..82c026a3 100644 --- a/src/test/java/org/reflections/VfsTest.java +++ b/src/test/java/org/reflections/VfsTest.java @@ -1,17 +1,22 @@ package org.reflections; +import javassist.bytecode.ClassFile; import org.junit.jupiter.api.Test; import org.reflections.util.ClasspathHelper; import org.reflections.vfs.SystemDir; import org.reflections.vfs.Vfs; import org.slf4j.Logger; +import java.io.BufferedInputStream; +import java.io.DataInputStream; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import static java.text.MessageFormat.format; import static org.junit.jupiter.api.Assertions.*; @@ -114,6 +119,35 @@ public void vfsFromDirWithJarInName() throws MalformedURLException { } } + @Test + public void vfsFromDirWithJarInJar() throws Exception { + URL resource = ClasspathHelper.contextClassLoader().getResource("jarWithBootLibJar.jar"); + URL innerJarUrl = new URL("jar:" + resource.toExternalForm() + "!/BOOT-INF/lib/jarWithManifest.jar"); + + assertFalse(Vfs.DefaultUrlTypes.jarUrl.matches(innerJarUrl)); + Vfs.Dir jarUrlDir = Vfs.DefaultUrlTypes.jarUrl.createDir(innerJarUrl); + assertNotEquals(innerJarUrl.getPath(), jarUrlDir.getPath()); + + assertTrue(Vfs.DefaultUrlTypes.jarInputStream.matches(innerJarUrl)); + Vfs.Dir jarInputStreamDir = Vfs.DefaultUrlTypes.jarInputStream.createDir(innerJarUrl); + assertEquals(innerJarUrl.getPath(), jarInputStreamDir.getPath()); + + List files = StreamSupport.stream(jarInputStreamDir.getFiles().spliterator(), false).collect(Collectors.toList()); + assertEquals(1, files.size()); + Vfs.File file1 = files.get(0); + assertEquals("empty.class", file1.getName()); + assertEquals("pack/empty.class", file1.getRelativePath()); + + for (Vfs.File file : jarInputStreamDir.getFiles()) { + try (DataInputStream dis = new DataInputStream(new BufferedInputStream(file.openInputStream()))) { + ClassFile classFile = new ClassFile(dis); + assertEquals("org.reflections.empty", classFile.getName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + private void testVfsDir(Vfs.Dir dir) { List files = new ArrayList<>(); for (Vfs.File file : dir.getFiles()) { diff --git a/src/test/resources/jarWithBootLibJar.jar b/src/test/resources/jarWithBootLibJar.jar new file mode 100644 index 0000000000000000000000000000000000000000..456c6da7d953d7aa151f76dbf772fb81c4eef1f6 GIT binary patch literal 942 zcmWIWW@Zs#VBp|jII}b$cvC&oJ81?621y_mVPIkKbq#UU_4ISo_jUC1baM?3(erit zcZz}Gv+tSHKHj=|7kRyPwa%S6zd6X@it&S|kDf9y1i%eE4KxsF4g(X200K@RcJlWR z0UOASM`LNo0ylG zmRek*2jqu_a56A^oz6)F;nE6j21b^zKa)dK!j56jUcY$$@|l#(qUW#ME(AWR6mezO77$laOK;|7&3vBF!K!u9 z)M$bQ{}WCFCPN?3tUO20rEx`mNri=Z^OL_RvNvA)ZJZV}b7NO>X4Su|Y41Pps;^XkvK2eQBGT)q8t*D+(33S*;A zg9m(790CoOJOmprwCQRoI52ixEp+2K#F7)(yum_9Fi=5v$6~I;Ox|f7z;I4wKDn9U zp|(QoNsdR1!1!lm5&@<%Txky&H%I_iN<=mVTY^Iv5eQ_$lOH~9pyY=DRX`?O8z_B& uw8}Aqk`A`SC;`{m(6}63E7qjR3Q3x1$uhv3l?`MR3lPcy(`pqs7Xbie;3`f4 literal 0 HcmV?d00001