diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java index 31e13a0409..d2583f1a7f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java @@ -78,6 +78,23 @@ public void removeElements(String projectName, String docURI) { project.removeDocument(docURI); } } + + public DocumentElement getDocument(String docURI) { + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.addAll(this.projectRootElements.values()); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (element instanceof DocumentElement doc && doc.getDocURI().equals(docURI)) { + return doc; + } + + elementsToVisit.addAll(element.getChildren()); + } + + return null; + } public Bean[] getBeans() { List result = new ArrayList<>(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java index 3bb788c118..c1f18182cc 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java @@ -34,6 +34,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement; import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; @@ -131,7 +132,7 @@ protected void createSymbol(Annotation node, ITypeBinding annotationType, Collec Collection annotationsOnHandleEventMethod = ASTUtils.getAnnotations(handleEventMethod); AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc); - EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, handleEventMethodAnnotations); + EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, beanType.getQualifiedName(), handleEventMethodAnnotations); beanDefinition.addChild(eventElement); } } @@ -198,6 +199,40 @@ public boolean visit(MethodInvocation methodInvocation) { } }); } + + @Override + protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + // event listener - create child element, if necessary + try { + ITypeBinding typeBinding = typeDeclaration.resolveBinding(); + if (typeBinding == null) return; + + ITypeBinding inTypeHierarchy = ASTUtils.findInTypeHierarchy(typeDeclaration, doc, typeBinding, Set.of(Annotations.APPLICATION_LISTENER)); + if (inTypeHierarchy == null) return; + + MethodDeclaration handleEventMethod = findHandleEventMethod(typeDeclaration); + if (handleEventMethod == null) return; + + IMethodBinding methodBinding = handleEventMethod.resolveBinding(); + ITypeBinding[] parameterTypes = methodBinding.getParameterTypes(); + if (parameterTypes != null && parameterTypes.length == 1) { + + ITypeBinding eventType = parameterTypes[0]; + String eventTypeFq = eventType.getQualifiedName(); + + DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, handleEventMethod.getName()); + Location handleMethodLocation = new Location(doc.getUri(), nodeRegion.asRange()); + + Collection annotationsOnHandleEventMethod = ASTUtils.getAnnotations(handleEventMethod); + AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc); + + EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, typeBinding.getQualifiedName(), handleEventMethodAnnotations); + context.getBeans().add(new CachedBean(doc.getUri(), eventElement)); + } + } catch (BadLocationException e) { + log.error("", e); + } + } private MethodDeclaration findHandleEventMethod(TypeDeclaration type) { MethodDeclaration[] methods = type.getMethods(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexElement.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexElement.java index a881e050a7..844df76114 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexElement.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexElement.java @@ -21,11 +21,13 @@ public class EventListenerIndexElement extends AbstractSpringIndexElement { private final String eventType; private final Location location; + private final String containerBeanType; private final AnnotationMetadata[] annotations; - public EventListenerIndexElement(String eventType, Location location, AnnotationMetadata[] annotations) { + public EventListenerIndexElement(String eventType, Location location, String containerBeanType, AnnotationMetadata[] annotations) { this.eventType = eventType; this.location = location; + this.containerBeanType = containerBeanType; this.annotations = annotations; } @@ -41,4 +43,8 @@ public Location getLocation() { return location; } + public String getContainerBeanType() { + return containerBeanType; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java index 5c8428766b..9efe29ad21 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.WorkspaceSymbol; @@ -75,8 +76,17 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collec ITypeBinding eventType = getEventType(node, method); DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, method.getName()); Location location = new Location(doc.getUri(), nodeRegion.asRange()); + + String containerBeanType = null; + TypeDeclaration type = ASTUtils.findDeclaringType(method); + if (type != null) { + ITypeBinding binding = type.resolveBinding(); + if (binding != null) { + containerBeanType = binding.getQualifiedName(); + } + } - cachedBean.getBean().addChild(new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, annotations)); + cachedBean.getBean().addChild(new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, containerBeanType, annotations)); } } } catch (BadLocationException e) { @@ -84,6 +94,11 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collec } } + @Override + protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + super.addSymbolsPass1(typeDeclaration, context, doc); + } + private String createEventListenerSymbolLabel(Annotation node, MethodDeclaration method) { // event listener annotation type String annotationTypeName = getAnnotationTypeName(node); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java index b0c7af7a8e..f9c8d68883 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java @@ -40,6 +40,7 @@ import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -117,6 +118,7 @@ void testAnnotationBasedEventListenerIndexElements() throws Exception { EventListenerIndexElement listenerElement = (EventListenerIndexElement) children.get(0); assertEquals("org.springframework.context.ApplicationEvent", listenerElement.getEventType()); + assertEquals("com.example.events.demo.EventListenerPerAnnotation", listenerElement.getContainerBeanType()); Location location = listenerElement.getLocation(); assertNotNull(location); @@ -140,6 +142,7 @@ void testEventListenerIndexElementForEventListenerInterfaceImplementation() thro EventListenerIndexElement listenerElement = (EventListenerIndexElement) children.get(0); assertEquals("org.springframework.context.ApplicationEvent", listenerElement.getEventType()); + assertEquals("com.example.events.demo.EventListenerPerInterface", listenerElement.getContainerBeanType()); Location location = listenerElement.getLocation(); assertNotNull(location); @@ -147,6 +150,26 @@ void testEventListenerIndexElementForEventListenerInterfaceImplementation() thro assertEquals(new Range(new Position(10, 13), new Position(10, 31)), location.getRange()); } + @Test + void testEventListenerIndexElementForListenerInterfaceImplementationWithoutComponentAnnotation() throws Exception { + String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/EventListenerPerInterfaceAndBeanMethod.java").toUri().toString(); + + Bean[] beans = springIndex.getBeansOfDocument(docUri); + assertEquals(0, beans.length); + + DocumentElement document = springIndex.getDocument(docUri); + List children = document.getChildren(); + + EventListenerIndexElement listenerElement = children.stream().filter(element -> element instanceof EventListenerIndexElement).map(element -> (EventListenerIndexElement) element).findFirst().get(); + assertEquals("org.springframework.context.ApplicationEvent", listenerElement.getEventType()); + assertEquals("com.example.events.demo.EventListenerPerInterfaceAndBeanMethod", listenerElement.getContainerBeanType()); + + Location location = listenerElement.getLocation(); + assertNotNull(location); + assertEquals(docUri, location.getUri()); + assertEquals(new Range(new Position(8, 13), new Position(8, 31)), location.getRange()); + } + @Test void testEventPublisherIndexElements() throws Exception { String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString(); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/pom.xml index b4e9debc69..c484f44ebe 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/pom.xml +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/pom.xml @@ -40,6 +40,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-actuator +