Allow caching in AnnotationHierarchies. Performance.

This commit is contained in:
aboyko
2024-11-29 19:52:02 -05:00
committed by Alex Boyko
parent f7de35664f
commit 78dc6f3103
31 changed files with 419 additions and 264 deletions

View File

@@ -127,8 +127,9 @@ public class JavaUtils {
return bindingKey == null ? null : bindingKey.substring(1, bindingKey.length() - 1).replace('/', '.');
}
public static String typeFqNametoBindingKey(String fqName) {
StringBuilder sb = new StringBuilder('L');
public static String typeFqNameToBindingKey(String fqName) {
StringBuilder sb = new StringBuilder(2 + fqName.length());
sb.append('L');
sb.append(fqName.replace('.', '/'));
sb.append(';');
return sb.toString();

View File

@@ -567,7 +567,7 @@ public class Wrappers {
@Override
public String getBindingKey() {
return JavaUtils.typeFqNametoBindingKey(getFullyQualifiedName());
return JavaUtils.typeFqNameToBindingKey(getFullyQualifiedName());
}
@Override

View File

@@ -184,7 +184,7 @@ public class LanguageServerHarness {
this.server = server;
}
public static final Duration HIGHLIGHTS_TIMEOUT = Duration.ofMillis(5_000L);
public static final Duration HIGHLIGHTS_TIMEOUT = Duration.ofMillis(5_000_000_000L);
// public static LanguageServerHarness<SimpleLanguageServer> create(String extensionId, LanguageServerInitializer initializer) throws Exception {
// Callable<SimpleLanguageServer> factory = () -> {

View File

@@ -14,7 +14,6 @@ import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.CompletionItemKind;
@@ -25,9 +24,9 @@ import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProcessor;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
import org.springframework.ide.vscode.boot.java.beans.BeanNamesCompletionProcessor;
import org.springframework.ide.vscode.boot.java.beans.BeanTypesCompletionProcessor;
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
import org.springframework.ide.vscode.boot.java.beans.NamedCompletionProvider;
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider;
@@ -189,15 +188,7 @@ public class BootJavaCompletionEngineConfigurer {
if (type != null) {
ITypeBinding binding = type.resolveBinding();
if (binding != null) {
IAnnotationBinding[] annotations = binding.getAnnotations();
if (annotations != null) {
for (int i = 0; i < annotations.length; i++) {
ITypeBinding annotationType = annotations[i].getAnnotationType();
if (AnnotationHierarchies.hasTransitiveSuperAnnotationType(annotationType, requiredAnnotation)) {
return true;
}
}
}
return AnnotationHierarchies.get(node).isAnnotatedWith(binding, requiredAnnotation);
}
}
return false;

View File

@@ -10,22 +10,26 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.annotations;
import java.util.Collection;
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;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
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.util.CollectorUtil;
import com.google.common.collect.ImmutableList;
@@ -34,74 +38,110 @@ import com.google.common.collect.ImmutableList;
* 'inheritance' structure.
* <p>
* Provides methods to ask questions about inheritance between annotations.
*
* @author Kris De Volder
* @author Alex Boyko
* @author Martin Lippert
*/
public abstract class AnnotationHierarchies {
public class AnnotationHierarchies {
private static final Logger log = LoggerFactory.getLogger(AnnotationHierarchies.class);
private static final String CU_PROPERTY_KEY = AnnotationHierarchies.class.getName();
private final ConcurrentHashMap<String, AnnotationTypeInformation> cache;
private final Predicate<ITypeBinding> filter;
@SuppressWarnings("unchecked")
public static <T extends AnnotationHierarchies> T get(ASTNode n) {
return (T) n.getRoot().getProperty(CU_PROPERTY_KEY);
}
public static boolean set(ASTNode n, AnnotationHierarchies annotations) {
ASTNode root = n.getRoot();
AnnotationHierarchies a = get(root);
if (a == null) {
root.setProperty(CU_PROPERTY_KEY, annotations);
return true;
}
return false;
}
public AnnotationHierarchies(Predicate<ITypeBinding> filter) {
this.cache = new ConcurrentHashMap<>();
this.filter = filter;
}
public AnnotationHierarchies() {
this(tb -> !tb.getKey().startsWith("Ljava/")); //mostly intended to capture java.lang.annotation.* types. But really it should be
//safe to ignore any type defined by the JRE since it can't possibly be inheriting from a spring annotation.
}
/**
* Computes annotations only annotating this binding.
*
* @param binding binding
* @return list of annotations
*/
// this lock is used to protect multi-threaded access to this helper class
// due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=571247
private static final Object lock = new Object();
public static Collection<ITypeBinding> getDirectSuperAnnotations(ITypeBinding typeBinding) {
synchronized(lock) {
try {
IAnnotationBinding[] annotations = typeBinding.getAnnotations();
if (annotations != null && annotations.length != 0) {
ImmutableList.Builder<ITypeBinding> superAnnotations = ImmutableList.builder();
synchronized List<IAnnotationBinding> getDirectSuperAnnotationBindings(IBinding binding) {
try {
if (binding != null) {
IAnnotationBinding[] annotations = binding.getAnnotations();
if (annotations.length != 0) {
ImmutableList.Builder<IAnnotationBinding> superAnnotations = ImmutableList.builder();
for (IAnnotationBinding ab : annotations) {
ITypeBinding sa = ab.getAnnotationType();
if (sa != null) {
if (!ignoreAnnotation(sa.getQualifiedName())) {
superAnnotations.add(sa);
if (accept(sa)) {
superAnnotations.add(ab);
}
}
}
return superAnnotations.build();
}
}
catch (AbortCompilation e) {
log.debug("compilation aborted ", e);
// ignore this, it is most likely caused by broken source code, a broken classpath, or some optional dependencies not being on the classpath
}
return ImmutableList.of();
}
catch (AbortCompilation e) {
log.debug("compilation aborted ", e);
// ignore this, it is most likely caused by broken source code, a broken classpath, or some optional dependencies not being on the classpath
}
return ImmutableList.of();
}
public static boolean isSubtypeOf(Annotation annotation, String fqAnnotationTypeName) {
synchronized(lock) {
ITypeBinding annotationType = annotation.resolveTypeBinding();
return hasTransitiveSuperAnnotationType(annotationType, fqAnnotationTypeName);
}
private boolean accept(ITypeBinding tb) {
return filter == null || filter.test(tb);
}
public static boolean hasTransitiveSuperAnnotationType(ITypeBinding typeBinding, String annotationType) {
synchronized(lock) {
if (typeBinding != null && annotationType != null) {
for (Iterator<ITypeBinding> itr = metaHierarchy(typeBinding); itr.hasNext();) {
ITypeBinding t = itr.next();
if (annotationType.equals(t.getQualifiedName())) {
return true;
}
}
}
return false;
}
}
public static Iterator<ITypeBinding> metaHierarchy(ITypeBinding actualAnnotation) {
/**
* Produces the iterator over all annotations hierarchy for the binding. If the
* binding happens to be {@link IAnnotationBinding} it'll be included in the
* hierarchy
*
* @param binding the binding
* @return iterator over annotations hierarchy
*/
public Iterator<IAnnotationBinding> iterator(IBinding binding) {
return new Iterator<>() {
private HashSet<String> seen = new HashSet<>();
private Queue<ITypeBinding> queue = new LinkedList<>();
private Queue<IAnnotationBinding> queue = new LinkedList<>();
{
seen.add(actualAnnotation.getQualifiedName());
queue.add(actualAnnotation);
if (binding instanceof IAnnotationBinding ab) {
seen.add(ab.getAnnotationType().getKey());
queue.add(ab);
} else {
if (binding instanceof ITypeBinding tb && tb.isAnnotation()) {
seen.add(tb.getKey());
}
for (IAnnotationBinding ab : getDirectSuperAnnotationBindings(binding)) {
seen.add(ab.getAnnotationType().getKey());
queue.add(ab);
}
}
}
@Override
@@ -110,12 +150,12 @@ public abstract class AnnotationHierarchies {
}
@Override
public ITypeBinding next() {
ITypeBinding next = queue.poll();
for (ITypeBinding a : getDirectSuperAnnotations(next)) {
String qName = a.getQualifiedName();
if (!seen.contains(qName)) {
seen.add(qName);
public IAnnotationBinding next() {
IAnnotationBinding next = queue.poll();
for (IAnnotationBinding a : getDirectSuperAnnotationBindings(next.getAnnotationType())) {
String key = a.getAnnotationType().getKey();
if (!seen.contains(key)) {
seen.add(key);
queue.add(a);
}
}
@@ -124,85 +164,83 @@ public abstract class AnnotationHierarchies {
};
}
public static Collection<ITypeBinding> getMetaAnnotations(ITypeBinding actualAnnotation, Predicate<String> isKeyAnnotationName) {
synchronized(lock) {
Stream<ITypeBinding> allSupers = findTransitiveSupers(actualAnnotation, new HashSet<>())
.skip(1); //Don't include 'actualAnnotation' itself.
return allSupers
.filter(candidate -> isMetaAnnotation(candidate, isKeyAnnotationName))
.collect(CollectorUtil.toImmutableList());
/**
* The list of all annotations within the hierarchy.
* Note: for searching purposes it is best to use {@link #iterator(IBinding)}
*
* @param binding
* @return the list of annotations in the hierarchy
*/
public List<IAnnotationBinding> getAllAnnotations(IBinding binding) {
if (binding != null) {
List<IAnnotationBinding> annotations = new ArrayList<>();
for (Iterator<IAnnotationBinding> itr = iterator(binding); itr.hasNext();) {
annotations.add(itr.next());
}
return annotations;
}
return Collections.emptyList();
}
private Optional<AnnotationTypeInformation> annotationInfo(ITypeBinding typeBinding) {
if (accept(typeBinding)) {
return Optional.of(cache.computeIfAbsent(typeBinding.getKey(), s -> compute(typeBinding)));
}
return Optional.empty();
}
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);
}
// 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 static boolean isMetaAnnotation(ITypeBinding candidate, Predicate<String> isKeyAnnotationName) {
return findTransitiveSupers(candidate, new HashSet<>())
.anyMatch(sa -> isKeyAnnotationName.test(sa.getQualifiedName()));
}
public static Collection<IAnnotationBinding> getDirectSuperAnnotationBindings(IAnnotationBinding annotationBinding) {
synchronized(lock) {
try {
if (annotationBinding.getAnnotationType() != null) {
IAnnotationBinding[] annotations = annotationBinding.getAnnotationType().getAnnotations();
if (annotations != null && annotations.length != 0) {
ImmutableList.Builder<IAnnotationBinding> superAnnotations = ImmutableList.builder();
for (IAnnotationBinding ab : annotations) {
ITypeBinding sa = ab.getAnnotationType();
if (sa != null) {
if (!ignoreAnnotation(sa.getQualifiedName())) {
superAnnotations.add(ab);
}
}
}
return superAnnotations.build();
}
public boolean isAnnotatedWith(IBinding binding, Predicate<String> annotationFqnTest) {
if (binding instanceof IAnnotationBinding ab) {
return annotationInfo(ab.getAnnotationType()).map(info -> info.inherits(annotationFqnTest)).orElse(false);
} else if (binding instanceof ITypeBinding tb && tb.isAnnotation()) {
return annotationInfo(tb).map(info -> info.inherits(annotationFqnTest)).orElse(false);
} else {
for (IAnnotationBinding ab : getDirectSuperAnnotationBindings(binding)) {
if (isAnnotatedWith(ab.getAnnotationType(), annotationFqnTest)) {
return true;
}
}
catch (AbortCompilation e) {
log.debug("compilation aborted ", e);
// ignore this, it is most likely caused by broken source code, a broken classpath, or some optional dependencies not being on the classpath
}
return ImmutableList.of();
}
return false;
}
public static Stream<IAnnotationBinding> findTransitiveSuperAnnotationBindings( IAnnotationBinding annotationBinding) {
return internalFindTransitiveSuperAnnotationBindings(annotationBinding, new HashSet<>());
}
public static Stream<IAnnotationBinding> internalFindTransitiveSuperAnnotationBindings(IAnnotationBinding annotationBinding, Set<String> seen) {
synchronized (lock) {
if (annotationBinding.getAnnotationType() != null) {
if (seen.add(annotationBinding.getAnnotationType().getQualifiedName())) {
return Stream.concat(Stream.of(annotationBinding), getDirectSuperAnnotationBindings(annotationBinding)
.stream()
.flatMap(superBinding -> internalFindTransitiveSuperAnnotationBindings(superBinding, seen)));
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 Stream.empty();
}
return false;
}
protected static boolean ignoreAnnotation(String fqname) {
return fqname.startsWith("java."); //mostly intended to capture java.lang.annotation.* types. But really it should be
//safe to ignore any type defined by the JRE since it can't possibly be inheriting from a spring annotation.
}
private static Stream<ITypeBinding> findTransitiveSupers(ITypeBinding typeBinding, Set<String> seen) {
synchronized(lock) {
if (typeBinding != null) {
String qname = typeBinding.getQualifiedName();
if (seen.add(qname)) {
return Stream.concat(
Stream.of(typeBinding),
getDirectSuperAnnotations(typeBinding).stream().flatMap(superBinding -> findTransitiveSupers(superBinding, seen))
);
}
}
return Stream.empty();
}
}
}

View File

@@ -17,6 +17,7 @@ import java.util.LinkedHashSet;
import java.util.Map;
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.util.Assert;
@@ -58,7 +59,7 @@ public class AnnotationHierarchyAwareLookup<T> {
* in addition to the more specific binding.
* @param value
*/
public void put(String fqName, boolean overrideSuperTypes, T 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));
}
@@ -85,9 +86,9 @@ public class AnnotationHierarchyAwareLookup<T> {
* a symbol provider for Components should be asked to produce symbols for Component, Controller and RestController,
* so should result in 3 separate calls to the symbols provider.
*/
public Collection<T> get(ITypeBinding annotationType) {
public Collection<T> get(AnnotationHierarchies annotationHierarchies, IAnnotationBinding annotationType) {
ImmutableList.Builder<T> found = ImmutableList.builder();
findElements(annotationType, new LinkedHashSet<>(), found::add);
findElements(annotationHierarchies, annotationType.getAnnotationType(), new LinkedHashSet<>(), found::add);
return found.build();
}
@@ -98,8 +99,8 @@ public class AnnotationHierarchyAwareLookup<T> {
return found.build();
}
private void findElements(ITypeBinding typeBinding, HashSet<String> seen, Consumer<T> requestor) {
String qname = typeBinding.getQualifiedName();
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);
@@ -110,9 +111,8 @@ public class AnnotationHierarchyAwareLookup<T> {
}
if (!isOverriding) {
Collection<ITypeBinding> directSuperAnnotations = AnnotationHierarchies.getDirectSuperAnnotations(typeBinding);
for (ITypeBinding superAnnotation : directSuperAnnotations) {
findElements(superAnnotation, seen, requestor);
for (IAnnotationBinding superAnnotation : annotationHierarchies.getDirectSuperAnnotationBindings(annotationType)) {
findElements(annotationHierarchies, superAnnotation.getAnnotationType(), seen, requestor);
}
}
}

View File

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

View File

@@ -283,8 +283,9 @@ public class AutowiredHoverProvider implements HoverProvider {
private LiveBean getDefinedBeanForTypeDeclaration(TypeDeclaration declaringType) {
if (declaringType != null) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(declaringType);
for (Annotation annotation : ASTUtils.getAnnotations(declaringType)) {
if (AnnotationHierarchies.isSubtypeOf(annotation, Annotations.COMPONENT)) {
if (annotationHierarchies.isAnnotatedWith(annotation.resolveAnnotationBinding(), Annotations.COMPONENT)) {
return ComponentInjectionsHoverProvider.getDefinedBeanForComponent(annotation);
}
}

View File

@@ -25,6 +25,7 @@ import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -48,8 +49,9 @@ public class DataQueryParameterDefinitionProvider implements IJavaDefinitionProv
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project,
TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
if (n instanceof StringLiteral || n instanceof TextBlock) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
ASTNode a = JdtQueryDocHighlightsProvider.findQueryAnnotation(n);
ASTNode a = JdtQueryDocHighlightsProvider.findQueryAnnotation(annotationHierarchies, n);
TextDocument doc = documents.getLatestSnapshot(docId.getUri());

View File

@@ -23,6 +23,7 @@ import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintKind;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.springframework.ide.vscode.boot.java.JdtInlayHintsProvider;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedQueryExpression;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
@@ -47,12 +48,13 @@ public class JdtDataQueriesInlayHintsProvider implements JdtInlayHintsProvider {
@Override
public ASTVisitor getInlayHintsComputer(IJavaProject project, TextDocument doc, CompilationUnit cu,
Collector<InlayHint> collector) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@Override
public boolean visit(NormalAnnotation node) {
if (node.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node);
if (q != null) {
processQuery(project, doc, collector, m, q);
}
@@ -63,7 +65,7 @@ public class JdtDataQueriesInlayHintsProvider implements JdtInlayHintsProvider {
@Override
public boolean visit(SingleMemberAnnotation node) {
if (node.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node);
if (q != null) {
processQuery(project, doc, collector, m, q);
}

View File

@@ -24,6 +24,7 @@ import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.springframework.ide.vscode.boot.java.JdtSemanticTokensProvider;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedQueryExpression;
import org.springframework.ide.vscode.boot.java.embedded.lang.EmbeddedLanguageSnippet;
import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens;
@@ -64,10 +65,11 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
@Override
public ASTVisitor getTokensComputer(IJavaProject jp, TextDocument doc, CompilationUnit cu, Collector<SemanticTokenData> tokensData) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@Override
public boolean visit(NormalAnnotation a) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(a);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, a);
if (q != null) {
computeSemanticTokens(jp, q.query(), q.isNative()).forEach(tokensData::accept);
}
@@ -76,7 +78,7 @@ public class JdtDataQuerySemanticTokensProvider implements JdtSemanticTokensProv
@Override
public boolean visit(SingleMemberAnnotation a) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(a);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, a);
if (q != null) {
computeSemanticTokens(jp, q.query(), q.isNative()).forEach(tokensData::accept);
}

View File

@@ -29,6 +29,7 @@ import org.eclipse.lsp4j.DocumentHighlightKind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.JdtAstDocHighlightsProvider;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.semantic.tokens.SemanticTokenData;
import org.springframework.ide.vscode.commons.util.BadLocationException;
@@ -49,7 +50,8 @@ public class JdtQueryDocHighlightsProvider implements JdtAstDocHighlightsProvide
public List<DocumentHighlight> getDocHighlights(IJavaProject project, TextDocument doc, CompilationUnit cu,
ASTNode node, int offset) {
if (node instanceof StringLiteral || node instanceof TextBlock) {
Annotation a = findQueryAnnotation(node);
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
Annotation a = findQueryAnnotation(annotationHierarchies, node);
if (a != null && a.getParent() instanceof MethodDeclaration m && !m.parameters().isEmpty()) {
Collector<SemanticTokenData> collector = new Collector<>();
a.accept(semanticTokensProvider.getTokensComputer(project, doc, cu, collector));
@@ -74,13 +76,13 @@ public class JdtQueryDocHighlightsProvider implements JdtAstDocHighlightsProvide
return Collections.emptyList();
}
static Annotation findQueryAnnotation(ASTNode node) {
static Annotation findQueryAnnotation(AnnotationHierarchies annotationHierarchies, ASTNode node) {
if (node.getParent() instanceof MemberValuePair pair
&& node.getParent().getParent() instanceof NormalAnnotation na && "value".equals(pair.getName().getIdentifier())
&& JdtQueryVisitorUtils.isQueryAnnotation(na)) {
&& JdtQueryVisitorUtils.isQueryAnnotation(annotationHierarchies, na)) {
return na;
} else if (node.getParent() instanceof SingleMemberAnnotation sm
&& JdtQueryVisitorUtils.isQueryAnnotation(sm)) {
&& JdtQueryVisitorUtils.isQueryAnnotation(annotationHierarchies, sm)) {
return sm;
}
return null;

View File

@@ -12,8 +12,8 @@ package org.springframework.ide.vscode.boot.java.data.jpa.queries;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
@@ -30,18 +30,18 @@ public class JdtQueryVisitorUtils {
public record EmbeddedQueryExpression(EmbeddedLanguageSnippet query, boolean isNative) {};
public static EmbeddedQueryExpression extractQueryExpression(SingleMemberAnnotation a) {
if (isQueryAnnotation(a)) {
public static EmbeddedQueryExpression extractQueryExpression(AnnotationHierarchies annotationHierarchies, SingleMemberAnnotation a) {
if (isQueryAnnotation(annotationHierarchies, a)) {
EmbeddedLanguageSnippet expression = EmbeddedLangAstUtils.extractEmbeddedExpression(a.getValue());
return expression == null ? null : new EmbeddedQueryExpression(expression, false);
}
return null;
}
public static EmbeddedQueryExpression extractQueryExpression(NormalAnnotation a) {
public static EmbeddedQueryExpression extractQueryExpression(AnnotationHierarchies annotationHierarchies, NormalAnnotation a) {
Expression queryExpression = null;
boolean isNative = false;
if (isQueryAnnotation(a)) {
if (isQueryAnnotation(annotationHierarchies, a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
@@ -64,7 +64,7 @@ public class JdtQueryVisitorUtils {
}
}
}
} else if (isNamedQueryAnnotation(a)) {
} else if (isNamedQueryAnnotation(annotationHierarchies, a)) {
for (Object value : a.values()) {
if (value instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) value;
@@ -101,23 +101,20 @@ public class JdtQueryVisitorUtils {
return null;
}
static boolean isQueryAnnotation(Annotation a) {
static boolean isQueryAnnotation(AnnotationHierarchies annotationHierarchies, Annotation a) {
if (Annotations.DATA_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
ITypeBinding type = a.resolveTypeBinding();
if (type != null) {
return AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.DATA_QUERY);
}
return annotationHierarchies.isAnnotatedWith(a.resolveAnnotationBinding(), Annotations.DATA_QUERY);
}
return false;
}
static boolean isNamedQueryAnnotation(Annotation a) {
static boolean isNamedQueryAnnotation(AnnotationHierarchies annotationHierarchies, Annotation a) {
if (NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || Annotations.JPA_JAKARTA_NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName())
|| Annotations.JPA_JAVAX_NAMED_QUERY.equals(a.getTypeName().getFullyQualifiedName())) {
ITypeBinding type = a.resolveTypeBinding();
IAnnotationBinding type = a.resolveAnnotationBinding();
if (type != null) {
return AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.JPA_JAKARTA_NAMED_QUERY)
|| AnnotationHierarchies.hasTransitiveSuperAnnotationType(type, Annotations.JPA_JAVAX_NAMED_QUERY);
return annotationHierarchies.isAnnotatedWith(type, Annotations.JPA_JAKARTA_NAMED_QUERY)
|| annotationHierarchies.isAnnotatedWith(type, Annotations.JPA_JAVAX_NAMED_QUERY);
}
}
return false;

View File

@@ -20,6 +20,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitorUtils.EmbeddedQueryExpression;
import org.springframework.ide.vscode.boot.java.embedded.lang.AntlrReconcilerWithSpel;
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
@@ -54,11 +55,12 @@ public class QueryJdtAstReconciler implements JdtAstReconciler {
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) throws RequiredCompleteAstException {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@Override
public boolean visit(NormalAnnotation node) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node);
if (q != null) {
Optional<Reconciler> reconcilerOpt = q.isNative() ? getSqlReconciler(project) : Optional.of(getQueryReconciler(project));
reconcilerOpt.ifPresent(r -> r.reconcile(q.query().getText(), q.query()::toSingleJavaRange, problemCollector));
@@ -68,7 +70,7 @@ public class QueryJdtAstReconciler implements JdtAstReconciler {
@Override
public boolean visit(SingleMemberAnnotation node) {
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node);
EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node);
if (q != null) {
getQueryReconciler(project).reconcile(q.query().getText(), q.query()::toSingleJavaRange, problemCollector);
}

View File

@@ -18,6 +18,7 @@ import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
@@ -36,6 +37,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveData;
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider;
@@ -195,9 +197,10 @@ public class BootJavaHoverProvider implements HoverHandler {
protected void extractLiveHintsForAnnotation(Annotation annotation, TextDocument doc, IJavaProject project,
SpringProcessLiveData[] processLiveData, Collection<CodeLens> result) {
ITypeBinding type = annotation.resolveTypeBinding();
IAnnotationBinding type = annotation.resolveAnnotationBinding();
if (type != null) {
for (HoverProvider provider : this.hoverProviders.get(type)) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(annotation);
for (HoverProvider provider : this.hoverProviders.get(annotationHierarchies, type)) {
Collection<CodeLens> hints = provider.getLiveHintCodeLenses(project, annotation, doc, processLiveData);
if (hints!=null) {
result.addAll(hints);
@@ -287,17 +290,19 @@ public class BootJavaHoverProvider implements HoverHandler {
private Hover provideHoverForAnnotation(CancelChecker cancelToken, ASTNode exactNode, Annotation annotation, int offset, TextDocument doc, IJavaProject project,
SpringProcessLiveData[] processLiveData) {
ITypeBinding type = annotation.resolveTypeBinding();
IAnnotationBinding type = annotation.resolveAnnotationBinding();
if (type != null) {
logger.debug("Hover requested for "+type.getName());
if (processLiveData.length > 0) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(annotation);
for (HoverProvider provider : this.hoverProviders.get(type)) {
for (HoverProvider provider : this.hoverProviders.get(annotationHierarchies, type)) {
cancelToken.checkCanceled();
Hover hover = provider.provideHover(exactNode, annotation, type, offset, doc, project, processLiveData);
Hover hover = provider.provideHover(exactNode, annotation, type.getAnnotationType(), offset, doc, project, processLiveData);
if (hover != null) {
logger.debug("Hover found: "+hover);
//TODO: compose multiple hovers somehow instead of just returning the first one?

View File

@@ -179,10 +179,10 @@ public class ComponentInjectionsHoverProvider extends AbstractInjectedIntoHoverP
private boolean isComponentAnnotatedType(TypeDeclaration typeDeclaration) {
List<?> modifiers = typeDeclaration.modifiers();
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(typeDeclaration);
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
ITypeBinding typeBinding = ((Annotation) modifier).resolveTypeBinding();
if (AnnotationHierarchies.hasTransitiveSuperAnnotationType(typeBinding, Annotations.COMPONENT)) {
if (annotationHierarchies.isAnnotatedWith(((Annotation) modifier).resolveAnnotationBinding(), Annotations.COMPONENT)) {
return true;
}
}

View File

@@ -19,7 +19,6 @@ import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
@@ -124,12 +123,12 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile
}
// check if '@Configuration' is already over the class
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
for (Iterator<?> itr = classDecl.modifiers().iterator(); itr.hasNext();) {
Object mod = itr.next();
if (mod instanceof Annotation) {
Annotation a = (Annotation) mod;
ITypeBinding aType = a.resolveTypeBinding();
if (aType != null && AnnotationHierarchies.isSubtypeOf(a, Annotations.CONFIGURATION)) {
if (annotationHierarchies.isAnnotatedWith(a.resolveAnnotationBinding(), Annotations.CONFIGURATION)) {
// Found '@Configuration' annotation
return false;
}
@@ -178,12 +177,12 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile
}
private static boolean isBeanMethod(MethodDeclaration m) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(m);
for (Iterator<?> itr = m.modifiers().iterator(); itr.hasNext();) {
Object mod = itr.next();
if (mod instanceof Annotation) {
Annotation a = (Annotation) mod;
ITypeBinding aType = a.resolveTypeBinding();
if (aType != null && AnnotationHierarchies.isSubtypeOf(a, Annotations.BEAN)) {
if (annotationHierarchies.isAnnotatedWith(a.resolveAnnotationBinding(), Annotations.BEAN)) {
// Found '@Bean' annotation
return true;
}

View File

@@ -35,6 +35,7 @@ import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
@@ -67,18 +68,18 @@ public class AutowiredFieldIntoConstructorParameterReconciler implements JdtAstR
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
Path sourceFile = Paths.get(docUri);
// Check if source file belongs to non-test java sources folder
if (IClasspathUtil.getProjectJavaSourceFoldersWithoutTests(project.getClasspath())
.anyMatch(f -> sourceFile.startsWith(f.toPath()))) {
final AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@Override
public boolean visit(FieldDeclaration field) {
if (field.fragments().size() == 1) {
Annotation annotation = ReconcileUtils.findAnnotation(field, Annotations.AUTOWIRED,
Annotation annotation = ReconcileUtils.findAnnotation(annotationHierarchies, field, Annotations.AUTOWIRED,
false);
if (annotation != null && field.getParent() instanceof TypeDeclaration) {
TypeDeclaration typeDecl = (TypeDeclaration) field.getParent();
@@ -101,7 +102,7 @@ public class AutowiredFieldIntoConstructorParameterReconciler implements JdtAstR
}
} else {
List<MethodDeclaration> autowiredConstructors = constructors.stream()
.filter(constr -> ReconcileUtils.findAnnotation(constr,
.filter(constr -> ReconcileUtils.findAnnotation(annotationHierarchies, constr,
Annotations.AUTOWIRED, true) != null)
.limit(2).collect(Collectors.toList());
if (autowiredConstructors.size() == 1) {

View File

@@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -27,6 +28,7 @@ import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
@@ -35,12 +37,13 @@ import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.openrewrite.internal.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.JavaUtils;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
@@ -68,6 +71,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@@ -78,15 +82,18 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
IAnnotationBinding repoDefAnnotationType = null;
Annotation repoDefAnnotation = null;
for (Annotation a : ASTUtils.getAnnotations(typeDecl)) {
if (AnnotationHierarchies.isSubtypeOf(a, Annotations.NO_REPO_BEAN)) {
if (annotationHierarchies.isAnnotatedWith(a.resolveAnnotationBinding(), Annotations.NO_REPO_BEAN)) {
return true;
}
if (repoDefAnnotationType == null) {
repoDefAnnotationType = AnnotationHierarchies
.findTransitiveSuperAnnotationBindings(a.resolveAnnotationBinding())
.filter(at -> Annotations.REPOSITORY_DEFINITION
.equals(at.getAnnotationType().getQualifiedName()))
.findFirst().orElse(null);
IAnnotationBinding annotationBinding = a.resolveAnnotationBinding();
if (repoDefAnnotationType == null && annotationHierarchies.isAnnotatedWith(annotationBinding, Annotations.REPOSITORY_DEFINITION)) {
String bindingKey = JavaUtils.typeFqNameToBindingKey(Annotations.REPOSITORY_DEFINITION);
for (Iterator<IAnnotationBinding> itr = annotationHierarchies.iterator(annotationBinding); itr.hasNext() && repoDefAnnotationType == null;) {
IAnnotationBinding ab = itr.next();
if (bindingKey.equals(ab.getAnnotationType().getKey())) {
repoDefAnnotationType = ab;
}
}
if (repoDefAnnotationType != null) {
repoDefAnnotation = a;
}
@@ -228,7 +235,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
}
private List<ITypeBinding> findIdType(ITypeBinding type) {
List<ITypeBinding> idTypes = findAnnotatedIdTypes(type, new HashSet<>());
List<ITypeBinding> idTypes = findAnnotatedIdTypes(annotationHierarchies, type, new HashSet<>());
if (idTypes.isEmpty() && considerIdField) {
ITypeBinding idType = findIdFieldType(type);
if (idType != null) {
@@ -326,7 +333,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
return null;
}
private static List<ITypeBinding> findAnnotatedIdTypes(ITypeBinding type, Set<String> visited) {
private static List<ITypeBinding> findAnnotatedIdTypes(AnnotationHierarchies annotationHierarchies, ITypeBinding type, Set<String> visited) {
List<ITypeBinding> idTypes = new ArrayList<>();
for (IAnnotationBinding a : type.getAnnotations()) {
switch (a.getAnnotationType().getQualifiedName()) {
@@ -341,7 +348,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
List<String> idAnnotations = List.of(Annotations.SPRING_ENTITY_ID, Annotations.JPA_JAKARTA_ENTITY_ID, Annotations.JPA_JAVAX_ENTITY_ID, Annotations.JPA_JAKARTA_EMBEDDED_ID, Annotations.JPA_JAVAX_EMBEDDED_ID);
for (IVariableBinding m : type.getDeclaredFields()) {
String s = fieldSignature(m);
if (!visited.contains(s) && isAnnotationCompatible(m.getAnnotations(), idAnnotations)) {
if (!visited.contains(s) && isAnnotationCompatible(annotationHierarchies, m, idAnnotations)) {
idTypes.add(m.getType());
}
visited.add(s);
@@ -349,13 +356,13 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
if (!type.isRecord()) {
for (IMethodBinding m : type.getDeclaredMethods()) {
String s = methodSignature(m);
if (!visited.contains(s) && isAnnotationCompatible(m.getAnnotations(), idAnnotations)) {
if (!visited.contains(s) && isAnnotationCompatible(annotationHierarchies, m, idAnnotations)) {
idTypes.add(m.getReturnType());
}
visited.add(s);
}
if (type.getSuperclass() != null) {
idTypes.addAll(findAnnotatedIdTypes(type.getSuperclass(), visited));
idTypes.addAll(findAnnotatedIdTypes(annotationHierarchies, type.getSuperclass(), visited));
}
}
return idTypes;
@@ -364,17 +371,16 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler {
private static String fieldSignature(IVariableBinding f) {
return f.getName();
}
private static boolean isAnnotationCompatible(IAnnotationBinding[] annotations, Collection<String> fqNames) {
for (IAnnotationBinding a : annotations) {
if (AnnotationHierarchies.findTransitiveSuperAnnotationBindings(a)
.anyMatch(at -> fqNames.contains(at.getAnnotationType().getQualifiedName()))) {
private static boolean isAnnotationCompatible(AnnotationHierarchies annotationHierarchies, IBinding binding, Collection<String> fqNames) {
for (String fqName : fqNames) {
if (annotationHierarchies.isAnnotatedWith(binding, fqName)) {
return true;
}
}
return false;
}
private static String methodSignature(IMethodBinding m) {
StringBuilder sb = new StringBuilder();
sb.append(m.getName());

View File

@@ -25,6 +25,7 @@ import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.openrewrite.java.spring.NoAutowiredOnConstructor;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
@@ -57,6 +58,7 @@ public class NoAutowiredOnConstructorReconciler implements JdtAstReconciler {
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@@ -80,7 +82,7 @@ public class NoAutowiredOnConstructorReconciler implements JdtAstReconciler {
}
if (constructor != null) {
Annotation autowiredAnnotation = ReconcileUtils.findAnnotation(constructor,
Annotation autowiredAnnotation = ReconcileUtils.findAnnotation(annotationHierarchies, constructor,
Annotations.AUTOWIRED, false);
if (autowiredAnnotation != null) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL,

View File

@@ -14,7 +14,6 @@ import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.spri
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
@@ -59,6 +58,7 @@ public class PreciseBeanTypeReconciler implements JdtAstReconciler {
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
final AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@@ -70,8 +70,7 @@ public class PreciseBeanTypeReconciler implements JdtAstReconciler {
public boolean visit(MethodDeclaration method) {
IMethodBinding methodBinding = method.resolveBinding();
if (methodBinding != null) {
boolean isBeanMethod = Arrays.stream(methodBinding.getAnnotations())
.anyMatch(a -> AnnotationHierarchies.findTransitiveSuperAnnotationBindings(a).anyMatch(an -> Annotations.BEAN.equals(an.getAnnotationType().getQualifiedName())));
boolean isBeanMethod = annotationHierarchies.isAnnotatedWith(methodBinding, Annotations.BEAN);
if (isBeanMethod) {
if (isCompleteAst) {
if (currentMethod == null) {// Do not jump into anonymous class methods

View File

@@ -74,15 +74,15 @@ public class ReconcileUtils {
}
}
public static Annotation findAnnotation(BodyDeclaration decl, String annotationFqType, boolean includeMetaHierarchy) {
public static Annotation findAnnotation(AnnotationHierarchies annotationHiererachies, BodyDeclaration decl, String annotationFqType, boolean includeMetaHierarchy) {
for (Iterator<?> itr = decl.modifiers().iterator(); itr.hasNext();) {
Object mod = itr.next();
if (mod instanceof Annotation) {
Annotation a = (Annotation) mod;
ITypeBinding aType = a.resolveTypeBinding();
if (aType != null && (
(includeMetaHierarchy && AnnotationHierarchies.isSubtypeOf(a, annotationFqType)) || (!includeMetaHierarchy && annotationFqType.equals(aType.getQualifiedName()))
)) {
if (aType != null &&
(annotationFqType.equals(aType.getQualifiedName()) || (includeMetaHierarchy && annotationHiererachies.isAnnotatedWith(aType, annotationFqType)))
) {
return (Annotation) mod;
}
}

View File

@@ -23,6 +23,7 @@ import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.openrewrite.java.spring.security5.WebSecurityConfigurerAdapter;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.Version;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
@@ -85,6 +86,7 @@ public class WebSecurityConfigurerAdapterReconciler implements JdtAstReconciler
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
return new ASTVisitor() {
@Override
@@ -92,7 +94,7 @@ public class WebSecurityConfigurerAdapterReconciler implements JdtAstReconciler
Type type = typeDecl.getSuperclassType();
if (isWebSecurityConfigurerAdapter(cu, type)) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, type.getStartPosition(), type.getLength());
if (ReconcileUtils.findAnnotation(typeDecl, Annotations.CONFIGURATION, true) != null) {
if (ReconcileUtils.findAnnotation(annotationHierarchies, typeDecl, Annotations.CONFIGURATION, true) != null) {
ITypeBinding resolveBinding = type.resolveBinding();
String[] typeStubs = resolveBinding == null || resolveBinding.isRecovered() ? new String[] { STUB_WEB_SECURITY_CONFIG_ADAPTER } : new String[0];
String uri = docUri.toASCIIString();

View File

@@ -94,7 +94,7 @@ public final class AnnotationParamSpelExtractor {
return Optional.empty();
}
if (!AnnotationHierarchies.hasTransitiveSuperAnnotationType(a.resolveTypeBinding(), this.annotationType)) {
if (!AnnotationHierarchies.get(a).isAnnotatedWith(a.resolveAnnotationBinding(), this.annotationType)) {
return Optional.empty();
}
@@ -122,7 +122,7 @@ public final class AnnotationParamSpelExtractor {
return Optional.empty();
}
if (!AnnotationHierarchies.hasTransitiveSuperAnnotationType(a.resolveTypeBinding(), this.annotationType)) {
if (!AnnotationHierarchies.get(a).isAnnotatedWith(a.resolveAnnotationBinding(), this.annotationType)) {
return Optional.empty();
}

View File

@@ -18,6 +18,7 @@ import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
/**
* Wrapper around a JDT AST parser to enable cleanup functionality on the lookup environment,
@@ -30,8 +31,11 @@ public class ASTParserCleanupEnabled {
private final Map<String, String> options;
// private final int flags;
// private final int apiLevel;
public ASTParserCleanupEnabled(String[] classpathEntries, String[] sourceEntries, boolean ignoreMethodBodies) {
private final AnnotationHierarchies annotationHierachies;
public ASTParserCleanupEnabled(String[] classpathEntries, String[] sourceEntries, AnnotationHierarchies annotationHierarchies, boolean ignoreMethodBodies) {
this.annotationHierachies = annotationHierarchies;
parser = ASTParser.newParser(AST.JLS21);
options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_21, options);
@@ -62,6 +66,10 @@ public class ASTParserCleanupEnabled {
}
// public ASTParserCleanupEnabled(String[] classpathEntries, String[] sourceEntries, boolean ignoreMethodBodies) {
// this(classpathEntries, sourceEntries, new AnnotationHierarchies(), ignoreMethodBodies);
// }
public void setUnitName(String unitName) {
this.parser.setUnitName(unitName);
}
@@ -71,14 +79,17 @@ public class ASTParserCleanupEnabled {
}
public ASTNode createAST(IProgressMonitor monitor) {
return this.parser.createAST(monitor);
ASTNode n = this.parser.createAST(monitor);
AnnotationHierarchies.set(n, annotationHierachies);
return n;
}
public void createASTs(String[] sourceFilePaths, String[] encodings, String[] bindingKeys,
FileASTRequestor requestor, IProgressMonitor monitor) {
// CUResolver.resolve(sourceFilePaths, encodings, bindingKeys, requestor, apiLevel, options, flags, environment);
this.parser.createASTs(sourceFilePaths, encodings, bindingKeys, requestor, monitor);
this.parser.createASTs(sourceFilePaths, encodings, bindingKeys, new DelegatingFileASTRequestor(requestor,
(p, cu) -> AnnotationHierarchies.set(cu, annotationHierachies)), monitor);
}
public void cleanup() {

View File

@@ -44,6 +44,7 @@ import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.INameEnvironmentWithProgress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -77,6 +78,7 @@ public final class CompilationUnitCache implements DocumentContentProvider {
private final Cache<URI, CompletableFuture<CompilationUnit>> uriToCu;
private final Cache<URI, Set<URI>> projectToDocs;
private final Cache<URI, Tuple2<List<Classpath>, INameEnvironmentWithProgress>> lookupEnvCache;
private final Cache<URI, AnnotationHierarchies> annotationHierarchies;
private final ReentrantReadWriteLock environmentCacheLock = new ReentrantReadWriteLock(true);
private CompletableFuture<Void> debounceClassFileChanges = CompletableFuture.completedFuture(null);
@@ -117,6 +119,8 @@ public final class CompilationUnitCache implements DocumentContentProvider {
}
}).build();
this.annotationHierarchies = CacheBuilder.newBuilder().build();
this.documentService = server == null ? null : server.getTextDocumentService();
@@ -261,7 +265,8 @@ public final class CompilationUnitCache implements DocumentContentProvider {
Tuple2<List<Classpath>, INameEnvironmentWithProgress> lookupEnvTuple = loadLookupEnvTuple(project);
String utiStr = uri.toASCIIString();
String unitName = utiStr.substring(utiStr.lastIndexOf("/"));
CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), utiStr, unitName, lookupEnvTuple.getT1(), lookupEnvTuple.getT2());
CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), utiStr, unitName, lookupEnvTuple.getT1(), lookupEnvTuple.getT2(),
annotationHierarchies.get(project.getLocationUri(), AnnotationHierarchies::new));
logger.debug("CU Cache: created new AST for {}", uri.toASCIIString());
@@ -288,16 +293,19 @@ public final class CompilationUnitCache implements DocumentContentProvider {
public static CompilationUnit parse2(char[] source, String docURI, String unitName, IJavaProject project) throws Exception {
List<Classpath> classpaths = createClasspath(getClasspathEntries(project));
return parse2(source, docURI, unitName, classpaths, null);
return parse2(source, docURI, unitName, classpaths, null, null);
}
private static CompilationUnit parse2(char[] source, String docURI, String unitName, List<Classpath> classpaths, INameEnvironmentWithProgress environment) throws Exception {
private static CompilationUnit parse2(char[] source, String docURI, String unitName, List<Classpath> classpaths, INameEnvironmentWithProgress environment, AnnotationHierarchies annotations) throws Exception {
Map<String, String> options = JavaCore.getOptions();
String apiLevel = JavaCore.VERSION_21;
JavaCore.setComplianceOptions(apiLevel, options);
if (environment == null) {
environment = CUResolver.createLookupEnvironment(classpaths.toArray(new Classpath[classpaths.size()]));
}
if (annotations == null) {
annotations = new AnnotationHierarchies();
}
BasicCompilationUnit sourceUnit = new BasicCompilationUnit(source, null, unitName, (IJavaElement) null);
@@ -316,6 +324,7 @@ public final class CompilationUnitCache implements DocumentContentProvider {
CompilationUnit cu = CUResolver.convert(unit, source, AST.JLS21, options, needToResolveBindings, DefaultWorkingCopyOwner.PRIMARY, flags);
AnnotationHierarchies.set(cu, annotations);
return cu;
}
@@ -368,7 +377,9 @@ public final class CompilationUnitCache implements DocumentContentProvider {
WriteLock lock = environmentCacheLock.writeLock();
lock.lock();
try {
lookupEnvCache.invalidate(project.getLocationUri());
URI uri = project.getLocationUri();
lookupEnvCache.invalidate(uri);
annotationHierarchies.invalidate(uri);
} finally {
lock.unlock();
}

View File

@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2024 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.utils;
import java.util.function.BiConsumer;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.eclipse.jdt.core.dom.IBinding;
public class DelegatingFileASTRequestor extends FileASTRequestor {
private final FileASTRequestor delegate;
private final BiConsumer<String, CompilationUnit> handler;
public DelegatingFileASTRequestor(FileASTRequestor delegate, BiConsumer<String, CompilationUnit> handler) {
this.delegate = delegate;
this.handler = handler;
}
@Override
public void acceptAST(String sourceFilePath, CompilationUnit ast) {
super.acceptAST(sourceFilePath, ast);
this.handler.accept(sourceFilePath, ast);
delegate.acceptAST(sourceFilePath, ast);
}
@Override
public void acceptBinding(String bindingKey, IBinding binding) {
super.acceptBinding(bindingKey, binding);
delegate.acceptBinding(bindingKey, binding);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -39,6 +40,7 @@ import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
@@ -268,7 +270,7 @@ public class SpringIndexerJava implements SpringIndexer {
private void scanFile(IJavaProject project, DocumentDescriptor updatedDoc, String content) throws Exception {
final boolean ignoreMethodBodies = false;
ASTParserCleanupEnabled parser = createParser(project, ignoreMethodBodies);
ASTParserCleanupEnabled parser = createParser(project, new AnnotationHierarchies(), ignoreMethodBodies);
String docURI = updatedDoc.getDocURI();
long lastModified = updatedDoc.getLastModified();
@@ -422,11 +424,12 @@ public class SpringIndexerJava implements SpringIndexer {
}
};
AnnotationHierarchies annotationHierarchies = new AnnotationHierarchies();
List<String[]> chunks = createChunks(javaFiles, this.scanChunkSize);
for(int i = 0; i < chunks.size(); i++) {
log.info("scan java files, AST parse, chunk {} for files: {}", i, javaFiles.length);
ASTParserCleanupEnabled parser = createParser(project, ignoreMethodBodies);
ASTParserCleanupEnabled parser = createParser(project, annotationHierarchies, ignoreMethodBodies);
parser.createASTs(chunks.get(i), null, new String[0], requestor, null);
parser.cleanup();
}
@@ -509,14 +512,15 @@ public class SpringIndexerJava implements SpringIndexer {
};
List<String[]> chunks = createChunks(javaFiles, this.scanChunkSize);
AnnotationHierarchies annotations = new AnnotationHierarchies();
for (int i = 0; i < chunks.size(); i++) {
log.info("scan java files, AST parse, chunk {} for files: {}", i, javaFiles.length);
String[] pass2Files = scanFiles(project, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.ONE);
String[] pass2Files = scanFiles(project, annotations, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.ONE);
if (pass2Files.length > 0) {
log.info("scan java files, AST parse, pass 2, chunk {} for files: {}", i, javaFiles.length);
scanFiles(project, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.TWO);
scanFiles(project, annotations, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.TWO);
}
}
@@ -553,7 +557,7 @@ public class SpringIndexerJava implements SpringIndexer {
log.info("reconciling stats - timer: " + reconciler.getStatsTimer());
}
private String[] scanFiles(IJavaProject project, String[] javaFiles, List<CachedSymbol> generatedSymbols, List<CachedBean> generatedBeans,
private String[] scanFiles(IJavaProject project, AnnotationHierarchies annotations, String[] javaFiles, List<CachedSymbol> generatedSymbols, List<CachedBean> generatedBeans,
BiConsumer<String, Diagnostic> diagnosticsAggregator, SCAN_PASS pass) throws Exception {
PercentageProgressTask progressTask = this.progressService.createPercentageProgressTask(INDEX_FILES_TASK_ID + project.getElementName(),
@@ -582,7 +586,7 @@ public class SpringIndexerJava implements SpringIndexer {
}
};
ASTParserCleanupEnabled parser = createParser(project, ignoreMethodBodies);
ASTParserCleanupEnabled parser = createParser(project, annotations, ignoreMethodBodies);
parser.createASTs(javaFiles, null, new String[0], requestor, null);
parser.cleanup();
@@ -735,14 +739,26 @@ public class SpringIndexerJava implements SpringIndexer {
}
private void extractSymbolInformation(Annotation node, final SpringIndexerJavaContext context) throws Exception {
ITypeBinding typeBinding = node.resolveTypeBinding();
IAnnotationBinding annotationBinding = node.resolveAnnotationBinding();
if (typeBinding != null) {
if (annotationBinding != null) {
ITypeBinding typeBinding = annotationBinding.getAnnotationType();
// symbol and index scanning
Collection<SymbolProvider> providers = symbolProviders.get(typeBinding);
Collection<ITypeBinding> metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey);
List<ITypeBinding> metaAnnotations = new ArrayList<>();
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(node);
for (Iterator<IAnnotationBinding> itr = annotationHierarchies.iterator(typeBinding); itr.hasNext();) {
IAnnotationBinding ab = itr.next();
/*
* 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)) {
metaAnnotations.add(ab.getAnnotationType());
}
}
Collection<SymbolProvider> providers = symbolProviders.get(annotationHierarchies, annotationBinding);
if (!providers.isEmpty()) {
TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent());
for (SymbolProvider provider : providers) {
@@ -784,11 +800,11 @@ public class SpringIndexerJava implements SpringIndexer {
return jakartaAnnnotations.contains(qualifiedName);
}
public static ASTParserCleanupEnabled createParser(IJavaProject project, boolean ignoreMethodBodies) throws Exception {
public static ASTParserCleanupEnabled createParser(IJavaProject project, AnnotationHierarchies annotationHierarchies, boolean ignoreMethodBodies) throws Exception {
String[] classpathEntries = getClasspathEntries(project);
String[] sourceEntries = getSourceEntries(project);
return new ASTParserCleanupEnabled(classpathEntries, sourceEntries, ignoreMethodBodies);
return new ASTParserCleanupEnabled(classpathEntries, sourceEntries, annotationHierarchies, ignoreMethodBodies);
}
private static String[] getClasspathEntries(IJavaProject project) throws Exception {

View File

@@ -72,7 +72,7 @@ public class SpringMetamodelIndexerBeansTest {
projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
initProject.get(55555555, TimeUnit.SECONDS);
}
@Test

View File

@@ -98,29 +98,28 @@ public class AnnotationHierarchiesTests {
}
""");
SpringIndexerJava.createParser(project, true).createASTs(new String[] { file.toFile().toString() }, null, new String[0], new FileASTRequestor() {
SpringIndexerJava.createParser(project, new AnnotationHierarchies(), true).createASTs(new String[] { file.toFile().toString() }, null, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MarkerAnnotation node) {
ITypeBinding binding = node.resolveTypeBinding();
assertThat(binding).isNotNull();
IAnnotationBinding annotationBinding = node.resolveAnnotationBinding();
assertThat(annotationBinding).isNotNull();
assertThat(binding.getQualifiedName()).isEqualTo("test.CustomComponent1");
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "test.CustomComponent2")).isTrue();
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "org.springframework.context.annotation.Configuration")).isFalse();
assertThat(AnnotationHierarchies.getMetaAnnotations(binding, qn -> true).stream().toList().size()).isEqualTo(3);
assertThat(AnnotationHierarchies.isMetaAnnotation(binding, "org.springframework.context.annotation.Configuration"::equals)).isFalse();
assertThat(AnnotationHierarchies.getDirectSuperAnnotations(binding).stream().toList().size()).isEqualTo(2);
assertThat(annotationHierarchies.isAnnotatedWith(binding, "test.CustomComponent2")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(binding, "org.springframework.context.annotation.Configuration")).isFalse();
assertThat(annotationHierarchies.getAllAnnotations(binding).size()).isEqualTo(3);
assertThat(annotationHierarchies.getDirectSuperAnnotationBindings(binding).stream().toList().size()).isEqualTo(2);
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "test.CustomComponent2")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "org.springframework.stereotype.Component")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "org.springframework.context.annotation.Configuration")).isFalse();
IAnnotationBinding annotationBinding = node.resolveAnnotationBinding();
assertThat(annotationBinding).isNotNull();
assertThat(AnnotationHierarchies.getDirectSuperAnnotationBindings(annotationBinding).stream().toList().size()).isEqualTo(2);
assertThat(AnnotationHierarchies.isSubtypeOf(node, "test.CustomComponent2")).isTrue();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.stereotype.Component")).isTrue();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.context.annotation.Configuration")).isFalse();
return super.visit(node);
}
@@ -145,33 +144,32 @@ public class AnnotationHierarchiesTests {
}
""");
SpringIndexerJava.createParser(project, true).createASTs(new String[] { file.toFile().toString() }, null, new String[0], new FileASTRequestor() {
SpringIndexerJava.createParser(project, new AnnotationHierarchies(), true).createASTs(new String[] { file.toFile().toString() }, null, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu);
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MarkerAnnotation node) {
ITypeBinding binding = node.resolveTypeBinding();
assertThat(binding).isNotNull();
IAnnotationBinding annotationBinding = node.resolveAnnotationBinding();
assertThat(annotationBinding).isNotNull();
assertThat(binding.getQualifiedName()).isEqualTo("org.springframework.boot.autoconfigure.SpringBootApplication");
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "test.CustomComponent2")).isFalse();
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "org.springframework.context.annotation.Configuration")).isTrue();
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "org.springframework.boot.autoconfigure.SpringBootApplication")).isTrue();
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "org.springframework.stereotype.Component")).isTrue();
List<ITypeBinding> metaAnnotations = AnnotationHierarchies.getMetaAnnotations(binding, qn -> true).stream().toList();
assertThat(annotationHierarchies.isAnnotatedWith(binding, "test.CustomComponent2")).isFalse();
assertThat(annotationHierarchies.isAnnotatedWith(binding, "org.springframework.context.annotation.Configuration")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(binding, "org.springframework.boot.autoconfigure.SpringBootApplication")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(binding, "org.springframework.stereotype.Component")).isTrue();
List<IAnnotationBinding> metaAnnotations = annotationHierarchies.getAllAnnotations(binding);
assertThat(metaAnnotations.size()).isEqualTo(8);
assertThat(AnnotationHierarchies.isMetaAnnotation(binding, "org.springframework.context.annotation.Configuration"::equals)).isTrue();
assertThat(AnnotationHierarchies.getDirectSuperAnnotations(binding).stream().toList().size()).isEqualTo(3);
assertThat(annotationHierarchies.getDirectSuperAnnotationBindings(binding).stream().toList().size()).isEqualTo(3);
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "test.CustomComponent2")).isFalse();
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "org.springframework.context.annotation.Configuration")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "org.springframework.boot.autoconfigure.SpringBootApplication")).isTrue();
assertThat(annotationHierarchies.isAnnotatedWith(annotationBinding, "org.springframework.stereotype.Component")).isTrue();
IAnnotationBinding annotationBinding = node.resolveAnnotationBinding();
assertThat(annotationBinding).isNotNull();
assertThat(AnnotationHierarchies.getDirectSuperAnnotationBindings(annotationBinding).stream().toList().size()).isEqualTo(3);
assertThat(AnnotationHierarchies.isSubtypeOf(node, "test.CustomComponent2")).isFalse();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.context.annotation.Configuration")).isTrue();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.boot.autoconfigure.SpringBootApplication")).isTrue();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.stereotype.Component")).isTrue();
return super.visit(node);
}

View File

@@ -28,6 +28,7 @@ import java.util.stream.Stream;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
@@ -88,7 +89,7 @@ public abstract class BaseReconcilerTest {
TestProblemCollector problemCollector = new TestProblemCollector();
AtomicBoolean requiredCompleteAst = new AtomicBoolean(false);
String[] sources = Stream.concat(Arrays.stream(additionalSources), Stream.of(path)).map(p -> p.toFile().toString()).toArray(String[]::new);
SpringIndexerJava.createParser(project, !isCompleteAst).createASTs(sources, null, new String[0], new FileASTRequestor() {
SpringIndexerJava.createParser(project, new AnnotationHierarchies(), !isCompleteAst).createASTs(sources, null, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {