From bd0b5794107bb0a9cb8eb973ef0e0169ee761d18 Mon Sep 17 00:00:00 2001 From: Ivan Kalachyev Date: Fri, 20 Jun 2025 13:42:47 +0300 Subject: [PATCH 1/4] fix: refactoring classpath scanning --- .../internal/helper/CollectionHelper.java | 91 +++++++++++++++---- .../internal/scanner/ClassPathScanner.java | 73 +++++++++------ 2 files changed, 115 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/neonbee/internal/helper/CollectionHelper.java b/src/main/java/io/neonbee/internal/helper/CollectionHelper.java index c0807a30f..37cd48cdc 100644 --- a/src/main/java/io/neonbee/internal/helper/CollectionHelper.java +++ b/src/main/java/io/neonbee/internal/helper/CollectionHelper.java @@ -4,27 +4,40 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; -import java.util.stream.Stream; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.shareddata.Shareable; public final class CollectionHelper { + + /** + * The default load factor used in hash-based collections (e.g., HashMap, HashSet). + *

+ * This matches the default load factor used internally by Java's standard library, and is used to calculate initial + * capacity to minimize rehashing. + */ + public static final float LOAD_FACTOR = 0.75f; + + /** + * The default initial capacity for hash-based collections. + *

+ * This value aligns with Java's default initial capacity for HashMap and HashSet when no explicit size is provided. + */ + public static final int DEFAULT_CAPACITY = 16; + /** * This helper class cannot be instantiated. */ @@ -38,8 +51,14 @@ private CollectionHelper() {} * @return a mutable deep copy of the given list */ public static List mutableCopyOf(List list) { - return Optional.ofNullable(list).map(List::stream).orElseGet(Stream::empty).map(CollectionHelper::copyOf) - .collect(Collectors.toCollection(ArrayList::new)); + if (list == null) { + return new ArrayList<>(); + } + List copy = new ArrayList<>(list.size()); + for (T item : list) { + copy.add(copyOf(item)); + } + return copy; } /** @@ -50,8 +69,17 @@ public static List mutableCopyOf(List list) { * @return a mutable deep copy of the given set */ public static Set mutableCopyOf(Set set) { - return Optional.ofNullable(set).map(Set::stream).orElseGet(Stream::empty).map(CollectionHelper::copyOf) - .collect(Collectors.toCollection(HashSet::new)); + if (set == null) { + return new HashSet<>(); + } + + int initialCapacity = Math.max((int) (set.size() / LOAD_FACTOR) + 1, DEFAULT_CAPACITY); + + Set copy = new HashSet<>(initialCapacity); + for (T item : set) { + copy.add(copyOf(item)); + } + return copy; } /** @@ -65,8 +93,14 @@ public static Set mutableCopyOf(Set set) { * @return a mutable deep copy of the given collection */ public static > C mutableCopyOf(C collection, Supplier collectionFactory) { - return Optional.ofNullable(collection).map(Collection::stream).orElseGet(Stream::empty) - .map(CollectionHelper::copyOf).collect(Collectors.toCollection(collectionFactory)); + C copy = collectionFactory.get(); + if (collection == null) { + return copy; + } + for (T item : collection) { + copy.add(copyOf(item)); + } + return copy; } /** @@ -78,9 +112,16 @@ public static > C mutableCopyOf(C collection, Supplie * @return a mutable deep copy of the given map */ public static Map mutableCopyOf(Map map) { - return Optional.ofNullable(map).map(Map::entrySet).map(Set::stream).orElseGet(Stream::empty) - .collect(Collectors.toMap(entry -> copyOf(entry.getKey()), entry -> copyOf(entry.getValue()), - (valueA, valueB) -> valueB, NullLiberalMergingHashMap::new)); + Map copy = new NullLiberalMergingHashMap<>(); + if (map == null) { + return copy; + } + for (Map.Entry entry : map.entrySet()) { + K keyCopy = copyOf(entry.getKey()); + V valueCopy = copyOf(entry.getValue()); + copy.put(keyCopy, valueCopy); + } + return copy; } /** @@ -150,10 +191,16 @@ public static T copyOf(T object) { * @return a new case-insensitive treemap as mutable deep copy of the given map */ public static Map mapToCaseInsensitiveTreeMap(Map map) { - return Optional.ofNullable(map).map(Map::entrySet).map(Set::stream).orElseGet(Stream::empty) - .collect(Collectors.toMap(entry -> copyOf(entry.getKey()), entry -> copyOf(entry.getValue()), - (valueA, valueB) -> valueB, - () -> new NullLiberalMergingTreeMap<>(String.CASE_INSENSITIVE_ORDER))); + Map copy = new NullLiberalMergingTreeMap<>(String.CASE_INSENSITIVE_ORDER); + if (map == null) { + return copy; + } + for (Map.Entry entry : map.entrySet()) { + K keyCopy = copyOf(entry.getKey()); + V valueCopy = copyOf(entry.getValue()); + copy.put(keyCopy, valueCopy); + } + return copy; } /** @@ -163,10 +210,14 @@ public static Map mapToCaseInsensitiveTreeMap(Map> multiMapToMap(MultiMap multiMap) { - return multiMap.entries().stream() - .collect(Collectors., String, List>toMap(Map.Entry::getKey, - entry -> Collections.singletonList(entry.getValue()), - (listA, listB) -> Stream.concat(listA.stream(), listB.stream()).toList())); + Map> result = new HashMap<>(); + if (multiMap == null) { + return result; + } + for (Map.Entry entry : multiMap.entries()) { + result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue()); + } + return result; } /** diff --git a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java index 9cb1f24fc..d6dfe814c 100644 --- a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java +++ b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java @@ -22,8 +22,10 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; import java.util.jar.Manifest; import java.util.regex.Pattern; @@ -34,7 +36,6 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; -import com.google.common.collect.Streams; import io.neonbee.internal.helper.FileSystemHelper; import io.neonbee.internal.helper.JarHelper; @@ -189,38 +190,52 @@ public Future> scanForAnnotation(Vertx vertx, Class> scanForAnnotation(Vertx vertx, List> annotationClasses, + public Future> scanForAnnotation(Vertx vertx, + List> annotationClasses, ElementType... elementTypes) { Future> classesFromDirectories = scanWithPredicate(vertx, ClassPathScanner::isClassFile); Future> classesFromJars = scanJarFilesWithPredicate(vertx, ClassPathScanner::isClassFile) - .map(classes -> classes.stream().map(JarHelper::extractFilePath).toList()); - - return Future.all(classesFromDirectories, classesFromJars) - .compose(compositeResult -> vertx.executeBlocking(() -> { - List classVisitors = annotationClasses.stream() - .map(annotationClass -> new AnnotationClassVisitor(annotationClass, elementTypes)) - .toList(); - Streams.concat(classesFromDirectories.result().stream(), classesFromJars.result().stream()) - .forEach(name -> { - try { - String resourceName = name.replace(".", "/").replaceFirst("/class$", ".class"); - for (AnnotationClassVisitor acv : classVisitors) { - ClassReader classReader = - new ClassReader(classLoader.getResourceAsStream(resourceName)); - classReader.accept(acv, 0); - } - } catch (IOException e) { - /* - * nothing to do here, depending on which part of the reading it failed, deployable - * could be set or not - */ - } - }); - - return classVisitors.stream().flatMap(acv -> acv.getClassNames().stream()).distinct() - .toList(); - })); + .map(uris -> { + List result = new ArrayList<>(uris.size()); + for (URI uri : uris) { + result.add(JarHelper.extractFilePath(uri)); + } + return result; + }); + + return Future.all(classesFromDirectories, classesFromJars).compose(v -> vertx.executeBlocking(() -> { + List classVisitors = new ArrayList<>(annotationClasses.size()); + for (Class annotationClass : annotationClasses) { + classVisitors.add(new AnnotationClassVisitor(annotationClass, elementTypes)); + } + + processClasses(classesFromDirectories.result(), classVisitors); + processClasses(classesFromJars.result(), classVisitors); + + Set matchedClassNames = new HashSet<>(); + for (AnnotationClassVisitor visitor : classVisitors) { + matchedClassNames.addAll(visitor.getClassNames()); + } + + return new ArrayList<>(matchedClassNames); + })); + } + + private void processClasses(List classNames, List classVisitors) { + for (String name : classNames) { + String resourcePath = name.replace('.', '/').replaceFirst("/class$", ".class"); + try (InputStream input = classLoader.getResourceAsStream(resourcePath)) { + if (input != null) { + ClassReader reader = new ClassReader(input); + for (AnnotationClassVisitor visitor : classVisitors) { + reader.accept(visitor, 0); + } + } + } catch (IOException e) { + // NOPMD Optionally log or collect failed paths for debugging + } + } } /** From c57863807be85458d6fd6afb11bf2c3ea95f06f2 Mon Sep 17 00:00:00 2001 From: Ivan Kalachyev Date: Tue, 1 Jul 2025 15:31:33 +0300 Subject: [PATCH 2/4] fix: ignore empty catch --- .../java/io/neonbee/internal/scanner/ClassPathScanner.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java index d6dfe814c..e28e3f3e7 100644 --- a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java +++ b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java @@ -232,8 +232,7 @@ private void processClasses(List classNames, List Date: Tue, 1 Jul 2025 15:32:03 +0300 Subject: [PATCH 3/4] fix: ignore empty catch --- src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java index e28e3f3e7..487225e3c 100644 --- a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java +++ b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java @@ -232,7 +232,7 @@ private void processClasses(List classNames, List Date: Tue, 1 Jul 2025 17:12:25 +0300 Subject: [PATCH 4/4] fix: ignore empty catch --- src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java index 487225e3c..f262a6882 100644 --- a/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java +++ b/src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java @@ -222,6 +222,7 @@ public Future> scanForAnnotation(Vertx vertx, })); } + @SuppressWarnings("PMD.EmptyCatchBlock") private void processClasses(List classNames, List classVisitors) { for (String name : classNames) { String resourcePath = name.replace('.', '/').replaceFirst("/class$", ".class");