Skip to content

Commit

Permalink
GH-1041: reconciler that finds non-registered aot processor beans upd…
Browse files Browse the repository at this point in the history
…ated to use spring index instead of symbols
  • Loading branch information
martinlippert committed Jan 9, 2025
1 parent 02e3ede commit 44e4974
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ public class JdtConfig {
return new BeanPostProcessingIgnoreInAotReconciler(server.getQuickfixRegistry());
}

@Bean NotRegisteredBeansReconciler notRegisteredBeansReconciler(SimpleLanguageServer server) {
return new NotRegisteredBeansReconciler(server.getQuickfixRegistry());
@Bean NotRegisteredBeansReconciler notRegisteredBeansReconciler(SimpleLanguageServer server, SpringMetamodelIndex springIndex) {
return new NotRegisteredBeansReconciler(server.getQuickfixRegistry(), springIndex);
}

@Bean EntityIdForRepoReconciler entityIdForRepoReconciler(SimpleLanguageServer server) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,18 @@ public Bean[] getBeansWithName(String project, String name) {
Bean[] allBeans = this.beansPerProject.get(project);

if (allBeans != null) {
return Arrays.stream(allBeans).filter(bean -> bean.getName().equals(name)).collect(Collectors.toList()).toArray(new Bean[0]);
return Arrays.stream(allBeans).filter(bean -> bean.getName().equals(name)).toArray(Bean[]::new);
}
else {
return null;
}
}

public Bean[] getBeansWithType(String project, String type) {
Bean[] allBeans = this.beansPerProject.get(project);

if (allBeans != null) {
return Arrays.stream(allBeans).filter(bean -> bean.getType().equals(type)).toArray(Bean[]::new);
}
else {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2204 VMware, Inc.
* Copyright (c) 2023, 2024 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -27,23 +27,16 @@
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.WorkspaceSymbol;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType;
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.beans.ConfigBeanSymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.DefineMethod;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
Expand All @@ -52,19 +45,19 @@
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;

public class NotRegisteredBeansReconciler implements JdtAstReconciler, ApplicationContextAware {
public class NotRegisteredBeansReconciler implements JdtAstReconciler {

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

private ApplicationContext applicationContext;

private QuickfixRegistry registry;
private SpringMetamodelIndex springIndex;

public NotRegisteredBeansReconciler(QuickfixRegistry registry) {
this.registry = registry;
public NotRegisteredBeansReconciler(QuickfixRegistry registry, SpringMetamodelIndex springIndex) {
this.registry = registry;
this.springIndex = springIndex;
}

@Override
Expand All @@ -77,11 +70,6 @@ public ProblemType getProblemType() {
return SpringAotJavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

@Override
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {

Expand All @@ -90,79 +78,81 @@ public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUni
@Override
public boolean visit(TypeDeclaration node) {
if (!node.isInterface() && !Modifier.isAbstract(node.getModifiers())) {

ITypeBinding type = node.resolveBinding();
if (type != null && ReconcileUtils.implementsAnyType(AOT_BEANS, type)) {
String beanClassName =type.getQualifiedName();
SpringSymbolIndex index = applicationContext.getBean(SpringSymbolIndex.class);
List<WorkspaceSymbol> beanSymbols = index.getSymbols(data -> {
SymbolAddOnInformation[] additionalInformation = data.getAdditionalInformation();
if (additionalInformation != null) {
for (SymbolAddOnInformation info : additionalInformation) {
if (info instanceof BeansSymbolAddOnInformation) {
BeansSymbolAddOnInformation info2 = (BeansSymbolAddOnInformation) info;
return beanClassName.equals(info2.getBeanType());
}
}
}
return false;
}).limit(1).collect(Collectors.toList());
String beanClassName = type.getQualifiedName();

if (beanSymbols.isEmpty()) {
Builder<FixDescriptor> fixListBuilder = ImmutableList.builder();
for (EnhancedSymbolInformation s : index.getEnhancedSymbols(project)) {
if (s.getAdditionalInformation() != null) {
ConfigBeanSymbolAddOnInformation configInfo = Arrays.stream(s.getAdditionalInformation()).filter(ConfigBeanSymbolAddOnInformation.class::isInstance).map(ConfigBeanSymbolAddOnInformation.class::cast).findFirst().orElse(null);
if (configInfo != null) {
for (IMethodBinding constructor : type.getDeclaredMethods()) {
if (constructor.isConstructor()) {
String constructorParamsSignature = "(" + Arrays.stream(constructor.getParameterTypes()).map(pt -> typePattern(pt)).collect(Collectors.joining(",")) + ")";
String beanMethodName = "get" + type.getName();
String pattern = beanMethodName + constructorParamsSignature;
String contructorParamsLabel = "(" + Arrays.stream(constructor.getParameterTypes()).map(NotRegisteredBeansReconciler::typeStr).collect(Collectors.joining(", ")) + ")";

Builder<String> paramBuilder = ImmutableList.builder();
for (int i = 0; i < constructor.getParameterNames().length && i < constructor.getParameterTypes().length; i++) {
ITypeBinding paramType = constructor.getParameterTypes()[i];
String paramName = constructor.getParameterNames()[i];
paramBuilder.add(typeStr(paramType) + ' ' + paramName);
}
String paramsStr = String.join(", ", paramBuilder.build().toArray(String[]::new));

final Set<String> allFqTypes = new HashSet<>();
allFqTypes.add(Annotations.BEAN);
allFqTypes.addAll(allFQTypes(constructor));
fixListBuilder.add(new FixDescriptor(DefineMethod.class.getName(), List.of(s.getSymbol().getLocation().getLeft().getUri()), "Define bean in config '" + configInfo.getBeanID() + "' with constructor " + contructorParamsLabel)
.withRecipeScope(RecipeScope.FILE)
.withParameters(Map.of(
"targetFqName", configInfo.getBeanType(),
"signature", pattern,
"template", "@Bean\n"
+ type.getName() + " " + beanMethodName + "(" + paramsStr + ") {\n"
+ "return new " + type.getName() + "(" + Arrays.stream(constructor.getParameterNames()).collect(Collectors.joining(", ")) + ");\n"
+ "}\n",
"imports", allFqTypes.toArray(String[]::new),
"typeStubs", new String[0]/*new String[] { source.printAll() }*/,
"classpath", IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath().toString()).toArray(String[]::new)

))
);
}
}
}
}
}
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), getProblemType().getLabel(), node.getName().getStartPosition(), node.getName().getLength());
ReconcileUtils.setRewriteFixes(registry, problem, fixListBuilder.build());
problemCollector.accept(problem);
Bean[] registeredBeans = springIndex.getBeansWithType(project.getElementName(), beanClassName);

if (registeredBeans == null || registeredBeans.length == 0) {
createProblemAndQuickFixes(project, problemCollector, node, type);
}
}
}
return super.visit(node);
}

};
}

protected void createProblemAndQuickFixes(IJavaProject project, IProblemCollector problemCollector, TypeDeclaration node, ITypeBinding type) {
Builder<FixDescriptor> fixListBuilder = ImmutableList.builder();

Bean[] configBeans = getConfigurationBeans(project);

for (Bean configBean : configBeans) {
for (IMethodBinding constructor : type.getDeclaredMethods()) {
if (constructor.isConstructor()) {
String constructorParamsSignature = "(" + Arrays.stream(constructor.getParameterTypes()).map(pt -> typePattern(pt)).collect(Collectors.joining(",")) + ")";
String beanMethodName = "get" + type.getName();
String pattern = beanMethodName + constructorParamsSignature;
String contructorParamsLabel = "(" + Arrays.stream(constructor.getParameterTypes()).map(NotRegisteredBeansReconciler::typeStr).collect(Collectors.joining(", ")) + ")";

Builder<String> paramBuilder = ImmutableList.builder();
for (int i = 0; i < constructor.getParameterNames().length && i < constructor.getParameterTypes().length; i++) {
ITypeBinding paramType = constructor.getParameterTypes()[i];
String paramName = constructor.getParameterNames()[i];
paramBuilder.add(typeStr(paramType) + ' ' + paramName);
}
String paramsStr = String.join(", ", paramBuilder.build().toArray(String[]::new));

final Set<String> allFqTypes = new HashSet<>();
allFqTypes.add(Annotations.BEAN);
allFqTypes.addAll(allFQTypes(constructor));
fixListBuilder.add(new FixDescriptor(DefineMethod.class.getName(), List.of(configBean.getLocation().getUri()), "Define bean in config '" + configBean.getName() + "' with constructor " + contructorParamsLabel)
.withRecipeScope(RecipeScope.FILE)
.withParameters(Map.of(
"targetFqName", configBean.getType(),
"signature", pattern,
"template", "@Bean\n"
+ type.getName() + " " + beanMethodName + "(" + paramsStr + ") {\n"
+ "return new " + type.getName() + "(" + Arrays.stream(constructor.getParameterNames()).collect(Collectors.joining(", ")) + ");\n"
+ "}\n",
"imports", allFqTypes.toArray(String[]::new),
"typeStubs", new String[0]/*new String[] { source.printAll() }*/,
"classpath", IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath().toString()).toArray(String[]::new)

))
);
}
}
}

ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), getProblemType().getLabel(), node.getName().getStartPosition(), node.getName().getLength());
ReconcileUtils.setRewriteFixes(registry, problem, fixListBuilder.build());
problemCollector.accept(problem);
}

private Bean[] getConfigurationBeans(IJavaProject project) {
Bean[] beans = springIndex.getBeansOfProject(project.getElementName());
if (beans != null) {
return Arrays.stream(beans).filter(bean -> bean.isConfiguration()).toArray(Bean[]::new);
}
else {
return new Bean[0];
}
}

private static Set<String> allFQTypes(IBinding binding) {
ImmutableSet.Builder<String> b = ImmutableSet.builder();
if (binding instanceof IMethodBinding) {
Expand Down
Loading

0 comments on commit 44e4974

Please sign in to comment.