GH-1405: take setter injection into account while indexing injection points

This commit is contained in:
Martin Lippert
2024-11-13 15:48:12 +01:00
parent b5fff4e5f1
commit 899b7372e4
4 changed files with 194 additions and 63 deletions

View File

@@ -412,6 +412,63 @@ public class ASTUtils {
public static InjectionPoint[] findInjectionPoints(MethodDeclaration method, TextDocument doc) throws BadLocationException {
List<InjectionPoint> result = new ArrayList<>();
findInjectionPoints(method, doc, result, false);
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
}
public static InjectionPoint[] findInjectionPoints(TypeDeclaration type, TextDocument doc) throws BadLocationException {
List<InjectionPoint> result = new ArrayList<>();
findInjectionPoints(type.getMethods(), doc, result);
findInjectionPoints(type.getFields(), doc, result);
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
}
private static void findInjectionPoints(MethodDeclaration[] methods, TextDocument doc, List<InjectionPoint> result) throws BadLocationException {
int constructorCount = 0;
// special rule that if there is a single constructor, it doesn't have to have an autowired or inject annotation on it
MethodDeclaration singleConstructor = null;
for (MethodDeclaration method : methods) {
if (method.isConstructor()) {
constructorCount++;
singleConstructor = method;
}
}
if (constructorCount == 1) {
findInjectionPoints(singleConstructor, doc, result, false);
}
// look for all methods with annotations (whether constructors or regular methods)
for (MethodDeclaration method : methods) {
findInjectionPoints(method, doc, result, true);
}
}
public static void findInjectionPoints(MethodDeclaration method, TextDocument doc, List<InjectionPoint> result, boolean checkForAnnotation) throws BadLocationException {
Collection<Annotation> annotationsOnMethod = getAnnotations(method);
if (checkForAnnotation) {
boolean isAutowired = false;
for (Annotation annotation : annotationsOnMethod) {
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
if (Annotations.AUTOWIRED.equals(qualifiedName)
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)) {
isAutowired = true;
}
}
if (!isAutowired) {
return;
}
}
List<?> parameters = method.parameters();
for (Object object : parameters) {
@@ -427,70 +484,65 @@ public class ASTUtils {
Location location = new Location(doc.getUri(), range);
AnnotationMetadata[] annotations = getAnnotationsMetadata(getAnnotations(variable), doc);
List<Annotation> allAnnotations = new ArrayList<>();
allAnnotations.addAll(annotationsOnMethod);
allAnnotations.addAll(getAnnotations(variable));
AnnotationMetadata[] annotations = getAnnotationsMetadata(allAnnotations, doc);
result.add(new InjectionPoint(name, type, location, annotations));
}
}
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
}
public static InjectionPoint[] findInjectionPoints(TypeDeclaration type, TextDocument doc) throws BadLocationException {
List<InjectionPoint> result = new ArrayList<>();
MethodDeclaration[] methods = type.getMethods();
for (MethodDeclaration method : methods) {
if (method.isConstructor()) {
result.addAll(Arrays.asList(ASTUtils.findInjectionPoints(method, doc)));
private static void findInjectionPoints(FieldDeclaration[] fields, TextDocument doc, List<InjectionPoint> result) throws BadLocationException {
for (FieldDeclaration field : fields) {
findInjectionPoints(field, doc, result);
}
}
private static void findInjectionPoints(FieldDeclaration field, TextDocument doc, List<InjectionPoint> result) throws BadLocationException {
boolean autowiredField = false;
List<Annotation> fieldAnnotations = new ArrayList<>();
List<?> modifiers = field.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
fieldAnnotations.add(annotation);
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
if (Annotations.AUTOWIRED.equals(qualifiedName)
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)
|| Annotations.VALUE.equals(qualifiedName)) {
autowiredField = true;
}
}
}
FieldDeclaration[] fields = type.getFields();
for (FieldDeclaration field : fields) {
boolean autowiredField = false;
List<Annotation> fieldAnnotations = new ArrayList<>();
List<?> modifiers = field.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
fieldAnnotations.add(annotation);
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
if (Annotations.AUTOWIRED.equals(qualifiedName)
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)
|| Annotations.VALUE.equals(qualifiedName)) {
autowiredField = true;
}
}
}
if (autowiredField) {
List<?> fragments = field.fragments();
for (Object fragment : fragments) {
if (fragment instanceof VariableDeclarationFragment) {
VariableDeclarationFragment varFragment = (VariableDeclarationFragment) fragment;
String fieldName = varFragment.getName().toString();
DocumentRegion region = ASTUtils.nodeRegion(doc, varFragment.getName());
Range range = doc.toRange(region);
Location fieldLocation = new Location(doc.getUri(), range);
String fieldType = field.getType().resolveBinding().getQualifiedName();
AnnotationMetadata[] annotationsMetadata = getAnnotationsMetadata(fieldAnnotations, doc);
result.add(new InjectionPoint(fieldName, fieldType, fieldLocation, annotationsMetadata));
}
}
}
if (!autowiredField) {
return;
}
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
List<?> fragments = field.fragments();
for (Object fragment : fragments) {
if (fragment instanceof VariableDeclarationFragment) {
VariableDeclarationFragment varFragment = (VariableDeclarationFragment) fragment;
String fieldName = varFragment.getName().toString();
DocumentRegion region = ASTUtils.nodeRegion(doc, varFragment.getName());
Range range = doc.toRange(region);
Location fieldLocation = new Location(doc.getUri(), range);
String fieldType = field.getType().resolveBinding().getQualifiedName();
AnnotationMetadata[] annotationsMetadata = getAnnotationsMetadata(fieldAnnotations, doc);
result.add(new InjectionPoint(fieldName, fieldType, fieldLocation, annotationsMetadata));
}
}
}
public static AnnotationMetadata[] getAnnotationsMetadata(Collection<Annotation> annotations, TextDocument doc) {

View File

@@ -35,6 +35,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.Annotations;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
@@ -186,6 +187,53 @@ public class SpringMetamodelIndexerBeansTest {
assertEquals(ip2Location, injectionPoints[1].getLocation());
}
@Test
void testSetterInjectionPointsFromConstructor() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "setterInjectionService");
assertEquals(1, beans.length);
String docUri = directory.toPath().resolve("src/main/java/org/test/injections/SetterInjectionService.java").toUri().toString();
InjectionPoint[] injectionPoints = beans[0].getInjectionPoints();
assertEquals(2, injectionPoints.length);
assertEquals("bean1", injectionPoints[0].getName());
assertEquals("org.test.BeanClass1", injectionPoints[0].getType());
assertEquals(new Location(docUri, new Range(new Position(21, 33), new Position(21, 38))), injectionPoints[0].getLocation());
AnnotationMetadata[] point1Annotations = injectionPoints[0].getAnnotations();
assertEquals(2, point1Annotations.length);
assertEquals(Annotations.AUTOWIRED, point1Annotations[0].getAnnotationType());
assertEquals(new Location(docUri, new Range(new Position(19, 1), new Position(19, 11))), point1Annotations[0].getLocation());
assertEquals(0, point1Annotations[0].getAttributes().size());
assertEquals(Annotations.QUALIFIER, point1Annotations[1].getAnnotationType());
assertEquals(new Location(docUri, new Range(new Position(20, 1), new Position(20, 41))), point1Annotations[1].getLocation());
assertEquals(1, point1Annotations[1].getAttributes().size());
assertEquals(1, point1Annotations[1].getAttributes().get("value").length);
assertEquals("setter-injection-qualifier", point1Annotations[1].getAttributes().get("value")[0].getName());
assertEquals(new Location(docUri, new Range(new Position(20, 12), new Position(20, 40))), point1Annotations[1].getAttributes().get("value")[0].getLocation());
assertEquals("bean2", injectionPoints[1].getName());
assertEquals("org.test.BeanClass2", injectionPoints[1].getType());
assertEquals(new Location(docUri, new Range(new Position(26, 83), new Position(26, 88))), injectionPoints[1].getLocation());
AnnotationMetadata[] point2Annotations = injectionPoints[1].getAnnotations();
assertEquals(2, point2Annotations.length);
assertEquals(Annotations.AUTOWIRED, point2Annotations[0].getAnnotationType());
assertEquals(new Location(docUri, new Range(new Position(25, 1), new Position(25, 11))), point2Annotations[0].getLocation());
assertEquals(0, point2Annotations[0].getAttributes().size());
assertEquals(Annotations.QUALIFIER, point2Annotations[1].getAnnotationType());
assertEquals(new Location(docUri, new Range(new Position(26, 22), new Position(26, 71))), point2Annotations[1].getLocation());
assertEquals(1, point2Annotations[1].getAttributes().size());
assertEquals(1, point2Annotations[1].getAttributes().get("value").length);
assertEquals("setter-injection-qualifier-on-param", point2Annotations[1].getAttributes().get("value")[0].getName());
assertEquals(new Location(docUri, new Range(new Position(26, 33), new Position(26, 70))), point2Annotations[1].getAttributes().get("value")[0].getLocation());
}
@Test
void testBeanInjectionPointsFromAutowiredFields() {
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "autowiredInjectionService");

View File

@@ -18,12 +18,12 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.ArrayUtils;
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.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,10 +58,8 @@ public class NamedReferencesProviderTest {
private File directory;
private IJavaProject project;
private Bean bean1;
private Bean bean2;
private String tempJavaDocUri1;
private String tempJavaDocUri2;
private String tempJavaDocUri;
@BeforeEach
@@ -78,12 +76,11 @@ public class NamedReferencesProviderTest {
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TestDependsOnClass.java").toUri().toString();
tempJavaDocUri1 = directory.toPath().resolve("src/main/java/org/test/TempClass1.java").toUri().toString();
tempJavaDocUri2 = directory.toPath().resolve("src/main/java/org/test/TempClass2.java").toUri().toString();
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
Bean[] beans = ArrayUtils.add(springIndex.getBeansOfProject(project.getElementName()), bean1);
springIndex.updateBeans(project.getElementName(), beans);
}
@Test
@@ -111,7 +108,6 @@ public class NamedReferencesProviderTest {
}
@Test
@Disabled // TODO: need to include setter injection in spring index as a first step, then resurrect this test case
public void testNamedRefersToOtherNamedValues() throws Exception {
Editor editor = harness.newEditor(LanguageId.JAVA, """
package org.test;
@@ -124,11 +120,11 @@ public class NamedReferencesProviderTest {
String expectedDefinitionUri1 = directory.toPath().resolve("src/main/java/org/test/jakarta/SimpleMovieLister.java").toUri().toString();
Location expectedLocation1 = new Location(expectedDefinitionUri1,
new Range(new Position(24, 38), new Position(24, 62)));
new Range(new Position(27, 45), new Position(27, 61)));
String expectedDefinitionUri2 = directory.toPath().resolve("src/main/java/org/test/javax/SimpleMovieLister.java").toUri().toString();
Location expectedLocation2 = new Location(expectedDefinitionUri2,
new Range(new Position(24, 38), new Position(24, 62)));
new Range(new Position(27, 45), new Position(27, 61)));
List<? extends Location> references = editor.getReferences();
assertEquals(2, references.size());

View File

@@ -0,0 +1,35 @@
package org.test.injections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.test.BeanClass1;
import org.test.BeanClass2;
@Service
public class SetterInjectionService {
private BeanClass1 bean1;
private BeanClass2 bean2;
private BeanClass1 somethingElse;
public SetterInjectionService() {
}
@Autowired
@Qualifier("setter-injection-qualifier")
public void setBean1(BeanClass1 bean1) {
this.bean1 = bean1;
}
@Autowired
public void setBean2(@Qualifier("setter-injection-qualifier-on-param") BeanClass2 bean2) {
this.bean2 = bean2;
}
public void setSomethingElse(BeanClass1 somethingElseNotInjected) {
this.somethingElse = somethingElseNotInjected;
}
}