Skip to content

Commit 44e4974

Browse files
committed
GH-1041: reconciler that finds non-registered aot processor beans updated to use spring index instead of symbols
1 parent 02e3ede commit 44e4974

File tree

4 files changed

+124
-144
lines changed

4 files changed

+124
-144
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ public class JdtConfig {
120120
return new BeanPostProcessingIgnoreInAotReconciler(server.getQuickfixRegistry());
121121
}
122122

123-
@Bean NotRegisteredBeansReconciler notRegisteredBeansReconciler(SimpleLanguageServer server) {
124-
return new NotRegisteredBeansReconciler(server.getQuickfixRegistry());
123+
@Bean NotRegisteredBeansReconciler notRegisteredBeansReconciler(SimpleLanguageServer server, SpringMetamodelIndex springIndex) {
124+
return new NotRegisteredBeansReconciler(server.getQuickfixRegistry(), springIndex);
125125
}
126126

127127
@Bean EntityIdForRepoReconciler entityIdForRepoReconciler(SimpleLanguageServer server) {

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,18 @@ public Bean[] getBeansWithName(String project, String name) {
102102
Bean[] allBeans = this.beansPerProject.get(project);
103103

104104
if (allBeans != null) {
105-
return Arrays.stream(allBeans).filter(bean -> bean.getName().equals(name)).collect(Collectors.toList()).toArray(new Bean[0]);
105+
return Arrays.stream(allBeans).filter(bean -> bean.getName().equals(name)).toArray(Bean[]::new);
106+
}
107+
else {
108+
return null;
109+
}
110+
}
111+
112+
public Bean[] getBeansWithType(String project, String type) {
113+
Bean[] allBeans = this.beansPerProject.get(project);
114+
115+
if (allBeans != null) {
116+
return Arrays.stream(allBeans).filter(bean -> bean.getType().equals(type)).toArray(Bean[]::new);
106117
}
107118
else {
108119
return null;

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

Lines changed: 72 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2204 VMware, Inc.
2+
* Copyright (c) 2023, 2024 VMware, Inc.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -27,23 +27,16 @@
2727
import org.eclipse.jdt.core.dom.ITypeBinding;
2828
import org.eclipse.jdt.core.dom.Modifier;
2929
import org.eclipse.jdt.core.dom.TypeDeclaration;
30-
import org.eclipse.lsp4j.WorkspaceSymbol;
31-
import org.springframework.beans.BeansException;
32-
import org.springframework.context.ApplicationContext;
33-
import org.springframework.context.ApplicationContextAware;
34-
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
30+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
3531
import org.springframework.ide.vscode.boot.java.Annotations;
3632
import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType;
37-
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolAddOnInformation;
38-
import org.springframework.ide.vscode.boot.java.beans.ConfigBeanSymbolAddOnInformation;
39-
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
40-
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
4133
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
4234
import org.springframework.ide.vscode.commons.java.IJavaProject;
4335
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
4436
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
4537
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
4638
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
39+
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
4740
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
4841
import org.springframework.ide.vscode.commons.rewrite.java.DefineMethod;
4942
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
@@ -52,19 +45,19 @@
5245
import com.google.common.collect.ImmutableList.Builder;
5346
import com.google.common.collect.ImmutableSet;
5447

55-
public class NotRegisteredBeansReconciler implements JdtAstReconciler, ApplicationContextAware {
48+
public class NotRegisteredBeansReconciler implements JdtAstReconciler {
5649

5750
private static final List<String> AOT_BEANS = List.of(
5851
"org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor",
5952
"org.springframework.beans.factory.aot.BeanRegistrationAotProcessor"
6053
);
6154

62-
private ApplicationContext applicationContext;
63-
6455
private QuickfixRegistry registry;
56+
private SpringMetamodelIndex springIndex;
6557

66-
public NotRegisteredBeansReconciler(QuickfixRegistry registry) {
67-
this.registry = registry;
58+
public NotRegisteredBeansReconciler(QuickfixRegistry registry, SpringMetamodelIndex springIndex) {
59+
this.registry = registry;
60+
this.springIndex = springIndex;
6861
}
6962

7063
@Override
@@ -77,11 +70,6 @@ public ProblemType getProblemType() {
7770
return SpringAotJavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT;
7871
}
7972

80-
@Override
81-
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
82-
this.applicationContext = applicationContext;
83-
}
84-
8573
@Override
8674
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
8775

@@ -90,79 +78,81 @@ public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUni
9078
@Override
9179
public boolean visit(TypeDeclaration node) {
9280
if (!node.isInterface() && !Modifier.isAbstract(node.getModifiers())) {
81+
9382
ITypeBinding type = node.resolveBinding();
9483
if (type != null && ReconcileUtils.implementsAnyType(AOT_BEANS, type)) {
95-
String beanClassName =type.getQualifiedName();
96-
SpringSymbolIndex index = applicationContext.getBean(SpringSymbolIndex.class);
97-
List<WorkspaceSymbol> beanSymbols = index.getSymbols(data -> {
98-
SymbolAddOnInformation[] additionalInformation = data.getAdditionalInformation();
99-
if (additionalInformation != null) {
100-
for (SymbolAddOnInformation info : additionalInformation) {
101-
if (info instanceof BeansSymbolAddOnInformation) {
102-
BeansSymbolAddOnInformation info2 = (BeansSymbolAddOnInformation) info;
103-
return beanClassName.equals(info2.getBeanType());
104-
}
105-
}
106-
}
107-
return false;
108-
}).limit(1).collect(Collectors.toList());
84+
String beanClassName = type.getQualifiedName();
10985

110-
if (beanSymbols.isEmpty()) {
111-
Builder<FixDescriptor> fixListBuilder = ImmutableList.builder();
112-
for (EnhancedSymbolInformation s : index.getEnhancedSymbols(project)) {
113-
if (s.getAdditionalInformation() != null) {
114-
ConfigBeanSymbolAddOnInformation configInfo = Arrays.stream(s.getAdditionalInformation()).filter(ConfigBeanSymbolAddOnInformation.class::isInstance).map(ConfigBeanSymbolAddOnInformation.class::cast).findFirst().orElse(null);
115-
if (configInfo != null) {
116-
for (IMethodBinding constructor : type.getDeclaredMethods()) {
117-
if (constructor.isConstructor()) {
118-
String constructorParamsSignature = "(" + Arrays.stream(constructor.getParameterTypes()).map(pt -> typePattern(pt)).collect(Collectors.joining(",")) + ")";
119-
String beanMethodName = "get" + type.getName();
120-
String pattern = beanMethodName + constructorParamsSignature;
121-
String contructorParamsLabel = "(" + Arrays.stream(constructor.getParameterTypes()).map(NotRegisteredBeansReconciler::typeStr).collect(Collectors.joining(", ")) + ")";
122-
123-
Builder<String> paramBuilder = ImmutableList.builder();
124-
for (int i = 0; i < constructor.getParameterNames().length && i < constructor.getParameterTypes().length; i++) {
125-
ITypeBinding paramType = constructor.getParameterTypes()[i];
126-
String paramName = constructor.getParameterNames()[i];
127-
paramBuilder.add(typeStr(paramType) + ' ' + paramName);
128-
}
129-
String paramsStr = String.join(", ", paramBuilder.build().toArray(String[]::new));
130-
131-
final Set<String> allFqTypes = new HashSet<>();
132-
allFqTypes.add(Annotations.BEAN);
133-
allFqTypes.addAll(allFQTypes(constructor));
134-
fixListBuilder.add(new FixDescriptor(DefineMethod.class.getName(), List.of(s.getSymbol().getLocation().getLeft().getUri()), "Define bean in config '" + configInfo.getBeanID() + "' with constructor " + contructorParamsLabel)
135-
.withRecipeScope(RecipeScope.FILE)
136-
.withParameters(Map.of(
137-
"targetFqName", configInfo.getBeanType(),
138-
"signature", pattern,
139-
"template", "@Bean\n"
140-
+ type.getName() + " " + beanMethodName + "(" + paramsStr + ") {\n"
141-
+ "return new " + type.getName() + "(" + Arrays.stream(constructor.getParameterNames()).collect(Collectors.joining(", ")) + ");\n"
142-
+ "}\n",
143-
"imports", allFqTypes.toArray(String[]::new),
144-
"typeStubs", new String[0]/*new String[] { source.printAll() }*/,
145-
"classpath", IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath().toString()).toArray(String[]::new)
146-
147-
))
148-
);
149-
}
150-
}
151-
}
152-
}
153-
}
154-
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), getProblemType().getLabel(), node.getName().getStartPosition(), node.getName().getLength());
155-
ReconcileUtils.setRewriteFixes(registry, problem, fixListBuilder.build());
156-
problemCollector.accept(problem);
86+
Bean[] registeredBeans = springIndex.getBeansWithType(project.getElementName(), beanClassName);
87+
88+
if (registeredBeans == null || registeredBeans.length == 0) {
89+
createProblemAndQuickFixes(project, problemCollector, node, type);
15790
}
15891
}
15992
}
16093
return super.visit(node);
16194
}
162-
16395
};
16496
}
16597

98+
protected void createProblemAndQuickFixes(IJavaProject project, IProblemCollector problemCollector, TypeDeclaration node, ITypeBinding type) {
99+
Builder<FixDescriptor> fixListBuilder = ImmutableList.builder();
100+
101+
Bean[] configBeans = getConfigurationBeans(project);
102+
103+
for (Bean configBean : configBeans) {
104+
for (IMethodBinding constructor : type.getDeclaredMethods()) {
105+
if (constructor.isConstructor()) {
106+
String constructorParamsSignature = "(" + Arrays.stream(constructor.getParameterTypes()).map(pt -> typePattern(pt)).collect(Collectors.joining(",")) + ")";
107+
String beanMethodName = "get" + type.getName();
108+
String pattern = beanMethodName + constructorParamsSignature;
109+
String contructorParamsLabel = "(" + Arrays.stream(constructor.getParameterTypes()).map(NotRegisteredBeansReconciler::typeStr).collect(Collectors.joining(", ")) + ")";
110+
111+
Builder<String> paramBuilder = ImmutableList.builder();
112+
for (int i = 0; i < constructor.getParameterNames().length && i < constructor.getParameterTypes().length; i++) {
113+
ITypeBinding paramType = constructor.getParameterTypes()[i];
114+
String paramName = constructor.getParameterNames()[i];
115+
paramBuilder.add(typeStr(paramType) + ' ' + paramName);
116+
}
117+
String paramsStr = String.join(", ", paramBuilder.build().toArray(String[]::new));
118+
119+
final Set<String> allFqTypes = new HashSet<>();
120+
allFqTypes.add(Annotations.BEAN);
121+
allFqTypes.addAll(allFQTypes(constructor));
122+
fixListBuilder.add(new FixDescriptor(DefineMethod.class.getName(), List.of(configBean.getLocation().getUri()), "Define bean in config '" + configBean.getName() + "' with constructor " + contructorParamsLabel)
123+
.withRecipeScope(RecipeScope.FILE)
124+
.withParameters(Map.of(
125+
"targetFqName", configBean.getType(),
126+
"signature", pattern,
127+
"template", "@Bean\n"
128+
+ type.getName() + " " + beanMethodName + "(" + paramsStr + ") {\n"
129+
+ "return new " + type.getName() + "(" + Arrays.stream(constructor.getParameterNames()).collect(Collectors.joining(", ")) + ");\n"
130+
+ "}\n",
131+
"imports", allFqTypes.toArray(String[]::new),
132+
"typeStubs", new String[0]/*new String[] { source.printAll() }*/,
133+
"classpath", IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath().toString()).toArray(String[]::new)
134+
135+
))
136+
);
137+
}
138+
}
139+
}
140+
141+
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), getProblemType().getLabel(), node.getName().getStartPosition(), node.getName().getLength());
142+
ReconcileUtils.setRewriteFixes(registry, problem, fixListBuilder.build());
143+
problemCollector.accept(problem);
144+
}
145+
146+
private Bean[] getConfigurationBeans(IJavaProject project) {
147+
Bean[] beans = springIndex.getBeansOfProject(project.getElementName());
148+
if (beans != null) {
149+
return Arrays.stream(beans).filter(bean -> bean.isConfiguration()).toArray(Bean[]::new);
150+
}
151+
else {
152+
return new Bean[0];
153+
}
154+
}
155+
166156
private static Set<String> allFQTypes(IBinding binding) {
167157
ImmutableSet.Builder<String> b = ImmutableSet.builder();
168158
if (binding instanceof IMethodBinding) {

0 commit comments

Comments
 (0)