Skip to content

Commit

Permalink
GH-1348: identifying event publishers now while indexing
Browse files Browse the repository at this point in the history
  • Loading branch information
martinlippert committed Jan 30, 2025
1 parent 158c555 commit 2c8a52e
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2023 Pivotal, Inc.
* Copyright (c) 2017, 2025 Pivotal, 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
Expand Down Expand Up @@ -90,7 +90,9 @@ public class Annotations {

public static final String SCHEDULED = "org.springframework.scheduling.annotation.Scheduled";
public static final String EVENT_LISTENER = "org.springframework.context.event.EventListener";

public static final String APPLICATION_LISTENER = "org.springframework.context.ApplicationListener";
public static final String EVENT_PUBLISHER = "org.springframework.context.ApplicationEventPublisher";

public static final Map<String, String> AOP_ANNOTATIONS = Map.of(
"org.aspectj.lang.annotation.Pointcut", "Pointcut",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolKind;
Expand All @@ -30,6 +35,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
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;
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
Expand Down Expand Up @@ -130,10 +136,65 @@ protected void createSymbol(Annotation node, ITypeBinding annotationType, Collec
}
}
}

// event publisher checks
for (InjectionPoint injectionPoint : injectionPoints) {
if (Annotations.EVENT_PUBLISHER.equals(injectionPoint.getType())) {
context.getNextPassFiles().add(context.getFile());
}
}

context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), new EnhancedSymbolInformation(symbol)));
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
}

@Override
protected void addSymbolsPass2(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
TypeDeclaration type = (TypeDeclaration) node.getParent();
type.accept(new ASTVisitor() {

@Override
public boolean visit(MethodInvocation methodInvocation) {
try {
String methodName = methodInvocation.getName().toString();
if ("publishEvent".equals(methodName)) {

IMethodBinding methodBinding = methodInvocation.resolveMethodBinding();
boolean doesInvokeEventPublisher = Annotations.EVENT_PUBLISHER.equals(methodBinding.getDeclaringClass().getQualifiedName());
if (doesInvokeEventPublisher) {
List<?> arguments = methodInvocation.arguments();
if (arguments != null && arguments.size() == 1) {

ITypeBinding eventTypeBinding = ((Expression) arguments.get(0)).resolveTypeBinding();
if (eventTypeBinding != null) {

DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, methodInvocation);
Location location;
location = new Location(doc.getUri(), nodeRegion.asRange());

EventPublisherIndexElement eventPublisherIndexElement = new EventPublisherIndexElement(eventTypeBinding.getQualifiedName(), location);
Bean publisherBeanElement = findBean(node, methodInvocation, context, doc);
if (publisherBeanElement != null) {
publisherBeanElement.addChild(eventPublisherIndexElement);
}

// symbol
String symbolLabel = "@EventPublisher (" + eventTypeBinding.getName() + ")";
WorkspaceSymbol symbol = new WorkspaceSymbol(symbolLabel, SymbolKind.Interface, Either.forLeft(location));
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol);
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol));
}
}
}
}

} catch (BadLocationException e) {
log.error("", e);
}
return super.visit(methodInvocation);
}
});
}

private MethodDeclaration findHandleEventMethod(TypeDeclaration type) {
MethodDeclaration[] methods = type.getMethods();
Expand All @@ -148,6 +209,24 @@ private MethodDeclaration findHandleEventMethod(TypeDeclaration type) {
}
return null;
}

private Bean findBean(Annotation annotation, MethodInvocation methodInvocation, SpringIndexerJavaContext context, TextDocument doc) {
TypeDeclaration declaringType = ASTUtils.findDeclaringType(methodInvocation);
if (declaringType != null) {
String beanName = BeanUtils.getBeanNameFromComponentAnnotation(annotation, declaringType);
if (beanName != null) {
Optional<Bean> first = context.getBeans().stream().filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri()))
.map(cachedBean -> cachedBean.getBean())
.filter(bean -> bean instanceof Bean)
.map(bean -> (Bean) bean)
.filter(bean -> bean.getName().equals(beanName))
.findFirst();
return first.get();
}
}

return null;
}

protected String beanLabel(String searchPrefix, String annotationTypeName, Collection<String> metaAnnotationNames, String beanName, String beanType) {
StringBuilder symbolLabel = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2025 Broadcom
* 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:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.events;

import org.eclipse.lsp4j.Location;
import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement;

/**
* @author Martin Lippert
*/
public class EventPublisherIndexElement extends AbstractSpringIndexElement {

private final String eventType;
private final Location location;

public EventPublisherIndexElement(String eventType, Location location) {
this.eventType = eventType;
this.location = location;
}

public String getEventType() {
return eventType;
}

public Location getLocation() {
return location;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
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.SpringIndexElement;
Expand Down Expand Up @@ -87,6 +88,15 @@ void testEventListenerSymbolForEventListenerInterfaceImplementation() throws Exc
SpringIndexerHarness.assertDocumentSymbols(indexer, docUri,
SpringIndexerHarness.symbol("@Component", "@+ 'eventListenerPerInterface' (@Component) EventListenerPerInterface"));
}

@Test
void testEventPublisherSymbol() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString();

SpringIndexerHarness.assertDocumentSymbols(indexer, docUri,
SpringIndexerHarness.symbol("@Component", "@+ 'customEventPublisher' (@Component) CustomEventPublisher"),
SpringIndexerHarness.symbol("this.publisher.publishEvent(new CustomEvent())", "@EventPublisher (CustomEvent)"));
}

@Test
void testAnnotationBasedEventListenerIndexElements() throws Exception {
Expand Down Expand Up @@ -134,4 +144,27 @@ void testEventListenerIndexElementForEventListenerInterfaceImplementation() thro
assertEquals(new Range(new Position(10, 13), new Position(10, 31)), location.getRange());
}

@Test
void testEventPublisherIndexElements() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString();

Bean[] beans = springIndex.getBeansOfDocument(docUri);
assertEquals(1, beans.length);

Bean listenerComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("customEventPublisher")).findFirst().get();
assertEquals("com.example.events.demo.CustomEventPublisher", listenerComponentBean.getType());

List<SpringIndexElement> children = listenerComponentBean.getChildren();
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof EventPublisherIndexElement);

EventPublisherIndexElement publisherElement = (EventPublisherIndexElement) children.get(0);
assertEquals("com.example.events.demo.CustomEvent", publisherElement.getEventType());

Location location = publisherElement.getLocation();
assertNotNull(location);
assertEquals(docUri, location.getUri());
assertEquals(new Range(new Position(15, 2), new Position(15, 48)), location.getRange());
}

}

0 comments on commit 2c8a52e

Please sign in to comment.