GH-1348: identifying event publishers now while indexing
This commit is contained in:
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -13,14 +13,19 @@ package org.springframework.ide.vscode.boot.java.beans;
|
||||
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;
|
||||
@@ -30,6 +35,7 @@ import org.slf4j.Logger;
|
||||
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;
|
||||
@@ -130,10 +136,65 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -148,6 +209,24 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
|
||||
}
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
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;
|
||||
@@ -87,6 +88,15 @@ public class SpringIndexerEventsTest {
|
||||
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 {
|
||||
@@ -134,4 +144,27 @@ public class SpringIndexerEventsTest {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user