From e133c8dd84589cdae785876546a1ae669fa32aa7 Mon Sep 17 00:00:00 2001 From: ronma Date: Thu, 14 Oct 2021 21:38:40 +0700 Subject: [PATCH 01/17] fix npe on empty Resources store --- src/main/java/org/reflections/scanners/Scanners.java | 3 ++- src/main/java/org/reflections/util/UtilQueryBuilder.java | 2 +- src/test/java/org/reflections/MoreTests.java | 8 +++++--- src/test/java/org/reflections/ReflectionUtilsTest.java | 5 ++--- 4 files changed, 10 insertions(+), 8 deletions(-) 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/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/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/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 .*")); From 92da32158aa17fa6a93fa4d4eceae8ba3283152d Mon Sep 17 00:00:00 2001 From: ronma Date: Fri, 15 Oct 2021 08:05:22 +0700 Subject: [PATCH 02/17] ReflectionUtils.Methods to use getDeclaredMethods --- src/main/java/org/reflections/ReflectionUtils.java | 2 +- .../org/reflections/ReflectionUtilsQueryTest.java | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/reflections/ReflectionUtils.java b/src/main/java/org/reflections/ReflectionUtils.java index 418c6394..af329709 100644 --- a/src/main/java/org/reflections/ReflectionUtils.java +++ b/src/main/java/org/reflections/ReflectionUtils.java @@ -137,7 +137,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 = 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}]}" )); } From cd6073f24a586093374205731ebf353010dea843 Mon Sep 17 00:00:00 2001 From: ronma Date: Fri, 15 Oct 2021 08:13:55 +0700 Subject: [PATCH 03/17] add ReflectionUtils.Resources --- .../java/org/reflections/ReflectionUtils.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/org/reflections/ReflectionUtils.java b/src/main/java/org/reflections/ReflectionUtils.java index af329709..9bcd394d 100644 --- a/src/main/java/org/reflections/ReflectionUtils.java +++ b/src/main/java/org/reflections/ReflectionUtils.java @@ -1,17 +1,22 @@ package org.reflections; +import org.reflections.util.ClasspathHelper; import org.reflections.util.QueryFunction; import org.reflections.util.ReflectionUtilsPredicates; import org.reflections.util.UtilQueryBuilder; +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; 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.Enumeration; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -147,6 +152,24 @@ public QueryFunction> of(AnnotatedElement ele public static final UtilQueryBuilder, Field> Fields = element -> ctx -> Arrays.stream(element.getDeclaredFields()).collect(Collectors.toCollection(LinkedHashSet::new)); + /** query resources using {@link ClasspathHelper#contextClassLoader()}
{@code get(Resources.with(name)) -> Set }
*/ + public static final UtilQueryBuilder Resources = + element -> ctx -> { + try { + ClassLoader classLoader = ClasspathHelper.contextClassLoader(); + Enumeration resources = classLoader.getResources(element); + Set urls = new HashSet<>(); + while (resources.hasMoreElements()) urls.add(resources.nextElement()); + if (urls.isEmpty()) { + URL resource = Class.class.getResource(element); + if (resource != null) urls.add(resource); + } + return urls; + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + public static UtilQueryBuilder extendType() { return element -> { if (element instanceof Class && !((Class) element).isAnnotation()) { From 90bfc719d8bd7a5260ae0dde10d6b3c14a653f2a Mon Sep 17 00:00:00 2001 From: ronma Date: Fri, 15 Oct 2021 08:14:30 +0700 Subject: [PATCH 04/17] add JdkTests --- src/test/java/org/reflections/JdkTests.java | 315 ++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 src/test/java/org/reflections/JdkTests.java diff --git a/src/test/java/org/reflections/JdkTests.java b/src/test/java/org/reflections/JdkTests.java new file mode 100644 index 00000000..8eae0315 --- /dev/null +++ b/src/test/java/org/reflections/JdkTests.java @@ -0,0 +1,315 @@ +package org.reflections; + +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.junit.jupiter.api.Assertions.assertFalse; +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", "SwitchStatementWithTooFewBranches"}) +public class JdkTests { + + private final URL urls = ClasspathHelper.forClass(Object.class); + + @BeforeAll + static void initJrtUrlType() { + if (!Vfs.getDefaultUrlTypes().get(0).getClass().equals(JrtUrlType.class)) { + Vfs.addDefaultURLTypes(new JrtUrlType()); + } + } + + @Test + public void checkSubTypesAndSuperTypes() { + Map> diff = reflect( + Scanners.SubTypes, + ReflectionUtils.SuperTypes, + Class.class); + + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkTypesAnnotatedAndAnnotationTypes() { + Map> diff = reflect( + Scanners.TypesAnnotated, + ReflectionUtils.AnnotationTypes, + Class.class); + + switch (jdk()) { + case 15: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "jdk.internal.PreviewFeature"))); + break; + case 17: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "jdk.internal.javac.PreviewFeature"))); + break; + default: + assertEquals(diff, Collections.emptyMap()); + } + } + + @Test + public void checkMethodsAnnotatedAndAnnotationTypes() { + Map> diff = reflect( + Scanners.MethodsAnnotated, + ReflectionUtils.AnnotationTypes, + Method.class); + + switch (jdk()) { + case 8: + // todo fix differences @A2 such as - @A1 public @A2 result method(...) + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "com.sun.istack.internal.NotNull", + "com.sun.istack.internal.Nullable", + "sun.reflect.CallerSensitive", + "java.lang.invoke.LambdaForm$Hidden"))); + break; + case 11: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "java.lang.invoke.LambdaForm$Hidden"))); + break; + case 15: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "jdk.internal.reflect.CallerSensitive", + "jdk.internal.PreviewFeature"))); + break; + case 17: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "jdk.internal.reflect.CallerSensitive"))); + break; + default: + assertEquals(diff, Collections.emptyMap()); + } + } + + @Test + public void checkConstructorsAnnotatedAndAnnotationTypes() { + Map> diff = reflect( + Scanners.ConstructorsAnnotated, + ReflectionUtils.AnnotationTypes, + Constructor.class); + + assertEquals(diff, Collections.emptyMap()); + } + + @Test + public void checkFieldsAnnotatedAndAnnotationTypes() { + Map> diff = reflect( + Scanners.FieldsAnnotated, + ReflectionUtils.AnnotationTypes, + Field.class); + + switch (jdk()) { + case 8: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "com.sun.istack.internal.NotNull", + "com.sun.istack.internal.Nullable"))); + break; + case 15: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "jdk.internal.PreviewFeature"))); + break; + case 17: + assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( + "jdk.internal.vm.annotation.Stable"))); + break; + default: + assertEquals(diff, Collections.emptyMap()); + } + } + + @Test + public void checkResources() { + Reflections reflections = new Reflections( + new ConfigurationBuilder() + .addUrls(urls) + .addScanners(Scanners.Resources)); + + Set diff = new HashSet<>(); + reflections.getStore().get(Scanners.Resources.index()) + .values().forEach(resources -> + resources.forEach(resource -> { + Set urls = get(ReflectionUtils.Resources.get(resource)); + for (URL url : urls) { + try { + if (!Files.exists(JrtUrlType.getJrtRealPath(url))) { + diff.add(resource); + } + } catch (Exception e) { + diff.add(resource); + } + } + })); + + switch (jdk()) { + case 8: + assertEquals(diff, new HashSet<>(Arrays.asList("META-INF/MANIFEST.MF"))); + break; + default: + 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) { + System.out.print(scanner.index()); + measure("before"); + + Reflections reflections = new Reflections( + new ConfigurationBuilder() + .addUrls(urls) + .addScanners(scanner)); + measure("scan"); + + Map> diffMap = findDiff(reflections, scanner, utilQueryBuilder, resultType); + measure("query"); + + reflections.getStore().clear(); + measure("clear"); + System.out.println(); + + return diffMap; + } + + private Map> findDiff( + Reflections reflections, Scanner scanner, UtilQueryBuilder reflectionUtilsFunction, Class resultType) { + Map> missing = new HashMap<>(); + Map> mmap = reflections.getStore().get(scanner.index()); + assertFalse(mmap.isEmpty()); + mmap.forEach((key, strings) -> + strings.forEach(string -> { + //noinspection unchecked + F element = (F) reflections.forName(string, resultType); + if (element == null || !reflections.toNames(get(reflectionUtilsFunction.get(element))).contains(key)) { + missing.computeIfAbsent(key, k -> new HashSet<>()).add(string); + } + })); + return missing; + } + + private void measure(String s) { + System.out.printf(" -> %s %s", s, kb(mem())); + gc(); + System.out.printf(" (gc -> %s)", kb(mem())); + } + + private void gc() { + for (int i = 0; i < 3; i++) { + Runtime.getRuntime().gc(); + System.runFinalization(); + try { + Thread.sleep(250); + } catch (InterruptedException e) { /*java sucks*/ } + } + } + + private long mem() { + return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + } + + private String kb(long mem2) { + return (mem2 / 1024) + "kb"; + } + + private int jdk() { + String[] versionElements = System.getProperty("java.version").split("\\."); + int discard = Integer.parseInt(versionElements[0]); + return discard == 1 ? Integer.parseInt(versionElements[1]) : discard; + } + + 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(); + } + } +} From d37287676e377a6bc2ceeb38bd86e93052b634eb Mon Sep 17 00:00:00 2001 From: ronma Date: Fri, 15 Oct 2021 08:22:28 +0700 Subject: [PATCH 05/17] fragile/flaky --- src/test/java/org/reflections/JdkTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/reflections/JdkTests.java b/src/test/java/org/reflections/JdkTests.java index 8eae0315..5ca591d9 100644 --- a/src/test/java/org/reflections/JdkTests.java +++ b/src/test/java/org/reflections/JdkTests.java @@ -93,12 +93,12 @@ public void checkMethodsAnnotatedAndAnnotationTypes() { assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( "com.sun.istack.internal.NotNull", "com.sun.istack.internal.Nullable", - "sun.reflect.CallerSensitive", - "java.lang.invoke.LambdaForm$Hidden"))); + "sun.reflect.CallerSensitive"))); break; case 11: + case 13: assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "java.lang.invoke.LambdaForm$Hidden"))); + "jdk.internal.reflect.CallerSensitive"))); break; case 15: assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( From 0f528e2586c78fe1b94bd64da204d112cc4da8d2 Mon Sep 17 00:00:00 2001 From: ronma Date: Sat, 16 Oct 2021 09:45:45 +0700 Subject: [PATCH 06/17] fix scanner index for deprecated scanners :( --- .../java/org/reflections/scanners/AbstractScanner.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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); From abf7056eece1bef3e53c3aa29b885d4ffe010832 Mon Sep 17 00:00:00 2001 From: ronma Date: Sat, 16 Oct 2021 10:52:20 +0700 Subject: [PATCH 07/17] deprecated scanners javadocs --- README.md | 69 +++++++++++++------ .../scanners/FieldAnnotationsScanner.java | 5 +- .../scanners/MethodAnnotationsScanner.java | 6 +- .../scanners/MethodParameterScanner.java | 22 +++++- .../scanners/ResourcesScanner.java | 5 +- .../reflections/scanners/SubTypesScanner.java | 11 +-- .../scanners/TypeAnnotationsScanner.java | 5 +- .../java/org/reflections/ReflectionsTest.java | 42 +++++++++-- 8 files changed, 125 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 9aa03ebd..67005d9e 100644 --- a/README.md +++ b/README.md @@ -43,39 +43,57 @@ 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, see below.* ### Scan -Creating Reflections instance requires providing scanning configuration: +Creating Reflections instance requires providing scanning configuration. + +Typical example - scan package with default scanners: ```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 default scanners +Reflections reflections = new Reflections("com.my.project"); + +// or similarly using ConfigurationBuilder: Reflections reflections = new Reflections( new ConfigurationBuilder() .forPackage("com.my.project") .filterInputsBy(new FilterBuilder().includePackage("com.my.project"))); +``` -// or similarly -Reflections reflections = new Reflections("com.my.project"); +Other examples: +```java +import static org.reflections.scanners.Scanners.*; -// another example -Reflections reflections = new Reflections( +// scan package with specific scanners +new Reflections( new ConfigurationBuilder() + .forPackage("com.my.project") + .filterInputsBy(new FilterBuilder().includePackage("com.my.project"))) + .setScanners(MethodsAnnotated, MethodsSignature, MethodsReturn) + +// this is equivalent to +new Reflections("com.my.project", MethodsAnnotated, MethodsSignature, MethodsReturn); + +// scan urls with all standard scanners, use include/exlude input filter +new Reflections( + new ConfigurationBuilder() + // scan given urls .addUrls(ClasspathHelper.forPackage("com.my.project")) // same as forPackage - .setScanners(Scanners.values()) // all standard scanners - .filterInputsBy(new FilterBuilder() // optionally include/exclude packages + .addUrls(anotherUrl) + // filter include 'com.my.project', but exclude 'com.my.project.exclude' + .filterInputsBy(new FilterBuilder() .includePackage("com.my.project") - .excludePackage("com.my.project.exclude"))); + .excludePackage("com.my.project.exclude"))) + // all standard scanners + .setScanners(Scanners.values()); ``` *See more in [ConfigurationBuilder](https://ronmamo.github.io/reflections/org/reflections/util/ConfigurationBuilder.html).* 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). +If not specified, default scanners `SubTypes` and `TypesAnnotated` are used. 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.** 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. @@ -96,7 +114,7 @@ Set> modules = Set> singletons = reflections.get(TypesAnnotated.with(Singleton.class).asClass()); -// MethodAnnotated +// MethodsAnnotated Set resources = reflections.get(MethodsAnnotated.with(GetMapping.class).as(Method.class)); @@ -144,7 +162,7 @@ Scanner queries return `Set` by default, if not using `as() / asClass()` Set moduleNames = reflections.get(SubTypes.of(Module.class)); -Set singleNames = +Set singletonNames = reflections.get(TypesAnnotated.with(Singleton.class)); ``` Note that previous 0.9.x API is still supported, for example: @@ -163,16 +181,23 @@ Set> singletons = | `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(ConstructorsAnnotated.with(A))` | getConstructorsAnnotatedWith(A) *(1)*| | `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, ...) | +| `get(MethodsParameter.with(P))` | getMethodsWithParameter(P) *(2)*| +| `get(MethodsSignature.of(P, ...))` | getMethodsWithSignature(P, ...) *(2)*| +| `get(MethodsReturn.of(T))` | getMethodsReturn(T) *(2)*| +| `get(ConstructorsParameter.with(P))` | getConstructorsWithParameter(P) *(2)*| +| `get(ConstructorsSignature.of(P, ...))` | getConstructorsWithSignature(P, ...) *(2)*| *Note: `asClass()` and `as()` mappings were omitted* + +*breaking change (1): MethodsAnnotatedScanner does not include Constructors scanning, use instead Scanners.ConstructorsAnnotated* + +*breaking change (2): MethodParameterScanner was removed, use instead as required: +Scanners.MethodsParameter, Scanners.MethodsSignature, Scanners.MethodsReturn, Scanners.ConstructorsParameter, Scanners.ConstructorsSignature* + +*migration: use Scanners enum instead ## ReflectionUtils 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/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/test/java/org/reflections/ReflectionsTest.java b/src/test/java/org/reflections/ReflectionsTest.java index f497cd44..5bd6014d 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; @@ -41,13 +46,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 +250,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()); } // From c242325095674a9c4f110a88c76bf001cf6ed8ab Mon Sep 17 00:00:00 2001 From: ronma Date: Sat, 16 Oct 2021 10:53:48 +0700 Subject: [PATCH 08/17] JdkTests cleanup --- src/test/java/org/reflections/JdkTests.java | 101 +++++--------------- 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/src/test/java/org/reflections/JdkTests.java b/src/test/java/org/reflections/JdkTests.java index 5ca591d9..d5ba8da3 100644 --- a/src/test/java/org/reflections/JdkTests.java +++ b/src/test/java/org/reflections/JdkTests.java @@ -37,7 +37,7 @@ * SubTypes/SuperTypes, TypesAnnotated/AnnotatedTypes, MethodsAnnotated/AnnotatedTypes, Resources etc... *

tested with AdoptOpenJDK */ -@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument", "SwitchStatementWithTooFewBranches"}) +@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument"}) public class JdkTests { private final URL urls = ClasspathHelper.forClass(Object.class); @@ -66,18 +66,10 @@ public void checkTypesAnnotatedAndAnnotationTypes() { ReflectionUtils.AnnotationTypes, Class.class); - switch (jdk()) { - case 15: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.PreviewFeature"))); - break; - case 17: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.javac.PreviewFeature"))); - break; - default: - assertEquals(diff, Collections.emptyMap()); - } + Arrays.asList("jdk.internal.PreviewFeature", // jdk 15 + "jdk.internal.javac.PreviewFeature") // jdk 17 + .forEach(diff::remove); + assertEquals(diff, Collections.emptyMap()); } @Test @@ -87,31 +79,15 @@ public void checkMethodsAnnotatedAndAnnotationTypes() { ReflectionUtils.AnnotationTypes, Method.class); - switch (jdk()) { - case 8: - // todo fix differences @A2 such as - @A1 public @A2 result method(...) - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "com.sun.istack.internal.NotNull", - "com.sun.istack.internal.Nullable", - "sun.reflect.CallerSensitive"))); - break; - case 11: - case 13: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.reflect.CallerSensitive"))); - break; - case 15: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.reflect.CallerSensitive", - "jdk.internal.PreviewFeature"))); - break; - case 17: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.reflect.CallerSensitive"))); - break; - default: - assertEquals(diff, Collections.emptyMap()); - } + // 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 @@ -131,23 +107,12 @@ public void checkFieldsAnnotatedAndAnnotationTypes() { ReflectionUtils.AnnotationTypes, Field.class); - switch (jdk()) { - case 8: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "com.sun.istack.internal.NotNull", - "com.sun.istack.internal.Nullable"))); - break; - case 15: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.PreviewFeature"))); - break; - case 17: - assertEquals(diff.keySet(), new HashSet<>(Arrays.asList( - "jdk.internal.vm.annotation.Stable"))); - break; - default: - assertEquals(diff, Collections.emptyMap()); - } + 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 @@ -163,24 +128,14 @@ public void checkResources() { resources.forEach(resource -> { Set urls = get(ReflectionUtils.Resources.get(resource)); for (URL url : urls) { - try { - if (!Files.exists(JrtUrlType.getJrtRealPath(url))) { - diff.add(resource); - } - } catch (Exception e) { - diff.add(resource); - } + try { if (!Files.exists(JrtUrlType.getJrtRealPath(url))) diff.add(resource); } + catch (Exception e) { diff.add(resource); } } })); - switch (jdk()) { - case 8: - assertEquals(diff, new HashSet<>(Arrays.asList("META-INF/MANIFEST.MF"))); - break; - default: - assertEquals(diff, Collections.emptySet()); - } - + Arrays.asList("META-INF/MANIFEST.MF") // jdk 8 + .forEach(diff::remove); + assertEquals(diff, Collections.emptySet()); } @Test @@ -251,12 +206,6 @@ private String kb(long mem2) { return (mem2 / 1024) + "kb"; } - private int jdk() { - String[] versionElements = System.getProperty("java.version").split("\\."); - int discard = Integer.parseInt(versionElements[0]); - return discard == 1 ? Integer.parseInt(versionElements[1]) : discard; - } - public static class JrtUrlType implements Vfs.UrlType { @Override public boolean matches(URL url) throws Exception { From ac3e4c6787b3fcd88dc5b2f520bd217857bed61b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Oct 2021 13:26:41 +0700 Subject: [PATCH 09/17] Bump maven-javadoc-plugin from 3.1.1 to 3.3.1 (#352) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ef68cbb2560b2b209b8ad9e5f446cad78adf44ae Mon Sep 17 00:00:00 2001 From: Julian Rubin Date: Sat, 16 Oct 2021 15:31:37 +0200 Subject: [PATCH 10/17] 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 --- .../java/org/reflections/Configuration.java | 2 +- .../java/org/reflections/Reflections.java | 33 ++++++++++++------- .../util/ConfigurationBuilder.java | 2 +- .../ReflectionsExpandSupertypesTest.java | 17 ++++++++++ src/test/java/test/classes/a/BaseClass.java | 5 +++ .../java/test/classes/a/TestAnnotation.java | 10 ++++++ .../java/test/classes/b/ChildrenClass.java | 6 ++++ 7 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 src/test/java/test/classes/a/BaseClass.java create mode 100644 src/test/java/test/classes/a/TestAnnotation.java create mode 100644 src/test/java/test/classes/b/ChildrenClass.java 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/Reflections.java b/src/main/java/org/reflections/Reflections.java index 71d39d07..c8f6660d 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); } @@ -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 (!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/util/ConfigurationBuilder.java b/src/main/java/org/reflections/util/ConfigurationBuilder.java index aff76c4f..2b5bf3ba 100644 --- a/src/main/java/org/reflections/util/ConfigurationBuilder.java +++ b/src/main/java/org/reflections/util/ConfigurationBuilder.java @@ -230,7 +230,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/test/java/org/reflections/ReflectionsExpandSupertypesTest.java b/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java index 3123ac65..79bb1657 100644 --- a/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java +++ b/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java @@ -1,11 +1,18 @@ package org.reflections; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; +import test.classes.a.BaseClass; +import test.classes.a.TestAnnotation; +import test.classes.b.ChildrenClass; import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -48,4 +55,14 @@ public void testNotExpandSupertypes() { Collection> subTypesOf1 = refDontExpand.getSubTypesOf(TestModel.A.class); assertFalse(subTypesOf1.contains(TestModel.B.class)); } + + @Test + void testDetectInheritedAnnotations() { + final Reflections reflections = new Reflections("test.classes.b"); + final Set> typesAnnotatedWith = reflections.getTypesAnnotatedWith(TestAnnotation.class); + + final Set> expected = + Stream.of(BaseClass.class, ChildrenClass.class).collect(Collectors.toSet()); + Assertions.assertEquals(typesAnnotatedWith, expected); + } } diff --git a/src/test/java/test/classes/a/BaseClass.java b/src/test/java/test/classes/a/BaseClass.java new file mode 100644 index 00000000..414de604 --- /dev/null +++ b/src/test/java/test/classes/a/BaseClass.java @@ -0,0 +1,5 @@ +package test.classes.a; + +@TestAnnotation +public class BaseClass { +} diff --git a/src/test/java/test/classes/a/TestAnnotation.java b/src/test/java/test/classes/a/TestAnnotation.java new file mode 100644 index 00000000..a481769a --- /dev/null +++ b/src/test/java/test/classes/a/TestAnnotation.java @@ -0,0 +1,10 @@ +package test.classes.a; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface TestAnnotation { +} diff --git a/src/test/java/test/classes/b/ChildrenClass.java b/src/test/java/test/classes/b/ChildrenClass.java new file mode 100644 index 00000000..02923de8 --- /dev/null +++ b/src/test/java/test/classes/b/ChildrenClass.java @@ -0,0 +1,6 @@ +package test.classes.b; + +import test.classes.a.BaseClass; + +public class ChildrenClass extends BaseClass { +} From 75a8917f8ca20d367b11a2719673760ed724a7c1 Mon Sep 17 00:00:00 2001 From: ronma Date: Sat, 16 Oct 2021 20:39:00 +0700 Subject: [PATCH 11/17] npe fix --- src/main/java/org/reflections/Reflections.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/reflections/Reflections.java b/src/main/java/org/reflections/Reflections.java index c8f6660d..1240f740 100644 --- a/src/main/java/org/reflections/Reflections.java +++ b/src/main/java/org/reflections/Reflections.java @@ -333,7 +333,7 @@ public void expandSuperTypes(Map> subTypesStore, Map> subTypesStore, Map> typesAnnotatedStore, String key, Class type) { Set typeAnnotations = ReflectionUtils.getAnnotations(type); - if (!typeAnnotations.isEmpty()) { + if (typesAnnotatedStore != null && !typeAnnotations.isEmpty()) { String typeName = type.getName(); for (Annotation typeAnnotation : typeAnnotations) { String annotationName = typeAnnotation.annotationType().getName(); From 177d5f90da4da15150274751b43ebbbb16ce879c Mon Sep 17 00:00:00 2001 From: ronma Date: Sun, 17 Oct 2021 23:39:54 +0700 Subject: [PATCH 12/17] ConfigurationBuilder.setParallel fluent --- src/main/java/org/reflections/util/ConfigurationBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/reflections/util/ConfigurationBuilder.java b/src/main/java/org/reflections/util/ConfigurationBuilder.java index 2b5bf3ba..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 From 716659c5a5413e5e52ef65a87f0ebf5027bd0aae Mon Sep 17 00:00:00 2001 From: ronma Date: Sun, 17 Oct 2021 23:40:38 +0700 Subject: [PATCH 13/17] readme --- README.md | 71 +++++++++---------- .../java/org/reflections/Reflections.java | 2 +- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 67005d9e..96d000e1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ 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, see below.* @@ -48,10 +48,10 @@ Set> annotated = ### Scan Creating Reflections instance requires providing scanning configuration. -Typical example - scan package with default scanners: +Typical example - scan package with the default scanners: ```java -// typical usage: scan package with default scanners +// typical usage: scan package with default scanners SubTypes, TypesAnnotated Reflections reflections = new Reflections("com.my.project"); // or similarly using ConfigurationBuilder: @@ -72,10 +72,10 @@ new Reflections( .filterInputsBy(new FilterBuilder().includePackage("com.my.project"))) .setScanners(MethodsAnnotated, MethodsSignature, MethodsReturn) -// this is equivalent to +// similarly, use the convenient constructor new Reflections("com.my.project", MethodsAnnotated, MethodsSignature, MethodsReturn); -// scan urls with all standard scanners, use include/exlude input filter +// another example: scan urls with all standard scanners, use include/exlude input filter new Reflections( new ConfigurationBuilder() // scan given urls @@ -92,8 +92,8 @@ new Reflections( *See more in [ConfigurationBuilder](https://ronmamo.github.io/reflections/org/reflections/util/ConfigurationBuilder.html).* Note that: -* **Scanners must be configured in order to be queried, otherwise an empty result is returned.** -If not specified, default scanners `SubTypes` and `TypesAnnotated` are used. 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). +* **[Scanners](https://ronmamo.github.io/reflections/org/reflections/scanners/Scanners.html) must be configured in order to be queried, otherwise an empty result is returned.** +If not specified, default scanners `SubTypes` and `TypesAnnotated` will be used. For all standard Scanners use `Scanners.values()` [(src)](src/main/java/org/reflections/scanners/Scanners.java). * **All relevant URLs should be configured.** 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. @@ -101,7 +101,6 @@ Consider adding inputs filter in case too many classes are scanned. ### 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.*; @@ -110,8 +109,8 @@ import static org.reflections.scanners.Scanners.*; Set> modules = reflections.get(SubTypes.of(Module.class).asClass()); -// TypesAnnotated -Set> singletons = +// TypesAnnotated (*1) +Set> singletonAnnotated = reflections.get(TypesAnnotated.with(Singleton.class).asClass()); // MethodsAnnotated @@ -157,47 +156,40 @@ Set pathParam = *See more examples in [ReflectionsQueryTest](src/test/java/org/reflections/ReflectionsQueryTest.java).* -Scanner queries return `Set` by default, if not using `as() / asClass()` mappers: +Note that previous 0.9.x APIs are still supported, for example: ```java -Set moduleNames = - reflections.get(SubTypes.of(Module.class)); +Set> modules = reflections.getSubTypesOf(Module.class); +// similar to: reflections.get(SubTypes.of(T).asClass()) -Set singletonNames = - reflections.get(TypesAnnotated.with(Singleton.class)); +Set> singletons = reflections.getTypesAnnotatedWith(Singleton.class); +// similar to: reflections.get(SubTypes.of(TypesAnnotated.with(A)).asClass()) ``` -Note that previous 0.9.x API is still supported, for example: -```java -Set> modules = - reflections.getSubTypesOf(Module.class); -Set> singletons = - reflections.getTypesAnnotatedWith(Singleton.class); -``` +_(*1) The equivalent of `getTypesAnnotatedWith(A)` is `get(SubTypes.of(TypesAnnotated.with(A)))`, and not just `TypesAnnotated.with(A)`_ +

    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) *(1)*| -| `get(FieldsAnnotated.with(A))` | getFieldsAnnotatedWith(A) | -| `get(Resources.with(regex))` | getResources(regex) | -| `get(MethodsParameter.with(P))` | getMethodsWithParameter(P) *(2)*| -| `get(MethodsSignature.of(P, ...))` | getMethodsWithSignature(P, ...) *(2)*| -| `get(MethodsReturn.of(T))` | getMethodsReturn(T) *(2)*| -| `get(ConstructorsParameter.with(P))` | getConstructorsWithParameter(P) *(2)*| -| `get(ConstructorsSignature.of(P, ...))` | getConstructorsWithSignature(P, ...) *(2)*| +| Scanners | previous 0.9.x API | previous Scanner | +| -------- | ------------------ | ------ | +| `get(SubType.of(T))` | getSubTypesOf(T) | ~~SubTypesScanner~~ | +| `get(SubTypes.of(`
        `TypesAnnotated.with(A)))` | getTypesAnnotatedWith(A) | ~~TypeAnnotationsScanner~~ | +| `get(MethodsAnnotated.with(A))` | getMethodsAnnotatedWith(A) | ~~MethodAnnotationsScanner~~ | +| `get(ConstructorsAnnotated.with(A))` | getConstructorsAnnotatedWith(A) *(1)*| ~~MethodAnnotationsScanner~~ | +| `get(FieldsAnnotated.with(A))` | getFieldsAnnotatedWith(A) | ~~FieldAnnotationsScanner~~ | +| `get(Resources.with(regex))` | getResources(regex) | ~~ResourcesScanner~~ | +| `get(MethodsParameter.with(P))` | getMethodsWithParameter(P) *(2)*
    ~~getMethodsWithAnyParamAnnotated(P)~~| ~~MethodParameterScanner~~
    *obsolete* | +| `get(MethodsSignature.of(P, ...))` | getMethodsWithSignature(P, ...) *(2)
    ~~getMethodsMatchParams(P, ...)~~*| " | +| `get(MethodsReturn.of(T))` | getMethodsReturn(T) *(2)*| " | +| `get(ConstructorsParameter.with(P))` | getConstructorsWithParameter(P) *(2)
    ~~getConstructorsWithAnyParamAnnotated(P)~~*| " | +| `get(ConstructorsSignature.of(P, ...))` | getConstructorsWithSignature(P, ...) *(2)
    ~~getConstructorsMatchParams(P, ...)~~*| " | *Note: `asClass()` and `as()` mappings were omitted* -*breaking change (1): MethodsAnnotatedScanner does not include Constructors scanning, use instead Scanners.ConstructorsAnnotated* +*breaking change (1): MethodsAnnotatedScanner does not include constructor annotation scanning, use instead Scanners.ConstructorsAnnotated* -*breaking change (2): MethodParameterScanner was removed, use instead as required: +*breaking change (2): MethodParameterScanner is obsolete, use instead as required: Scanners.MethodsParameter, Scanners.MethodsSignature, Scanners.MethodsReturn, Scanners.ConstructorsParameter, Scanners.ConstructorsSignature* - -*migration: use Scanners enum instead
    ## ReflectionUtils @@ -212,6 +204,7 @@ 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 annotations = get(Annotations.of(T)); Set> annotationTypes = get(AnnotationTypess.of(T)); ``` diff --git a/src/main/java/org/reflections/Reflections.java b/src/main/java/org/reflections/Reflections.java index 1240f740..6454497f 100644 --- a/src/main/java/org/reflections/Reflections.java +++ b/src/main/java/org/reflections/Reflections.java @@ -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): *
      From 5a3a855770708552272c7c0ed002a61d6a237a78 Mon Sep 17 00:00:00 2001 From: ronma Date: Sun, 17 Oct 2021 23:41:22 +0700 Subject: [PATCH 14/17] arranged ReflectionsExpandSupertypesTest --- .../ReflectionsExpandSupertypesTest.java | 109 +++++++++++------- .../java/org/reflections/ReflectionsTest.java | 15 +-- src/test/java/test/classes/a/BaseClass.java | 5 - .../java/test/classes/a/TestAnnotation.java | 10 -- .../java/test/classes/b/ChildrenClass.java | 6 - 5 files changed, 72 insertions(+), 73 deletions(-) delete mode 100644 src/test/java/test/classes/a/BaseClass.java delete mode 100644 src/test/java/test/classes/a/TestAnnotation.java delete mode 100644 src/test/java/test/classes/b/ChildrenClass.java diff --git a/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java b/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java index 79bb1657..a59d2756 100644 --- a/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java +++ b/src/test/java/org/reflections/ReflectionsExpandSupertypesTest.java @@ -1,68 +1,97 @@ package org.reflections; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; -import test.classes.a.BaseClass; -import test.classes.a.TestAnnotation; -import test.classes.b.ChildrenClass; -import java.util.Collection; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +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 testDetectInheritedAnnotations() { - final Reflections reflections = new Reflections("test.classes.b"); - final Set> typesAnnotatedWith = reflections.getTypesAnnotatedWith(TestAnnotation.class); + 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)); - final Set> expected = - Stream.of(BaseClass.class, ChildrenClass.class).collect(Collectors.toSet()); - Assertions.assertEquals(typesAnnotatedWith, expected); + 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 5bd6014d..dbfb3dbb 100644 --- a/src/test/java/org/reflections/ReflectionsTest.java +++ b/src/test/java/org/reflections/ReflectionsTest.java @@ -21,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.*; @@ -316,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/test/classes/a/BaseClass.java b/src/test/java/test/classes/a/BaseClass.java deleted file mode 100644 index 414de604..00000000 --- a/src/test/java/test/classes/a/BaseClass.java +++ /dev/null @@ -1,5 +0,0 @@ -package test.classes.a; - -@TestAnnotation -public class BaseClass { -} diff --git a/src/test/java/test/classes/a/TestAnnotation.java b/src/test/java/test/classes/a/TestAnnotation.java deleted file mode 100644 index a481769a..00000000 --- a/src/test/java/test/classes/a/TestAnnotation.java +++ /dev/null @@ -1,10 +0,0 @@ -package test.classes.a; - -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -@Inherited -public @interface TestAnnotation { -} diff --git a/src/test/java/test/classes/b/ChildrenClass.java b/src/test/java/test/classes/b/ChildrenClass.java deleted file mode 100644 index 02923de8..00000000 --- a/src/test/java/test/classes/b/ChildrenClass.java +++ /dev/null @@ -1,6 +0,0 @@ -package test.classes.b; - -import test.classes.a.BaseClass; - -public class ChildrenClass extends BaseClass { -} From e8ef652d16699168a329749ae7f891ca168eb7fa Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 18 Oct 2021 09:11:13 +0800 Subject: [PATCH 15/17] Correct the wrong match (#334) * Correct the wrong matc * Fetch upstream Co-authored-by: ileler --- src/main/java/org/reflections/vfs/Vfs.java | 18 +++++++++++------- src/test/java/org/reflections/VfsTest.java | 17 +++++++++++++++++ src/test/resources/jarInJar.jar | Bin 0 -> 37333 bytes 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100755 src/test/resources/jarInJar.jar 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/VfsTest.java b/src/test/java/org/reflections/VfsTest.java index 817ac829..a05202e0 100644 --- a/src/test/java/org/reflections/VfsTest.java +++ b/src/test/java/org/reflections/VfsTest.java @@ -114,6 +114,23 @@ public void vfsFromDirWithJarInName() throws MalformedURLException { } } + @Test + public void vfsFromDirWithJarInJar() { + try { + URL innerjarurl = new URL(format("jar:file:{0}!/BOOT-INF/lib/{1}", ReflectionsTest.getUserDir() + "/src/test/resources/jarInJar.jar", "slf4j-api-1.7.30.jar")); + + assertFalse(Vfs.DefaultUrlTypes.jarUrl.matches(innerjarurl)); + assertTrue(Vfs.DefaultUrlTypes.jarInputStream.matches(innerjarurl)); + + Vfs.Dir jarUrlDir = Vfs.DefaultUrlTypes.jarUrl.createDir(innerjarurl); + assertNotEquals(innerjarurl.getPath(), jarUrlDir.getPath()); + + Vfs.Dir jarInputStreamDir = Vfs.DefaultUrlTypes.jarInputStream.createDir(innerjarurl); + assertEquals(innerjarurl.getPath(), jarInputStreamDir.getPath()); + } catch (Exception e) { + } + } + private void testVfsDir(Vfs.Dir dir) { List files = new ArrayList<>(); for (Vfs.File file : dir.getFiles()) { diff --git a/src/test/resources/jarInJar.jar b/src/test/resources/jarInJar.jar new file mode 100755 index 0000000000000000000000000000000000000000..a92880bbe9e7f176633cf42b38791707dafd86c4 GIT binary patch literal 37333 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 Date: Mon, 18 Oct 2021 09:17:53 +0700 Subject: [PATCH 16/17] arranged VfsTest --- src/test/java/org/reflections/VfsTest.java | 43 ++++++++++++++------- src/test/resources/jarInJar.jar | Bin 37333 -> 0 bytes src/test/resources/jarWithBootLibJar.jar | Bin 0 -> 942 bytes 3 files changed, 30 insertions(+), 13 deletions(-) delete mode 100755 src/test/resources/jarInJar.jar create mode 100644 src/test/resources/jarWithBootLibJar.jar diff --git a/src/test/java/org/reflections/VfsTest.java b/src/test/java/org/reflections/VfsTest.java index a05202e0..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.*; @@ -115,19 +120,31 @@ public void vfsFromDirWithJarInName() throws MalformedURLException { } @Test - public void vfsFromDirWithJarInJar() { - try { - URL innerjarurl = new URL(format("jar:file:{0}!/BOOT-INF/lib/{1}", ReflectionsTest.getUserDir() + "/src/test/resources/jarInJar.jar", "slf4j-api-1.7.30.jar")); - - assertFalse(Vfs.DefaultUrlTypes.jarUrl.matches(innerjarurl)); - assertTrue(Vfs.DefaultUrlTypes.jarInputStream.matches(innerjarurl)); - - Vfs.Dir jarUrlDir = Vfs.DefaultUrlTypes.jarUrl.createDir(innerjarurl); - assertNotEquals(innerjarurl.getPath(), jarUrlDir.getPath()); - - Vfs.Dir jarInputStreamDir = Vfs.DefaultUrlTypes.jarInputStream.createDir(innerjarurl); - assertEquals(innerjarurl.getPath(), jarInputStreamDir.getPath()); - } catch (Exception e) { + 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); + } } } diff --git a/src/test/resources/jarInJar.jar b/src/test/resources/jarInJar.jar deleted file mode 100755 index a92880bbe9e7f176633cf42b38791707dafd86c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37333 zcmaI6Q;aZ75T-k}ZQJ&IW81cE+qP}nwr$(Cz5njT=A2D-E9p);m8aiSUsR)7UJ4il z1pop90${pCUJ2m;V<-S105T%V0<;pcqVzHXvJ#>qO3HLHqQ8>>08cX0Q_@njbPKRj zv{bXxb4`j2%gp;HhbKS)@>1XsjebD4*#Aw1`CpU&-vU$rhXCY%UO_oI<^LrB{J+5e zDG&5NyZ`L}4#NKD{okOqg(3a_3wEf${{MwV_8O)_(#*IC*WxfdBzU`T+nY z|AHqV#wEieDJYjF!ac{X#$OooLG;SB?|Hqo0LAu=Hu0ZX0+6%6{ALO0cM=PPjrdS6k6;ynx#Aw=Yq6_ZuUXS4RsI0NFA)) zV{1y)!Gp5|e!1d;3zKHebUVBz*A3KlxGd@1cL)9IjZ=Vj0(jzGS!9W$F^C1|_elGg zbLy}l+z<kt05x)dp3(da3PNdX5Ery1Q;XT9(ljrh-67gz~wTQr-= zUxf4jR+smm{!6%vKcZgmfS`g>U^QR$F2W~gh0^MWo*RWHbDlePVhsEOY{h||tRYX7 z;3d*9srLLPm_vkWM0|&l;gK6zz$eGGO={QYM88O{P}A-|CF{)8Hn}NY4c@ivy0(-P zEPO>=2U7!|5zXg$SGn>cOPS2pZ}?^ot{(!f{w}(4Q(ob%{`RYaeBWCJ`7~>CVJP|I zgXr=T;{OS;Z)P%Rc<c)VPHaf*ETeR*J?hdbv!1J%q1 zun;Gg#gvv$Xt@fRd&g9Q)$C(?fPKh+JM5*K$!=7Vvu3YqS#Hok6?78$P|nx9D0k?I zBc?ZMPe^k4u?7=`TYd*+?{X4HlxbQYqqpuh%kd4_4x91BgT^>L>PTkx6SV6>9YRF& zyWP#qIIhj?G2jb#t-}?^^-f@LAR7e*=4`y2knz=Sl*TSMVN)^?}x~Gh` z*9b-+^in)qX}B>Ol9eu)GK~O7QrGQRzZ0b1%ZMZMAx#X9X^W!oJJW|G-?=540hbC@DgrAr=Tnt6S38>{X94~pk&4WS*D22 z*)G%0F7)}9Bd;%+A{iaV8jfy3%2Xd3RW0_e$q-G`CGb;I$BXOeJIK=kX9Dnl^ixVN zY$&Kn=!mIkuBoVK7~*SD{kSSiNyWOne?EGS_d;&1n)YapdSUW?{ukBa!L@avDR9wM zGK`h+ftTi?H93|4!}xn^RTY~{*n%3Q3qX_A*-Hqqr3A3ONwZc$(Hsw`2=s^NO~c1P zB5@s6z!88?f%Mp5>jplCQWWQewWo6Bl4kbi5PJ6PB{n%g{_ToR!f$1kUQ5ysl^V3C z0APD*8|4&bMqZC{6*5#s4}tr88*V>Eos0X6$9;MlKKA;h-?@#SV3n4zZ3RKYZNC1K zo3w2OEggBko!1NAag;IKYDJRs`x@>TWyX}#>l5|~HtKq5yS@CQYcf@ZL`w~z`5FQ( zETxo+G%7XroWDDupy0~!lM}*9*O{>p8~o8q8YkhyDPiE}__ALT;en*FD4k*Y(|NyvnuDwmpB!5d z4!CE^%fHy+NfAku z`6OW!tYV?%H7>SV#JE(p@I;>8obF*nEMYm5k@R=FzkagaR=nyTmAjYn^&xh%dbT!RVfquW?vQOEQ<1uJL=-*t19WLL^=P7k)JzBZ z#BY@m$Tv!T(|oEck+Z=1fT2x5#SaL*-jCVF4DD4WVyL4j%(K zc+qNRsGj(d#GyO$eJSH>7Qf4oD=3R`=|SUH1!RH>I&nw&lU*@?(%`{XLwwnq9JvFn zJ&n{kW^Di+C~|f4@kFk8>Wd{6(GH1ubH1g+rirD3ec5aHdP}~Gh)vbPTJ2Fpr-Mk3 zgKuyyGj63wJC#Br-Op!=04RWyH-i~oZV3$=j8qN5aBDOJgZ{k)d+~ChDQZaoC4*ph z)Dz1G;M?Pm=eK^qL;Rj!cBr-*dCv(sNuC$FK^+CiSS@$4X>IhMQH(zy2Z++tC%`KV zNzZ!>jOZfV6qpD7C_qGaZYy6{^E<4<8*QS^nttaEFD8kJ$j`PJJQCm*z_IIgY(rK! zU%8qjeftsRTP;+vZ9h{5wm7;+U3gaFp9>$41avvM1$1=HaLoLq2LWrMt33F5_ZoDn z>tt-p76XaiH{AOuK&VC86J_t>)DA#n>8h1kGp%^eY0Tiu%L%0PFWGPE6<)yQtSwsj zI!*M{&{C|1)Ke4Yk45fk`Gi3fuwNMF72SQ@Ufy8WKEY%fOMV z*yZ(vBo(rXw?m}OcoB&noDY-sZ^#7|u<3^Jh;j4|Z#X}n1KI|xux6jcqraz#3#tB; zd(>X#I!5JnX;>~WsEGiN=WY= z&f^!0TQ8B-5z2ViHpt)2eBJCN*+$gsZKq<-q5p zJm1^7Cw5Yv#WYQT9dMEs*34S=GU6=}D|BWFxTM!r+K9#?+ch5}a_zBT*YO!g6;pkG zBid)#+@^f33&yi2^yojQizgY}nTsp?52szGGL9Uyj4e`)^;hy5@Q!5$nmLjob)i84 zbvuW!M@6&ng|O&^39B5;gu07-BB%avUVq&M#iz~Hu2VNOF|HP5TQJH(N`Qw}-hS5n z6~HH6mv6`)$B2=vW$4kWqN_iiw zwXzBY*tvZdaFy}PnZjiQg=}n_lz+!hRMV2pcVMOcT}&B($OJ?`Mk0YRD|Qs6K=BYj z&}V?0`&PA$_s9wN=nQ~^uB6Zf-XSACBO31$rsKr|zmO5=vSxuXsQh5xl>ozn{6xV4 zVS#?*Bbfk%h(L%lK-++6ljZ*P>ipXn^8o;JLJ(Nzd2>wNc2OO6V#mmr-+ffUJv?fF-)f}xAm?q&TbmHy9wgt` zG~do0Ih*=^6DpiZmi!ID>Igrs}7Z;P1h*Q>m)9~aZD>#6%ZGxc{rF&@YnU=9@qNFo&gV!zKkJfwkR z>`tTqR)M<`&`IU5TM;^2pQtix>?khxcU}NJA|I&sR~GcV4wqW8cG1>I?@Ob&aT7}wx;lpNJO$m(lMO{r5M|(dyD=VKxTz#*uRGWX+n3XT9 zH>&f3pG&J^xWeR)F0+%@@X`~!%EZZDyW)MrE>|p@=93k6^H=yya4+L<`_p^!HH6$% z=;wa(efFB!Z8;msy?O6n!?MtFaH-~9G{9x+Jb(OR98F zo{YbH4p|)gJfp{1YVm2mO;G$h721LeN%EFCz*=5M34ObF#~(5V^bI>c-yLc z@h%zl9E;qDewEbQzqQa7y*nx?W`(*u7YJ44aSuRsN1IY?0k2rSB7!E9{t{F#A>KNj zYZHWBqQF-QWZ%SR5Ve{(A5F(V~a z$1W~wWOepUTy<0Vcx6TO(_FaYl3}w&Y0ewIL)G#Y<1n~?X>iVMI)1-o2uF({+Y*f< z`kuNXg1PL%Ow?MoUH?}6(G|#Wqe8GcQKlV;-=ux+=q-37ga11S+|?2{c!P=yXH!z$ zPtK@u!9ORaWIjnQNaT>eC=UK4qsjE}GKX+nY4e~FI)*zlZD`rTJ@E?ih-0Wx|L|rr zGJ&&K@xpB^FXKU`8D@z0gaAf6Cp)J)e~Fie$=T*keHWR;pC!hPN^YAryZcSWUw-4> zP7vQ1Ue=+UouA$4RDV80oPztLdpsI`60tkzacBNJ=~oIjnBMqhO?1Z#)Dw$pcsJ~q z#Hsh424@O)3x#cZi*MO@)Ot8}tt4~9dJo(9sYBsQ(&M06rmf9?dw1+gbU%od5nJ44 z6~(dch@?M?36c>yn)e(vzVFjF*Le%aZzk0f0^bxE&o5AYf&DKdN-;uGk@F1F>f(Q= zi1nTyhW%3$pM!!p=e$VHWo#LeE{(rar>W(~TOSFv|4^^2M<{5KaO#_%D57PWJEneZ zs2m2XqBLq;X?0Szh)5z|4wJ_36r>OcMTQ5;DH+&{cJv+ZxA0~fA^ZI3m|5}1ei{CP zoX_l+{6+aod{G*sTU5~CuAp!0Pc3i#F#GlGd^MUV=MeK=JI>&fjXMQwe84 zK(Z!6N{eLEW2nS}uvNjHj_HrXiDrZ9B>%}dX}+ zqY|DteNxlx_|>4h+<2s7fyEYYA@V*TN}MQpM>mDH7G7nMvb9U{)`JN$`8+k2pT}`# zhJlWPax-awyv}arOa3e^v7JhSel4}yijXH556ud{r;QZ;4y~Cz=XW)UXsG2}#itwo zGe?;DH=iOo3l=MTDmyDX`gbQABgv)qeUuuy!JRb#O44GTpAS+%a&nS#@&O-?M4Sm! zs!I{1IKcaIq!22EBPTBin0z0d*1v6Cy&I6k24My_W-X^7q1kh^UN~@WH~0vvu8`&hiBbk0 zQ&VO_%EsS!64~;%2CO7gaFjOKxTGLp_1a^rDBn(o2p>yehbC%UjbZM}!FO z?yZeML{}w!x{UfKpfQPv-@9dq%sYNkGN8bDGTQNDHn8k{om~a*r*vk2PDA_iA7&8R zovAEB&Q^<51j}~vc+eRRnC`)lQel2i*yaIrns3+LiaAx!YTe{{6yH*#di&F~Ttz)e z(xX3izeF_ediRbryV;^TwPJ?`BV`%$Qr{eY8L5LY=_LFzifqX(-Su&B2F(?8okkT4 z-ZxX>q-)8U=c=GAcbTOVDryCOTiTwN7H_e>6gHFW*b7s6mZ@YfHv6plK{a9cQRkJrzi8 zB5)pNSsGv#CH(a|0=Zm2@d1lA01#;TMLKt}Y;_3pQr&uvpK3etPaqMfVf|kCE3R_( z=e6KH@zHN`9FY0v!(Kv6Xbf6u@28vs`EBGa2z>cC&?0QcXGF$)Oo7I6pcmeCh_VUT z0rHH;G6$6LOQTWbIe;aEjAamhZ$bH8CKVP42+c9L@&Ig5{=EYL3#4;Df|%;~#PTss zdTsyDS@Vy_s#aCW<$~JVFSTt;QVhs`N{&K+!;*1nYD!lQ8O=))nh z1UxBr;MxsTU4T`JCa_q7ydx`_4yLtvjAmysb7rwlOurN!NLftD1R;L=!_+B+*tzad z8%F{b(f;eAnU1`R%B$AA3NL6r`)-RBY4ey2%C4EKk7b8xr)ka~-{$Y8E9i3YDQF=K zw23g1{ZRjht>yt3o~pWd04Tx>RfzlOK6Gu#ST-4PoK5%h84LU+0AyA8L%Jg6YJ3Yv z&|Y=_tk|ve9+sGcT_Y(9G{@mvdkjDooJ9`T-N0a9a+8q6fG%Y*blW*feW+zy%F&l0 z8JT(Q&kH?5`pbs zM$kp$74VZt6EfA^QN4@BxCS&6yvQ_WY4g~zUzdrAh4_0xF?h6Ot`-;LSX5?kk> z7a@FmCd=)Y;rJ-*6cQ(ExZ$FI<#b84LuD@mun7U>w+I8!@F++I)ywJ4WxNjDjwO{N zNxVK5R(%|{8|lhfRU$+#^+H4uRi(AUFa_{CuehnZ2@mB0x+&4Ji>5NrwlOHfOL zF-i&L;~=WynH)Z`gJinT3xUMO+29qhaadI z;#_`1hFqE)7hLUj0RcG`G&~AqwtnOlLuLa9z8i{_2_G7y~7k9f`j{RY#pci0z! z*MuH}<{`myC7vL}Yl;U>T(LfZoGAynui>63)QTDoSsBU?hKAj+NsCm6DWGW9eFVP;{BN}JE6pta9G#Q2oLe9i!KukUb!BQ07%SN`J=iDiFKNI1VKH12h z#p85CTOh*=T>mg3+l28%F%35o(o7{<#-EE!4et$`ie+I^;E%}LzPi)T2jPh}g{K8n z?b>vRx0*wjWs0Z?;A%`Ks)}e6mZhbFjZyDKRUxGCTHr(1k9VGti*Fr&;H%E?$ zDsG54o3w(iGxvGv9zKyaa`{to3%mtUZ%62~YeLV<8N7W>&e<8Nn95`uq;k(YNm4E5 zj-ME_ZvGK;0%;@(ej4Lz#jud`6GKVkN+}W|=;7r>HCm0j_Sa7#;S)fALzKwA4K9@W zG(o;K!o4|ydKXDMRlN%QAts!BF)NT_6>x_;4+Ai363#Oe?diTkG_z$HOGVtawR}l+ z&5oU-p8g(;!0GX!DqvzSmYoMewvMB{TDJzyk!VrS>R=sg6D&^BNX~$fl&M>swpqqi zkn3-qL%hgSCaRGE>UiaA>YaNx&!$XtJ*1cif)+Th*&;Ma9&+v>(eW+jx3WWZ@DQ{F z%~cI$*uhJ?FZqKP1r4r?Je( z{iw88fQhN+0KA8Sm?qj`50Yci+E{wQN3I&NcLU3w7?ZZ8b72m{~092 zrVcIIgOy>Wub|qq4P8^+@yL=Dq!~c<%^@8kZI6<=*i5W)ul~FRP`4#X!avEiTil!7 z7k>U>wF|$%=2+uy|3R^(teu>mF!pBuSD%32fUB5&4^it+7+}J!H$VV-i2w<(^SZ)i z0IPD|EOhfl>3>Q;u^5K(`hvfY>0zlA+VJnpYyQ`J<-FtNn>P>e4CC5JKBN_i1zm!k^f~>ogL~#^P&c`Ox zw@|)Hv4m#OOvyB^Y_WpYjGoN@sg`VHYUxkP-_BCu#@#EzW`j8_nQq}-5eWL||tzI5%03nf?DQU`TnbRSY*6Wd6?dM^y7ZfeAp_GOm zQd$;bD+j-hX}4l9RhK@o8QUmK-mC|fF1FWBte;6LBhWyF#hE;J^d6Lz?B9Q0fS8?> z$Wi4MEbUvkBYmd!Xqz0D0 zY6>dL7bQ|+e$2wj2+D?Fs%qdL$&a1fcI30Rk~k8kE*&k7Q4U)sE!$}#LL$qSJB8m{wE$p(7l)IY zBd+qbJmyE{Bk3pUpORZLlEg73J^BG=^=4^nGKnL!i2Tm4+YE^vgOzOZv#wLDysEYT zldF#fid1tIP&na7`D|1g>6-ZS{7Ob{30?RGg%H>Vn5RPdN_;U}o+G$ih0S*Ho9rWkzEYFXf!GI8u z*oT|k2pnnDZ&R%_F_tzF0E%9m=%$@)WKruR@KC^SY7vW5INdNAhVLJ6#9K_S9gZz- zP6GAOP23JZ>qY>2QV&KcZ46oYK>z+sRFv#e70g^!#5DfS&c8iN!(M^Rn(0+hFT)NF z=&_kK6yh?vtsjI(;_k&v>@!yNhR}^`E}fp)z=Tvy!rd1R!(ObG4*HKB2AWk9v6#Xv zlplS_r%muybOYS#+nzueC9G=}dEYNQn>HV0T4LQ{_k;O=6-u802oe*J%*tKx3)V** zG2Zqf+&Rs%x7Q6gcZ$3o6Xp6axS-D~%o2d~wgXw;t)UzN8cqJlUP*P&>z_oJ)7dGs<0f1%4=_ zH-bI%6Jotj2$s;9HTkr7$;$I+uIr*T#H&CZ9#C#-<%}$zZ7#ixAaSceme^MjprENF z;95L(`KKwr8kds94`I?oWIM(fNw}q}gG8N}u_B3#R8OF=6#^8Qj(wDyS}G_pvQfRo}g$8!1ZDm;HDu;?+_oj3zBd}G(~n8lFOewA_6!F^~yKaMT3Xh zqq$+rV5ljRcMPhnYx~F%YRQ@r>L$Y2^9tV|JP1U+FoGL$iW@oN!Sim8T7}kl>k^F z&+}R(;%)q?Z=l7%qc42ha_%bMITZ=I&}LQSGioe;oeB=zXH4l;wKG_@w;n2A-d&a) zDj!}ty1v*sgwkvB+jrmOISa6Vw66`lcI9oHv-&anoCjlir2?tvHDLt8zq-z3LR&SF zKGgz`h3pV@R)%1e!opO`-9U0kB5WtPDCef2p2QEv}R^wy25|azAwAW2as+<<0J=09F%c^v% zSVm9mewJ^l+B+-*u#!40PcmQY3fVq2Q!af#A9f;NC^ipw;x7ib~6lp zpV*Kni`ivrpqTR@aT;}L+2RO6B|h;HwBo{R=D5l*mEFc&X7wsJGY%uqlTkj9ua`ms z(wFC-axbh1i(bs`H`HI1@_G#tD^(7Zz4OEaQw}X8*;b{APocO=IQ9Iv)Z1}Q(oik( zv4#yhee{KGZc_7h%%-0UN{@3Ftyu-Ii&F%;bjxFGi}=wSfZ^*e1$_H~^l=D6Ejj=Aj16}VLS+U`A55GQ&#n^~3Xnvl-~+h;En z&4z@c8pxklnL!TOpO)D7FEYgTWh{>` zGx7o+yCAyCPc>`@hzH5=vpxQ)CL--LXD=qQkB;-M!h1Sw(<+J;%ZJ6g{4%nleI#VV zIUjH+aQ`Y?3(y$CtpQ?$Y`mwRXSF0edB8{!o#*A++1)x;dr}VZg*R>wHQJKn;OpA@ zbDtze=7ecd^&Gs^INmQ2vs(F5PMX?N#$XDqB#pqACu}WUv&)O?C6>Z)h!4vpo=Xwz zcCFzPBnz*CUlf|F-yucP3ygAqWeGsDj5C>Qho#swzJ3Ftk2}JPOeZ`sM;n{*f9iv~ zV$UBLdRrF2)T`F0@F$Y;lc%r+PCEc!H+;Oxgf)wA39e=XClv!j2IiK}n1cnv^pMrr zP;2`|HB?j$w&uoIT4hz*Tx6+ONKV$7+Af`DRrJSANuS|RsJx7gb(4yH#^-(y07D&K z*KHXOU$6B8DGYyvHdNpI;YqacNH1DdB&3jZCRaR5=FB;HF}af=73q;M>P$WAKYnuh zvtyPq9-lL;{M^my4dZzH#Q#Mn3+Df2Eg`kI)UUcZGPNT530_>2hBxD^(gGVBr)7`VO#R#Rq>Q(`c;rvM6qstxe^C+?nuP& z3cNOF9Z7Sq1YeTl!Bru=$i%XPxC|(S<_Qq4~*1nXx-E`Qm%R%xhVWWbVb0ZZHG4dPquN z$^!CxbHrV3!WvJEUYpr=865ZDHdZ~w@wrBmHKaIeg+>)8P-d8M%FvW57D2vyK^b}^iA)0*J-$%Y z4?FiPmA?Y9g5J%RvqXWKYWI9mksKqPOaJe&St=07jB+urKV2J!n3~HYNM=1uj0|~7 z45d0AXEBP+NzY+1b_mCEf=`mzw;vc=fZUW))R{TIcHOE14tHcQr8 zAA~3)$f3mueo~zL4vl>0_D{INC}}H^P`D2;Tc}fvW@L1msP7-QPftUUH0g^ZHWPRNTBKlmq3_*kdHrl zBH!j_sWf!upJ$9Q-&>K6q~1r7&b0h1PBE1@Y~~&Jyi_s2FQur4XeU$jB2iU`5{0~1 z+^(bSO5HJ33`kgIHu{$3ZgIgS**~9;6T>K`GZw%rpdoiYtq%(*44UG`Lh)zrvHSLS zBk%Nku8uaSdXb`x;|z+bh=>WZOv2`2Sh6^S&V~Z_AaUeXnU7IuJ!&ei zdskG`BSa^omv8uV|DuFKP>~5ZA)2+~(>F3r@1>kmq;P<=_^1FL`kZqiVoht~&Cc@= z%_y&=V|y#{(xl181R`l4Y*0wrv=z3unFNRC5|ni+e79wQ(eyECZ9C?&P*5D9uFgi* z(+WYqmv41ewDT+6ZUohIcAc^(MTP?W4$ahc7+jd71*nUF;8B%^lm_l4W(XjvB&gNS z738}~j;bd~myXrN6>ehZX;loH7O29GU_d;@=T*iYtwX}6-FwmU)J>(~!eFRpjMFg8 z#KW~Md9_a?`=4kW_km7&9)=Na{vyGL=R+M5tot7vBK-q-igFa?$4O4|d?H8+rbrs3 zCKA73S%Z*v9wNkqdwUe+$>we(gOGY262wLOiOlooA`4d!53y;1a6Hx@^lG~CTyL{Rm~?u0)n4lz=BvqcSlv@m=SX@s8#KidDfUH`*iCJV@hGblV&t z%@QHo>$A}wOJB#(q+W}Fr2XHN0_+yadvo7Qa^V)x?r4UehfJ{sYXUiFPq*jM3Ysc^ zvyWdv2JZbD0~XI$7_3J#U|J!^=WSxO&Q*^MT2>j44I-Uv_9_d~E;bwF`4do)olwVc z(QD`M71dEl*&q|>$;rkLrppr^o!;nOkrT11)y8?pJJfrg9GQeHts@Mf5e!^585tGH z>qOyoJiQ#_IDk<8dW9Y|>QD$b{3-zEE<9HGe_CGqO`1qCVZtmbSbob8Jpx4GM%3p` z(+4!~Rog4`K2OYTj#xuLsjOy!nnL|`KB|!A2DYAeEj@c-2Cc1@BkHz!^Z%*e2fP0h-QDd zDsRQ=mp)M;T#%iex4-Rj(I9zQb>Twu1y8HF>iY%{d4J2BPT;m*5L9k@e>eE$HNfil z0si>iyuW-sWBmzv|GXC5x}4y~(r1LFTgyLJML5v#_Vf2wrCN(nfyM3}j5}+1yKTyM zHkzQiSPyF3?0YV6xAGKt(eZtMcVN5t+Ox+7004a30RTq)T!%(uV?Rqr{bC;fBqgC% z2zQdv75ug`<4MJj5CeUMBJUnxaZ^O0?`zUyP#?lj_$NUZ=S>RnaWQiC{Y4wa?5wq| zl|*p>ni46*E_9eBhaFj~mWy?%mn`Zn{`+S_W3A}1XIe%cj54X-A-m_^=g2qPIp^Aa z=zY82;dk=}CLUQlAX%**#WIAo>+a-xKg1h%<6GvdxquzaSFHi(*QN{kVXW2hAaas) z4Qv0-V%-yoT)E3Hdx`GoUL){hs$#UReNU+U#=HCXb(J7^!As1x%ka2k8d%Bp;NTz< zb(x%<>SGGlUFyRR)*b5OGb$G~AQ`sTC%*>&k4T{d{vS@o5*#0u{3hZe6?0v0w1-H)^|=vRjkl|iwBKdEs}+WcMfLtAd@@Hm&6hi||vuX~WLL;#KR zJdoB2)^CVo-@lso6Z4ZSeYlOZP91=eBa+(Ez)D+H(;$D=3> zh*Z>85%hxW+~|BUc2p#0%SrhK`Z7_Pbf%n{P!r*VyMU)LZSAw-ryh0Ti0 zNE3`r!cs*bNm_t6D=MTR%Y5#8!#-Hvf53ht*3+n$Y{>puj4p+AXcW@ z2Y>JI&7CKHDOiT8;I&*kVniL&A_X_>IhKYG>CHYnFh6C>|MX_(&ot(EqZ0}VX zR)l1=6!z?}^Di@JF7K(>TJsG%$DzS%V5=ko!P(OJ6?%fo6?sCY$LBj1AV3idRBdfB zPZA_Kl;T~EK&~7p15k_HsqA{PG}6X;F)5d;YfS?YlRkXO=qU+hxQ&F;h(YHJfwUfd z&mvl8oy(A=W5=XVnV2cZOhK!x#@&C5wu%fQI2-&q_wpEvrO*YsE*h`(rYMqUd;Xp0 z|8XEKEWDhL&W>f|3uaqE`6(BQS+X*t8m)$y(h?9aS>2_3R4o3$k#P^E;e;)HMoPj^ z_OjRx0p_}{^Cz^3!xW>dRHB!bjh+Gnj-$ZW-k|3dkx!G;nY%G7m$CF8i~4cvB%H!= zqDrIW-xL(4X~L~^TQS3(m034)c<^l2NwwwP#m`J1#DS@g$>^L zedufTPjs8%ZAwr7J}G>EEtCAxP7fgR-c~&wiLL?PZsz7C%zUDLEV7@O}SmrzQuRi`efAgBjLHiRf zMubLqiIxp^J2SpNSE$vTrpP9NiLcO|s8D&yx14@qMXC`Vz?s(kXzZygnQ~we}as0PpAiKGb#w6yG%RzhAZXWHmhv@b47%trLlqP(z z6Kj;NgCFKB%Sn#-LnWqZnJb@smpPG*DF1P|HwEL53b2i%#lNgJsIC^cPiLl_5gksT zd>j}8nSVRo)PG%kS#;t+<|HvA6oF_-{27Lc&JzGnz-k?D`S~0vS7Q(4ps4LVyHSD; zK%GI^4RjO{gK2(LD}eU6QXSRwLcpVh46pNE@Ly3{0&>k8N=xoS+?1*Dr6(fTEpACz*yyLb{pziq~K;XO2PC! zw7N+!?$)>a($xp}DRC_qe==#!RV`=7N~ zKJ^G^@GU_{mh|+$M04NKtxXtQMwM^w2la-|dxJpr=?Dcei0z%adF172yXR1>1Ii|( zOz9T|g@adMZQ@}@(Mlz{%f(#GY$rr4M7U|CoOSaz_a_bho8#AGcJ0}no{{T5C>zX+ zwm4N@9W&FidJbt;g5!oY8`bGuKhn`8Pb6raIy{m(EV(sZj2+x^^lD5p)Ewwh275cy zvnK-*GCshFc8HOQ`U;PPA-$|;wdW`0OgS0teF-ZzTY50#ecbhUa0?PTu?xDpGFRc8 zi{W$TALaOexP?N4pm{ex0fo!9aoB9|O=FMTaYbm{Iy^&Q1@Cx1@`g*%^Z(Qdj^G2g zR}h%5QL`DlvVbaM7)9IF`!C>l%W=GP14Bt!&V_^x`s1Z-?uT@PAh@IkT_Uf-FGAzr z;SxUD4#ZPf@7<2TdwnC&vk!UCBaSp(h~}9Qi%BVtV|vH4GqWL;@@M6oa$+YYo+qE1bzLF%vPx8Yy- zpSlCOCT6@m?t`uq&ARBqgOQ1qkN|s4BRck7a$@4m)H+DVP4nVq%g=ZqK!>R+Q7evj zSitz7fr&#A4KkEp9amgptibKdzvmmxN_do$7N-TYOpOzM>nm!MVv0^I%Bkr(Wti}g zILDGNM^uD{UN3*+ZfJvFTUIX3W zWK(omZJiVNTZ?-@MD}IMm(dLe@jhz;8-Xb8+#Ki&%V6kpLI|0nB%(nd`P0A+6|dVV z2~Q}T^e2m`!IDGS%GT7;=imLFm^=F(oR6QIbjdx^ux$HFv%sIjo;Cr|iGpNTVN zAX!U>z)HZ1o0kYV8YgG=hl;<^qlV1z0TZ!hz_)nMTe%3zx?`r(0nEt60`VcEEF;n} z!aeU#&LB~6!YrfE3cxOZgR0lVAadZWk|naIU=)o{BvK>BI*ZPj2sQH0)+B;v54@gjzc>-3!kmJ) zl1|YR=XyZcKBmc^G!p(LWG74|0zzN-*}7g!kkFqPyqTsh1#6=b&A~p56TgQW3*=q} z%BRx815NB*L^sH92u7(Ze%B*&PQgc+Cs4`ZtfP_3$<-4ehl{iWz+PNK(Coor&xdYF z!)>RhcUYztQL8_eE%^Q7o7L9xM{N2mCSg06@ngI*nVaGF1{{9tp^_!)GhYF*<~WX( z_TpAWhVhZ>Qz_LySX6pb!5%zcr$`H*SKKF!&d1rdN1Y3{A{wVSQces_eLez$S*o%1 zlMC6tKDz+BcS`0)Q(-gqxAAf=^o;frgI&oewl29LjJZ2K(BpG)dCm3p{qWW(L=)BI z0RsW73;eceNNM<385Q~YZoK=IrUs&zSf70$b}w;W!|MNoJnpl~%uw2VG9q9>9$=*a zwt_%t;1OGK(s#1ea83mpeD`GT0}$^A_MP@J3^u-?Xpl7cQ9@tXy12c=_2%o@J8(U> z-j;f$u~(qxQCN3x{)!?pUr2MQ-qiB6QY^68S(EQjEN|br4E1VEXKGbuvEG*6bhjrS zb!FRon4$;HG%yYu{(j+XxD9`ZbT9raG|MQ~W|-WM87B(~#7&or&tAqO+(Hm7!47?D zGCkip$BvOd8!-Wv^(gOA$tsy3~L4n}NIH-8>=cm!MFRi4bJffzXNA~E)Tya3i{dLf}c9OkjIj*2Io+-m3=E+v35b5lgh&hy#F}~Njw4aGs zX}?2305}?_$Kj#FVzkB^6$aBh$Dj%7M`Mvl`!aPfj}92Lxl3tOxwdtw+a>zzbUBqJYC9W4Ul83bH3gelxUZB}{{!P>-!Dh&Qt#jm`(8MHpJd zNFXy+KhX1Tpir``gI@?3krg?7h#=$X)+z#1yg$&sG53Z*(LR=hS=BU=)wnud5TPA6 zbT)0u1t|LOaBMCMu2snpuRd=6zcBQ|EhM`Gy$80;BW2B~G z7PiwNTCkbZ4O-1}uPSlQ3k_7p+7{AcF&Q$j?Hq(?_ z=Ea%MqdMk@N=}w?U}#zG)6K=#PBj*Aez0(Qn8lF->dX+ZcR^JgndEg!Afq($3Nx}H zx~Hm^A%0${^&*A}(%KIm?HOI%EKal>NLQa{gdfum5}}SfAcBx!!27NkG)?2wCmrA( zrzVhWfAbP&0`tC2*&xsGA5qK7uaM-oyp|vn(vW$^{8DO$#9f$)=*iuudYyd?=YXSa z5Z<#ShsgKaIdjPnosA6VFaiH4&M>wp?%7QYXKzqQ(SuahS!-d^`c;`u+R% z77&geF)ZvjWkpU-eu<%qSQw!@k-)U3LwI7e3&hPS>}bPJ<~wUi>$bwSpaL(j9?so1(8{6h>E>LXu1 zyZ+rOYbP&Kop#;NS+cn}3CC9koG-Dd*ef?f&@`UkZ9w*l-L8i(DL zLiy}3;J>S6JNHc);*UCZ{~yNQF-X>>Sr{GLwr$(CZLG0v+qONk#H;7jb`7S7vobR%X{zSzW0H0RSNVzb(oC2vkr~{Kx0V=Vp>6W6uDhsF+TG!pMSE zMnW($bvkK4#@w}=B7H_<LE8vUDg6LoZ6RDDdo>w-Yp%7e^J zuC3|NDKQ&r_R3$e`&e=kBD)ct_fUtUCqBSo>)ZXY=IuH8qagkxImL%XhseP&~XNu-gD zMpv-`q$mz80af2_>2PpW0f*xl7A3DH(spllnU_np#cEUDE@D|`#+Os{$C5T zrk8=TI_8gD5{r%JrV#Y8W-hQ4ln7L;c){VaQELm&gmH*$mn^*nfh+;M9vsNlpY&_M z;CR!;m@|Q7**?_Zah$~fVfJn3^U^Hajut$Sx^|_$^5P z3q&pnhx{!{{|ltAz%5HZXkZiwKZ$$j9#VfCgs;pkO}`TeKjAB7{{sj;!e{i}2?#yX zXW(8MNH39l#GXl@KJsVi9%`UEq_6ZXci<|79{DS8KR(0`fqT+k9Ecy0d(@syAV2bF z$R2KBFVbhs-VMl&65?*-0KfIEynEgr@bAOk`+c%kcGL&qj?XcupZfl%Tm6pLHL$dO zKM(t*iN1++ShRW^9J5mIwy7yK(*k2U$BgBPduy7!^{T4A2@-1RrUoKvYEy&f30$b4 z#88)evi1FhZaQ(Nxtr&X-4I*{1QRA-oUq3+Go(fCq1`0TCc6mqx{VilRh=Zv7FPvje>XJ zNCSUTC@*Y`E<$aYrI{dZpr8hk>pPMSkZ)>yQ4f=z)DyVtrs%m-wCF+SNBUG4@vKZC z7YKsBC5?zWOJn`1RR!P13Rhg#6uo_~weos};fNHUJ3U)F*rNKQqpL_;?Zg_BmW5G+ zB1ux!$jSc@6E6jz4uaCiGKB^3x@b&peDzGLaNEWjMt&vj95-KTc8RU`;%x?4=4|%Q zdfnJv@zvHdWO~b*{$gSFz_cAp%-Y(Rimt+}8mpp)EqQ@55T;kq5~k}eptW%x&!59B zf2nyoJ2Yk2yW3A`*xh7~hB`kswfyqU>m7|2N^8@a7>U1xB(aXUes$o?h@%E#_0sFa zF;_1EX;tNgdkp8i8YOY$17NSJDgSmqriGyB2j%KkXST4G>cuai=R?5w78@DfJhFo0 z+*GZu-9AuZi`OaiLgpi~Hw__R?j8!k(R}3yGOQ@Ggf0|YLb-Q$AASlDjk;5uzl46v zk;=&usq-r|k}J$7y=~w_^2S<`n1i#0&%WE?{2#vrdvrYdCT$BG-_un{pI5weea2w)**k9lp@V~nM#=YX( z6c5BAut{x`{{na<|JD6Bo--7K$S$^p;b8n1XiaKkJQxYbCbo@kV?1i;sS55df0U=B zrcT-8Yts(|-xGx718x|4um48!PNC{Ss}mJ7yTuZyR3j6l=BT#z9|9X-^DcgV-1CmZ z>GNS>QT>syM!=>K#HK*P29g6CTzsq>3Jp{a96YL&KlpBHaEyR89>k13ZCAZ1(0iP6 z;Ytw@Xbo~fDl?CZY?DkmfL7^tL+0B%duH-(p}1=d3ojYPo$K!ZAh9*C!D?H+pNmwh zTF{es;M3TqR#e4vP%hYKg^fa^tJ0mw=NZq6xW;%}q-OGPLDWm3Xz3YS5=+9=6H)Sn)#R+*)dT1z_v{Ljt|HH;|=#W3Jl5ELNH|$BeKP{AW{!1R; z0YdD6`&-K3)xjVH35noyBU79kD2{V!<1_qgJg$xP6_FK+^Wyc*^Qb^)|4j(zArO9) zZEqQlC-ZJ?))ye#$vd=aot!{b85J$R&Y@-Vk12+(qA!~V>4Q3^aiKi!!ukAOn0cqqB|s} zt@8@9dJ3~*PDk=qrd_vTSy9^8?MUcJ3%i07C#2UD^rS>R)+0x4ERIO6Y3RR(TDD@R za4{#PIYPP~(JMQkD>@tF)9UKGk|QX$LMgX$>$`$uj!pc{z2G-`n2kNw?I`GhMLpW7 zWBzMA9^+K+*Y%`^-T>v1NnKkkxPK2!jm}@}*d&~SK8J$7zA`b*9?Aog%JkV}>XKS> z?4OB}qio>{vcekT(}2b9nPigF!jdMWhZOWeqwXD1;&vs7=@E@RFbP|ZiO0NIj6EWX zBtqZwo4$zN_H{xVA6 z;eof^6(7=t!;*;JNkLCfmXcOp(4`W2e~*NxFGozTXzYOrWW5LF(;>41a{SV*D-4UC5|~c>0CgoNr+Nc`1k8c71A>eSG|f- zr5zX2Ga9*PQkE&;FI7mb?;46eF41b@g|LC2(a__9JvK=tps(VI`Dfgu1>;l###!Yr zchZFPtOs1N{Vq6JAzedC6O%4~XS!1ux>P5nuSdCO5}&drGG(KL_J}BnPirsC8b2+i zg7#>Kzh2$C?B`Cd7}WE?p!s8*f}Rh-J5xEdJ7~a&Tj`DThkXG*hJ5b^g(iGHzrE~C zOb`#ukyc}krLN&jVZ^jgKn?cTnoXqvD}C(GP;;Cx{9FKjd?!P=BSru07=c~hU!6@` zMJ4-mf4OC4W0W-d)6FU0>H*P`+P9BFTHB>um8oIG&u~AtO{XTv~haxHlYHbGj57dtM-^ZrzeEQf6oVJ_HKO-kBrMRcZdPkxo06 z#){RGZn`QrO5>A4KhK^4U%3%3Lx_hV*=GJIC+Hyukp2nh?4lE2WZtx8wqbOxXED%+ zohW& z_`FRZp*6PeyIttMJI-)RpE*ygiMAn5CuvP0swXM+UY32d`aNQI;$l+`RdF3%;BvHK zI+C`N+13Kuxdq>E>ghdg@0Gise^sP2P5=bR0tNs`g8%@a|NnJO z_zzpaKY6>GA}G98qiRJ|wwI~#LV#to)mQ0}0;k||z!nl4DBJ}c!IHZu>Ksxkl*G~* zko2FV>4d+8Ofh!S_r)2rQ&?Iq+p0hIman(HW;wlXwx4=`zTVLNIY09cpsr&MnXakb z^NDb2dsGyZ6JpSy(eDtVpn8-F4y@WWlx`(_(^O^lusO9827kk7s@`h!BZFE2g#fo6 z)^euWVYWOq?rYZ&6XZfuv~1dD>51#JodF(cN^j?fsACSZ8CoLF@xg0+|Y?g zZ`yBoF22x|=~i~?Y2U2zPv4w%mih?DbYosRdr!i9H{vxcp+)@83@bh4hyKSG1Y$ku zq7`uBpj@jx)AM$aaMkh*iTlpv==AF(Bse|ZRsiZtt(X%fS+_1dT{OVCqKlNF<+6vt zgo|cW2VU9gcdSlEj-gto0B&3m3hiByq6H+Qm} zp4GXb(ohc+&pX}+tB*hx4EZYyza{yX8}8L3YNzYWC5GPbdrTj zioiT+t?WJsuMOa{MbQ!|+w5_fN;*BYS>^|SmX!vYE;rbWUz1lzoSx_EfIZSmKY)<+ z()Y1_L4j^=UBay4Vv`!s&As9tPirpG8*%viIh^lj_rYkjf`hVN(|0kq2CmW%(SHf~IHl|+ zWbh8VL-`5YsQd`t!JI$SOryU_=j<5~J6GfiNgs}$*Xk}%3ix8X~dcw6)_*b?Mm_b<<9|6hGf{)!LF>EVhhiZYtWzhy!AcX9iv)Z6uO6hdi+fcXzE7}v%4FL~vJ%`0hDDh@P z!|xRgp|tiG?l9bTzkKuP12{&51PcgB#64d_?W|-%j#pGaH7LT1rd!JtRM*P3pR_cf z2P88z)+FoctH)Gn?5=sNi=uHFb}@%;Yf;~&$DT z$Lhyl2mSxlMqLaM2{JH(XI{+MmFaE4&$GJ$IA4<-9MZzNOQ6(TtE(H2HbQ!7DnKa!|}>hKqP{&b@n-43-DTOYX%?aMyINq6+XNEW-OMB#6kQf!MH!3FnLMk zx8FR@H5L~IQszB#=fR;`P|pr*`h8UzLK`}LPeb}t>I-#PKcbL1%~0(1t?)FQuw<98 zaK`JS0S2joNke31m}U6$^J1d$q4rhT!X=F)Z0HHe`u@rB7}-B}-xpt;Bb%ELr><|< zrC>lH%7#b^pFoy{379?zHVK7vvnH^*@oDp`&8!Bai%l!apWnyKhIAdJ47ITega5S)VP?a<~cDG%2T3$bZ z<{E+>XclbDyj)5c{OpX}iO1JRQlHsC}2FH%bKn}B^zKWgqhKVD-X=py7y@0I^*^{ zs-s*=o1{fgj^vxHmGpPrWV%|k*;R%c>LO7uc;%?a)Z9{`MRjvKI}SL|7_K$swPyV45`V%mArrBgKjgWM^nS-TLaqMrlLOD#*!^zbZeoQ>^fTmH8EsI8*0KZ z-xlZ7Hn+0`D4csV-!ks9Mluz+otN7s>41ho8fQpFj{i7-=Ex&;D(w)o8beRie;f9jFPf{xkUC#Drq?9P%rG(UG|JMs2i!UT$EQnpwL-h@~brs zh^^eq4bw(4DwEBVU}DFUaVecD-)(*BlTclC~mG> z;R_GL;^Oa@#>U-61e&${%6wT*&RXc=Fs+k#kldA|OMmuGtOEwT6oB9+Fr<{r>`D8T zrV*&keMTQye1bhJJt(=ZB55~Yq46NOd6yI=)=r}lA%B+vyt$ek zD5y+My||mu6|EDL2zctyUu{R|bX~y+A8%QPno*~tUBV?~IsAhP z9xTo!szGup)*pq3i1t4{+9l^C0ewKk+zG_N*LVg+Ip$ zYzRESH)<8$bh?B7+#o8e-3a|)YCW2HT)foTh-r>KFE>tD5=1aB#Aw;TPk1FHVvfO=z;cm@iZkNOIl$CDkTCj{wM@C4 z(`n5lB8hGEhOSr}jbb6SkKOG&e&Nr5DRsDopRhLh#Eod1zY`U1y*%;1$+#HLS<%ozytofDHxa~bQ{mWJRFW%H0_WDp2V=za6X zfV}bYAo281rru!9CM&Fc%#aTG4s6rij_(%06$dhl|5gsEh14D)x+e#&Qd892%N%oK z>~_Qs>t#VJua$dfkEPikWlVom?!*Nx==a%tgyvRmZ?0|OHVlXC@gbp?YH z1DknVLdyGmd|Vx+$bHI-Z^Om9@(}v&sSs6dQ{59+V$W1gXG%S#=6ibQY zLGi(VkIhD8C*;)7Yprn#*zS3vq=GE+PK(Q)Yo0*L8O0h$p_|>&&^ef3Bkv!^>(h8m zNs(K_87^)qx=#wk;Ooy2pAl6u`on?bxgW8gYsf3ROG|9%8X2I1z(SZHg^&VpAz0Ij z73~Yq)wGstR1W$76dIq!vf=u#!1{kre6;@%`ac3flJx%x2pPT+kVY&{UYKwW7W^h@ z;#*694Ll8+mx>M{n==4{|D_e`a?z0`(|SL(dfyMf7sj-sD_8EPrtHJTN&fQvXJ`?e&9_sfJwkEHr-@-+_(F#hVBG6FOPflh~unN_}$>Nd7Ipa z^^!a8!vP2HVY=a_?5HgaV`OUcYYwdT9-k|4Z`6~`gcHpd3e3>mA@}R7>>_>jBjCg| zW@3ps6n;uU*342ItK4mr>A43Se#A^tqi$iRikU^-Q=q0X&*=^beW5QTZ_Eyu>iixk zKl)|VThdn~Z^#}H{lH$Je_rYP)Apoqt@fyIQD33FL3@CD!Fqu718V!!{|?;*^+J7H zPV}1U2Ga{km;e2=a9RQCqw2ohtJR*>E$g4vbp1md*l(;?EN|EzF#VufWWJw=r^h`0 zSGfOtLMXG}`QXt30Du_*0C4}8C*(g35?XEs+RMxQqp7r-z79AE$5Y~^>efg?LS!5* z*%YbbG~E3H4D|Pnv_#T1V6n8w(vlEhHAWVacT-Xj)unJ0(FiwY1JR ze?D?Pa(_vxAw7iccE0pD&GH_)&G5cV$p75T%OlQj0EH!`wV$!+=?P6yZ@E_`k5Zx6 zx-Nd{oE=b|sZSP(;WgXp4Z1A+P8&s))4M3m=#t}7?{M$Vsy%?E+c`a`G%-&~V(+xT zt980VL$`CeV?(!dyJwZ_Ss8}4)f(Uqu2RI4B??-Vb#rRuah)($r~Ghtqin6y$E%jJ zby8++h4u++dTwh|UV(vdNz3Z=s7-olx4_jW&%qCG|o3y_U*pO?N7%Z;nJ>w>DnXiV~678n}g0{ zhq$C0+T$Ycyd?eOgO8GS^73%_|$BSJ$^&IP~7d@f)gR@Zp-5hfdv3JVLKpZ*Pe|z4ByTyTitG z-~LJb6s3CCNAT@udW<7sA&Y}@-{DdG=Ld1^uZU#T_1)vxr&p(sY#%ko_pf%0wyfua zV!!hF;=@CTfVI<2S;UC%f)OPzSqCK7-a(W_wY}`}D+?x9PwjTw7dP7nS67a%F0Za1 zJ6}l_vsY~W(rO29u2SH8T5?T5NlJS9h&py|XL4Yg?K3tn4mqoDncv*nd$}<;w;mThFv!f|{f)C1(l()zzPYx%vp%SD z7&iiTez^br)Vc(pxo^mfRtrx8KZIN$!esbf1Pp9iwGKg6Sg*+8=DN~hK}LPg0BCZ3 zi7LB8XJ@Q#rz^i+wIkDBYTyDIS5V9gl*c3hh}$r`^47L>MC%E z%hy|5S+;ru?aZj_D$G}`rjahg*rXUSZlK=9iULX6!i=h>c4gwoBK9Fun(?hLWGxMc z%V4ZK5>Q7;hyaVV-z`Q%;^Q@!fB`ey&p$}jUP=*};lK=t(Wmw`zZMT)84_)OT9*JJ zN-Sd)I_18F!GI_ONsJQFItm6fFviY;14kl&(!_BMA^JXr@v0)XTn9x)i4zGgkPl1{ zBydN`%pE;sAOMnKF)dFfwC@QcARhf;p_{ zF4%k#gDpnlg#lm*pDQ`a@U@7eN^8P`GZrvwp>67RljIE{XHqFqw-*sA%cp^hCe}LX zEVQOy!&#(hjR@gDq~$qDKSAC2kNq0P(~hMW5<=5hGMKVSD1s%0I0#Kesecf6B93Jw zASKzS&SPS6M)1{dW^43lvA}T+nRI|5a6%^zEFK6ruV6GBsik6pQwB0L(0x6KJuD_T z%9__IklU~W47%ApYU1>5)~k{hId#grt3TN(dP_&7e-tR@D`+EQ1P+@La#)wVw=4t~< z%fMia;T6RMncfntVWCe1k}@Lm#ge0lghq?l$6J{2=Wim$j0y`JEDa^f!8cM>mXd@Q z8hV53oS1mu0fm`P)fH_JqR5!63q~dOCP4|$kAomZu6%4T zBafYb3LTB3lLpKQ3`vj}@40%^XnCBf2Oq!+g9`zVn=|<$LVHj-!H}G}jRaU1nxv#@ z$dSaO18JPzBH>^k}dwmASIfsKM|&i5k96R;gkK zMie8;kvAm*GUp(xA8#7pmneyg0Cv$NH(xN)juAW`Ux!muJMre_y?GO7gtLi-g(4dG z#E5agFrsh@aD@aFH`F=2GCT)w*=bh}x2GtprQCHvdlAh`REo%23Nq)apI2Dw)ZUBa zy4=^mo@>^1(2u%DQB~5J@~y5ItkG$11(-9Tv1AFm1|$kxKT3Ma6p5dH zVfpB3`IU|$kR&Y;CgVRT2MHXCzg3B9ltEks*U{k{Qd(hs<$M>^M@eUQX&uVn8)Kz8 zM2PLiFQ@8sZOy$Zd&?Eg&L_!aWiDS=k{5bnXbQnYHTPU5D$!Ir-IuX`!|=~E0dB!Y zG>!S+Mu@D6gk5bFs27G-FI4N4K+`)xjIY+rX#tg1z>^^jW?fu}i-}#L4yy<*Mc#Gv z4=K36Ej7ESczOCUrDBM5&Wbpo$F5pD^1ky?ZAC4~%c$!p)pGTXY~&!#OPHW8vWfPL zV8ESOU9c_TE>PFIuSulWQ}Lp#3MUX8K1hUL~WdG}wj@M=Ms?l6-!b-?_bZGMqe4K*jiq_pY9{KKc=Dys&)t46Q;B`~hF^ z)O-9UwBk@?$uLrrFbC!`7a^Jt_U&6D9=F$EM@oG;jy70{pu}H&75_9Yz0wULGr)!u z7auC&Xg!_x<;_hUHN{vBi&pfRYg-19XD-C z0pk+w)M``w20o-;0+D>sR7WInzqmO0wmiCwBs#9p6DKg`yv&Bw$|uH84&K~36mR(z zJ+b2is&wJWoJ*Wx`3zjF8l`Z}gbe@A*LJ}1-U;g`(Qo)DOOCH)^$ZtkSQRjv9lJkHOTfZxs039GN5<0?L>n?;68ZqiTjUEUi#C1c~oFA~L) z;Mf=ge*B{<%*>Tus6 zgU?!ym+QPzJEbWvW$P5D=x`D8s%j$u>S>hj^Qzy?bePaqpX}eABR>dR+%YN+opZm9 znfHn7@Nn5g>-?n1^rxh=Kj~xS7K2BA{`{DC`J*#=WK9Jhi*d_KF;jck4;G^#fC?+9jN9HcwV^rVl2KL-4Ip&A%}~AE@p~AP z*(EF9XO_o}Dzr1<+Tpn+b_>0&d)T3yYxWhC9>ZZllr>g`=B1$hW&HY)laKBNT$S_1 zGQNSv3#1g9^oW2&duOW(lFv(LW=L5)Aw;pPDI*NY5iwtH-L<}fkc^u!$puv{GLuO) zm&!_p(>E%RS)ev@hj-=4)FxFWt!u9;n~wG;u9`B4{lEyOI(;R=EZZrJHts7Xb=EQQ#}+_^LC<#B|GT_*q?g8=J)!H3S_dAWRgt{ zPg+(YBNN%|xHLE@0&F!$y?ga0utKVo%9`#g@5Kt7E!+FMBa_es*XoHo%ciA=E*rV+ zV-~k^hwIRhqi_wv!|w61q+UrxIX-x`MiJpj`P&&wqK6x_k$1=3F^UWU-egBbr}gj2 zIyDd5HDLPt!*sw#>j~o95X9taVGoKOgt*CSdPfsxC|yZOkJ_^drzc^VHMA|nn28(d z_0ZzxA!lsF>7utsE>D2&)k*obN3h4;^m#wzce|O>UeK>r)5o|1Kc1o=w#VJvc|Y*? zo2k=Y=r3^Q_ZzVvUd*qT!XE{(ABgvxqL;D{<5NF^q94IW{^a=|hIe?gNB+QXaprgY zB3(z>@MrIK6GgkF-1D!9XqOw|zclel(@?g|>Ejm(c5FF!%RN9HKOm&u@KdSpOuM3f z%PaRovOZ-K(e*Klz;%T1#iW`d&YPV&bm=~dWcDAQL-(NO77ievgTG0Fh4mSrAbGRN z_Q=H`r^v=1lAxmTOAI5cuyD=>VS>n`FHH6wx@HQx?R{~2F68<#|CtPw{+&0F2(RuH zIXdnU9f@P!p)@@Zwiro6NS^B!c!5Y&+G$R! zexDvRijm^)Zu`V3x>uz)2WlIUfdAfrX_2z^`+5qIv?u(8$HR6N>>jyxAa@WXUUe8U?K?Gd+N^u!l=!epsq3p$fZsFX^qM1@HB6dq5Fh^bD@)Ivzq6%K@7m=D@ylfNAVaSC$&0$AoF z@JWEc4^lkp|A?6Hg;LH3qp}Off2_M;w44s&bgsqOmAq7 ze^fWZWx?u`(GyrdIQLfh$l8=!?_+lN+99;fz96A(1&sXN?j4w#Q+#~bM1*dB$S`RU z?f@2zH~eT%%1)!U_~lvwYF`^6)S#V@xkuZSad9MLu@aYyY;Z~2V-V|;}~n%*@hEq z#nZ_O#k`c2z!rnpjMlGgNsn`Xs6aV5bJbwgtw(T?orm~i(1o*E6KpMMY=em24Q|Rme9bn=%>)rm z?lrAOn)Y@0oqI0l+v2<}pVNIX^zMMk-jQEfO}Q$jd1#jHG8EB83}cRJ@IA6&WO zA8(Ew8PsEaopEXL<6O8$CQ>JsYetA&Kj`jN5LbBaTCO8TM~A%yr3NAzj0bmNzfoSf zKlSrz4hFOnEjKkg0HJo)4)z^3ZfZRuO|y%$$jVz?U<`zvvTbdvd4rluaD)&2e}x)o zKV>p6sSA9J%w{H&qpulh1&lTJumwHy zBx&`o@08@$E7rKziL@80(?&skAVj4>wZ(5aj#_DkeX=o4R?fzM=8X#TZ63)!gVs@&^TDC&9lp44r zgb&W3KEsq0u)BU=nvC#*g@F4OcJq*(?oR^)2T*r)r5pr-+L7Y%3S< z=8$${FQhe?irY@`Iq}XzQkGS{+h*$>L=^C4R-IN!TmemVGWyXMucx(WFkUhX8!prK zI&ZpSws5Yh@k=hJ+N72x#|fLEI2|U(3uHI-75}kZJ;JU?eo=;fF?BCgm)jTahehLn1@+Dp}jR#-Pf-SUP$crg$gSw-kCi~-hoxi#4%!7i74!J9>eygYz_hQ-uU+{G9-0(uM_ot538hBP%T4{hxwaDJH zv#I<~S;e0+SrtHW+P#n#A@+4LswCXA*h6%PcyJ~d71LTu+5}@q*^AB3$JV!@=gcUB zpg%H`IfE-*mMHvU z&gciS-n7)a4+IP2r2+h6*KdZW+tK)b`94|c#ZC)_2;nA0I3u$&+rE!nWuQl|c`nb$ z>&Gp2!CEfSF#pgGFDTwxFeIK@$m~Mi+|}H|EXURj+qzBY+nd391AdUJQM3juwxtwU z`k#LV0SWH?dW{DJ0LTRTx3A^@`(GR`N}F<<0w~$XNLj)5u&qF}>XiP$sugxB5{go! zA`D7}VukeP4sbJIqoGe+@ZMVZ?|@(BzelCxe*?gG9x`*AxyCm8eSg2h?lC{(T63PXay@WrFKYou`762bp*xk&B6z)fU5SUqjWH%KD`K8Tpwv zhw0S4gybN^)PI)DSK@V_wAOLu0+L=9D-@=Wx$b%FlCHRs%)@B{h01&%_uoPMM4@mo z(YpCvUPNiyXgP!0J0DakfMs;T1fA(D?v1BFwMQkal_>&E(@QcD9oh9jIGa(bMHO8# z&Z6)AT6%hM!BVz$3MOe?9I1@`y4>W^2E=R1goUcJ{Y=52w&%EK%X=1&dy+kBL7D+~ z+8LQn#k=d8-3m1oQ)*zjl>F0GqH@qIL5*2K?vbS(A*?43y2CJHmHomhW02Sj{Yf>l zXMgprE?>aW@1OvLQKs0!5u_RAlx}tmMEziyP>DmWVdT30Tx;LWrBd7A7K>kJ>j0cp zh4^ZolK1_uyso13i*$Nm00336e|tmz*Ru3h%8LRhywJ@Bg!d`}9;hBIR8Il&0wD`R zCA7>a>HA4ZrPA0kNi%GyUq`cd)CbY|Gq1p(im`6Ol(e-DT-(z-qwOadh z;lYt|Tgndji;fW#?g;r`h5)$kOOd1L36sbvvGS-q%C|bA)Yp{m^MPqpri^6_XEj`or+-Kq5#nx9-mtshix?LwkHP|)8eA7&?Co-GPl+_+oO~ZRp zT%0p=xhyuhH7Jcb)~A|LG`fGwLU?^}VIrfsU~doeoArfGRr2WI))o>>vNntdW7AlPhND{1xWyDZI#%dx!i!MEn+P&v`(R_xYWc| z2Eb?KC25n=-sIORmsmGhE`-f!zxXlBh+y#8ySdFNm}!|=w)$j2YgxPc14Bh&7#{T* ziC`6JEKY@51JgqZ#8{%oOY}|Qbf=RHFP#lQZByDP#L=D_EV9pfKLSlLO4)6`>xW>| zP6D|*EL1_@uTjez4N_SIkm1<+D8j8YMxcamNbikKIcS?ww@J-I6BZ2`H5yLuCA?Gv z$+Z?wXP8vx4r)l)b_lwq8%~+H?#&*hktRtNCdb!Cv@#7Jdj;O>Hh)ssqCHW@PU}-uBgq+sG zLU)nNTr)?$WFINAzq5kJi6A}JDk&>!Wg8xKHivF0hvQj-m(CJ`9?c`In zQPA-SR@JiwgJhoX>$n5Jlgc?9d;jq>b^rPD=m9uNhk^)z6wDpyuAb^~2#rtOG_rXT zMn>b-Z%4q^Pb=*ViSj=(Gq*zBb}>dxs?28KcDLIYIKz-7`Q@X@d1d9oqpj=`{H9N? z9mvpU7fN%RVlb3pFqL~RWm*p2@Cf~{5bwOJ*WZ7&{?%WtPw+qF_}hf7?VL;ujs8y! z;eYi1fB*uoc7Am;)Pe4uFDNo~#GU9Nbnp-ei{5}&KWbF7tQNt$CK*-?ystmLN%l69 z;u1?v4ztsrOAEJMe!X3Qqk2PvfM`HeC*b61Y>mFcP*T`QEHw-x_Rm~GUE2-ZO-Gwr z!<<&2QL9Vla`4!bCOFIycE8p})lm2T-XoathQqtVXPX#sbl2%)p_$rE+hsVMfX-p} ziw#K3DXDq|`>%L$)rYYMcLn<^hNGqF218Z#{&=X`0wS3p3v$KaGsx2mv zoFkNnu+V`izmWfPop~V=t=j*d3fgc00LcGd=l>3)P}f$+UPb)|t0Nf^Cb%#0$2ALd zLM%);A8LO~HNb>uMyLR7Dz+J8;F1s)W4_mkcJP$;l=PH#@;RlywW8AnTeV`Pw^^K| zH(#|>|5&PdZrAlWKX(&jLK5n)(Trl|`X`sXySICmZ{g4XyRPo{LJ!ucG!>YEw6rQ*ERGK6(viJl z&HcgcA%qHywRFp$|3cGw;t6cgt1! zjSY!a-?4M`%BjYnW+={OV(2K3M_WkU=d;?e6v4|T`_s!`GJkXz>(F6l6CXnB$;&l* zbhb$dI27aPYLB-pry*!+vXBIm92cA_&1lO=aJjRwwx^r%Mr#v~a?wPgY#CXonTg8V zN+4&Qj+tBz8YnFTL?bXnX<<-!k~b<2T;Q zIwk|ZdDZ|eo0JQUbIUu@Bz*H1;odt(n?eN7o)OtkR#wc^HLwEOaHcR=LEXx*$_)+H zH8UaBwVNhl5rgp*^UVGH&RvCgh^gn&yk$#&{f?2~D$As$es^=b6+U1svg{RuzJ=+52+Da|V{8UBFCPPzU(JblW=jz~ zgm-W$${5Ukfq%zwrDz(HN@l*+1M?`+N01a?)nH0%J3M8vmWq6UgVhz0$KG)pt^{IKZu%h!gY{(G3ftNr*)lkG^k66nkX$z#xR;=6; zU$OMXyJCwbF75nnCf!2Dd4t>LG3fusx3m^Wsnvd&^m6L6Oj};hSK;{NRGjW2ke5eR zTz>zL7DS$vH;YY4cCucPj#Em#eZ7jr0DBe5;AOwD=M#5hmqL}gN}w_8=lgp8@lGbV ztKIhVYyKil>5CYXx7x}R_Nip4)v+Z#dl%_HQ+UTr_H_p~s{{{KTGESjIQdS<-&)AW zF(t>?QbTI&yChB7AYiVAqa*>ZF8+E>bZuj{fYr{~xj%{2jrXF&vh27vH#;w3W!`au z3`SR~hkiDw(|0nGjYIb!6Cu{G^ZoS5XXYZY>xID=BAPZbm?q~$ z9^(^Fz~@utZ-zOSxBd~;B-<>!5$?pfgYDz77>e|$oyx|?^ddGGwa-I0GUGWgNxl7N zgONzv=$3~h%BpEdQ20n=yd1&%qK~NzwsC3$noV*xtsj|9&W82V*!hRk3XrHq#Tb`Z zp51PTjwQFR{1Fr05j1YFyQ|0wG^0MzwS=ztGPtXP z@OJbSQn)1_rqX20$+0j*u$Ks?s_8NeC}kRu&J@^jAQWItbKqZgDhvz~UJDe$Smm%v zO8vAbYNAYh|MKOMFvIOu7c}F7q5QE;wTWWIY7^5CI#eG)r7zJ?|6aVNSN9Wxt}bzm zSI}g%u6^0Z!*wblD%uMj#|#t0u70-UgXCeaVe3+B{(&9Csi;GjVy^AaWCLkGB7C_# z5xdawSMEi>i?|ug;fUzKi@LWzLTL`D_xq*;Ry@W9GPcRnqJV3q;gw9dzP;gX6o*OvuEm*)WzBmZIvmJ9J%rw+5PbK@h%$E zQR@|)HP=_qoG(`Wm;#calX$`SVxG-`-R66mHFZ}e3$w*fJH|3>VK9Cb&gRyaLI}1N zC)OV2^0mqyMT~=w={2h`gZU*f&}6YGh28SD%%KVy$m7SW{pI!q-$cmdyGlZ;Mpnjk zZfBC0Adyk0eL8T7R<>Q@w$?r*$=JKLR=d&tQoVOY!GVifd86B!=_K=9ENyV=!;x|O zo&a(^oX(M$ntI=B4^RX{9VX~EX_Xjw>u;RolpV-mh- z%S0<=w5{$nR5xag5Mky_Kk@}Q$cg)|FHcg7@?4{Z(RBAd4WXO~f4S|)o;*MNuc7&Q zQGbS(tI%=Q3x#uf-$7z2N$U2_bkRO*LnynU7GFe4wCfzP5NZ={#SwKGV5+fcCE}-p z`f>`vFCR<{j|hu;BFwz0d@n%HZMGBfbRi8BqnJ@*B04w6*7psx1f7MHJDrbmfGV5G zVXE-9$ibF?{rIAyU+?A26>;#|>jmIO-B02{^&0b8d1Nvb0=#fHR{KS-`L!>mgU-6H ziU~iPWW{7K+7z3CFJRvGx6(i$wTnW+M}<;GC*4FJRgaesg=pZ)l%zLYxFLvS-v_N8 z&bN&IL&Ft%KJ?yRR(uO++ljzYkq3=g1-=7|Vcq^sF=X5+-MM@POQ`27&Is}6UeT)% zdl18C)*HHIYws@?xzMgDos9mjTjJxs9i{#ru15%ZwD-1g?(zoRSKnu%;L?|%=c{)C zv)uRA5S#Z!BV_CD$l4e*6tExOIBv0@d2`I;sIo7g z*Bx>Mp-@LJ1*HdI%F%YdlA~T- zrBcL!OYhIJFX^kzD#*612G-U;Nu*O2Z<+h6(J0#73Nbo~`*Lh2+ew<|A@E#Z5DX-PBi?7o!M zbz$eWGeWFGJ3ZiZd-R7#b8{KKP^w62^BdI|;rU4SBz5i>e|=GDb|S_V6xkxd*SpVE z=^3rE1b*MI77AG6YuCS3+nJAqvim)X?C>!HCiA>tZ1zm~nq<$_WmEdv4m{>VL8Z`Y z;~>JR8X`A$J-KVxIVp$X)aH!NS06c4^V@`95?Ym8T+w0ZeGKZ7jS@DnX_;!WQfw@l zdg1*R>Bq}Yy{3b+8Uwe6)h9~I8y+XeXy46V3e5%$N_dWKP|3U=Y9VhMRTW;T+g04v zIQZmlT`S{=ifm=te0uaWjHw>9E=07@t}k)*c&KWzsFrWVt11xF&=XF!{ zMhG5Rx&?9#x51UN>pOnTY4I5Fjp0S|hltRQ@znBGWZ{rA+OM7`2D4Ga`jk400Nt5J zVvO2>j$_i2`}&1PVoC3oEy@aJA{9^8nUP0)a?C*=fKq%g@t<3nvo=53kET^1Q_rH7 zC-xFy^MihK&&5Qy-8Pn$eRTTmflOANZ-ic*Pj&zi6$|e4JeHCtjU$RPzrmM9q_oKgTkQZaH7B5 zD7f-ia7;EH{DWC-_5@AU9}{5vgR{KKoVz&r;n%(ad~|mq^xf|J^>S%dD$6uziXNL+ z4a2xX*WN26SRK)8&-|vXl6S%pyL}bAf$a&j2xRP^n)KRyE!I#f`3WJ)P~XAL$-{;=8p zH7BhU$n*3{a32FPO^Uur`c8p0)&cOnbUF) z8=->K)dM1SAS4C-;q%I1JEelu9|)_O&whYUzjE>^)Z@=UnG_wbC1rja#DrHt&d`~G zb6F3uVSTsGp*Ts+ziyZ@zxQOJm$ra&)t#4cv3ISjRgI%W^(3f>Ht)Bu#gxKgBW!R{3C=eos2Ryri-q^ zo}Tj?W^SA~>{>ybvaTMXnVg_=*;VMUcirhARxD& z;L5RAtIm})SUO>$u9OrX*bcz5(>RHs4YgpO`+8qC)+%tz+vMkk)$u>WRa}grGB}c~ zb{^~QGdx*u%PnizFl)h^ALJUdW+KiF85lw zq;|`_Va1IQH|)c&(;XQt@B`dKoibZ^A^-p=006YWSlHwMasVzaE?}%!OZQ_C_96A3 z){{Sn9B}VX{W~<2^yHqaX)1#>`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 From d6bf3950a0e77367e2a66567ed28b1c70a324e9c Mon Sep 17 00:00:00 2001 From: ronma Date: Mon, 25 Oct 2021 21:13:22 +0700 Subject: [PATCH 17/17] minors --- README.md | 130 ++++++++---------- .../java/org/reflections/ReflectionUtils.java | 20 +-- src/test/java/org/reflections/JdkTests.java | 102 +++++++------- 3 files changed, 110 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 96d000e1..6fd33bd0 100644 --- a/README.md +++ b/README.md @@ -43,22 +43,30 @@ Set> annotated = 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, see below.* +Or using previous 0.9.x APIs, for example: -### Scan -Creating Reflections instance requires providing scanning configuration. +```java +Set> subTypes = + reflections.getSubTypesOf(SomeType.class); + +Set> annotated = + reflections.getTypesAnnotatedWith(SomeAnnotation.class); +``` -Typical example - scan package with the default scanners: +*Note that there are some breaking changes with Reflections 0.10+, along with performance improvements and more functional API, see below.* -```java -// typical usage: scan package with default scanners SubTypes, TypesAnnotated -Reflections reflections = new Reflections("com.my.project"); +### Scan +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: -// or similarly using ConfigurationBuilder: +```java +// 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 using the convenient constructor +Reflections reflections = new Reflections("com.my.project"); ``` Other examples: @@ -66,37 +74,23 @@ Other examples: import static org.reflections.scanners.Scanners.*; // scan package with specific scanners -new Reflections( +Reflections reflections = new Reflections( new ConfigurationBuilder() .forPackage("com.my.project") - .filterInputsBy(new FilterBuilder().includePackage("com.my.project"))) - .setScanners(MethodsAnnotated, MethodsSignature, MethodsReturn) + .filterInputsBy(new FilterBuilder().includePackage("com.my.project").excludePackage("com.my.project.exclude")) + .setScanners(TypesAnnotated, MethodsAnnotated, MethodsReturn)); -// similarly, use the convenient constructor -new Reflections("com.my.project", MethodsAnnotated, MethodsSignature, MethodsReturn); - -// another example: scan urls with all standard scanners, use include/exlude input filter -new Reflections( - new ConfigurationBuilder() - // scan given urls - .addUrls(ClasspathHelper.forPackage("com.my.project")) // same as forPackage - .addUrls(anotherUrl) - // filter include 'com.my.project', but exclude 'com.my.project.exclude' - .filterInputsBy(new FilterBuilder() - .includePackage("com.my.project") - .excludePackage("com.my.project.exclude"))) - // all standard scanners - .setScanners(Scanners.values()); +// scan package with all standard scanners +Reflections reflections = new Reflections("com.my.project", Scanners.values()); ``` -*See more in [ConfigurationBuilder](https://ronmamo.github.io/reflections/org/reflections/util/ConfigurationBuilder.html).* - Note that: -* **[Scanners](https://ronmamo.github.io/reflections/org/reflections/scanners/Scanners.html) must be configured in order to be queried, otherwise an empty result is returned.** -If not specified, default scanners `SubTypes` and `TypesAnnotated` will be used. For all standard Scanners 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 @@ -110,17 +104,13 @@ Set> modules = reflections.get(SubTypes.of(Module.class).asClass()); // TypesAnnotated (*1) -Set> singletonAnnotated = +Set> singletons = reflections.get(TypesAnnotated.with(Singleton.class).asClass()); // 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)); @@ -130,7 +120,7 @@ Set properties = reflections.get(Resources.with(".*\\.properties")); ``` -Member scanners: +More scanners: ```java // MethodsReturn @@ -145,50 +135,51 @@ 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).* -Note that previous 0.9.x APIs are still supported, for example: -```java -Set> modules = reflections.getSubTypesOf(Module.class); -// similar to: reflections.get(SubTypes.of(T).asClass()) - -Set> singletons = reflections.getTypesAnnotatedWith(Singleton.class); -// similar to: reflections.get(SubTypes.of(TypesAnnotated.with(A)).asClass()) -``` - -_(*1) The equivalent of `getTypesAnnotatedWith(A)` is `get(SubTypes.of(TypesAnnotated.with(A)))`, and not just `TypesAnnotated.with(A)`_ +*Note that previous 0.9.x APIs are still supported*

      - Compare Scanners and previous 0.9.x API + 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) | ~~TypeAnnotationsScanner~~ | +| `get(SubTypes.of(`
          `TypesAnnotated.with(A)))` | getTypesAnnotatedWith(A) *(1)*| ~~TypeAnnotationsScanner~~ | | `get(MethodsAnnotated.with(A))` | getMethodsAnnotatedWith(A) | ~~MethodAnnotationsScanner~~ | -| `get(ConstructorsAnnotated.with(A))` | getConstructorsAnnotatedWith(A) *(1)*| ~~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) *(2)*
      ~~getMethodsWithAnyParamAnnotated(P)~~| ~~MethodParameterScanner~~
      *obsolete* | -| `get(MethodsSignature.of(P, ...))` | getMethodsWithSignature(P, ...) *(2)
      ~~getMethodsMatchParams(P, ...)~~*| " | -| `get(MethodsReturn.of(T))` | getMethodsReturn(T) *(2)*| " | -| `get(ConstructorsParameter.with(P))` | getConstructorsWithParameter(P) *(2)
      ~~getConstructorsWithAnyParamAnnotated(P)~~*| " | -| `get(ConstructorsSignature.of(P, ...))` | getConstructorsWithSignature(P, ...) *(2)
      ~~getConstructorsMatchParams(P, ...)~~*| " | +| `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* -*breaking change (1): MethodsAnnotatedScanner does not include constructor annotation scanning, use instead Scanners.ConstructorsAnnotated* +*(1): The equivalent of `getTypesAnnotatedWith(A)` is `get(SubTypes.of(TypesAnnotated.with(A)))`, including SubTypes* -*breaking change (2): MethodParameterScanner is obsolete, use instead as required: +*(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*
      @@ -204,14 +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 @@ -224,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); @@ -274,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/src/main/java/org/reflections/ReflectionUtils.java b/src/main/java/org/reflections/ReflectionUtils.java index 9bcd394d..64102d08 100644 --- a/src/main/java/org/reflections/ReflectionUtils.java +++ b/src/main/java/org/reflections/ReflectionUtils.java @@ -5,7 +5,6 @@ import org.reflections.util.ReflectionUtilsPredicates; import org.reflections.util.UtilQueryBuilder; -import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; @@ -15,7 +14,6 @@ import java.net.URL; import java.util.Arrays; import java.util.Collections; -import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -152,23 +150,9 @@ public QueryFunction> of(AnnotatedElement ele public static final UtilQueryBuilder, Field> Fields = element -> ctx -> Arrays.stream(element.getDeclaredFields()).collect(Collectors.toCollection(LinkedHashSet::new)); - /** query resources using {@link ClasspathHelper#contextClassLoader()}
      {@code get(Resources.with(name)) -> Set }
      */ + /** query url resources using {@link ClassLoader#getResources(java.lang.String)}
      {@code get(Resources.with(name)) -> Set }
      */ public static final UtilQueryBuilder Resources = - element -> ctx -> { - try { - ClassLoader classLoader = ClasspathHelper.contextClassLoader(); - Enumeration resources = classLoader.getResources(element); - Set urls = new HashSet<>(); - while (resources.hasMoreElements()) urls.add(resources.nextElement()); - if (urls.isEmpty()) { - URL resource = Class.class.getResource(element); - if (resource != null) urls.add(resource); - } - return urls; - } catch (IOException e) { - throw new RuntimeException(e); - } - }; + element -> ctx -> new HashSet<>(ClasspathHelper.forResource(element)); public static UtilQueryBuilder extendType() { return element -> { diff --git a/src/test/java/org/reflections/JdkTests.java b/src/test/java/org/reflections/JdkTests.java index d5ba8da3..630c5361 100644 --- a/src/test/java/org/reflections/JdkTests.java +++ b/src/test/java/org/reflections/JdkTests.java @@ -1,5 +1,6 @@ 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; @@ -28,7 +29,6 @@ import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.reflections.ReflectionUtils.get; /** @@ -40,17 +40,35 @@ @SuppressWarnings({"ArraysAsListWithZeroOrOneArgument"}) public class JdkTests { - private final URL urls = ClasspathHelper.forClass(Object.class); + private static Reflections reflections; @BeforeAll - static void initJrtUrlType() { + 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 checkSubTypesAndSuperTypes() { + public void checkSubTypes() { Map> diff = reflect( Scanners.SubTypes, ReflectionUtils.SuperTypes, @@ -60,7 +78,7 @@ public void checkSubTypesAndSuperTypes() { } @Test - public void checkTypesAnnotatedAndAnnotationTypes() { + public void checkTypesAnnotated() { Map> diff = reflect( Scanners.TypesAnnotated, ReflectionUtils.AnnotationTypes, @@ -73,7 +91,7 @@ public void checkTypesAnnotatedAndAnnotationTypes() { } @Test - public void checkMethodsAnnotatedAndAnnotationTypes() { + public void checkMethodsAnnotated() { Map> diff = reflect( Scanners.MethodsAnnotated, ReflectionUtils.AnnotationTypes, @@ -91,7 +109,7 @@ public void checkMethodsAnnotatedAndAnnotationTypes() { } @Test - public void checkConstructorsAnnotatedAndAnnotationTypes() { + public void checkConstructorsAnnotated() { Map> diff = reflect( Scanners.ConstructorsAnnotated, ReflectionUtils.AnnotationTypes, @@ -101,7 +119,7 @@ public void checkConstructorsAnnotatedAndAnnotationTypes() { } @Test - public void checkFieldsAnnotatedAndAnnotationTypes() { + public void checkFieldsAnnotated() { Map> diff = reflect( Scanners.FieldsAnnotated, ReflectionUtils.AnnotationTypes, @@ -117,21 +135,18 @@ public void checkFieldsAnnotatedAndAnnotationTypes() { @Test public void checkResources() { - Reflections reflections = new Reflections( - new ConfigurationBuilder() - .addUrls(urls) - .addScanners(Scanners.Resources)); - Set diff = new HashSet<>(); - reflections.getStore().get(Scanners.Resources.index()) - .values().forEach(resources -> - resources.forEach(resource -> { - Set urls = get(ReflectionUtils.Resources.get(resource)); - for (URL url : urls) { - try { if (!Files.exists(JrtUrlType.getJrtRealPath(url))) diff.add(resource); } - catch (Exception e) { diff.add(resource); } - } - })); + 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); @@ -147,63 +162,42 @@ public void checkMethodsSignature() { private Map> reflect( Scanner scanner, UtilQueryBuilder utilQueryBuilder, Class resultType) { - System.out.print(scanner.index()); - measure("before"); - - Reflections reflections = new Reflections( - new ConfigurationBuilder() - .addUrls(urls) - .addScanners(scanner)); - measure("scan"); - - Map> diffMap = findDiff(reflections, scanner, utilQueryBuilder, resultType); - measure("query"); - - reflections.getStore().clear(); - measure("clear"); - System.out.println(); - - return diffMap; - } - - private Map> findDiff( - Reflections reflections, Scanner scanner, UtilQueryBuilder reflectionUtilsFunction, Class resultType) { - Map> missing = new HashMap<>(); Map> mmap = reflections.getStore().get(scanner.index()); - assertFalse(mmap.isEmpty()); + 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(reflectionUtilsFunction.get(element))).contains(key)) { + 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 void measure(String s) { - System.out.printf(" -> %s %s", s, kb(mem())); + private static void measure(String s) { + System.out.printf("-> %s %s ", s, mb(mem())); gc(); - System.out.printf(" (gc -> %s)", kb(mem())); + System.out.printf("(gc -> %s)%n", mb(mem())); } - private void gc() { + private static void gc() { for (int i = 0; i < 3; i++) { Runtime.getRuntime().gc(); System.runFinalization(); try { - Thread.sleep(250); + Thread.sleep(100); } catch (InterruptedException e) { /*java sucks*/ } } } - private long mem() { + private static long mem() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } - private String kb(long mem2) { - return (mem2 / 1024) + "kb"; + private static String mb(long mem2) { + return (mem2 / 1024 / 1024) + "mb"; } public static class JrtUrlType implements Vfs.UrlType {