Allow caching in AnnotationHierarchies. Performance.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -567,7 +567,7 @@ public class Wrappers {
|
||||
|
||||
@Override
|
||||
public String getBindingKey() {
|
||||
return JavaUtils.typeFqNametoBindingKey(getFullyQualifiedName());
|
||||
return JavaUtils.typeFqNameToBindingKey(getFullyQualifiedName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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 = () -> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user