Define bean in configuration class quickfix
This commit is contained in:
@@ -14,13 +14,10 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -169,18 +166,4 @@ public class IClasspathUtil {
|
||||
.collect(CollectorUtil.toImmutableList());
|
||||
}
|
||||
|
||||
|
||||
public static Set<String> getBinaryClasspathEntries(IJavaProject project) throws Exception {
|
||||
if (project == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
IClasspath classpath = project.getClasspath();
|
||||
Stream<File> classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream();
|
||||
return classpathEntries
|
||||
.filter(file -> file.exists())
|
||||
.map(file -> file.getAbsolutePath()).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.java.JavaTemplate;
|
||||
import org.openrewrite.java.MethodMatcher;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.JavaType;
|
||||
|
||||
public class DefineMethod extends Recipe {
|
||||
|
||||
private static final String MESSAGE = "methodPresent";
|
||||
|
||||
private String targetFqName;
|
||||
|
||||
private String signature;
|
||||
|
||||
private String template;
|
||||
|
||||
private List<String> typeStubs = Collections.emptyList();
|
||||
|
||||
private List<String> imports = Collections.emptyList();
|
||||
|
||||
private List<String> classpath = Collections.emptyList();
|
||||
|
||||
public DefineMethod() {
|
||||
}
|
||||
|
||||
public DefineMethod(String targetFqName, String signature, String template, List<String> imports, List<String> typeStubs, List<String> classpath) {
|
||||
this.targetFqName = targetFqName;
|
||||
this.signature = signature;
|
||||
this.template = template;
|
||||
this.imports = imports;
|
||||
this.typeStubs = typeStubs;
|
||||
this.classpath = classpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Define a bean in type";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
return new JavaIsoVisitor<ExecutionContext>() {
|
||||
|
||||
private MethodMatcher matcher = new MethodMatcher(targetFqName + ' ' + signature);
|
||||
|
||||
@Override
|
||||
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
|
||||
JavaType.Method type = method.getMethodType();
|
||||
if (type != null && matcher.matches(type)) {
|
||||
getCursor().dropParentUntil(J.ClassDeclaration.class::isInstance).putMessage(MESSAGE, true);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
|
||||
J.ClassDeclaration c = super.visitClassDeclaration(classDecl, executionContext);
|
||||
Boolean methodPresent = getCursor().pollMessage(MESSAGE);
|
||||
if (template != null && methodPresent == null) {
|
||||
JavaType.FullyQualified type = c.getType();
|
||||
if (type != null && targetFqName.equals(type.getFullyQualifiedName())) {
|
||||
JavaTemplate t = JavaTemplate.builder(() -> getCursor(), template)
|
||||
.javaParser(() -> JavaParser
|
||||
.fromJavaVersion()
|
||||
.dependsOn(typeStubs.toArray(new String[typeStubs.size()]))
|
||||
.classpath(classpath.stream().map(s -> Paths.get(s)).collect(Collectors.toList()))
|
||||
.build())
|
||||
|
||||
.imports(imports.toArray(new String[imports.size()])).build();
|
||||
J.Block body = classDecl.getBody().withTemplate(t, classDecl.getBody().getCoordinates().addMethodDeclaration((m, n) -> 1));
|
||||
for (String fq : imports) {
|
||||
maybeAddImport(fq);
|
||||
}
|
||||
c = c.withBody(body);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -232,7 +231,7 @@ public class ORAstUtils {
|
||||
|
||||
public static JavaParser createJavaParser(IJavaProject project) {
|
||||
try {
|
||||
List<Path> classpath = IClasspathUtil.getBinaryClasspathEntries(project).stream().map(s -> new File(s).toPath()).collect(Collectors.toList());
|
||||
List<Path> classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList());
|
||||
JavaParser jp = JavaParser.fromJavaVersion().build();
|
||||
jp.setClasspath(classpath);
|
||||
return jp;
|
||||
|
||||
@@ -549,6 +549,11 @@ public class SpringSymbolIndex implements InitializingBean {
|
||||
.map(enhanced -> enhanced.getSymbol());
|
||||
}
|
||||
|
||||
public List<EnhancedSymbolInformation> getEnhancedSymbols(IJavaProject project) {
|
||||
List<EnhancedSymbolInformation> list = symbolsByProject.get(project.getElementName());
|
||||
return list == null ? Collections.emptyList() : Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
synchronized private CompletableFuture<IJavaProject> projectInitializedFuture(IJavaProject project) {
|
||||
if (project == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2022 Pivotal, 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
|
||||
@@ -21,6 +21,7 @@ public class Annotations {
|
||||
public static final String CONDITIONAL = "org.springframework.context.annotation.Conditional";
|
||||
|
||||
public static final String COMPONENT = "org.springframework.stereotype.Component";
|
||||
public static final String CONFIGURATION = "org.springframework.context.annotation.Configuration";
|
||||
public static final String REPOSITORY = "org.springframework.stereotype.Repository";
|
||||
|
||||
public static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.eclipse.lsp4j.WorkspaceSymbol;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
|
||||
@@ -64,8 +65,14 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
|
||||
WorkspaceSymbol symbol = new WorkspaceSymbol(
|
||||
beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface,
|
||||
Either.forLeft(new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()))));
|
||||
|
||||
SymbolAddOnInformation[] addon = new SymbolAddOnInformation[] {new BeansSymbolAddOnInformation(beanName, beanType.getQualifiedName())};
|
||||
|
||||
SymbolAddOnInformation[] addon = new SymbolAddOnInformation[0];
|
||||
if (Annotations.CONFIGURATION.equals(annotationType.getQualifiedName())
|
||||
|| metaAnnotations.stream().anyMatch(t -> Annotations.CONFIGURATION.equals(t.getQualifiedName()))) {
|
||||
addon = new SymbolAddOnInformation[] {new ConfigBeanSymbolAddOnInformation(beanName, beanType.getQualifiedName())};
|
||||
} else {
|
||||
addon = new SymbolAddOnInformation[] {new BeansSymbolAddOnInformation(beanName, beanType.getQualifiedName())};
|
||||
}
|
||||
|
||||
return new EnhancedSymbolInformation(symbol, addon);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
public class ConfigBeanSymbolAddOnInformation extends BeansSymbolAddOnInformation {
|
||||
|
||||
public ConfigBeanSymbolAddOnInformation(String beanID, String beanType) {
|
||||
super(beanID, beanType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ package org.springframework.ide.vscode.boot.java.rewrite;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -139,7 +139,7 @@ public class RewriteReconciler implements JavaReconciler {
|
||||
|
||||
JavaParser javaParser = ORAstUtils.createJavaParser(project);
|
||||
if (javaParser != null) {
|
||||
List<CompilationUnit> cus = ORAstUtils.parseInputs(javaParser, docs.stream().map(d -> new Parser.Input(Path.of(d.getUri()), () -> {
|
||||
List<CompilationUnit> cus = ORAstUtils.parseInputs(javaParser, docs.stream().map(d -> new Parser.Input(Paths.get(URI.create(d.getUri())), () -> {
|
||||
return new ByteArrayInputStream(d.get().getBytes());
|
||||
})).collect(Collectors.toList()));
|
||||
|
||||
|
||||
@@ -12,29 +12,47 @@ package org.springframework.ide.vscode.boot.java.rewrite.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbol;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.J.ClassDeclaration;
|
||||
import org.openrewrite.java.tree.JavaType;
|
||||
import org.openrewrite.java.tree.JavaType.FullyQualified;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType;
|
||||
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.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
|
||||
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public class NotRegisteredBeansProblem implements RecipeSpringJavaProblemDescriptor {
|
||||
|
||||
private static final String DEFINE_METHOD_RECIPE = "org.springframework.ide.vscode.commons.rewrite.java.DefineMethod";
|
||||
|
||||
private static final List<String> AOT_BEANS = List.of(
|
||||
"org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor",
|
||||
"org.springframework.beans.factory.aot.BeanRegistrationAotProcessor",
|
||||
@@ -66,8 +84,58 @@ public class NotRegisteredBeansProblem implements RecipeSpringJavaProblemDescrip
|
||||
}
|
||||
return false;
|
||||
}).limit(1).collect(Collectors.toList());
|
||||
Builder<FixDescriptor> fixListBuilder = ImmutableList.builder();
|
||||
List<JavaType.Method> constructors = c.getType().getMethods().stream().filter(m -> m.isConstructor()).collect(Collectors.toList());
|
||||
if (beanSymbols.isEmpty()) {
|
||||
return c = c.withName(c.getName().withMarkers(c.getName().getMarkers().add(new FixAssistMarker(Tree.randomId(), getId()))));
|
||||
SourceFile source = getCursor().firstEnclosing(SourceFile.class);
|
||||
String uri = source.getSourcePath().toUri().toString();
|
||||
FixAssistMarker marker = new FixAssistMarker(Tree.randomId(), getId());
|
||||
JavaProjectFinder projectFinder = applicationContext.getBean(JavaProjectFinder.class);
|
||||
if (projectFinder != null) {
|
||||
IJavaProject project = projectFinder.find(new TextDocumentIdentifier(uri)).orElse(null);
|
||||
if (project != null) {
|
||||
|
||||
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 (JavaType.Method constructor : constructors) {
|
||||
String constructorParamsSignature = "(" + constructor.getParameterTypes().stream().map(pt -> typePattern(pt)).collect(Collectors.joining(",")) + ")";
|
||||
String beanMethodName = "get" + type.getClassName();
|
||||
String pattern = beanMethodName + constructorParamsSignature;
|
||||
String contructorParamsLabel = "(" + constructor.getParameterTypes().stream().map(NotRegisteredBeansProblem::typeStr).collect(Collectors.joining(", ")) + ")";
|
||||
|
||||
Builder<String> paramBuilder = ImmutableList.builder();
|
||||
for (int i = 0; i < constructor.getParameterNames().size() && i < constructor.getParameterTypes().size(); i++) {
|
||||
JavaType paramType = constructor.getParameterTypes().get(i);
|
||||
String paramName = constructor.getParameterNames().get(i);
|
||||
paramBuilder.add(typeStr(paramType) + ' ' + paramName);
|
||||
}
|
||||
String paramsStr = String.join(", ", paramBuilder.build().toArray(String[]::new));
|
||||
|
||||
fixListBuilder.add(new FixDescriptor(DEFINE_METHOD_RECIPE, 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.getClassName() + " " + beanMethodName + "(" + paramsStr + ") {\n"
|
||||
+ "return new " + type.getClassName() + "(" + constructor.getParameterNames().stream().collect(Collectors.joining(", ")) + ");\n"
|
||||
+ "}\n",
|
||||
"imports", allFQTypes(constructor).toArray(String[]::new),
|
||||
"typeStubs", new String[] { source.printAll() },
|
||||
"classpath", IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath().toString()).toArray(String[]::new)
|
||||
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
marker.withFixes(fixListBuilder.build().toArray(FixDescriptor[]::new));
|
||||
c = c.withName(c.getName().withMarkers(c.getName().getMarkers().add(marker)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +144,25 @@ public class NotRegisteredBeansProblem implements RecipeSpringJavaProblemDescrip
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static Set<String> allFQTypes(JavaType type) {
|
||||
ImmutableSet.Builder<String> b = ImmutableSet.builder();
|
||||
if (type instanceof JavaType.FullyQualified) {
|
||||
b.add(((JavaType.FullyQualified) type).getFullyQualifiedName());
|
||||
if (type instanceof JavaType.Parameterized) {
|
||||
((JavaType.Parameterized) type).getTypeParameters().forEach(t -> b.addAll(allFQTypes(t)));
|
||||
}
|
||||
} else if (type instanceof JavaType.Array) {
|
||||
b.addAll(allFQTypes(((JavaType.Array) type).getElemType()));
|
||||
} else if (type instanceof JavaType.Method) {
|
||||
JavaType.Method m = (JavaType.Method) type;
|
||||
b.addAll(allFQTypes(m.getDeclaringType()));
|
||||
b.addAll(allFQTypes(m.getReturnType()));
|
||||
m.getParameterTypes().forEach(pt -> b.addAll(allFQTypes(pt)));
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return springBootVersionGreaterOrEqual(3, 0, 0).test(project);
|
||||
@@ -86,5 +172,39 @@ public class NotRegisteredBeansProblem implements RecipeSpringJavaProblemDescrip
|
||||
public ProblemType getProblemType() {
|
||||
return Boot3JavaProblemType.JAVA_BEAN_NOT_REGISTERED_IN_AOT;
|
||||
}
|
||||
|
||||
private static String typePattern(JavaType type) {
|
||||
if (type instanceof JavaType.Primitive) {
|
||||
if (type.equals(JavaType.Primitive.String)) {
|
||||
return ((JavaType.Primitive) type).getClassName();
|
||||
}
|
||||
return ((JavaType.Primitive) type).getKeyword();
|
||||
} else if (type instanceof JavaType.FullyQualified) {
|
||||
return ((JavaType.FullyQualified) type).getFullyQualifiedName();
|
||||
} else if (type instanceof JavaType.Array) {
|
||||
JavaType elemType = ((JavaType.Array) type).getElemType();
|
||||
return typePattern(elemType) + "[]";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String typeStr(JavaType type) {
|
||||
if (type instanceof JavaType.Primitive) {
|
||||
if (type.equals(JavaType.Primitive.String)) {
|
||||
return ((JavaType.Primitive) type).getClassName();
|
||||
}
|
||||
return ((JavaType.Primitive) type).getKeyword();
|
||||
} else if (type instanceof JavaType.Parameterized) {
|
||||
JavaType.Parameterized parametereized = (JavaType.Parameterized) type;
|
||||
return parametereized.getClassName() + "<" + parametereized.getTypeParameters().stream().map(NotRegisteredBeansProblem::typeStr).collect(Collectors.joining(", ")) + ">";
|
||||
} else if (type instanceof JavaType.FullyQualified) {
|
||||
return ((JavaType.FullyQualified) type).getClassName();
|
||||
} else if (type instanceof JavaType.Array) {
|
||||
JavaType elemType = ((JavaType.Array) type).getElemType();
|
||||
return typeStr(elemType) + "[]";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user