Skip to content

Commit 2cf95ab

Browse files
committed
GH-1462: add event listener index elements for interface implementations without component annotation
1 parent 4d7c8d9 commit 2cf95ab

File tree

6 files changed

+103
-3
lines changed

6 files changed

+103
-3
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,23 @@ public void removeElements(String projectName, String docURI) {
7878
project.removeDocument(docURI);
7979
}
8080
}
81+
82+
public DocumentElement getDocument(String docURI) {
83+
ArrayDeque<SpringIndexElement> elementsToVisit = new ArrayDeque<>();
84+
elementsToVisit.addAll(this.projectRootElements.values());
85+
86+
while (!elementsToVisit.isEmpty()) {
87+
SpringIndexElement element = elementsToVisit.pop();
88+
89+
if (element instanceof DocumentElement doc && doc.getDocURI().equals(docURI)) {
90+
return doc;
91+
}
92+
93+
elementsToVisit.addAll(element.getChildren());
94+
}
95+
96+
return null;
97+
}
8198

8299
public Bean[] getBeans() {
83100
List<Bean> result = new ArrayList<>();

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.slf4j.Logger;
3535
import org.slf4j.LoggerFactory;
3636
import org.springframework.ide.vscode.boot.java.Annotations;
37+
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
3738
import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement;
3839
import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement;
3940
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
@@ -131,7 +132,7 @@ protected void createSymbol(Annotation node, ITypeBinding annotationType, Collec
131132
Collection<Annotation> annotationsOnHandleEventMethod = ASTUtils.getAnnotations(handleEventMethod);
132133
AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc);
133134

134-
EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, handleEventMethodAnnotations);
135+
EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, beanType.getQualifiedName(), handleEventMethodAnnotations);
135136
beanDefinition.addChild(eventElement);
136137
}
137138
}
@@ -198,6 +199,40 @@ public boolean visit(MethodInvocation methodInvocation) {
198199
}
199200
});
200201
}
202+
203+
@Override
204+
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
205+
// event listener - create child element, if necessary
206+
try {
207+
ITypeBinding typeBinding = typeDeclaration.resolveBinding();
208+
if (typeBinding == null) return;
209+
210+
ITypeBinding inTypeHierarchy = ASTUtils.findInTypeHierarchy(typeDeclaration, doc, typeBinding, Set.of(Annotations.APPLICATION_LISTENER));
211+
if (inTypeHierarchy == null) return;
212+
213+
MethodDeclaration handleEventMethod = findHandleEventMethod(typeDeclaration);
214+
if (handleEventMethod == null) return;
215+
216+
IMethodBinding methodBinding = handleEventMethod.resolveBinding();
217+
ITypeBinding[] parameterTypes = methodBinding.getParameterTypes();
218+
if (parameterTypes != null && parameterTypes.length == 1) {
219+
220+
ITypeBinding eventType = parameterTypes[0];
221+
String eventTypeFq = eventType.getQualifiedName();
222+
223+
DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, handleEventMethod.getName());
224+
Location handleMethodLocation = new Location(doc.getUri(), nodeRegion.asRange());
225+
226+
Collection<Annotation> annotationsOnHandleEventMethod = ASTUtils.getAnnotations(handleEventMethod);
227+
AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc);
228+
229+
EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, typeBinding.getQualifiedName(), handleEventMethodAnnotations);
230+
context.getBeans().add(new CachedBean(doc.getUri(), eventElement));
231+
}
232+
} catch (BadLocationException e) {
233+
log.error("", e);
234+
}
235+
}
201236

202237
private MethodDeclaration findHandleEventMethod(TypeDeclaration type) {
203238
MethodDeclaration[] methods = type.getMethods();

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexElement.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ public class EventListenerIndexElement extends AbstractSpringIndexElement {
2121

2222
private final String eventType;
2323
private final Location location;
24+
private final String containerBeanType;
2425
private final AnnotationMetadata[] annotations;
2526

26-
public EventListenerIndexElement(String eventType, Location location, AnnotationMetadata[] annotations) {
27+
public EventListenerIndexElement(String eventType, Location location, String containerBeanType, AnnotationMetadata[] annotations) {
2728
this.eventType = eventType;
2829
this.location = location;
30+
this.containerBeanType = containerBeanType;
2931
this.annotations = annotations;
3032
}
3133

@@ -41,4 +43,8 @@ public Location getLocation() {
4143
return location;
4244
}
4345

46+
public String getContainerBeanType() {
47+
return containerBeanType;
48+
}
49+
4450
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.eclipse.jdt.core.dom.IVariableBinding;
1919
import org.eclipse.jdt.core.dom.MethodDeclaration;
2020
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
21+
import org.eclipse.jdt.core.dom.TypeDeclaration;
2122
import org.eclipse.lsp4j.Location;
2223
import org.eclipse.lsp4j.SymbolKind;
2324
import org.eclipse.lsp4j.WorkspaceSymbol;
@@ -75,15 +76,29 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collec
7576
ITypeBinding eventType = getEventType(node, method);
7677
DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, method.getName());
7778
Location location = new Location(doc.getUri(), nodeRegion.asRange());
79+
80+
String containerBeanType = null;
81+
TypeDeclaration type = ASTUtils.findDeclaringType(method);
82+
if (type != null) {
83+
ITypeBinding binding = type.resolveBinding();
84+
if (binding != null) {
85+
containerBeanType = binding.getQualifiedName();
86+
}
87+
}
7888

79-
cachedBean.getBean().addChild(new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, annotations));
89+
cachedBean.getBean().addChild(new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, containerBeanType, annotations));
8090
}
8191
}
8292
} catch (BadLocationException e) {
8393
log.error("", e);
8494
}
8595
}
8696

97+
@Override
98+
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
99+
super.addSymbolsPass1(typeDeclaration, context, doc);
100+
}
101+
87102
private String createEventListenerSymbolLabel(Annotation node, MethodDeclaration method) {
88103
// event listener annotation type
89104
String annotationTypeName = getAnnotationTypeName(node);

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement;
4141
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
4242
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
43+
import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement;
4344
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
4445
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
4546
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
@@ -117,6 +118,7 @@ void testAnnotationBasedEventListenerIndexElements() throws Exception {
117118

118119
EventListenerIndexElement listenerElement = (EventListenerIndexElement) children.get(0);
119120
assertEquals("org.springframework.context.ApplicationEvent", listenerElement.getEventType());
121+
assertEquals("com.example.events.demo.EventListenerPerAnnotation", listenerElement.getContainerBeanType());
120122

121123
Location location = listenerElement.getLocation();
122124
assertNotNull(location);
@@ -140,13 +142,34 @@ void testEventListenerIndexElementForEventListenerInterfaceImplementation() thro
140142

141143
EventListenerIndexElement listenerElement = (EventListenerIndexElement) children.get(0);
142144
assertEquals("org.springframework.context.ApplicationEvent", listenerElement.getEventType());
145+
assertEquals("com.example.events.demo.EventListenerPerInterface", listenerElement.getContainerBeanType());
143146

144147
Location location = listenerElement.getLocation();
145148
assertNotNull(location);
146149
assertEquals(docUri, location.getUri());
147150
assertEquals(new Range(new Position(10, 13), new Position(10, 31)), location.getRange());
148151
}
149152

153+
@Test
154+
void testEventListenerIndexElementForListenerInterfaceImplementationWithoutComponentAnnotation() throws Exception {
155+
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/EventListenerPerInterfaceAndBeanMethod.java").toUri().toString();
156+
157+
Bean[] beans = springIndex.getBeansOfDocument(docUri);
158+
assertEquals(0, beans.length);
159+
160+
DocumentElement document = springIndex.getDocument(docUri);
161+
List<SpringIndexElement> children = document.getChildren();
162+
163+
EventListenerIndexElement listenerElement = children.stream().filter(element -> element instanceof EventListenerIndexElement).map(element -> (EventListenerIndexElement) element).findFirst().get();
164+
assertEquals("org.springframework.context.ApplicationEvent", listenerElement.getEventType());
165+
assertEquals("com.example.events.demo.EventListenerPerInterfaceAndBeanMethod", listenerElement.getContainerBeanType());
166+
167+
Location location = listenerElement.getLocation();
168+
assertNotNull(location);
169+
assertEquals(docUri, location.getUri());
170+
assertEquals(new Range(new Position(8, 13), new Position(8, 31)), location.getRange());
171+
}
172+
150173
@Test
151174
void testEventPublisherIndexElements() throws Exception {
152175
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString();

headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
<artifactId>spring-boot-starter-test</artifactId>
4141
<scope>test</scope>
4242
</dependency>
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-actuator</artifactId>
46+
</dependency>
4347
</dependencies>
4448

4549
<build>

0 commit comments

Comments
 (0)