AnnotationHierarchies switch to binding key from FQN

This commit is contained in:
aboyko
2024-12-05 14:52:17 -05:00
parent 78dc6f3103
commit c583cbcb88
5 changed files with 106 additions and 54 deletions

View File

@@ -10,12 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.annotations;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
@@ -30,6 +29,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.JavaUtils;
import com.google.common.collect.ImmutableList;
@@ -124,10 +124,13 @@ public class AnnotationHierarchies {
* @return iterator over annotations hierarchy
*/
public Iterator<IAnnotationBinding> iterator(IBinding binding) {
return iterator(binding, new LinkedHashSet<>());
}
private Iterator<IAnnotationBinding> iterator(IBinding binding, Set<String> seen) {
return new Iterator<>() {
private HashSet<String> seen = new HashSet<>();
private Queue<IAnnotationBinding> queue = new LinkedList<>();
private Queue<IAnnotationBinding> queue = new ArrayDeque<>(10);
{
if (binding instanceof IAnnotationBinding ab) {
@@ -154,8 +157,7 @@ public class AnnotationHierarchies {
IAnnotationBinding next = queue.poll();
for (IAnnotationBinding a : getDirectSuperAnnotationBindings(next.getAnnotationType())) {
String key = a.getAnnotationType().getKey();
if (!seen.contains(key)) {
seen.add(key);
if (seen.add(key)) {
queue.add(a);
}
}
@@ -192,55 +194,48 @@ public class AnnotationHierarchies {
private AnnotationTypeInformation compute(ITypeBinding typeBinding) {
Set<String> inherited = new LinkedHashSet<>();
String fqn = typeBinding.getQualifiedName();
// Add itself to the set to flag it as been seen
inherited.add(fqn);
collectInheritedAnnotations(typeBinding, inherited);
// remove itself from `inherited` set as we'd like to leave only inherited annotations FQNs
inherited.remove(fqn);
return new AnnotationTypeInformation(fqn, inherited);
// Iterating over all inherited annotations must fill in the `inherited` set with annotation binding keys
for (Iterator<IAnnotationBinding> itr = iterator(typeBinding, inherited); itr.hasNext(); itr.next()) {
// nothing to do
}
// remove itself from `inherited` set as we'd like to leave only inherited annotations binding keys in the set
String key = typeBinding.getKey();
inherited.remove(key);
return new AnnotationTypeInformation(key, inherited);
}
// recursively collect annotations of the given annotation type
private void collectInheritedAnnotations(ITypeBinding annotationType, Set<String> inherited) {
for (IAnnotationBinding annotation : getDirectSuperAnnotationBindings(annotationType)) {
ITypeBinding type = annotation.getAnnotationType();
boolean notSeenYet = inherited.add(type.getQualifiedName());
if (notSeenYet) {
collectInheritedAnnotations(type, inherited);
public boolean isAnnotatedWithAnnotationByBindingKey(IBinding binding, Predicate<String> bindingKeyTest) {
if (binding instanceof IAnnotationBinding ab) {
return annotationInfo(ab.getAnnotationType()).map(info -> info.inherits(bindingKeyTest)).orElse(false);
} else if (binding instanceof ITypeBinding tb && tb.isAnnotation()) {
return annotationInfo(tb).map(info -> info.inherits(bindingKeyTest)).orElse(false);
} else {
for (IAnnotationBinding ab : getDirectSuperAnnotationBindings(binding)) {
if (isAnnotatedWithAnnotationByBindingKey(ab.getAnnotationType(), bindingKeyTest)) {
return true;
}
}
}
return false;
}
public boolean isAnnotatedWith(IBinding binding, Predicate<String> annotationFqnTest) {
public boolean isAnnotatedWithAnnotationByBindingKey(IBinding binding, String annotationBindingKey) {
if (binding instanceof IAnnotationBinding ab) {
return annotationInfo(ab.getAnnotationType()).map(info -> info.inherits(annotationFqnTest)).orElse(false);
return annotationInfo(ab.getAnnotationType()).map(info -> info.inherits(annotationBindingKey)).orElse(false);
} else if (binding instanceof ITypeBinding tb && tb.isAnnotation()) {
return annotationInfo(tb).map(info -> info.inherits(annotationFqnTest)).orElse(false);
return annotationInfo(tb).map(info -> info.inherits(annotationBindingKey)).orElse(false);
} else {
for (IAnnotationBinding ab : getDirectSuperAnnotationBindings(binding)) {
if (isAnnotatedWith(ab.getAnnotationType(), annotationFqnTest)) {
if (isAnnotatedWithAnnotationByBindingKey(ab.getAnnotationType(), annotationBindingKey)) {
return true;
}
}
}
return false;
}
public boolean isAnnotatedWith(IBinding binding, String annotationTypeFqn) {
if (binding instanceof IAnnotationBinding ab) {
return annotationInfo(ab.getAnnotationType()).map(info -> info.inherits(annotationTypeFqn)).orElse(false);
} else if (binding instanceof ITypeBinding tb && tb.isAnnotation()) {
return annotationInfo(tb).map(info -> info.inherits(annotationTypeFqn)).orElse(false);
} else {
for (IAnnotationBinding ab : getDirectSuperAnnotationBindings(binding)) {
if (isAnnotatedWith(ab.getAnnotationType(), annotationTypeFqn)) {
return true;
}
}
}
return false;
return isAnnotatedWithAnnotationByBindingKey(binding, JavaUtils.typeFqNameToBindingKey(annotationTypeFqn));
}
}

View File

@@ -19,6 +19,7 @@ import java.util.function.Consumer;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.springframework.ide.vscode.commons.java.JavaUtils;
import org.springframework.ide.vscode.commons.util.Assert;
import com.google.common.collect.ImmutableList;
@@ -51,7 +52,7 @@ public class AnnotationHierarchyAwareLookup<T> {
/**
* Associates a value with a given annotation type (and all its subtypes implicitly).
*
* @param fqName Fully qualified type name for the annotation.
* @param bindingKey Fully qualified type name for the annotation.
* @param overrideSuperTypes Determines whether the binding has 'override' behavior. Override behavior
* means that this binding stops the search for additional bindings associated
* with a super type. If override behavior is disabled the search will continue
@@ -59,9 +60,9 @@ public class AnnotationHierarchyAwareLookup<T> {
* in addition to the more specific binding.
* @param value
*/
private void put(String fqName, boolean overrideSuperTypes, T value) {
Assert.isLegal(bindings.get(fqName)==null, "Multiple bindings to the same fqName are not supported");
bindings.put(fqName, new Binding<>(value, overrideSuperTypes));
private void put(String bindingKey, boolean overrideSuperTypes, T value) {
Assert.isLegal(bindings.get(bindingKey)==null, "Multiple bindings to the same fqName are not supported");
bindings.put(bindingKey, new Binding<>(value, overrideSuperTypes));
}
/**
@@ -100,9 +101,9 @@ public class AnnotationHierarchyAwareLookup<T> {
}
private void findElements(AnnotationHierarchies annotationHierarchies, ITypeBinding annotationType, HashSet<String> seen, Consumer<T> requestor) {
String qname = annotationType.getQualifiedName();
if (seen.add(qname)) {
Binding<T> binding = bindings.get(qname);
String bindingKey = annotationType.getKey();
if (seen.add(bindingKey)) {
Binding<T> binding = bindings.get(bindingKey);
boolean isOverriding = false;
if (binding != null) {
@@ -118,8 +119,8 @@ public class AnnotationHierarchyAwareLookup<T> {
}
}
public void put(String annotationName, T value) {
put(annotationName, true, value);
public void put(String fqn, T value) {
put(JavaUtils.typeFqNameToBindingKey(fqn), true, value);
}
public boolean containsKey(String fqName) {

View File

@@ -3,18 +3,18 @@ package org.springframework.ide.vscode.boot.java.annotations;
import java.util.Collection;
import java.util.function.Predicate;
record AnnotationTypeInformation(String fqn, Collection<String> inheritedAnnotations) {
record AnnotationTypeInformation(String bindingKey, Collection<String> inheritedAnnotations) {
public boolean inherits(String fullyQualifiedAnnotationType) {
return fqn.equals(fullyQualifiedAnnotationType) || inheritedAnnotations.contains(fullyQualifiedAnnotationType);
public boolean inherits(String annotationBindingKey) {
return bindingKey.equals(annotationBindingKey) || inheritedAnnotations.contains(annotationBindingKey);
}
public boolean inherits(Predicate<String> annotationFqnTest) {
if (annotationFqnTest.test(fqn)) {
public boolean inherits(Predicate<String> annotationBindingKeyTest) {
if (annotationBindingKeyTest.test(bindingKey)) {
return true;
}
for (String fqn : inheritedAnnotations) {
if (annotationFqnTest.test(fqn)) {
if (annotationBindingKeyTest.test(fqn)) {
return true;
}
}

View File

@@ -753,7 +753,7 @@ public class SpringIndexerJava implements SpringIndexer {
/*
* If meta annotations of the current annotation is a "sub-type" of one of the annotations from symbol providers then add it to meta annotations
*/
if (annotationHierarchies.isAnnotatedWith(ab, symbolProviders::containsKey)) {
if (annotationHierarchies.isAnnotatedWithAnnotationByBindingKey(ab, symbolProviders::containsKey)) {
metaAnnotations.add(ab.getAnnotationType());
}
}

View File

@@ -35,6 +35,7 @@ import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.index.cache.IndexCache;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
@@ -243,4 +244,59 @@ public class CompilationUnitCacheTest {
assertNotNull(cuAnother);
assertNotNull(cuAnother);
}
@Test
void annotation_hierarchies_unchanged_when_doc_changed() throws Exception {
harness.useProject(ProjectsHarness.dummyProject());
harness.intialize(null);
TextDocument doc = new TextDocument(harness.createTempUri(null), LanguageId.JAVA, 0, "package my.package\n" +
"\n" +
"public class SomeClass {\n" +
"\n" +
"}\n");
harness.newEditorFromFileUri(doc.getUri(), doc.getLanguageId());
CompilationUnit cu = getCompilationUnit(doc);
assertNotNull(cu);
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
harness.changeDocument(doc.getUri(), 0, 0, " ");
CompilationUnit cuAnother = getCompilationUnit(doc);
assertNotNull(cuAnother);
AnnotationHierarchies anotherAnnotationHierarchies = AnnotationHierarchies.get(cuAnother);
assertTrue(anotherAnnotationHierarchies == annotationHierarchies);
CompilationUnit cuYetAnother = getCompilationUnit(doc);
AnnotationHierarchies yetAnotherAnnotationHierarchies = AnnotationHierarchies.get(cuYetAnother);
assertTrue(anotherAnnotationHierarchies == yetAnotherAnnotationHierarchies);
}
@Test
void annotation_hierarchies_reset_by_project_change() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = directory.toPath().resolve("src/main/java/example/HelloWorldController.java").toUri().toString();
MavenJavaProject project = projects.mavenProject("test-request-mapping-live-hover");
harness.useProject(project);
harness.intialize(directory);
URI fileUri = new URI(docUri);
Path path = Paths.get(fileUri);
String content = new String(Files.readAllBytes(path));
TextDocument document = new TextDocument(docUri, LanguageId.JAVA, 0, content);
CompilationUnit cu = getCompilationUnit(document);
assertNotNull(cu);
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
projectObserver.doWithListeners(l -> l.changed(project));
CompilationUnit cuAnother = getCompilationUnit(document);
AnnotationHierarchies anotherAnnotationHierarchies = AnnotationHierarchies.get(cuAnother);
assertNotNull(cuAnother);
assertTrue(anotherAnnotationHierarchies != annotationHierarchies);
}
}