Skip to content

Commit

Permalink
Merge pull request #354 from Ladicek/improve-known-users
Browse files Browse the repository at this point in the history
improve `Index[View].getKnownUsers()`
  • Loading branch information
Ladicek authored May 13, 2024
2 parents fdad231 + 3a80388 commit ddba5d4
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 19 deletions.
10 changes: 6 additions & 4 deletions core/src/main/java/org/jboss/jandex/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -885,12 +885,14 @@ public final RecordComponentInfo recordComponent(String name) {

/**
* Returns a list of all record components declared by this class.
* This list may be empty, but never null.
* This list may be empty, but never {@code null}.
* <p>
* If this class is not a record, returns an empty list.
*
* @return a list of record components
* @return immutable list of record components
*/
public final List<RecordComponentInfo> recordComponents() {
if (extra == null || extra.recordComponents == null) {
if (!isRecord() || extra == null || extra.recordComponents == null) {
return Collections.emptyList();
}

Expand All @@ -907,7 +909,7 @@ public final List<RecordComponentInfo> recordComponents() {
* assumes that the bytecode order corresponds to declaration order, which is not guaranteed,
* but practically always holds.
*
* @return a list of record components
* @return immutable list of record components
* @since 2.4
*/
public final List<RecordComponentInfo> unsortedRecordComponents() {
Expand Down
42 changes: 36 additions & 6 deletions core/src/main/java/org/jboss/jandex/IndexView.java
Original file line number Diff line number Diff line change
Expand Up @@ -467,17 +467,37 @@ 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 <em>use</em> another class, the other class has to:
* <ul>
* <li>occur in the signature of the class (that is, in the superclass type,
* in the superinterface types, in the type parameters, or in the list of
* permitted subclasses), or</li>
* <li>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</li>
* <li>occur in the type of any of the class's fields or record components, or</li>
* <li>occur in the list of class references in the constant pool, as described
* by the JLS and JVMS.</li>
* </ul>
*
* @param className the name of the class to look for
* @return a non-null list of classes that use the specified class
*/
Collection<ClassInfo> 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 <em>use</em> another class, the other class has to:
* <ul>
* <li>occur in the signature of the class (that is, in the superclass type,
* in the superinterface types, in the type parameters, or in the list of
* permitted subclasses), or</li>
* <li>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</li>
* <li>occur in the type of any of the class's fields or record components, or</li>
* <li>occur in the list of class references in the constant pool, as described
* by the JLS and JVMS.</li>
* </ul>
*
* @param className the name of the class to look for
* @return a non-null list of classes that use the specified class
Expand All @@ -487,8 +507,18 @@ default Collection<ClassInfo> 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 <em>use</em> another class, the other class has to:
* <ul>
* <li>occur in the signature of the class (that is, in the superclass type,
* in the superinterface types, in the type parameters, or in the list of
* permitted subclasses), or</li>
* <li>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</li>
* <li>occur in the type of any of the class's fields or record components, or</li>
* <li>occur in the list of class references in the constant pool of the class,
* as described by the JLS and JVMS.</li>
* </ul>
*
* @param clazz the class to look for
* @return a non-null list of classes that use the specified class
Expand Down
90 changes: 81 additions & 9 deletions core/src/main/java/org/jboss/jandex/Indexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -417,7 +418,7 @@ void returnConstantAnnoAttributes(byte[] attributes) {
private Map<DotName, List<ClassInfo>> implementors;
private Map<DotName, ClassInfo> classes;
private Map<DotName, ModuleInfo> modules;
private Map<DotName, List<ClassInfo>> users;
private Map<DotName, Set<ClassInfo>> users; // must be a linked set for reproducibility
private NameTable names;
private GenericSignatureParser signatureParser;
private final TmpObjects tmpObjects = new TmpObjects();
Expand All @@ -442,7 +443,7 @@ private void initIndexMaps() {
modules = new HashMap<DotName, ModuleInfo>();

if (users == null)
users = new HashMap<DotName, List<ClassInfo>>();
users = new HashMap<DotName, Set<ClassInfo>>();

if (names == null)
names = new NameTable();
Expand Down Expand Up @@ -1106,6 +1107,7 @@ private void resolveTypeAnnotations() {
}

private void resolveUsers() throws IOException {
// class references in constant pool
int poolSize = constantPoolSize;
byte[] pool = constantPool;
int[] offsets = constantPoolOffsets;
Expand All @@ -1115,14 +1117,80 @@ 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<ClassInfo> usersOfClass = users.get(usedClass);
if (usersOfClass == null) {
usersOfClass = new ArrayList<ClassInfo>();
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);
}
for (DotName permittedSubclass : currentClass.permittedSubclasses()) {
recordUsedClass(permittedSubclass);
}
// 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<ClassInfo> usersOfClass = users.get(usedClass);
if (usersOfClass == null) {
usersOfClass = new LinkedHashSet<>();
users.put(usedClass, usersOfClass);
}
usersOfClass.add(this.currentClass);
}

private void updateTypeTargets() {
Expand Down Expand Up @@ -2583,7 +2651,11 @@ public Index complete() {
propagateTypeVariables();

try {
return Index.create(masterAnnotations, subclasses, subinterfaces, implementors, classes, modules, users);
Map<DotName, List<ClassInfo>> userLists = new HashMap<>();
for (Map.Entry<DotName, Set<ClassInfo>> entry : users.entrySet()) {
userLists.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return Index.create(masterAnnotations, subclasses, subinterfaces, implementors, classes, modules, userLists);
} finally {
masterAnnotations = null;
subclasses = null;
Expand Down
110 changes: 110 additions & 0 deletions core/src/test/java/org/jboss/jandex/test/KnownUsersTest.java
Original file line number Diff line number Diff line change
@@ -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<T> {
}

interface ImplementedInterface1<T> {
}

interface ImplementedInterface2<T> {
}

static class TestClass<T extends Number> extends SuperClass<CharSequence>
implements ImplementedInterface1<T>, ImplementedInterface2<RuntimeException> {
int i;

Map<String, List<Integer>> m;

TestClass(StringBuilder str) {
m = new HashMap<>();
m.put("foo", new ArrayList<>());
}

<U extends T, V extends Collection<?>, 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<ClassInfo> 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());
}
}

0 comments on commit ddba5d4

Please sign in to comment.