Skip to content

Commit 899b737

Browse files
committed
GH-1405: take setter injection into account while indexing injection points
1 parent b5fff4e commit 899b737

File tree

4 files changed

+188
-57
lines changed

4 files changed

+188
-57
lines changed

Diff for: headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java

+99-47
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,63 @@ public static void findSupertypes(ITypeBinding binding, Set<String> supertypesCo
412412

413413
public static InjectionPoint[] findInjectionPoints(MethodDeclaration method, TextDocument doc) throws BadLocationException {
414414
List<InjectionPoint> result = new ArrayList<>();
415+
findInjectionPoints(method, doc, result, false);
416+
417+
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
418+
}
419+
420+
public static InjectionPoint[] findInjectionPoints(TypeDeclaration type, TextDocument doc) throws BadLocationException {
421+
List<InjectionPoint> result = new ArrayList<>();
422+
423+
findInjectionPoints(type.getMethods(), doc, result);
424+
findInjectionPoints(type.getFields(), doc, result);
425+
426+
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
427+
}
428+
429+
private static void findInjectionPoints(MethodDeclaration[] methods, TextDocument doc, List<InjectionPoint> result) throws BadLocationException {
430+
int constructorCount = 0;
431+
432+
// special rule that if there is a single constructor, it doesn't have to have an autowired or inject annotation on it
433+
MethodDeclaration singleConstructor = null;
434+
435+
for (MethodDeclaration method : methods) {
436+
if (method.isConstructor()) {
437+
constructorCount++;
438+
singleConstructor = method;
439+
}
440+
}
441+
442+
if (constructorCount == 1) {
443+
findInjectionPoints(singleConstructor, doc, result, false);
444+
}
445+
446+
// look for all methods with annotations (whether constructors or regular methods)
447+
for (MethodDeclaration method : methods) {
448+
findInjectionPoints(method, doc, result, true);
449+
}
450+
}
451+
452+
public static void findInjectionPoints(MethodDeclaration method, TextDocument doc, List<InjectionPoint> result, boolean checkForAnnotation) throws BadLocationException {
453+
454+
Collection<Annotation> annotationsOnMethod = getAnnotations(method);
455+
456+
if (checkForAnnotation) {
457+
boolean isAutowired = false;
458+
459+
for (Annotation annotation : annotationsOnMethod) {
460+
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
461+
if (Annotations.AUTOWIRED.equals(qualifiedName)
462+
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
463+
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)) {
464+
isAutowired = true;
465+
}
466+
}
467+
468+
if (!isAutowired) {
469+
return;
470+
}
471+
}
415472

416473
List<?> parameters = method.parameters();
417474
for (Object object : parameters) {
@@ -427,70 +484,65 @@ public static InjectionPoint[] findInjectionPoints(MethodDeclaration method, Tex
427484

428485
Location location = new Location(doc.getUri(), range);
429486

430-
AnnotationMetadata[] annotations = getAnnotationsMetadata(getAnnotations(variable), doc);
487+
List<Annotation> allAnnotations = new ArrayList<>();
488+
allAnnotations.addAll(annotationsOnMethod);
489+
allAnnotations.addAll(getAnnotations(variable));
490+
491+
AnnotationMetadata[] annotations = getAnnotationsMetadata(allAnnotations, doc);
431492

432493
result.add(new InjectionPoint(name, type, location, annotations));
433494
}
434495
}
435-
436-
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
437496
}
438-
439-
public static InjectionPoint[] findInjectionPoints(TypeDeclaration type, TextDocument doc) throws BadLocationException {
440-
List<InjectionPoint> result = new ArrayList<>();
441497

442-
MethodDeclaration[] methods = type.getMethods();
443-
for (MethodDeclaration method : methods) {
444-
if (method.isConstructor()) {
445-
result.addAll(Arrays.asList(ASTUtils.findInjectionPoints(method, doc)));
446-
}
498+
private static void findInjectionPoints(FieldDeclaration[] fields, TextDocument doc, List<InjectionPoint> result) throws BadLocationException {
499+
for (FieldDeclaration field : fields) {
500+
findInjectionPoints(field, doc, result);
447501
}
502+
}
448503

449-
FieldDeclaration[] fields = type.getFields();
450-
for (FieldDeclaration field : fields) {
504+
private static void findInjectionPoints(FieldDeclaration field, TextDocument doc, List<InjectionPoint> result) throws BadLocationException {
505+
boolean autowiredField = false;
506+
507+
List<Annotation> fieldAnnotations = new ArrayList<>();
451508

452-
boolean autowiredField = false;
453-
454-
List<Annotation> fieldAnnotations = new ArrayList<>();
455-
456-
List<?> modifiers = field.modifiers();
457-
for (Object modifier : modifiers) {
458-
if (modifier instanceof Annotation) {
459-
Annotation annotation = (Annotation) modifier;
460-
fieldAnnotations.add(annotation);
461-
462-
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
463-
if (Annotations.AUTOWIRED.equals(qualifiedName)
464-
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
465-
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)
466-
|| Annotations.VALUE.equals(qualifiedName)) {
467-
autowiredField = true;
468-
}
509+
List<?> modifiers = field.modifiers();
510+
for (Object modifier : modifiers) {
511+
if (modifier instanceof Annotation) {
512+
Annotation annotation = (Annotation) modifier;
513+
fieldAnnotations.add(annotation);
514+
515+
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
516+
if (Annotations.AUTOWIRED.equals(qualifiedName)
517+
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
518+
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)
519+
|| Annotations.VALUE.equals(qualifiedName)) {
520+
autowiredField = true;
469521
}
470522
}
523+
}
471524

472-
if (autowiredField) {
473-
List<?> fragments = field.fragments();
474-
for (Object fragment : fragments) {
475-
if (fragment instanceof VariableDeclarationFragment) {
476-
VariableDeclarationFragment varFragment = (VariableDeclarationFragment) fragment;
477-
String fieldName = varFragment.getName().toString();
525+
if (!autowiredField) {
526+
return;
527+
}
528+
529+
List<?> fragments = field.fragments();
530+
for (Object fragment : fragments) {
531+
if (fragment instanceof VariableDeclarationFragment) {
532+
VariableDeclarationFragment varFragment = (VariableDeclarationFragment) fragment;
533+
String fieldName = varFragment.getName().toString();
478534

479-
DocumentRegion region = ASTUtils.nodeRegion(doc, varFragment.getName());
480-
Range range = doc.toRange(region);
481-
Location fieldLocation = new Location(doc.getUri(), range);
535+
DocumentRegion region = ASTUtils.nodeRegion(doc, varFragment.getName());
536+
Range range = doc.toRange(region);
537+
Location fieldLocation = new Location(doc.getUri(), range);
482538

483-
String fieldType = field.getType().resolveBinding().getQualifiedName();
539+
String fieldType = field.getType().resolveBinding().getQualifiedName();
484540

485-
AnnotationMetadata[] annotationsMetadata = getAnnotationsMetadata(fieldAnnotations, doc);
486-
487-
result.add(new InjectionPoint(fieldName, fieldType, fieldLocation, annotationsMetadata));
488-
}
489-
}
541+
AnnotationMetadata[] annotationsMetadata = getAnnotationsMetadata(fieldAnnotations, doc);
542+
543+
result.add(new InjectionPoint(fieldName, fieldType, fieldLocation, annotationsMetadata));
490544
}
491545
}
492-
493-
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
494546
}
495547

496548
public static AnnotationMetadata[] getAnnotationsMetadata(Collection<Annotation> annotations, TextDocument doc) {

Diff for: headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexerBeansTest.java

+48
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
3636
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
3737
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
38+
import org.springframework.ide.vscode.boot.java.Annotations;
3839
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
3940
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue;
4041
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
@@ -186,6 +187,53 @@ void testBeanInjectionPointsFromConstructor() {
186187
assertEquals(ip2Location, injectionPoints[1].getLocation());
187188
}
188189

190+
@Test
191+
void testSetterInjectionPointsFromConstructor() {
192+
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "setterInjectionService");
193+
assertEquals(1, beans.length);
194+
195+
String docUri = directory.toPath().resolve("src/main/java/org/test/injections/SetterInjectionService.java").toUri().toString();
196+
197+
InjectionPoint[] injectionPoints = beans[0].getInjectionPoints();
198+
assertEquals(2, injectionPoints.length);
199+
200+
assertEquals("bean1", injectionPoints[0].getName());
201+
assertEquals("org.test.BeanClass1", injectionPoints[0].getType());
202+
assertEquals(new Location(docUri, new Range(new Position(21, 33), new Position(21, 38))), injectionPoints[0].getLocation());
203+
204+
AnnotationMetadata[] point1Annotations = injectionPoints[0].getAnnotations();
205+
assertEquals(2, point1Annotations.length);
206+
207+
assertEquals(Annotations.AUTOWIRED, point1Annotations[0].getAnnotationType());
208+
assertEquals(new Location(docUri, new Range(new Position(19, 1), new Position(19, 11))), point1Annotations[0].getLocation());
209+
assertEquals(0, point1Annotations[0].getAttributes().size());
210+
211+
assertEquals(Annotations.QUALIFIER, point1Annotations[1].getAnnotationType());
212+
assertEquals(new Location(docUri, new Range(new Position(20, 1), new Position(20, 41))), point1Annotations[1].getLocation());
213+
assertEquals(1, point1Annotations[1].getAttributes().size());
214+
assertEquals(1, point1Annotations[1].getAttributes().get("value").length);
215+
assertEquals("setter-injection-qualifier", point1Annotations[1].getAttributes().get("value")[0].getName());
216+
assertEquals(new Location(docUri, new Range(new Position(20, 12), new Position(20, 40))), point1Annotations[1].getAttributes().get("value")[0].getLocation());
217+
218+
assertEquals("bean2", injectionPoints[1].getName());
219+
assertEquals("org.test.BeanClass2", injectionPoints[1].getType());
220+
assertEquals(new Location(docUri, new Range(new Position(26, 83), new Position(26, 88))), injectionPoints[1].getLocation());
221+
222+
AnnotationMetadata[] point2Annotations = injectionPoints[1].getAnnotations();
223+
assertEquals(2, point2Annotations.length);
224+
assertEquals(Annotations.AUTOWIRED, point2Annotations[0].getAnnotationType());
225+
assertEquals(new Location(docUri, new Range(new Position(25, 1), new Position(25, 11))), point2Annotations[0].getLocation());
226+
assertEquals(0, point2Annotations[0].getAttributes().size());
227+
228+
assertEquals(Annotations.QUALIFIER, point2Annotations[1].getAnnotationType());
229+
assertEquals(new Location(docUri, new Range(new Position(26, 22), new Position(26, 71))), point2Annotations[1].getLocation());
230+
assertEquals(1, point2Annotations[1].getAttributes().size());
231+
assertEquals(1, point2Annotations[1].getAttributes().get("value").length);
232+
assertEquals("setter-injection-qualifier-on-param", point2Annotations[1].getAttributes().get("value")[0].getName());
233+
assertEquals(new Location(docUri, new Range(new Position(26, 33), new Position(26, 70))), point2Annotations[1].getAttributes().get("value")[0].getLocation());
234+
235+
}
236+
189237
@Test
190238
void testBeanInjectionPointsFromAutowiredFields() {
191239
Bean[] beans = springIndex.getBeansWithName("test-spring-indexing", "autowiredInjectionService");

Diff for: headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java

+6-10
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
import java.util.concurrent.CompletableFuture;
1919
import java.util.concurrent.TimeUnit;
2020

21+
import org.apache.commons.lang3.ArrayUtils;
2122
import org.eclipse.lsp4j.Location;
2223
import org.eclipse.lsp4j.Position;
2324
import org.eclipse.lsp4j.Range;
2425
import org.eclipse.lsp4j.TextDocumentIdentifier;
2526
import org.junit.jupiter.api.BeforeEach;
26-
import org.junit.jupiter.api.Disabled;
2727
import org.junit.jupiter.api.Test;
2828
import org.junit.jupiter.api.extension.ExtendWith;
2929
import org.springframework.beans.factory.annotation.Autowired;
@@ -58,10 +58,8 @@ public class NamedReferencesProviderTest {
5858
private File directory;
5959
private IJavaProject project;
6060
private Bean bean1;
61-
private Bean bean2;
6261

6362
private String tempJavaDocUri1;
64-
private String tempJavaDocUri2;
6563
private String tempJavaDocUri;
6664

6765
@BeforeEach
@@ -78,12 +76,11 @@ public void setup() throws Exception {
7876

7977
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TestDependsOnClass.java").toUri().toString();
8078
tempJavaDocUri1 = directory.toPath().resolve("src/main/java/org/test/TempClass1.java").toUri().toString();
81-
tempJavaDocUri2 = directory.toPath().resolve("src/main/java/org/test/TempClass2.java").toUri().toString();
8279

8380
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
84-
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
85-
86-
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
81+
82+
Bean[] beans = ArrayUtils.add(springIndex.getBeansOfProject(project.getElementName()), bean1);
83+
springIndex.updateBeans(project.getElementName(), beans);
8784
}
8885

8986
@Test
@@ -111,7 +108,6 @@ public class TestDependsOnClass {
111108
}
112109

113110
@Test
114-
@Disabled // TODO: need to include setter injection in spring index as a first step, then resurrect this test case
115111
public void testNamedRefersToOtherNamedValues() throws Exception {
116112
Editor editor = harness.newEditor(LanguageId.JAVA, """
117113
package org.test;
@@ -124,11 +120,11 @@ public class TestDependsOnClass {
124120

125121
String expectedDefinitionUri1 = directory.toPath().resolve("src/main/java/org/test/jakarta/SimpleMovieLister.java").toUri().toString();
126122
Location expectedLocation1 = new Location(expectedDefinitionUri1,
127-
new Range(new Position(24, 38), new Position(24, 62)));
123+
new Range(new Position(27, 45), new Position(27, 61)));
128124

129125
String expectedDefinitionUri2 = directory.toPath().resolve("src/main/java/org/test/javax/SimpleMovieLister.java").toUri().toString();
130126
Location expectedLocation2 = new Location(expectedDefinitionUri2,
131-
new Range(new Position(24, 38), new Position(24, 62)));
127+
new Range(new Position(27, 45), new Position(27, 61)));
132128

133129
List<? extends Location> references = editor.getReferences();
134130
assertEquals(2, references.size());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.test.injections;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.beans.factory.annotation.Qualifier;
5+
import org.springframework.stereotype.Service;
6+
import org.test.BeanClass1;
7+
import org.test.BeanClass2;
8+
9+
@Service
10+
public class SetterInjectionService {
11+
12+
private BeanClass1 bean1;
13+
private BeanClass2 bean2;
14+
15+
private BeanClass1 somethingElse;
16+
17+
public SetterInjectionService() {
18+
}
19+
20+
@Autowired
21+
@Qualifier("setter-injection-qualifier")
22+
public void setBean1(BeanClass1 bean1) {
23+
this.bean1 = bean1;
24+
}
25+
26+
@Autowired
27+
public void setBean2(@Qualifier("setter-injection-qualifier-on-param") BeanClass2 bean2) {
28+
this.bean2 = bean2;
29+
}
30+
31+
public void setSomethingElse(BeanClass1 somethingElseNotInjected) {
32+
this.somethingElse = somethingElseNotInjected;
33+
}
34+
35+
}

0 commit comments

Comments
 (0)