AnnotationHierarchies switch to binding key from FQN
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user