Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 71 additions & 20 deletions src/main/java/io/neonbee/internal/helper/CollectionHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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).
* <p>
* 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.
* <p>
* 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.
*/
Expand All @@ -38,8 +51,14 @@ private CollectionHelper() {}
* @return a mutable deep copy of the given list
*/
public static <T> List<T> mutableCopyOf(List<T> 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<T> copy = new ArrayList<>(list.size());
for (T item : list) {
copy.add(copyOf(item));
}
return copy;
}

/**
Expand All @@ -50,8 +69,17 @@ public static <T> List<T> mutableCopyOf(List<T> list) {
* @return a mutable deep copy of the given set
*/
public static <T> Set<T> mutableCopyOf(Set<T> 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<T> copy = new HashSet<>(initialCapacity);
for (T item : set) {
copy.add(copyOf(item));
}
return copy;
}

/**
Expand All @@ -65,8 +93,14 @@ public static <T> Set<T> mutableCopyOf(Set<T> set) {
* @return a mutable deep copy of the given collection
*/
public static <T, C extends Collection<T>> C mutableCopyOf(C collection, Supplier<C> 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;
}

/**
Expand All @@ -78,9 +112,16 @@ public static <T, C extends Collection<T>> C mutableCopyOf(C collection, Supplie
* @return a mutable deep copy of the given map
*/
public static <K, V> Map<K, V> mutableCopyOf(Map<K, V> 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<K, V> copy = new NullLiberalMergingHashMap<>();
if (map == null) {
return copy;
}
for (Map.Entry<K, V> entry : map.entrySet()) {
K keyCopy = copyOf(entry.getKey());
V valueCopy = copyOf(entry.getValue());
copy.put(keyCopy, valueCopy);
}
return copy;
}

/**
Expand Down Expand Up @@ -150,10 +191,16 @@ public static <T> T copyOf(T object) {
* @return a new case-insensitive treemap as mutable deep copy of the given map
*/
public static <K extends String, V> Map<K, V> mapToCaseInsensitiveTreeMap(Map<K, V> 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<K, V> copy = new NullLiberalMergingTreeMap<>(String.CASE_INSENSITIVE_ORDER);
if (map == null) {
return copy;
}
for (Map.Entry<K, V> entry : map.entrySet()) {
K keyCopy = copyOf(entry.getKey());
V valueCopy = copyOf(entry.getValue());
copy.put(keyCopy, valueCopy);
}
return copy;
}

/**
Expand All @@ -163,10 +210,14 @@ public static <K extends String, V> Map<K, V> mapToCaseInsensitiveTreeMap(Map<K,
* @return a flat list mapping the key to a list of values
*/
public static Map<String, List<String>> multiMapToMap(MultiMap multiMap) {
return multiMap.entries().stream()
.collect(Collectors.<Map.Entry<String, String>, String, List<String>>toMap(Map.Entry::getKey,
entry -> Collections.singletonList(entry.getValue()),
(listA, listB) -> Stream.concat(listA.stream(), listB.stream()).toList()));
Map<String, List<String>> result = new HashMap<>();
if (multiMap == null) {
return result;
}
for (Map.Entry<String, String> entry : multiMap.entries()) {
result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(entry.getValue());
}
return result;
}

/**
Expand Down
73 changes: 44 additions & 29 deletions src/main/java/io/neonbee/internal/scanner/ClassPathScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -189,38 +190,52 @@ public Future<List<String>> scanForAnnotation(Vertx vertx, Class<? extends Annot
* @return a future to a list of resources on the class path
*/
@SuppressWarnings("PMD.EmptyCatchBlock")
public Future<List<String>> scanForAnnotation(Vertx vertx, List<Class<? extends Annotation>> annotationClasses,
public Future<List<String>> scanForAnnotation(Vertx vertx,
List<Class<? extends Annotation>> annotationClasses,
ElementType... elementTypes) {

Future<List<String>> classesFromDirectories = scanWithPredicate(vertx, ClassPathScanner::isClassFile);
Future<List<String>> classesFromJars = scanJarFilesWithPredicate(vertx, ClassPathScanner::isClassFile)
.map(classes -> classes.stream().map(JarHelper::extractFilePath).toList());

return Future.all(classesFromDirectories, classesFromJars)
.compose(compositeResult -> vertx.executeBlocking(() -> {
List<AnnotationClassVisitor> 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<String> 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<AnnotationClassVisitor> 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<String> matchedClassNames = new HashSet<>();
for (AnnotationClassVisitor visitor : classVisitors) {
matchedClassNames.addAll(visitor.getClassNames());
}

return new ArrayList<>(matchedClassNames);
}));
}

@SuppressWarnings("PMD.EmptyCatchBlock")
private void processClasses(List<String> classNames, List<AnnotationClassVisitor> 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
}
}
}

/**
Expand Down
Loading