GH-1462: add event listener index elements for interface implementations without component annotation

This commit is contained in:
Martin Lippert
2025-02-03 21:22:21 +01:00
parent 4921ac3d07
commit 4f675ccebb
6 changed files with 103 additions and 3 deletions

View File

@@ -78,6 +78,23 @@ public class SpringMetamodelIndex {
project.removeDocument(docURI);
}
}
public DocumentElement getDocument(String docURI) {
ArrayDeque<SpringIndexElement> 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<Bean> result = new ArrayList<>();

View File

@@ -34,6 +34,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
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 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
Collection<Annotation> 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 class ComponentSymbolProvider extends AbstractSymbolProvider {
}
});
}
@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<Annotation> 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();

View File

@@ -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 class EventListenerIndexElement extends AbstractSpringIndexElement {
return location;
}
public String getContainerBeanType() {
return containerBeanType;
}
}

View File

@@ -18,6 +18,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
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 @@ public class EventListenerSymbolProvider extends AbstractSymbolProvider {
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 @@ public class EventListenerSymbolProvider extends AbstractSymbolProvider {
}
}
@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);

View File

@@ -40,6 +40,7 @@ import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement
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 @@ public class SpringIndexerEventsTest {
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 @@ public class SpringIndexerEventsTest {
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 @@ public class SpringIndexerEventsTest {
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<SpringIndexElement> 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();

View File

@@ -40,6 +40,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>