Skip to content

Commit

Permalink
add StackedIndex
Browse files Browse the repository at this point in the history
A stacked index is a composition of multiple indices with overlay semantics.
Overlaying is done on class granularity. When the composition doesn't contain
duplicate classes (multiple classes with the same name), a `StackedIndex`
behaves just like a `CompositeIndex`, but when duplicates appear, the behavior
of `StackedIndex` is actually well defined.

This commit also adds a test for index navigation methods from `IndexView`,
as those have not been tested at all. Some tests reveal inconsistencies
in how `CompositeIndex` behaves in presence of duplicates, which is not nice,
but this commit intentionally doesn't modify `CompositeIndex` behavior.
  • Loading branch information
Ladicek committed Dec 11, 2023
1 parent 2f9b385 commit f0ee2bd
Show file tree
Hide file tree
Showing 4 changed files with 848 additions and 0 deletions.
326 changes: 326 additions & 0 deletions core/src/main/java/org/jboss/jandex/StackedIndex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
package org.jboss.jandex;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;

/**
* A stack of {@linkplain IndexView indexes} with overlay semantics. Overlaying is done on class
* granularity. That is, if a class is present in multiple indexes on the stack, only the top-most
* occurrence is considered; the other variants of the class present below on the stack are ignored.
*
* @since 3.2.0
*/
public final class StackedIndex implements IndexView {
// note that the top-most index comes first (reverse order compared to the `create()` methods)
private final IndexView[] stack;

private StackedIndex(IndexView[] stack) {
this.stack = stack;
}

/**
* Creates a stacked index from given {@code indexes}. The first element of the list is the bottom-most index
* on the stack, while the last element of the list is the top-most index on the stack.
*
* @param indexes indexes in stack order; must not be {@code null} and must not contain {@code null} elements
* @return stacked index, never {@code null}
*/
public static StackedIndex create(List<IndexView> indexes) {
return create(indexes.toArray(new IndexView[0]));
}

/**
* Creates a stacked index from given {@code indexes}. The first element of the array is the bottom-most index
* on the stack, while the last element of the array is the top-most index on the stack.
*
* @param indexes indexes in stack order; must not be {@code null} and must not contain {@code null} elements
* @return stacked index, never {@code null}
*/
public static StackedIndex create(IndexView... indexes) {
Objects.requireNonNull(indexes);

List<IndexView> stack = new ArrayList<>();
for (int i = indexes.length - 1; i >= 0; i--) {
IndexView index = indexes[i];
Objects.requireNonNull(index);
if (index instanceof StackedIndex) {
Collections.addAll(stack, ((StackedIndex) index).stack);
} else {
stack.add(index);
}
}
return new StackedIndex(stack.toArray(new IndexView[0]));
}

// ---

@Override
public Collection<ClassInfo> getKnownClasses() {
List<ClassInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ClassInfo clazz : idx.getKnownClasses()) {
if (seen.add(clazz.name())) {
result.add(clazz);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public ClassInfo getClassByName(DotName className) {
for (IndexView index : stack) {
ClassInfo result = index.getClassByName(className);
if (result != null) {
return result;
}
}
return null;
}

@Override
public Collection<ClassInfo> getKnownDirectSubclasses(DotName className) {
List<ClassInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ClassInfo clazz : idx.getKnownDirectSubclasses(className)) {
if (seen.add(clazz.name())) {
result.add(clazz);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public Collection<ClassInfo> getAllKnownSubclasses(DotName className) {
List<ClassInfo> result = new ArrayList<>();

Queue<DotName> worklist = new ArrayDeque<>();
Set<DotName> seen = new HashSet<>();

worklist.add(className);
while (!worklist.isEmpty()) {
DotName cls = worklist.remove();
for (IndexView index : stack) {
for (ClassInfo directSubclass : index.getKnownDirectSubclasses(cls)) {
worklist.add(directSubclass.name());
if (seen.add(directSubclass.name())) {
result.add(directSubclass);
}
}
}
}

return Collections.unmodifiableList(result);
}

@Override
public Collection<ClassInfo> getKnownDirectSubinterfaces(DotName interfaceName) {
List<ClassInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ClassInfo clazz : idx.getKnownDirectSubinterfaces(interfaceName)) {
if (seen.add(clazz.name())) {
result.add(clazz);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public Collection<ClassInfo> getAllKnownSubinterfaces(DotName interfaceName) {
List<ClassInfo> result = new ArrayList<>();

Queue<DotName> worklist = new ArrayDeque<>();
Set<DotName> seen = new HashSet<>();

worklist.add(interfaceName);
while (!worklist.isEmpty()) {
DotName iface = worklist.remove();
for (IndexView index : stack) {
for (ClassInfo directSubinterface : index.getKnownDirectSubinterfaces(iface)) {
worklist.add(directSubinterface.name());
if (seen.add(directSubinterface.name())) {
result.add(directSubinterface);
}
}
}
}

return Collections.unmodifiableList(result);
}

@Override
public Collection<ClassInfo> getKnownDirectImplementors(DotName interfaceName) {
List<ClassInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ClassInfo clazz : idx.getKnownDirectImplementors(interfaceName)) {
if (seen.add(clazz.name())) {
result.add(clazz);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public Collection<ClassInfo> getAllKnownImplementors(DotName interfaceName) {
List<ClassInfo> result = new ArrayList<>();

Queue<DotName> worklist = new ArrayDeque<>();
Set<DotName> seen = new HashSet<>();

worklist.add(interfaceName);
while (!worklist.isEmpty()) {
DotName iface = worklist.remove();
for (IndexView index : stack) {
for (ClassInfo directImplementor : index.getKnownDirectImplementors(iface)) {
if (directImplementor.isInterface()) {
worklist.add(directImplementor.name());
} else if (seen.add(directImplementor.name())) {
result.add(directImplementor);
}
}
}
}

return Collections.unmodifiableList(result);
}

@Override
public Collection<AnnotationInstance> getAnnotations(DotName annotationName) {
List<AnnotationInstance> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
Set<DotName> seenInThisIteration = new HashSet<>();
for (IndexView idx : stack) {
for (AnnotationInstance annotation : idx.getAnnotations(annotationName)) {
DotName inClass = nameOfDeclaringClass(annotation.target());
if (inClass == null) {
continue;
}
if (!seen.contains(inClass)) {
result.add(annotation);
seenInThisIteration.add(inClass);
}
}
seen.addAll(seenInThisIteration);
seenInThisIteration.clear();
}
return Collections.unmodifiableList(result);
}

@Override
public Collection<AnnotationInstance> getAnnotationsWithRepeatable(DotName annotationName, IndexView index) {
List<AnnotationInstance> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
Set<DotName> seenInThisIteration = new HashSet<>();
for (IndexView idx : stack) {
for (AnnotationInstance annotation : idx.getAnnotationsWithRepeatable(annotationName, index)) {
DotName inClass = nameOfDeclaringClass(annotation.target());
if (inClass == null) {
continue;
}
if (!seen.contains(inClass)) {
result.add(annotation);
seenInThisIteration.add(inClass);
}
}
seen.addAll(seenInThisIteration);
seenInThisIteration.clear();
}
return Collections.unmodifiableList(result);
}

private static DotName nameOfDeclaringClass(AnnotationTarget target) {
if (target == null) {
return null;
} else if (target.kind() == AnnotationTarget.Kind.CLASS) {
return target.asClass().name();
} else if (target.kind() == AnnotationTarget.Kind.METHOD) {
return target.asMethod().declaringClass().name();
} else if (target.kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
return target.asMethodParameter().method().declaringClass().name();
} else if (target.kind() == AnnotationTarget.Kind.FIELD) {
return target.asField().declaringClass().name();
} else if (target.kind() == AnnotationTarget.Kind.RECORD_COMPONENT) {
return target.asRecordComponent().declaringClass().name();
} else if (target.kind() == AnnotationTarget.Kind.TYPE) {
return nameOfDeclaringClass(target.asType().enclosingTarget());
} else {
throw new IllegalArgumentException("Unknown annotation target: " + target);
}
}

@Override
public Collection<ModuleInfo> getKnownModules() {
List<ModuleInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ModuleInfo module : idx.getKnownModules()) {
if (seen.add(module.name())) {
result.add(module);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public ModuleInfo getModuleByName(DotName moduleName) {
for (IndexView idx : stack) {
ModuleInfo result = idx.getModuleByName(moduleName);
if (result != null) {
return result;
}
}
return null;
}

@Override
public Collection<ClassInfo> getKnownUsers(DotName className) {
List<ClassInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ClassInfo clazz : idx.getKnownUsers(className)) {
if (seen.add(clazz.name())) {
result.add(clazz);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public Collection<ClassInfo> getClassesInPackage(DotName packageName) {
List<ClassInfo> result = new ArrayList<>();
Set<DotName> seen = new HashSet<>();
for (IndexView idx : stack) {
for (ClassInfo clazz : idx.getClassesInPackage(packageName)) {
if (seen.add(clazz.name())) {
result.add(clazz);
}
}
}
return Collections.unmodifiableList(result);
}

@Override
public Set<DotName> getSubpackages(DotName packageName) {
Set<DotName> result = new HashSet<>();
for (IndexView idx : stack) {
result.addAll(idx.getSubpackages(packageName));
}
return Collections.unmodifiableSet(result);
}
}
Loading

0 comments on commit f0ee2bd

Please sign in to comment.