GH-1465: take event listener types defined via annotation attribute into account when looking for references

Fixes GH-1465
This commit is contained in:
Martin Lippert
2025-02-04 17:39:26 +01:00
parent 0b6b6dff9c
commit b537496bd0
3 changed files with 57 additions and 41 deletions

View File

@@ -34,7 +34,6 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
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;
@@ -112,7 +111,7 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
Bean beanDefinition = new Bean(beanName, beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, isConfiguration);
// event listener - create child element, if necessary
// type implements event listener - move those already created event index elements under the bean node
List<CachedBean> alreadyCreatedEventListenerChilds = context.getBeans().stream()
.filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri()))
.filter(cachedBean -> cachedBean.getBean() instanceof EventListenerIndexElement)
@@ -123,31 +122,6 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
beanDefinition.addChild(eventListener.getBean());
}
// ITypeBinding inTypeHierarchy = ASTUtils.findInTypeHierarchy(type, doc, beanType, Set.of(Annotations.APPLICATION_LISTENER));
// if (inTypeHierarchy != null) {
//
// MethodDeclaration handleEventMethod = findHandleEventMethod(type);
// if (handleEventMethod != null) {
//
// IMethodBinding methodBinding = handleEventMethod.resolveBinding();
// ITypeBinding[] parameterTypes = methodBinding.getParameterTypes();
// if (parameterTypes != null && parameterTypes.length == 1) {
//
// ITypeBinding eventType = parameterTypes[0];
// String eventTypeFq = eventType.getQualifiedName();
//
// DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, handleEventMethod.getName());
// Location handleMethodLocation = new Location(doc.getUri(), nodeRegion.asRange());
//
// Collection<Annotation> annotationsOnHandleEventMethod = ASTUtils.getAnnotations(handleEventMethod);
// AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc);
//
// EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, beanType.getQualifiedName(), handleEventMethodAnnotations);
// beanDefinition.addChild(eventElement);
// }
// }
// }
// event publisher checks
for (InjectionPoint injectionPoint : injectionPoints) {
if (Annotations.EVENT_PUBLISHER.equals(injectionPoint.getType())) {
@@ -212,7 +186,6 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
@Override
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
// event listener - create child element, if necessary
try {
ITypeBinding typeBinding = typeDeclaration.resolveBinding();
if (typeBinding == null) return;

View File

@@ -10,9 +10,12 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.events;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
@@ -25,6 +28,8 @@ 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.AnnotationAttributeValue;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -55,19 +60,26 @@ public class EventReferenceProvider implements ReferenceProvider {
List<EventListenerIndexElement> listeners = index.getNodesOfType(EventListenerIndexElement.class);
List<EventPublisherIndexElement> publishers = index.getNodesOfType(EventPublisherIndexElement.class);
// when offset is inside an event listener, find the respective event type
Optional<String> listenerEventType = listeners.stream()
// when offset is inside an event listener, look for references from publishers
Optional<EventListenerIndexElement> listenerElement = listeners.stream()
.filter(listener -> listener.getLocation().getUri().equals(doc.getUri()))
.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();
if (listenerElement.isPresent()) {
Set<String> eventTypes = getListenerEventTypes(listenerElement.get());
List<Location> foundLocations = publishers.stream()
.filter(publisher -> publisher.getEventType().equals(eventType) || publisher.getEventTypesFromHierarchy().contains(eventType))
.filter(publisher -> {
if (eventTypes.contains(publisher.getEventType())) return true;
for (String listenerEventType : eventTypes) {
if (publisher.getEventTypesFromHierarchy().contains(listenerEventType)) {
return true;
}
}
return false;
})
.map(publisher -> publisher.getLocation())
.toList();
@@ -76,7 +88,7 @@ public class EventReferenceProvider implements ReferenceProvider {
}
}
// when offset is inside an event publisher, find the respective event type
// when offset is inside an event publisher, look for references from listeners
else {
Optional<EventPublisherIndexElement> publisherElement = publishers.stream()
.filter(publisher -> publisher.getLocation().getUri().equals(doc.getUri()))
@@ -84,12 +96,22 @@ public class EventReferenceProvider implements ReferenceProvider {
.findAny();
if (publisherElement.isPresent()) {
// use the publisher event type to look for listeners for that type
String eventType = publisherElement.get().getEventType();
Set<String> eventTypesFromHierarchy = publisherElement.get().getEventTypesFromHierarchy();
List<Location> foundLocations = listeners.stream()
.filter(listener -> listener.getEventType().equals(eventType) || eventTypesFromHierarchy.contains(listener.getEventType()))
.filter(listener -> {
Set<String> listenerEventTypes = getListenerEventTypes(listener);
for (String listenerEventType : listenerEventTypes) {
if (listenerEventType.equals(eventType)) {
return true;
}
if (eventTypesFromHierarchy.contains(listenerEventType)) {
return true;
}
}
return false;
})
.map(listener -> listener.getLocation())
.toList();
@@ -106,6 +128,29 @@ public class EventReferenceProvider implements ReferenceProvider {
return null;
}
private Set<String> getListenerEventTypes(EventListenerIndexElement eventListenerIndexElement) {
AnnotationMetadata[] annotations = eventListenerIndexElement.getAnnotations();
if (annotations != null && annotations.length > 0) {
for (AnnotationMetadata annotationMetadata : annotations) {
Map<String, AnnotationAttributeValue[]> attributes = annotationMetadata.getAttributes();
if (attributes.containsKey("classes")) {
AnnotationAttributeValue[] annotationAttributeValues = attributes.get("classes");
return Arrays.stream(annotationAttributeValues)
.map(attributeValue -> attributeValue.getName())
.collect(Collectors.toSet());
}
else if (attributes.containsKey("value")) {
AnnotationAttributeValue[] annotationAttributeValues = attributes.get("value");
return Arrays.stream(annotationAttributeValues)
.map(attributeValue -> attributeValue.getName())
.collect(Collectors.toSet());
}
}
}
return Set.of(eventListenerIndexElement.getEventType());
}
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());

View File

@@ -169,11 +169,9 @@ public class EventsReferencesProviderTest {
assertTrue(references.contains(expectedLocation2));
String expectedDefinitionUri3 = directory.toPath().resolve("src/main/java/com/example/events/demo/EventListenerPerInterfaceAndBeanMethod.java").toUri().toString();
Location expectedLocation3 = new Location(expectedDefinitionUri3, new Range(new Position(9, 13), new Position(9, 24)));
Location expectedLocation3 = new Location(expectedDefinitionUri3, new Range(new Position(7, 13), new Position(7, 31)));
assertTrue(references.contains(expectedLocation3));
}
}