Skip to content

Commit 8234bab

Browse files
committed
GH-1348: find references from listeners to publishers and vice versa now works
Fixes GH-1348
1 parent 2c8a52e commit 8234bab

File tree

11 files changed

+362
-28
lines changed

11 files changed

+362
-28
lines changed

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.ArrayList;
1515
import java.util.Collection;
1616
import java.util.HashMap;
17+
import java.util.List;
1718
import java.util.Map;
1819
import java.util.Optional;
1920
import java.util.Set;
@@ -33,6 +34,7 @@
3334
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
3435
import org.springframework.ide.vscode.boot.java.copilot.CopilotAgentCommandHandler;
3536
import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier;
37+
import org.springframework.ide.vscode.boot.java.events.EventReferenceProvider;
3638
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
3739
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
3840
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentHighlightEngine;
@@ -316,16 +318,19 @@ protected BootJavaHoverProvider createHoverHandler(JavaProjectFinder javaProject
316318
protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder,
317319
SpringMetamodelIndex index, SpringSymbolIndex symbolIndex, CompilationUnitCache cuCache) {
318320

319-
Map<String, ReferenceProvider> providers = new HashMap<>();
320-
321-
providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder, index));
322-
providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder, index));
323-
providers.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index));
324-
providers.put(Annotations.NAMED_JAKARTA, new NamedReferencesProvider(index, symbolIndex));
325-
providers.put(Annotations.NAMED_JAVAX, new NamedReferencesProvider(index, symbolIndex));
326-
providers.put(Annotations.PROFILE, new ProfileReferencesProvider(index));
321+
Map<String, ReferenceProvider> specificProviders = new HashMap<>();
322+
323+
specificProviders.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder, index));
324+
specificProviders.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder, index));
325+
specificProviders.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index));
326+
specificProviders.put(Annotations.NAMED_JAKARTA, new NamedReferencesProvider(index, symbolIndex));
327+
specificProviders.put(Annotations.NAMED_JAVAX, new NamedReferencesProvider(index, symbolIndex));
328+
specificProviders.put(Annotations.PROFILE, new ProfileReferencesProvider(index));
329+
330+
List<ReferenceProvider> unspecificProviders = new ArrayList<>();
331+
unspecificProviders.add(new EventReferenceProvider(index));
327332

328-
return new BootJavaReferencesHandler(this, cuCache, projectFinder, providers);
333+
return new BootJavaReferencesHandler(this, cuCache, projectFinder, specificProviders, unspecificProviders);
329334
}
330335

331336
protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2024 Broadcom
2+
* Copyright (c) 2024, 2025 Broadcom
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
@@ -28,6 +28,7 @@
2828
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
2929
import org.springframework.ide.vscode.commons.java.IJavaProject;
3030
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
31+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
3132

3233
/**
3334
* @author Martin Lippert
@@ -69,6 +70,11 @@ else if (node instanceof StringLiteral && node.getParent() instanceof MemberValu
6970
return null;
7071
}
7172

73+
@Override
74+
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
75+
return null;
76+
}
77+
7278
private List<? extends Location> provideReferences(IJavaProject project, String value) {
7379
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
7480

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2024 Broadcom
2+
* Copyright (c) 2024, 2025 Broadcom
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
@@ -28,6 +28,7 @@
2828
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
2929
import org.springframework.ide.vscode.commons.java.IJavaProject;
3030
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
31+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
3132

3233
/**
3334
* @author Martin Lippert
@@ -73,6 +74,11 @@ else if (node instanceof StringLiteral && node.getParent() instanceof ArrayIniti
7374
return null;
7475
}
7576

77+
@Override
78+
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
79+
return null;
80+
}
81+
7682
private List<? extends Location> provideReferences(IJavaProject project, String value) {
7783
Bean[] beans = this.springIndex.getBeans();
7884

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2024 Broadcom
2+
* Copyright (c) 2024, 2025 Broadcom
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
@@ -27,6 +27,7 @@
2727
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
2828
import org.springframework.ide.vscode.commons.java.IJavaProject;
2929
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
30+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
3031

3132
/**
3233
* @author Martin Lippert
@@ -66,6 +67,11 @@ else if (node instanceof StringLiteral && node.getParent() instanceof MemberValu
6667
return null;
6768
}
6869

70+
@Override
71+
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
72+
return null;
73+
}
74+
6975
private List<? extends Location> provideReferences(IJavaProject project, String value) {
7076
Bean[] beans = this.springIndex.getBeans();
7177

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 java.util.Arrays;
14+
import java.util.List;
15+
import java.util.Optional;
16+
17+
import org.eclipse.jdt.core.dom.ASTNode;
18+
import org.eclipse.jdt.core.dom.Annotation;
19+
import org.eclipse.jdt.core.dom.ITypeBinding;
20+
import org.eclipse.lsp4j.Location;
21+
import org.eclipse.lsp4j.Position;
22+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
26+
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
27+
import org.springframework.ide.vscode.commons.java.IJavaProject;
28+
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
29+
import org.springframework.ide.vscode.commons.util.BadLocationException;
30+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
31+
32+
/**
33+
* @author Martin Lippert
34+
*/
35+
public class EventReferenceProvider implements ReferenceProvider {
36+
37+
private static final Logger log = LoggerFactory.getLogger(EventReferenceProvider.class);
38+
39+
private final SpringMetamodelIndex index;
40+
41+
public EventReferenceProvider(SpringMetamodelIndex index) {
42+
this.index = index;
43+
}
44+
45+
@Override
46+
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node,
47+
Annotation annotation, ITypeBinding type, int offset) {
48+
return null;
49+
}
50+
51+
@Override
52+
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
53+
try {
54+
Position position = doc.toPosition(offset);
55+
56+
Bean[] beans = index.getBeans();
57+
58+
// when offset is inside an event listener, find the respective event type
59+
Optional<String> listenerEventType = Arrays.stream(beans)
60+
.filter(bean -> bean.getLocation().getUri().equals(doc.getUri()))
61+
.flatMap(bean -> bean.getChildren().stream())
62+
.filter(element -> element instanceof EventListenerIndexElement)
63+
.map(element -> (EventListenerIndexElement) element)
64+
.filter(eventListener -> isPositionInside(position, eventListener.getLocation()))
65+
.map(eventListener -> eventListener.getEventType())
66+
.findAny();
67+
68+
if (listenerEventType.isPresent()) {
69+
// use the listener event type to look for publishers for that type
70+
String eventType = listenerEventType.get();
71+
72+
List<Location> foundLocations = Arrays.stream(beans)
73+
.flatMap(bean -> bean.getChildren().stream())
74+
.filter(element -> element instanceof EventPublisherIndexElement)
75+
.map(element -> (EventPublisherIndexElement) element)
76+
.filter(publisher -> publisher.getEventType().equals(eventType))
77+
.map(publisher -> publisher.getLocation())
78+
.toList();
79+
80+
if (foundLocations.size() > 0) {
81+
return foundLocations;
82+
}
83+
}
84+
85+
// when offset is inside an event publisher, find the respective event type
86+
else {
87+
Optional<String> publisherEventType = Arrays.stream(beans)
88+
.filter(bean -> bean.getLocation().getUri().equals(doc.getUri()))
89+
.flatMap(bean -> bean.getChildren().stream())
90+
.filter(element -> element instanceof EventPublisherIndexElement)
91+
.map(element -> (EventPublisherIndexElement) element)
92+
.filter(eventListener -> isPositionInside(position, eventListener.getLocation()))
93+
.map(eventListener -> eventListener.getEventType())
94+
.findAny();
95+
96+
if (publisherEventType.isPresent()) {
97+
// use the listener event type to look for publishers for that type
98+
String eventType = publisherEventType.get();
99+
100+
List<Location> foundLocations = Arrays.stream(beans)
101+
.flatMap(bean -> bean.getChildren().stream())
102+
.filter(element -> element instanceof EventListenerIndexElement)
103+
.map(element -> (EventListenerIndexElement) element)
104+
.filter(listener -> listener.getEventType().equals(eventType))
105+
.map(listener-> listener.getLocation())
106+
.toList();
107+
108+
if (foundLocations.size() > 0) {
109+
return foundLocations;
110+
}
111+
}
112+
113+
}
114+
115+
} catch (BadLocationException e) {
116+
log.error("", e);
117+
}
118+
return null;
119+
}
120+
121+
private boolean isPositionInside(Position position, Location location) {
122+
boolean afterStart = position.getLine() > location.getRange().getStart().getLine()
123+
|| (position.getLine() == location.getRange().getStart().getLine() && position.getCharacter() >= location.getRange().getStart().getCharacter());
124+
125+
boolean beforeEnd = position.getLine() < location.getRange().getEnd().getLine()
126+
|| (position.getLine() == location.getRange().getEnd().getLine() && position.getCharacter() <= location.getRange().getEnd().getCharacter());
127+
128+
return afterStart && beforeEnd;
129+
}
130+
131+
}

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

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2017, 2024 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
@@ -11,6 +11,8 @@
1111
package org.springframework.ide.vscode.boot.java.handlers;
1212

1313
import java.net.URI;
14+
import java.util.ArrayList;
15+
import java.util.Iterator;
1416
import java.util.List;
1517
import java.util.Map;
1618
import java.util.Optional;
@@ -22,7 +24,6 @@
2224
import org.eclipse.jdt.core.dom.NodeFinder;
2325
import org.eclipse.lsp4j.Location;
2426
import org.eclipse.lsp4j.ReferenceParams;
25-
import org.eclipse.lsp4j.TextDocumentIdentifier;
2627
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
2728
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
2829
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
@@ -39,14 +40,17 @@ public class BootJavaReferencesHandler implements ReferencesHandler {
3940

4041
private final JavaProjectFinder projectFinder;
4142
private final BootJavaLanguageServerComponents server;
42-
private final Map<String, ReferenceProvider> referenceProviders;
43+
private final Map<String, ReferenceProvider> annotationSpecificReferenceProviders;
44+
private final List<ReferenceProvider> unspecificProviders;
4345
private final CompilationUnitCache cuCache;
4446

45-
public BootJavaReferencesHandler(BootJavaLanguageServerComponents server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder, Map<String, ReferenceProvider> specificProviders) {
47+
public BootJavaReferencesHandler(BootJavaLanguageServerComponents server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder,
48+
Map<String, ReferenceProvider> specificProviders, List<ReferenceProvider> unspecificProviders) {
4649
this.server = server;
4750
this.cuCache = cuCache;
4851
this.projectFinder = projectFinder;
49-
this.referenceProviders = specificProviders;
52+
this.annotationSpecificReferenceProviders = specificProviders;
53+
this.unspecificProviders = unspecificProviders;
5054
}
5155

5256
@Override
@@ -62,7 +66,7 @@ public List<? extends Location> handle(CancelChecker cancelToken, ReferenceParam
6266

6367
cancelToken.checkCanceled();
6468

65-
List<? extends Location> referencesResult = provideReferences(cancelToken, doc.getId(), offset);
69+
List<? extends Location> referencesResult = provideReferences(cancelToken, doc, offset);
6670
if (referencesResult != null) {
6771
return referencesResult;
6872
}
@@ -78,20 +82,32 @@ public List<? extends Location> handle(CancelChecker cancelToken, ReferenceParam
7882
return SimpleTextDocumentService.NO_REFERENCES;
7983
}
8084

81-
private List<? extends Location> provideReferences(CancelChecker cancelToken, TextDocumentIdentifier docID, int offset) throws Exception {
82-
Optional<IJavaProject> projectOptional = projectFinder.find(docID);
85+
private List<? extends Location> provideReferences(CancelChecker cancelToken, TextDocument doc, int offset) throws Exception {
86+
Optional<IJavaProject> projectOptional = projectFinder.find(doc.getId());
8387

8488
if (projectOptional.isPresent()) {
8589
IJavaProject project = projectOptional.get();
8690

87-
URI docUri = URI.create(docID.getUri());
91+
URI docUri = URI.create(doc.getUri());
8892

8993
return cuCache.withCompilationUnit(project, docUri, cu -> {
9094
cancelToken.checkCanceled();
9195

9296
ASTNode node = NodeFinder.perform(cu, offset, 0);
9397
if (node != null) {
94-
return provideReferencesForAnnotation(cancelToken, project, node, offset);
98+
List<Location> result = new ArrayList<>();
99+
100+
List<? extends Location> referencesForAnnotation = provideReferencesForAnnotation(cancelToken, project, node, offset);
101+
if (referencesForAnnotation != null) {
102+
result.addAll(referencesForAnnotation);
103+
}
104+
105+
List<? extends Location> otherReferences = provideUnspecificReferences(cancelToken, project, doc, node, offset);
106+
if (otherReferences != null) {
107+
result.addAll(otherReferences);
108+
}
109+
110+
return result.size() > 0 ? result : null;
95111
}
96112
else {
97113
return null;
@@ -102,6 +118,21 @@ private List<? extends Location> provideReferences(CancelChecker cancelToken, Te
102118
return null;
103119
}
104120

121+
private List<? extends Location> provideUnspecificReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
122+
List<Location> result = new ArrayList<>();
123+
124+
for (Iterator<ReferenceProvider> iterator = unspecificProviders.iterator(); iterator.hasNext();) {
125+
ReferenceProvider referenceProvider = iterator.next();
126+
127+
List<? extends Location> references = referenceProvider.provideReferences(cancelToken, project, doc, node, offset);
128+
if (references != null) {
129+
result.addAll(references);
130+
}
131+
}
132+
133+
return result;
134+
}
135+
105136
private List<? extends Location> provideReferencesForAnnotation(CancelChecker cancelToken, IJavaProject project, ASTNode node, int offset) {
106137
Annotation annotation = null;
107138

@@ -119,7 +150,7 @@ private List<? extends Location> provideReferencesForAnnotation(CancelChecker ca
119150
String qualifiedName = type.getQualifiedName();
120151

121152
if (qualifiedName != null) {
122-
ReferenceProvider provider = this.referenceProviders.get(qualifiedName);
153+
ReferenceProvider provider = this.annotationSpecificReferenceProviders.get(qualifiedName);
123154

124155
if (provider != null) {
125156
return provider.provideReferences(cancelToken, project, node, annotation, type, offset);

0 commit comments

Comments
 (0)