Skip to content

Commit 2c8a52e

Browse files
committed
GH-1348: identifying event publishers now while indexing
1 parent 158c555 commit 2c8a52e

File tree

4 files changed

+152
-1
lines changed

4 files changed

+152
-1
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017, 2023 Pivotal, Inc.
2+
* Copyright (c) 2017, 2025 Pivotal, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -90,7 +90,9 @@ public class Annotations {
9090

9191
public static final String SCHEDULED = "org.springframework.scheduling.annotation.Scheduled";
9292
public static final String EVENT_LISTENER = "org.springframework.context.event.EventListener";
93+
9394
public static final String APPLICATION_LISTENER = "org.springframework.context.ApplicationListener";
95+
public static final String EVENT_PUBLISHER = "org.springframework.context.ApplicationEventPublisher";
9496

9597
public static final Map<String, String> AOP_ANNOTATIONS = Map.of(
9698
"org.aspectj.lang.annotation.Pointcut", "Pointcut",

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@
1313
import java.util.Arrays;
1414
import java.util.Collection;
1515
import java.util.HashSet;
16+
import java.util.List;
17+
import java.util.Optional;
1618
import java.util.Set;
1719
import java.util.stream.Collectors;
1820
import java.util.stream.Stream;
1921

22+
import org.eclipse.jdt.core.dom.ASTVisitor;
2023
import org.eclipse.jdt.core.dom.Annotation;
24+
import org.eclipse.jdt.core.dom.Expression;
2125
import org.eclipse.jdt.core.dom.IMethodBinding;
2226
import org.eclipse.jdt.core.dom.ITypeBinding;
2327
import org.eclipse.jdt.core.dom.MethodDeclaration;
28+
import org.eclipse.jdt.core.dom.MethodInvocation;
2429
import org.eclipse.jdt.core.dom.TypeDeclaration;
2530
import org.eclipse.lsp4j.Location;
2631
import org.eclipse.lsp4j.SymbolKind;
@@ -30,6 +35,7 @@
3035
import org.slf4j.LoggerFactory;
3136
import org.springframework.ide.vscode.boot.java.Annotations;
3237
import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement;
38+
import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement;
3339
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
3440
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
3541
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
@@ -130,10 +136,65 @@ protected void createSymbol(Annotation node, ITypeBinding annotationType, Collec
130136
}
131137
}
132138
}
139+
140+
// event publisher checks
141+
for (InjectionPoint injectionPoint : injectionPoints) {
142+
if (Annotations.EVENT_PUBLISHER.equals(injectionPoint.getType())) {
143+
context.getNextPassFiles().add(context.getFile());
144+
}
145+
}
133146

134147
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), new EnhancedSymbolInformation(symbol)));
135148
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
136149
}
150+
151+
@Override
152+
protected void addSymbolsPass2(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
153+
TypeDeclaration type = (TypeDeclaration) node.getParent();
154+
type.accept(new ASTVisitor() {
155+
156+
@Override
157+
public boolean visit(MethodInvocation methodInvocation) {
158+
try {
159+
String methodName = methodInvocation.getName().toString();
160+
if ("publishEvent".equals(methodName)) {
161+
162+
IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
163+
boolean doesInvokeEventPublisher = Annotations.EVENT_PUBLISHER.equals(methodBinding.getDeclaringClass().getQualifiedName());
164+
if (doesInvokeEventPublisher) {
165+
List<?> arguments = methodInvocation.arguments();
166+
if (arguments != null && arguments.size() == 1) {
167+
168+
ITypeBinding eventTypeBinding = ((Expression) arguments.get(0)).resolveTypeBinding();
169+
if (eventTypeBinding != null) {
170+
171+
DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, methodInvocation);
172+
Location location;
173+
location = new Location(doc.getUri(), nodeRegion.asRange());
174+
175+
EventPublisherIndexElement eventPublisherIndexElement = new EventPublisherIndexElement(eventTypeBinding.getQualifiedName(), location);
176+
Bean publisherBeanElement = findBean(node, methodInvocation, context, doc);
177+
if (publisherBeanElement != null) {
178+
publisherBeanElement.addChild(eventPublisherIndexElement);
179+
}
180+
181+
// symbol
182+
String symbolLabel = "@EventPublisher (" + eventTypeBinding.getName() + ")";
183+
WorkspaceSymbol symbol = new WorkspaceSymbol(symbolLabel, SymbolKind.Interface, Either.forLeft(location));
184+
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol);
185+
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol));
186+
}
187+
}
188+
}
189+
}
190+
191+
} catch (BadLocationException e) {
192+
log.error("", e);
193+
}
194+
return super.visit(methodInvocation);
195+
}
196+
});
197+
}
137198

138199
private MethodDeclaration findHandleEventMethod(TypeDeclaration type) {
139200
MethodDeclaration[] methods = type.getMethods();
@@ -148,6 +209,24 @@ private MethodDeclaration findHandleEventMethod(TypeDeclaration type) {
148209
}
149210
return null;
150211
}
212+
213+
private Bean findBean(Annotation annotation, MethodInvocation methodInvocation, SpringIndexerJavaContext context, TextDocument doc) {
214+
TypeDeclaration declaringType = ASTUtils.findDeclaringType(methodInvocation);
215+
if (declaringType != null) {
216+
String beanName = BeanUtils.getBeanNameFromComponentAnnotation(annotation, declaringType);
217+
if (beanName != null) {
218+
Optional<Bean> first = context.getBeans().stream().filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri()))
219+
.map(cachedBean -> cachedBean.getBean())
220+
.filter(bean -> bean instanceof Bean)
221+
.map(bean -> (Bean) bean)
222+
.filter(bean -> bean.getName().equals(beanName))
223+
.findFirst();
224+
return first.get();
225+
}
226+
}
227+
228+
return null;
229+
}
151230

152231
protected String beanLabel(String searchPrefix, String annotationTypeName, Collection<String> metaAnnotationNames, String beanName, String beanType) {
153232
StringBuilder symbolLabel = new StringBuilder();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.events;
12+
13+
import org.eclipse.lsp4j.Location;
14+
import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement;
15+
16+
/**
17+
* @author Martin Lippert
18+
*/
19+
public class EventPublisherIndexElement extends AbstractSpringIndexElement {
20+
21+
private final String eventType;
22+
private final Location location;
23+
24+
public EventPublisherIndexElement(String eventType, Location location) {
25+
this.eventType = eventType;
26+
this.location = location;
27+
}
28+
29+
public String getEventType() {
30+
return eventType;
31+
}
32+
33+
public Location getLocation() {
34+
return location;
35+
}
36+
37+
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
3535
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
3636
import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement;
37+
import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement;
3738
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
3839
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
3940
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
@@ -87,6 +88,15 @@ void testEventListenerSymbolForEventListenerInterfaceImplementation() throws Exc
8788
SpringIndexerHarness.assertDocumentSymbols(indexer, docUri,
8889
SpringIndexerHarness.symbol("@Component", "@+ 'eventListenerPerInterface' (@Component) EventListenerPerInterface"));
8990
}
91+
92+
@Test
93+
void testEventPublisherSymbol() throws Exception {
94+
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString();
95+
96+
SpringIndexerHarness.assertDocumentSymbols(indexer, docUri,
97+
SpringIndexerHarness.symbol("@Component", "@+ 'customEventPublisher' (@Component) CustomEventPublisher"),
98+
SpringIndexerHarness.symbol("this.publisher.publishEvent(new CustomEvent())", "@EventPublisher (CustomEvent)"));
99+
}
90100

91101
@Test
92102
void testAnnotationBasedEventListenerIndexElements() throws Exception {
@@ -134,4 +144,27 @@ void testEventListenerIndexElementForEventListenerInterfaceImplementation() thro
134144
assertEquals(new Range(new Position(10, 13), new Position(10, 31)), location.getRange());
135145
}
136146

147+
@Test
148+
void testEventPublisherIndexElements() throws Exception {
149+
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString();
150+
151+
Bean[] beans = springIndex.getBeansOfDocument(docUri);
152+
assertEquals(1, beans.length);
153+
154+
Bean listenerComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("customEventPublisher")).findFirst().get();
155+
assertEquals("com.example.events.demo.CustomEventPublisher", listenerComponentBean.getType());
156+
157+
List<SpringIndexElement> children = listenerComponentBean.getChildren();
158+
assertEquals(1, children.size());
159+
assertTrue(children.get(0) instanceof EventPublisherIndexElement);
160+
161+
EventPublisherIndexElement publisherElement = (EventPublisherIndexElement) children.get(0);
162+
assertEquals("com.example.events.demo.CustomEvent", publisherElement.getEventType());
163+
164+
Location location = publisherElement.getLocation();
165+
assertNotNull(location);
166+
assertEquals(docUri, location.getUri());
167+
assertEquals(new Range(new Position(15, 2), new Position(15, 48)), location.getRange());
168+
}
169+
137170
}

0 commit comments

Comments
 (0)