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..f262a6882 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 extends Annot
* @return a future to a list of resources on the class path
*/
@SuppressWarnings("PMD.EmptyCatchBlock")
- public Future> 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 extends Annotation> 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);
+ }));
+ }
+
+ @SuppressWarnings("PMD.EmptyCatchBlock")
+ 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 - EmptyCatchBlock
+ }
+ }
}
/**