From 0e1dd52565daf661cdbeb4bfece347c79e5a7b72 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 31 Jan 2025 13:10:09 +0100 Subject: [PATCH] GH-1348: find references from listeners to publishers and vice versa now works Fixes GH-1348 --- .../BootJavaLanguageServerComponents.java | 21 +-- .../java/beans/NamedReferencesProvider.java | 8 +- .../java/beans/ProfileReferencesProvider.java | 8 +- .../beans/QualifierReferencesProvider.java | 8 +- .../java/events/EventReferenceProvider.java | 131 ++++++++++++++++++ .../handlers/BootJavaReferencesHandler.java | 53 +++++-- .../boot/java/handlers/ReferenceProvider.java | 7 +- .../ValuePropertyReferencesProvider.java | 7 +- .../test/EventsReferencesProviderTest.java | 128 +++++++++++++++++ .../test/SpringIndexerEventsTest.java | 3 +- .../events/demo/CustomEventListener.java | 14 ++ 11 files changed, 361 insertions(+), 27 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventReferenceProvider.java create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java rename headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/{beans => events}/test/SpringIndexerEventsTest.java (98%) create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventListener.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 422c0411c..ffa21afd2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -14,6 +14,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -33,6 +34,7 @@ import org.springframework.ide.vscode.boot.java.beans.QualifierReferencesProvide import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider; import org.springframework.ide.vscode.boot.java.copilot.CopilotAgentCommandHandler; import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier; +import org.springframework.ide.vscode.boot.java.events.EventReferenceProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine; import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentHighlightEngine; @@ -316,16 +318,19 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringMetamodelIndex index, SpringSymbolIndex symbolIndex, CompilationUnitCache cuCache) { - Map providers = new HashMap<>(); + Map specificProviders = new HashMap<>(); - providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder, index)); - providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder, index)); - providers.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index)); - providers.put(Annotations.NAMED_JAKARTA, new NamedReferencesProvider(index, symbolIndex)); - providers.put(Annotations.NAMED_JAVAX, new NamedReferencesProvider(index, symbolIndex)); - providers.put(Annotations.PROFILE, new ProfileReferencesProvider(index)); + specificProviders.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder, index)); + specificProviders.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder, index)); + specificProviders.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index)); + specificProviders.put(Annotations.NAMED_JAKARTA, new NamedReferencesProvider(index, symbolIndex)); + specificProviders.put(Annotations.NAMED_JAVAX, new NamedReferencesProvider(index, symbolIndex)); + specificProviders.put(Annotations.PROFILE, new ProfileReferencesProvider(index)); + + List unspecificProviders = new ArrayList<>(); + unspecificProviders.add(new EventReferenceProvider(index)); - return new BootJavaReferencesHandler(this, cuCache, projectFinder, providers); + return new BootJavaReferencesHandler(this, cuCache, projectFinder, specificProviders, unspecificProviders); } protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java index 4b25f249a..fa2f0099e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Broadcom + * Copyright (c) 2024, 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 @@ -28,6 +28,7 @@ import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert @@ -69,6 +70,11 @@ public class NamedReferencesProvider implements ReferenceProvider { return null; } + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) { + return null; + } + private List provideReferences(IJavaProject project, String value) { Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ProfileReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ProfileReferencesProvider.java index 028176d39..ea2ac63e7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ProfileReferencesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ProfileReferencesProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Broadcom + * Copyright (c) 2024, 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 @@ -28,6 +28,7 @@ import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert @@ -73,6 +74,11 @@ public class ProfileReferencesProvider implements ReferenceProvider { return null; } + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) { + return null; + } + private List provideReferences(IJavaProject project, String value) { Bean[] beans = this.springIndex.getBeans(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java index 072b9d9f5..79f5e21d7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Broadcom + * Copyright (c) 2024, 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 @@ -27,6 +27,7 @@ import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert @@ -66,6 +67,11 @@ public class QualifierReferencesProvider implements ReferenceProvider { return null; } + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) { + return null; + } + private List provideReferences(IJavaProject project, String value) { Bean[] beans = this.springIndex.getBeans(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventReferenceProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventReferenceProvider.java new file mode 100644 index 000000000..f2c218a25 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventReferenceProvider.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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 java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.BadLocationException; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +/** + * @author Martin Lippert + */ +public class EventReferenceProvider implements ReferenceProvider { + + private static final Logger log = LoggerFactory.getLogger(EventReferenceProvider.class); + + private final SpringMetamodelIndex index; + + public EventReferenceProvider(SpringMetamodelIndex index) { + this.index = index; + } + + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, + Annotation annotation, ITypeBinding type, int offset) { + return null; + } + + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) { + try { + Position position = doc.toPosition(offset); + + Bean[] beans = index.getBeans(); + + // when offset is inside an event listener, find the respective event type + Optional listenerEventType = Arrays.stream(beans) + .filter(bean -> bean.getLocation().getUri().equals(doc.getUri())) + .flatMap(bean -> bean.getChildren().stream()) + .filter(element -> element instanceof EventListenerIndexElement) + .map(element -> (EventListenerIndexElement) element) + .filter(eventListener -> isPositionInside(position, eventListener.getLocation())) + .map(eventListener -> eventListener.getEventType()) + .findAny(); + + if (listenerEventType.isPresent()) { + // use the listener event type to look for publishers for that type + String eventType = listenerEventType.get(); + + List foundLocations = Arrays.stream(beans) + .flatMap(bean -> bean.getChildren().stream()) + .filter(element -> element instanceof EventPublisherIndexElement) + .map(element -> (EventPublisherIndexElement) element) + .filter(publisher -> publisher.getEventType().equals(eventType)) + .map(publisher -> publisher.getLocation()) + .toList(); + + if (foundLocations.size() > 0) { + return foundLocations; + } + } + + // when offset is inside an event publisher, find the respective event type + else { + Optional publisherEventType = Arrays.stream(beans) + .filter(bean -> bean.getLocation().getUri().equals(doc.getUri())) + .flatMap(bean -> bean.getChildren().stream()) + .filter(element -> element instanceof EventPublisherIndexElement) + .map(element -> (EventPublisherIndexElement) element) + .filter(eventListener -> isPositionInside(position, eventListener.getLocation())) + .map(eventListener -> eventListener.getEventType()) + .findAny(); + + if (publisherEventType.isPresent()) { + // use the listener event type to look for publishers for that type + String eventType = publisherEventType.get(); + + List foundLocations = Arrays.stream(beans) + .flatMap(bean -> bean.getChildren().stream()) + .filter(element -> element instanceof EventListenerIndexElement) + .map(element -> (EventListenerIndexElement) element) + .filter(listener -> listener.getEventType().equals(eventType)) + .map(listener-> listener.getLocation()) + .toList(); + + if (foundLocations.size() > 0) { + return foundLocations; + } + } + + } + + } catch (BadLocationException e) { + log.error("", e); + } + return null; + } + + private boolean isPositionInside(Position position, Location location) { + boolean afterStart = position.getLine() > location.getRange().getStart().getLine() + || (position.getLine() == location.getRange().getStart().getLine() && position.getCharacter() >= location.getRange().getStart().getCharacter()); + + boolean beforeEnd = position.getLine() < location.getRange().getEnd().getLine() + || (position.getLine() == location.getRange().getEnd().getLine() && position.getCharacter() <= location.getRange().getEnd().getCharacter()); + + return afterStart && beforeEnd; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java index c77b40180..e62c073a0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2024 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 @@ -11,6 +11,8 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.net.URI; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -22,7 +24,6 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.ReferenceParams; -import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; @@ -39,14 +40,17 @@ public class BootJavaReferencesHandler implements ReferencesHandler { private final JavaProjectFinder projectFinder; private final BootJavaLanguageServerComponents server; - private final Map referenceProviders; + private final Map annotationSpecificReferenceProviders; + private final List unspecificProviders; private final CompilationUnitCache cuCache; - public BootJavaReferencesHandler(BootJavaLanguageServerComponents server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder, Map specificProviders) { + public BootJavaReferencesHandler(BootJavaLanguageServerComponents server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder, + Map specificProviders, List unspecificProviders) { this.server = server; this.cuCache = cuCache; this.projectFinder = projectFinder; - this.referenceProviders = specificProviders; + this.annotationSpecificReferenceProviders = specificProviders; + this.unspecificProviders = unspecificProviders; } @Override @@ -62,7 +66,7 @@ public class BootJavaReferencesHandler implements ReferencesHandler { cancelToken.checkCanceled(); - List referencesResult = provideReferences(cancelToken, doc.getId(), offset); + List referencesResult = provideReferences(cancelToken, doc, offset); if (referencesResult != null) { return referencesResult; } @@ -78,20 +82,32 @@ public class BootJavaReferencesHandler implements ReferencesHandler { return SimpleTextDocumentService.NO_REFERENCES; } - private List provideReferences(CancelChecker cancelToken, TextDocumentIdentifier docID, int offset) throws Exception { - Optional projectOptional = projectFinder.find(docID); + private List provideReferences(CancelChecker cancelToken, TextDocument doc, int offset) throws Exception { + Optional projectOptional = projectFinder.find(doc.getId()); if (projectOptional.isPresent()) { IJavaProject project = projectOptional.get(); - URI docUri = URI.create(docID.getUri()); + URI docUri = URI.create(doc.getUri()); return cuCache.withCompilationUnit(project, docUri, cu -> { cancelToken.checkCanceled(); ASTNode node = NodeFinder.perform(cu, offset, 0); if (node != null) { - return provideReferencesForAnnotation(cancelToken, project, node, offset); + List result = new ArrayList<>(); + + List referencesForAnnotation = provideReferencesForAnnotation(cancelToken, project, node, offset); + if (referencesForAnnotation != null) { + result.addAll(referencesForAnnotation); + } + + List otherReferences = provideUnspecificReferences(cancelToken, project, doc, node, offset); + if (otherReferences != null) { + result.addAll(otherReferences); + } + + return result.size() > 0 ? result : null; } else { return null; @@ -102,6 +118,21 @@ public class BootJavaReferencesHandler implements ReferencesHandler { return null; } + private List provideUnspecificReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) { + List result = new ArrayList<>(); + + for (Iterator iterator = unspecificProviders.iterator(); iterator.hasNext();) { + ReferenceProvider referenceProvider = iterator.next(); + + List references = referenceProvider.provideReferences(cancelToken, project, doc, node, offset); + if (references != null) { + result.addAll(references); + } + } + + return result; + } + private List provideReferencesForAnnotation(CancelChecker cancelToken, IJavaProject project, ASTNode node, int offset) { Annotation annotation = null; @@ -119,7 +150,7 @@ public class BootJavaReferencesHandler implements ReferencesHandler { String qualifiedName = type.getQualifiedName(); if (qualifiedName != null) { - ReferenceProvider provider = this.referenceProviders.get(qualifiedName); + ReferenceProvider provider = this.annotationSpecificReferenceProviders.get(qualifiedName); if (provider != null) { return provider.provideReferences(cancelToken, project, node, annotation, type, offset); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java index 55ac8e2be..614acf38a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2024 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 @@ -18,13 +18,14 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ public interface ReferenceProvider { - List provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, Annotation annotation, - ITypeBinding type, int offset); + List provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, Annotation annotation, ITypeBinding type, int offset); + List provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java index d9692906b..07deeb9ec 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2024 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 @@ -101,6 +101,11 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider { return null; } + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) { + return null; + } + public List findReferencesToPropertyKey(String propertyKey) { List fromPropertyFiles = findReferencesFromPropertyFiles(propertyKey); List fromAnnotations = findReferencesFromAnnotations(propertyKey); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java new file mode 100644 index 000000000..512a1a036 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/EventsReferencesProviderTest.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * 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.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Martin Lippert + */ +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import(SymbolProviderTestConf.class) +public class EventsReferencesProviderTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private SpringSymbolIndex indexer; + + private File directory; + + @BeforeEach + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-events-indexing/").toURI()); + + String projectDir = directory.toURI().toString(); + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.waitOperation(); + initProject.get(5, TimeUnit.SECONDS); + } + + @Test + public void testEventListenerFindsAllPublishers() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventListener.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package com.example.events.demo; + + import org.springframework.context.event.EventListener; + import org.springframework.stereotype.Component; + + @Component + public class CustomEventListener { + + @EventListener + public void handle<*>Event(CustomEvent event) { + System.out.println(event); + } + }""", tempJavaDocUri); + + List references = editor.getReferences(); + assertEquals(1, references.size()); + + String expectedDefinitionUri1 = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString(); + Location expectedLocation1 = new Location(expectedDefinitionUri1, new Range(new Position(15, 2), new Position(15, 48))); + + assertTrue(references.contains(expectedLocation1)); + } + + @Test + public void testEventPublisherFindsAllListeners() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisher.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package com.example.events.demo; + + import org.springframework.context.ApplicationEventPublisher; + import org.springframework.stereotype.Component; + + @Component + public class CustomEventPublisher { + + private ApplicationEventPublisher publisher; + + public CustomEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + public void foo() { + this.publisher.pu<*>blishEvent(new CustomEvent()); + } + }""", tempJavaDocUri); + + List references = editor.getReferences(); + assertEquals(1, references.size()); + + String expectedDefinitionUri1 = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventListener.java").toUri().toString(); + Location expectedLocation1 = new Location(expectedDefinitionUri1, new Range(new Position(9, 13), new Position(9, 24))); + + assertTrue(references.contains(expectedLocation1)); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerEventsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java similarity index 98% rename from headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerEventsTest.java rename to headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java index 08cef94ea..b016dbd3f 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerEventsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java @@ -8,7 +8,7 @@ * Contributors: * Broadcom - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.beans.test; +package org.springframework.ide.vscode.boot.java.events.test; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -33,6 +33,7 @@ import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; 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.beans.test.SpringIndexerHarness; 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; diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventListener.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventListener.java new file mode 100644 index 000000000..e602a785e --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventListener.java @@ -0,0 +1,14 @@ +package com.example.events.demo; + +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class CustomEventListener { + + @EventListener + public void handleEvent(CustomEvent event) { + System.out.println(event); + } + +}