Skip to content

Commit 025382b

Browse files
committed
GH-1431: replaced scan pass logic with exception handling and removed AbstractSymbolProvider
GH-1431: changed indexing of components to index embedded components like bean methods, request mappings, etc. GH-1431: added new tests for index structure Signed-off-by: Martin Lippert <[email protected]>
1 parent 0f081b9 commit 025382b

File tree

49 files changed

+1834
-871
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1834
-871
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2017, 2025 Pivotal, Inc.
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+
* Pivotal, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.beans;
12+
13+
import java.util.Collection;
14+
import java.util.HashSet;
15+
import java.util.List;
16+
import java.util.Set;
17+
18+
import org.eclipse.jdt.core.dom.ASTNode;
19+
import org.eclipse.jdt.core.dom.Annotation;
20+
import org.eclipse.jdt.core.dom.IAnnotationBinding;
21+
import org.eclipse.jdt.core.dom.ITypeBinding;
22+
import org.eclipse.jdt.core.dom.MethodDeclaration;
23+
import org.eclipse.jdt.core.dom.Modifier;
24+
import org.eclipse.jdt.core.dom.ParameterizedType;
25+
import org.eclipse.jdt.core.dom.Type;
26+
import org.eclipse.lsp4j.Location;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.springframework.ide.vscode.boot.java.Annotations;
30+
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
31+
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider;
32+
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
33+
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
34+
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
35+
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
36+
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
37+
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
38+
import org.springframework.ide.vscode.commons.util.BadLocationException;
39+
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
40+
import org.springframework.ide.vscode.commons.util.text.TextDocument;
41+
42+
import reactor.util.function.Tuple2;
43+
44+
/**
45+
* @author Martin Lippert
46+
* @author Kris De Volder
47+
*/
48+
public class BeansIndexer {
49+
50+
private static final Logger log = LoggerFactory.getLogger(BeansIndexer.class);
51+
52+
public static void indexBeanMethod(Bean configBean, Annotation node, SpringIndexerJavaContext context, TextDocument doc) {
53+
if (node == null) return;
54+
55+
ASTNode parent = node.getParent();
56+
if (parent == null || !(parent instanceof MethodDeclaration)) return;
57+
58+
MethodDeclaration method = (MethodDeclaration) parent;
59+
if (isMethodAbstract(method)) return;
60+
61+
boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method);
62+
if (isWebfluxRouter) {
63+
if (!context.isFullAst()) {
64+
throw new RequiredCompleteAstException();
65+
}
66+
}
67+
68+
boolean isFunction = isFunctionBean(method);
69+
ITypeBinding beanType = getBeanType(method);
70+
String markerString = getAnnotations(method);
71+
72+
for (Tuple2<String, DocumentRegion> nameAndRegion : BeanUtils.getBeanNamesFromBeanAnnotationWithRegions(node, doc)) {
73+
try {
74+
Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()));
75+
76+
String beanLabel = beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString);
77+
78+
InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc);
79+
80+
Set<String> supertypes = new HashSet<>();
81+
ASTUtils.findSupertypes(beanType, supertypes);
82+
83+
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
84+
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc);
85+
86+
Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, beanLabel);
87+
88+
if (isWebfluxRouter) {
89+
WebfluxRouterSymbolProvider.createWebfluxElements(beanDefinition, method, context, doc);
90+
}
91+
92+
configBean.addChild(beanDefinition);
93+
94+
} catch (BadLocationException e) {
95+
log.error("", e);
96+
}
97+
}
98+
}
99+
100+
public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
101+
StringBuilder symbolLabel = new StringBuilder();
102+
symbolLabel.append('@');
103+
symbolLabel.append(isFunctionBean ? '>' : '+');
104+
symbolLabel.append(' ');
105+
symbolLabel.append('\'');
106+
symbolLabel.append(beanName);
107+
symbolLabel.append('\'');
108+
109+
markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " ";
110+
symbolLabel.append(markerString);
111+
112+
symbolLabel.append(beanType);
113+
return symbolLabel.toString();
114+
}
115+
116+
public static ITypeBinding getBeanType(MethodDeclaration method) {
117+
return method.getReturnType2().resolveBinding();
118+
}
119+
120+
public static boolean isFunctionBean(MethodDeclaration method) {
121+
String returnType = null;
122+
123+
if (method.getReturnType2().isParameterizedType()) {
124+
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
125+
Type type = paramType.getType();
126+
ITypeBinding typeBinding = type.resolveBinding();
127+
returnType = typeBinding.getBinaryName();
128+
}
129+
else {
130+
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
131+
}
132+
133+
return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType)
134+
|| FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType);
135+
}
136+
137+
public static String getAnnotations(MethodDeclaration method) {
138+
StringBuilder result = new StringBuilder();
139+
140+
List<?> modifiers = method.modifiers();
141+
for (Object modifier : modifiers) {
142+
if (modifier instanceof Annotation) {
143+
Annotation annotation = (Annotation) modifier;
144+
IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding();
145+
String type = annotationBinding.getAnnotationType().getBinaryName();
146+
147+
if (type != null && !Annotations.BEAN.equals(type)) {
148+
result.append(' ');
149+
result.append(annotation.toString());
150+
}
151+
}
152+
}
153+
return result.toString();
154+
}
155+
156+
public static boolean isMethodAbstract(MethodDeclaration method) {
157+
List<?> modifiers = method.modifiers();
158+
for (Object modifier : modifiers) {
159+
if (modifier instanceof Modifier && ((Modifier) modifier).isAbstract()) {
160+
return true;
161+
}
162+
}
163+
return false;
164+
}
165+
166+
}

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

Lines changed: 12 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,29 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.beans;
1212

13-
import java.util.ArrayList;
1413
import java.util.Collection;
1514
import java.util.HashSet;
16-
import java.util.List;
1715
import java.util.Set;
1816

1917
import org.eclipse.jdt.core.dom.ASTNode;
2018
import org.eclipse.jdt.core.dom.Annotation;
21-
import org.eclipse.jdt.core.dom.Block;
22-
import org.eclipse.jdt.core.dom.IAnnotationBinding;
2319
import org.eclipse.jdt.core.dom.ITypeBinding;
2420
import org.eclipse.jdt.core.dom.MethodDeclaration;
25-
import org.eclipse.jdt.core.dom.Modifier;
26-
import org.eclipse.jdt.core.dom.ParameterizedType;
27-
import org.eclipse.jdt.core.dom.Type;
2821
import org.eclipse.jdt.core.dom.TypeDeclaration;
2922
import org.eclipse.lsp4j.Location;
3023
import org.eclipse.lsp4j.SymbolKind;
3124
import org.eclipse.lsp4j.WorkspaceSymbol;
3225
import org.eclipse.lsp4j.jsonrpc.messages.Either;
3326
import org.slf4j.Logger;
3427
import org.slf4j.LoggerFactory;
35-
import org.springframework.ide.vscode.boot.java.Annotations;
36-
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
37-
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider;
28+
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
3829
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
3930
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
4031
import org.springframework.ide.vscode.boot.java.utils.FunctionUtils;
41-
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS;
4232
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
4333
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
4434
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
4535
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
46-
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
4736
import org.springframework.ide.vscode.commons.util.BadLocationException;
4837
import org.springframework.ide.vscode.commons.util.text.DocumentRegion;
4938
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -54,7 +43,7 @@
5443
* @author Martin Lippert
5544
* @author Kris De Volder
5645
*/
57-
public class BeansSymbolProvider extends AbstractSymbolProvider {
46+
public class BeansSymbolProvider implements SymbolProvider {
5847

5948
private static final Logger log = LoggerFactory.getLogger(BeansSymbolProvider.class);
6049

@@ -66,91 +55,37 @@ public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITy
6655
if (parent == null || !(parent instanceof MethodDeclaration)) return;
6756

6857
MethodDeclaration method = (MethodDeclaration) parent;
69-
if (isMethodAbstract(method)) return;
58+
if (BeansIndexer.isMethodAbstract(method)) return;
7059

71-
List<SpringIndexElement> childElements = new ArrayList<>();
72-
73-
boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method);
74-
75-
// for webflux details, we need full method body ASTs
76-
if (isWebfluxRouter) {
77-
Block methodBody = method.getBody();
78-
if ((methodBody == null || methodBody.statements() == null || methodBody.statements().size() == 0)
79-
&& SCAN_PASS.ONE.equals(context.getPass())) {
80-
context.getNextPassFiles().add(context.getFile());
81-
return;
82-
}
83-
else {
84-
WebfluxRouterSymbolProvider.createWebfluxElements(method, context, doc, childElements);
85-
}
86-
} else if (!isWebfluxRouter && SCAN_PASS.TWO.equals(context.getPass())) {
87-
return;
88-
}
89-
90-
boolean isFunction = isFunctionBean(method);
60+
boolean isFunction = BeansIndexer.isFunctionBean(method);
9161

92-
ITypeBinding beanType = getBeanType(method);
93-
String markerString = getAnnotations(method);
62+
ITypeBinding beanType = BeansIndexer.getBeanType(method);
63+
String markerString = BeansIndexer.getAnnotations(method);
9464

95-
// lookup parent config
96-
SpringIndexElement configParent = findNearestConfigBean(context.getBeans(), doc.getUri());
97-
9865
for (Tuple2<String, DocumentRegion> nameAndRegion : BeanUtils.getBeanNamesFromBeanAnnotationWithRegions(node, doc)) {
9966
try {
10067
Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()));
10168

10269
WorkspaceSymbol symbol = new WorkspaceSymbol(
103-
beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString),
70+
BeansIndexer.beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString),
10471
SymbolKind.Interface,
10572
Either.forLeft(location)
10673
);
10774

108-
InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc);
109-
110-
Set<String> supertypes = new HashSet<>();
111-
ASTUtils.findSupertypes(beanType, supertypes);
112-
113-
Collection<Annotation> annotationsOnMethod = ASTUtils.getAnnotations(method);
114-
AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc);
115-
116-
Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, symbol.getName());
117-
if (childElements.size() > 0) {
118-
for (SpringIndexElement springIndexElement : childElements) {
119-
beanDefinition.addChild(springIndexElement);
120-
}
121-
}
122-
12375
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
124-
125-
if (configParent != null) {
126-
configParent.addChild(beanDefinition);
127-
}
128-
else {
129-
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
130-
}
13176

13277
} catch (BadLocationException e) {
13378
log.error("", e);
13479
}
13580
}
13681
}
13782

138-
private SpringIndexElement findNearestConfigBean(List<CachedBean> beans, String docURI) {
139-
int i = beans.size() - 1;
140-
141-
while (i >= 0 && beans.get(i).getDocURI().equals(docURI)) {
142-
if (beans.get(i).getBean() instanceof Bean bean && bean.isConfiguration() && docURI.equals(docURI)) {
143-
return beans.get(i).getBean();
144-
}
145-
i--;
146-
}
147-
148-
return null;
83+
@Override
84+
public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
85+
indexFunctionBeans(typeDeclaration, context, doc);
14986
}
15087

151-
@Override
152-
protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
153-
// this checks function beans that are defined as implementations of Function interfaces
88+
private void indexFunctionBeans(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
15489
ITypeBinding functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc);
15590
if (functionBean != null) {
15691
try {
@@ -159,7 +94,7 @@ protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJav
15994
Location beanLocation = new Location(doc.getUri(), doc.toRange(ASTUtils.nodeRegion(doc, typeDeclaration.getName())));
16095

16196
WorkspaceSymbol symbol = new WorkspaceSymbol(
162-
beanLabel(true, beanName, beanType.getName(), null),
97+
BeansIndexer.beanLabel(true, beanName, beanType.getName(), null),
16398
SymbolKind.Interface,
16499
Either.forLeft(beanLocation));
165100

@@ -183,70 +118,4 @@ protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJav
183118
}
184119
}
185120

186-
public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) {
187-
StringBuilder symbolLabel = new StringBuilder();
188-
symbolLabel.append('@');
189-
symbolLabel.append(isFunctionBean ? '>' : '+');
190-
symbolLabel.append(' ');
191-
symbolLabel.append('\'');
192-
symbolLabel.append(beanName);
193-
symbolLabel.append('\'');
194-
195-
markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " ";
196-
symbolLabel.append(markerString);
197-
198-
symbolLabel.append(beanType);
199-
return symbolLabel.toString();
200-
}
201-
202-
protected ITypeBinding getBeanType(MethodDeclaration method) {
203-
return method.getReturnType2().resolveBinding();
204-
}
205-
206-
private boolean isFunctionBean(MethodDeclaration method) {
207-
String returnType = null;
208-
209-
if (method.getReturnType2().isParameterizedType()) {
210-
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
211-
Type type = paramType.getType();
212-
ITypeBinding typeBinding = type.resolveBinding();
213-
returnType = typeBinding.getBinaryName();
214-
}
215-
else {
216-
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
217-
}
218-
219-
return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType)
220-
|| FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType);
221-
}
222-
223-
private String getAnnotations(MethodDeclaration method) {
224-
StringBuilder result = new StringBuilder();
225-
226-
List<?> modifiers = method.modifiers();
227-
for (Object modifier : modifiers) {
228-
if (modifier instanceof Annotation) {
229-
Annotation annotation = (Annotation) modifier;
230-
IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding();
231-
String type = annotationBinding.getAnnotationType().getBinaryName();
232-
233-
if (type != null && !Annotations.BEAN.equals(type)) {
234-
result.append(' ');
235-
result.append(annotation.toString());
236-
}
237-
}
238-
}
239-
return result.toString();
240-
}
241-
242-
private boolean isMethodAbstract(MethodDeclaration method) {
243-
List<?> modifiers = method.modifiers();
244-
for (Object modifier : modifiers) {
245-
if (modifier instanceof Modifier && ((Modifier) modifier).isAbstract()) {
246-
return true;
247-
}
248-
}
249-
return false;
250-
}
251-
252121
}

0 commit comments

Comments
 (0)