diff --git a/core/src/main/java/org/jboss/jandex/IndexView.java b/core/src/main/java/org/jboss/jandex/IndexView.java
index 3bc850ff..8751d597 100644
--- a/core/src/main/java/org/jboss/jandex/IndexView.java
+++ b/core/src/main/java/org/jboss/jandex/IndexView.java
@@ -452,8 +452,17 @@ default ModuleInfo getModuleByName(String moduleName) {
}
/**
- * Obtains a list of classes that use the specified class. In other words, a list of classes that include
- * a reference to the specified class in their constant pool.
+ * Returns a list of classes in this index that use the specified class. For one class
+ * to use another class, the other class has to:
+ *
+ * - occur in the signature of the class (that is, in the superclass type,
+ * in the superinterface types, or in the type parameters), or
+ * - occur in the signature of any of the class's methods (that is, in the return type,
+ * in the parameter types, in the exception types, or in the type parameters), or
+ * - occur in the type of any of the class's fields or record components, or
+ * - occur in the list of class references in the constant pool, as described
+ * by the JLS and JVMS.
+ *
*
* @param className the name of the class to look for
* @return a non-null list of classes that use the specified class
@@ -461,8 +470,17 @@ default ModuleInfo getModuleByName(String moduleName) {
Collection getKnownUsers(DotName className);
/**
- * Obtains a list of classes that use the specified class. In other words, a list of classes that include
- * a reference to the specified class in their constant pool.
+ * Returns a list of classes in this index that use the specified class. For one class
+ * to use another class, the other class has to:
+ *
+ * - occur in the signature of the class (that is, in the superclass type,
+ * in the superinterface types, or in the type parameters), or
+ * - occur in the signature of any of the class's methods (that is, in the return type,
+ * in the parameter types, in the exception types, or in the type parameters), or
+ * - occur in the type of any of the class's fields or record components, or
+ * - occur in the list of class references in the constant pool, as described
+ * by the JLS and JVMS.
+ *
*
* @param className the name of the class to look for
* @return a non-null list of classes that use the specified class
@@ -472,8 +490,17 @@ default Collection getKnownUsers(String className) {
}
/**
- * Obtains a list of classes that use the specified class. In other words, a list of classes that include
- * a reference to the specified class in their constant pool.
+ * Returns a list of classes in this index that use the specified class. For one class
+ * to use another class, the other class has to:
+ *
+ * - occur in the signature of the class (that is, in the superclass type,
+ * in the superinterface types, or in the type parameters), or
+ * - occur in the signature of any of the class's methods (that is, in the return type,
+ * in the parameter types, in the exception types, or in the type parameters), or
+ * - occur in the type of any of the class's fields or record components, or
+ * - occur in the list of class references in the constant pool of the class,
+ * as described by the JLS and JVMS.
+ *
*
* @param clazz the class to look for
* @return a non-null list of classes that use the specified class
diff --git a/core/src/main/java/org/jboss/jandex/Indexer.java b/core/src/main/java/org/jboss/jandex/Indexer.java
index 93d9e625..31b56fe3 100644
--- a/core/src/main/java/org/jboss/jandex/Indexer.java
+++ b/core/src/main/java/org/jboss/jandex/Indexer.java
@@ -35,6 +35,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -407,7 +408,7 @@ void returnConstantAnnoAttributes(byte[] attributes) {
private Map> implementors;
private Map classes;
private Map modules;
- private Map> users;
+ private Map> users;
private NameTable names;
private GenericSignatureParser signatureParser;
private final TmpObjects tmpObjects = new TmpObjects();
@@ -432,7 +433,7 @@ private void initIndexMaps() {
modules = new HashMap();
if (users == null)
- users = new HashMap>();
+ users = new HashMap>();
if (names == null)
names = new NameTable();
@@ -1082,6 +1083,7 @@ private void resolveTypeAnnotations() {
}
private void resolveUsers() throws IOException {
+ // class references in constant pool
int poolSize = constantPoolSize;
byte[] pool = constantPool;
int[] offsets = constantPoolOffsets;
@@ -1091,16 +1093,79 @@ private void resolveUsers() throws IOException {
if (pool[offset] == CONSTANT_CLASS) {
int nameIndex = (pool[++offset] & 0xFF) << 8 | (pool[++offset] & 0xFF);
DotName usedClass = names.convertToName(decodeUtf8Entry(nameIndex), '/');
- List usersOfClass = users.get(usedClass);
- if (usersOfClass == null) {
- usersOfClass = new ArrayList();
- users.put(usedClass, usersOfClass);
- }
- usersOfClass.add(this.currentClass);
+ recordUsedClass(usedClass);
+ }
+ }
+
+ // class declaration
+ for (TypeVariable typeParameter : currentClass.typeParameters()) {
+ recordUsedType(typeParameter);
+ }
+ recordUsedType(currentClass.superClassType());
+ for (Type interfaceType : currentClass.interfaceTypes()) {
+ recordUsedType(interfaceType);
+ }
+ // field declarations
+ for (FieldInfo field : fields) {
+ recordUsedType(field.type());
+ }
+ // method declarations (ignoring receiver types, they are always the current class)
+ for (MethodInfo method : methods) {
+ for (TypeVariable typeParameter : method.typeParameters()) {
+ recordUsedType(typeParameter);
+ }
+ recordUsedType(method.returnType());
+ for (Type parameterType : method.parameterTypes()) {
+ recordUsedType(parameterType);
}
+ for (Type exceptionType : method.exceptions()) {
+ recordUsedType(exceptionType);
+ }
+ }
+ // record component declarations
+ for (RecordComponentInfo recordComponent : recordComponents) {
+ recordUsedType(recordComponent.type());
+ }
+ }
+
+ private void recordUsedType(Type type) {
+ if (type == null) {
+ return;
+ }
+
+ switch (type.kind()) {
+ case CLASS:
+ recordUsedClass(type.asClassType().name());
+ break;
+ case PARAMETERIZED_TYPE:
+ recordUsedClass(type.asParameterizedType().name());
+ for (Type typeArgument : type.asParameterizedType().arguments()) {
+ recordUsedType(typeArgument);
+ }
+ break;
+ case ARRAY:
+ recordUsedType(type.asArrayType().elementType());
+ break;
+ case WILDCARD_TYPE:
+ recordUsedType(type.asWildcardType().bound());
+ break;
+ case TYPE_VARIABLE:
+ for (Type bound : type.asTypeVariable().boundArray()) {
+ recordUsedType(bound);
+ }
+ break;
}
}
+ private void recordUsedClass(DotName usedClass) {
+ Set usersOfClass = users.get(usedClass);
+ if (usersOfClass == null) {
+ usersOfClass = new LinkedHashSet<>(); // linked set for reproducibility
+ users.put(usedClass, usersOfClass);
+ }
+ usersOfClass.add(this.currentClass);
+ }
+
private void updateTypeTargets() {
for (Map.Entry> entry : typeAnnotations.entrySet()) {
AnnotationTarget key = entry.getKey();
@@ -2557,7 +2622,11 @@ public Index complete() {
propagateTypeVariables();
try {
- return new Index(masterAnnotations, subclasses, subinterfaces, implementors, classes, modules, users);
+ Map> userLists = new HashMap<>();
+ for (Map.Entry> entry : users.entrySet()) {
+ userLists.put(entry.getKey(), new ArrayList<>(entry.getValue()));
+ }
+ return new Index(masterAnnotations, subclasses, subinterfaces, implementors, classes, modules, userLists);
} finally {
masterAnnotations = null;
subclasses = null;
diff --git a/core/src/test/java/org/jboss/jandex/test/KnownUsersTest.java b/core/src/test/java/org/jboss/jandex/test/KnownUsersTest.java
new file mode 100644
index 00000000..ac8762a5
--- /dev/null
+++ b/core/src/test/java/org/jboss/jandex/test/KnownUsersTest.java
@@ -0,0 +1,110 @@
+package org.jboss.jandex.test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.Index;
+import org.jboss.jandex.test.util.IndexingUtil;
+import org.junit.jupiter.api.Test;
+
+public class KnownUsersTest {
+ static class SuperClass {
+ }
+
+ interface ImplementedInterface1 {
+ }
+
+ interface ImplementedInterface2 {
+ }
+
+ static class TestClass extends SuperClass
+ implements ImplementedInterface1, ImplementedInterface2 {
+ int i;
+
+ Map> m;
+
+ TestClass(StringBuilder str) {
+ m = new HashMap<>();
+ m.put("foo", new ArrayList<>());
+ }
+
+ , W extends Exception> U bar(Set extends Long> s, Queue super Double> q)
+ throws IllegalArgumentException, IllegalStateException, W {
+ // `toString()` to force a class reference to `File` into the constant pool
+ Paths.get("").toFile().toString();
+ return null;
+ }
+
+ static class NestedClass {
+ }
+
+ class InnerClass {
+ }
+ }
+
+ @Test
+ public void test() throws IOException {
+ Index index = Index.of(SuperClass.class, ImplementedInterface1.class, ImplementedInterface2.class, TestClass.class);
+ doTest(index);
+ doTest(IndexingUtil.roundtrip(index));
+ }
+
+ private void doTest(Index index) {
+ // from class signature
+ assertKnownUsers(index, SuperClass.class);
+ assertKnownUsers(index, ImplementedInterface1.class);
+ assertKnownUsers(index, ImplementedInterface2.class);
+ assertKnownUsers(index, Number.class);
+ assertKnownUsers(index, CharSequence.class);
+ assertKnownUsers(index, RuntimeException.class);
+ // from field types
+ assertKnownUsers(index, String.class);
+ assertKnownUsers(index, Integer.class);
+ // from method signatures
+ assertKnownUsers(index, StringBuilder.class);
+ assertKnownUsers(index, Collection.class);
+ assertKnownUsers(index, Exception.class);
+ assertKnownUsers(index, Set.class);
+ assertKnownUsers(index, Long.class);
+ assertKnownUsers(index, Queue.class);
+ assertKnownUsers(index, Double.class);
+ assertKnownUsers(index, IllegalArgumentException.class);
+ assertKnownUsers(index, IllegalStateException.class);
+ // from method bodies (class references in the constant pool)
+ assertKnownUsers(index, HashMap.class);
+ assertKnownUsers(index, ArrayList.class);
+ assertKnownUsers(index, Paths.class);
+ assertKnownUsers(index, Path.class);
+ assertKnownUsers(index, File.class);
+ // member classes (class references in the constant pool)
+ assertKnownUsers(index, TestClass.NestedClass.class);
+ assertKnownUsers(index, TestClass.InnerClass.class);
+ }
+
+ private void assertKnownUsers(Index index, Class> clazz) {
+ Collection knownUsers = index.getKnownUsers(clazz);
+
+ assertNotNull(knownUsers);
+ assertFalse(knownUsers.isEmpty());
+ for (ClassInfo knownUser : knownUsers) {
+ if (TestClass.class.getName().equals(knownUser.name().toString())) {
+ return;
+ }
+ }
+ fail("Expected " + TestClass.class.getName() + " to be a known user of " + clazz.getName());
+ }
+}