Skip to content

Commit

Permalink
GH-1348: find references from listeners to publishers and vice versa …
Browse files Browse the repository at this point in the history
…now works

Fixes GH-1348
  • Loading branch information
martinlippert committed Jan 31, 2025
1 parent 2c8a52e commit 8234bab
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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;
Expand All @@ -33,6 +34,7 @@
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;
Expand Down Expand Up @@ -316,16 +318,19 @@ protected BootJavaHoverProvider createHoverHandler(JavaProjectFinder javaProject
protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder,
SpringMetamodelIndex index, SpringSymbolIndex symbolIndex, CompilationUnitCache cuCache) {

Map<String, ReferenceProvider> providers = 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));
Map<String, ReferenceProvider> specificProviders = new HashMap<>();

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<ReferenceProvider> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -28,6 +28,7 @@
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
Expand Down Expand Up @@ -69,6 +70,11 @@ else if (node instanceof StringLiteral && node.getParent() instanceof MemberValu
return null;
}

@Override
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
return null;
}

private List<? extends Location> provideReferences(IJavaProject project, String value) {
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -28,6 +28,7 @@
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
Expand Down Expand Up @@ -73,6 +74,11 @@ else if (node instanceof StringLiteral && node.getParent() instanceof ArrayIniti
return null;
}

@Override
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
return null;
}

private List<? extends Location> provideReferences(IJavaProject project, String value) {
Bean[] beans = this.springIndex.getBeans();

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -27,6 +27,7 @@
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
Expand Down Expand Up @@ -66,6 +67,11 @@ else if (node instanceof StringLiteral && node.getParent() instanceof MemberValu
return null;
}

@Override
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
return null;
}

private List<? extends Location> provideReferences(IJavaProject project, String value) {
Bean[] beans = this.springIndex.getBeans();

Expand Down
Original file line number Diff line number Diff line change
@@ -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<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node,
Annotation annotation, ITypeBinding type, int offset) {
return null;
}

@Override
public List<? extends Location> 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<String> 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<Location> 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<String> 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<Location> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -22,7 +24,6 @@
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;
Expand All @@ -39,14 +40,17 @@ public class BootJavaReferencesHandler implements ReferencesHandler {

private final JavaProjectFinder projectFinder;
private final BootJavaLanguageServerComponents server;
private final Map<String, ReferenceProvider> referenceProviders;
private final Map<String, ReferenceProvider> annotationSpecificReferenceProviders;
private final List<ReferenceProvider> unspecificProviders;
private final CompilationUnitCache cuCache;

public BootJavaReferencesHandler(BootJavaLanguageServerComponents server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder, Map<String, ReferenceProvider> specificProviders) {
public BootJavaReferencesHandler(BootJavaLanguageServerComponents server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder,
Map<String, ReferenceProvider> specificProviders, List<ReferenceProvider> unspecificProviders) {
this.server = server;
this.cuCache = cuCache;
this.projectFinder = projectFinder;
this.referenceProviders = specificProviders;
this.annotationSpecificReferenceProviders = specificProviders;
this.unspecificProviders = unspecificProviders;
}

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

cancelToken.checkCanceled();

List<? extends Location> referencesResult = provideReferences(cancelToken, doc.getId(), offset);
List<? extends Location> referencesResult = provideReferences(cancelToken, doc, offset);
if (referencesResult != null) {
return referencesResult;
}
Expand All @@ -78,20 +82,32 @@ public List<? extends Location> handle(CancelChecker cancelToken, ReferenceParam
return SimpleTextDocumentService.NO_REFERENCES;
}

private List<? extends Location> provideReferences(CancelChecker cancelToken, TextDocumentIdentifier docID, int offset) throws Exception {
Optional<IJavaProject> projectOptional = projectFinder.find(docID);
private List<? extends Location> provideReferences(CancelChecker cancelToken, TextDocument doc, int offset) throws Exception {
Optional<IJavaProject> 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<Location> result = new ArrayList<>();

List<? extends Location> referencesForAnnotation = provideReferencesForAnnotation(cancelToken, project, node, offset);
if (referencesForAnnotation != null) {
result.addAll(referencesForAnnotation);
}

List<? extends Location> otherReferences = provideUnspecificReferences(cancelToken, project, doc, node, offset);
if (otherReferences != null) {
result.addAll(otherReferences);
}

return result.size() > 0 ? result : null;
}
else {
return null;
Expand All @@ -102,6 +118,21 @@ private List<? extends Location> provideReferences(CancelChecker cancelToken, Te
return null;
}

private List<? extends Location> provideUnspecificReferences(CancelChecker cancelToken, IJavaProject project, TextDocument doc, ASTNode node, int offset) {
List<Location> result = new ArrayList<>();

for (Iterator<ReferenceProvider> iterator = unspecificProviders.iterator(); iterator.hasNext();) {
ReferenceProvider referenceProvider = iterator.next();

List<? extends Location> references = referenceProvider.provideReferences(cancelToken, project, doc, node, offset);
if (references != null) {
result.addAll(references);
}
}

return result;
}

private List<? extends Location> provideReferencesForAnnotation(CancelChecker cancelToken, IJavaProject project, ASTNode node, int offset) {
Annotation annotation = null;

Expand All @@ -119,7 +150,7 @@ private List<? extends Location> provideReferencesForAnnotation(CancelChecker ca
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);
Expand Down
Loading

0 comments on commit 8234bab

Please sign in to comment.