diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java index 331e8a029..c10b1368e 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java @@ -21,6 +21,8 @@ import org.slf4j.LoggerFactory; public class SpringProjectUtil { + public static final String SPRING_BOOT = "spring-boot"; + private static final String VERION_PATTERN_STR = "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"; public static final Logger log = LoggerFactory.getLogger(SpringProjectUtil.class); @@ -37,7 +39,7 @@ public class SpringProjectUtil { } public static boolean isBootProject(IJavaProject jp) { - return hasSpecificLibraryOnClasspath(jp, "spring-boot", true); + return hasSpecificLibraryOnClasspath(jp, SPRING_BOOT, true); } public static boolean hasBootActuators(IJavaProject jp) { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java index b187dc269..2bc2b4806 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/composable/CompositeLanguageServerComponents.java @@ -19,6 +19,7 @@ import java.util.Set; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.HoverParams; @@ -105,16 +106,16 @@ public class CompositeLanguageServerComponents implements LanguageServerComponen this.codeActionHandler = new CodeActionHandler() { @Override - public List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, - IRegion region) { + public List> handle(CancelChecker cancelToken, + CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) { LanguageId language = doc.getLanguageId(); LanguageServerComponents subComponents = componentsByLanguageId.get(language); if (subComponents != null) { return subComponents.getCodeActionProvider() - .map(subEngine -> subEngine.handle(cancelToken, capabilities, doc, region)) + .map(subEngine -> subEngine.handle(cancelToken, capabilities, context, doc, region)) .orElse(Collections.emptyList()); } - //No applicable subEngine... + // No applicable subEngine... return Collections.emptyList(); } }; diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionHandler.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionHandler.java index de49c27c7..b50422ef1 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionHandler.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/CodeActionHandler.java @@ -1,9 +1,20 @@ +/******************************************************************************* + * 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.languageserver.util; import java.util.List; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -12,6 +23,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public interface CodeActionHandler { - List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, IRegion region); + List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 1055edca1..92a46d7f9 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -27,6 +27,8 @@ import java.util.stream.Collectors; import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; +import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.CodeLensParams; @@ -82,6 +84,7 @@ import org.springframework.ide.vscode.commons.util.text.Region; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; public class SimpleTextDocumentService implements TextDocumentService, DocumentEventListenerManager { @@ -425,23 +428,27 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE } private List> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TrackedDocument doc, CodeActionParams params) { - List> list = doc.getQuickfixes().stream() - .filter((fix) -> fix.appliesTo(params.getRange(), params.getContext())) - .map(f -> f.getCodeAction(params.getContext())) - .map(command -> Either.forRight(command)) - .collect(Collectors.toList()); + Builder> listBuilder = ImmutableList.builder(); + CodeActionContext context = params.getContext(); + if (!context.getDiagnostics().isEmpty() || (context.getOnly() != null && context.getOnly().contains(CodeActionKind.QuickFix))) { + doc.getQuickfixes().stream() + .filter((fix) -> fix.appliesTo(params.getRange(), context)) + .map(f -> f.getCodeAction(params.getContext())) + .map(command -> Either.forRight(command)) + .forEach(listBuilder::add); + } if (codeActionHandler != null) { try { int start = doc.getDocument().toOffset(params.getRange().getStart()); int end = doc.getDocument().toOffset(params.getRange().getEnd()); - list.addAll(codeActionHandler.handle(cancelToken, capabilities, doc.getDocument(), new Region(start, end - start))); + listBuilder.addAll(codeActionHandler.handle(cancelToken, capabilities, context, doc.getDocument(), new Region(start, end - start))); } catch (Exception e) { log.error("Failed to compute quick refactorings", e); } } - return list; + return listBuilder.build(); } private static CodeActionCapabilities getCodeActionCapabilities(ClientCapabilities capabilities) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java index 79af87818..2365f5b0a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java @@ -15,14 +15,17 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.ide.vscode.boot.java.handlers.AutowiredConstructorReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredConstructorReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler; import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; +import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNoPublicCodeAction; import org.springframework.ide.vscode.boot.java.rewrite.codeaction.ConvertAutowiredField; import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMapping; import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappings; import org.springframework.ide.vscode.boot.java.rewrite.quickfix.AutowiredConstructorQuickFixHandler; +import org.springframework.ide.vscode.boot.java.rewrite.quickfix.BeanMethodNoPublicQuickFixHandler; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -62,11 +65,20 @@ public class RewriteConfig implements InitializingBean { return new NoRequestMappings(server, projectFinder, rewriteRefactorings, orCuCache); } + @ConditionalOnClass({org.openrewrite.java.spring.BeanMethodsNotPublic.class}) + @Bean + BeanMethodsNoPublicCodeAction beanMethodsNoPublic(SimpleLanguageServer server, JavaProjectFinder projectFinder, + RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo, + ORCompilationUnitCache orCuCache) { + return new BeanMethodsNoPublicCodeAction(server, projectFinder, rewriteRefactorings, orCuCache); + } + @Override public void afterPropertiesSet() throws Exception { QuickfixRegistry registry = server.getQuickfixRegistry(); registry.register(AutowiredConstructorReconciler.REMOVE_UNNECESSARY_AUTOWIRED_FROM_CONSTRUCTOR, new AutowiredConstructorQuickFixHandler(server, projectFinder, orCuCache)); + registry.register(BeanMethodNotPublicReconciler.REMOVE_PUBLIC_FROM_BEAN_METHOD, new BeanMethodNoPublicQuickFixHandler(server, projectFinder, orCuCache)); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java index 02e622ee7..54e0a6a34 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java @@ -24,8 +24,10 @@ public enum SpringJavaProblemType implements ProblemType { JAVA_SPEL_EXPRESSION_SYNTAX(ERROR, "SpEL parser raised a ParseException", "SpEL Expression Syntax"), - JAVA_AUTOWIRED_CONSTRUCTOR(WARNING, "Unnecessary @Autowired over the only constructor", "Unnecessary @Autowired"); + JAVA_AUTOWIRED_CONSTRUCTOR(WARNING, "Unnecessary @Autowired over the only constructor", "Unnecessary @Autowired"), + JAVA_PUBLIC_BEAN_METHOD(HINT, "Public modifier on @Bean method. They no longer have to be public visibility to be usable by Spring.", "public @Bean method"); + private final ProblemSeverity defaultSeverity; private String description; private String label; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java index 6958c66dc..083677e8b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -44,14 +45,14 @@ public class BootJavaCodeActionProvider implements CodeActionHandler { } @Override - public List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, IRegion region) { + public List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) { Optional project = projectFinder.find(doc.getId()); if (project.isPresent()) { return cuCache.withCompilationUnit(project.get(), URI.create(doc.getId().getUri()), cu -> { ASTNode found = NodeFinder.perform(cu, region.getOffset(), region.getLength()); List> codeActions = new ArrayList<>(); for (JavaCodeAction jca : javaCodeActions) { - List> cas = jca.getCodeActions(capabilities, doc, region, project.get(), cu, found); + List> cas = jca.getCodeActions(capabilities, context, doc, region, project.get(), cu, found); if (cas != null) { codeActions.addAll(cas); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java index a3a01770e..e4b9e82bc 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java @@ -20,6 +20,10 @@ import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationParamReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredConstructorReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.Constants; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -87,7 +91,8 @@ public class BootJavaReconcileEngine implements IReconcileEngine { new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler), new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler), - new AutowiredConstructorReconciler(quickfixRegistry) + new AutowiredConstructorReconciler(quickfixRegistry), + new BeanMethodNotPublicReconciler(quickfixRegistry) }; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeAction.java index b77f52303..f2d0d865d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeAction.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -27,6 +28,6 @@ public interface JavaCodeAction { default WorkspaceEdit perform(List args) { return null; } - List> getCodeActions(CodeActionCapabilities capabilities, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node); + List> getCodeActions(CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AnnotationParamReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AnnotationParamReconciler.java similarity index 96% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AnnotationParamReconciler.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AnnotationParamReconciler.java index 545c52ce4..693291cdd 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AnnotationParamReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AnnotationParamReconciler.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.handlers; +package org.springframework.ide.vscode.boot.java.reconcilers; import java.util.List; import java.util.Set; @@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.StringLiteral; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; +import org.springframework.ide.vscode.boot.java.handlers.Reconciler; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.util.text.IDocument; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AnnotationReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AnnotationReconciler.java similarity index 94% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AnnotationReconciler.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AnnotationReconciler.java index b627829c4..007db670d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AnnotationReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AnnotationReconciler.java @@ -8,7 +8,7 @@ * Contributors: * VMware, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.handlers; +package org.springframework.ide.vscode.boot.java.reconcilers; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AutowiredConstructorReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredConstructorReconciler.java similarity index 97% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AutowiredConstructorReconciler.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredConstructorReconciler.java index 5892f92a2..64dc0850c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AutowiredConstructorReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredConstructorReconciler.java @@ -8,7 +8,7 @@ * Contributors: * VMware, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.handlers; +package org.springframework.ide.vscode.boot.java.reconcilers; import java.util.List; import java.util.Optional; @@ -41,7 +41,7 @@ public class AutowiredConstructorReconciler implements AnnotationReconciler { @Override public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector) { - Version version = SpringProjectUtil.getDependencyVersion(project, "spring-boot"); + Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT); if (version.getMajor() >= 2) { getSingleAutowiredConstructorDeclaringType(node, typeBinding).ifPresent(type -> { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java new file mode 100644 index 000000000..77d2c4e8b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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.reconcilers; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Modifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.SpringJavaProblemType; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.java.Version; +import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType; +import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +public class BeanMethodNotPublicReconciler implements AnnotationReconciler { + + private static final Logger log = LoggerFactory.getLogger(BeanMethodNotPublicReconciler.class); + + public static final String REMOVE_PUBLIC_FROM_BEAN_METHOD = "RemovePublicFromBeanMethod"; + + private QuickfixRegistry quickfixRegistry; + + public BeanMethodNotPublicReconciler(QuickfixRegistry quickfixRegistry) { + this.quickfixRegistry = quickfixRegistry; + } + + @Override + public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, + IProblemCollector problemCollector) { + + if (Annotations.BEAN.equals(typeBinding.getQualifiedName()) && node.getParent() instanceof MethodDeclaration) { + MethodDeclaration m = (MethodDeclaration) node.getParent(); + Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT); + if (version.getMajor() >= 2) { + IMethodBinding methodBinding = m.resolveBinding(); + if (isNotOverridingPublicMethod(methodBinding)) { + + ReconcileProblemImpl problem = ((List)m.modifiers()).stream() + .filter(Modifier.class::isInstance) + .map(Modifier.class::cast) + .filter(modifier -> modifier.isPublic()) + .findFirst() + .map(modifier -> new ReconcileProblemImpl( + SpringJavaProblemType.JAVA_PUBLIC_BEAN_METHOD, "public @Bean method", + modifier.getStartPosition(), modifier.getLength())) + .orElse(new ReconcileProblemImpl( + SpringJavaProblemType.JAVA_PUBLIC_BEAN_METHOD, "public @Bean method", + m.getName().getStartPosition(), m.getName().getLength())); + + QuickfixType quickfixType = quickfixRegistry.getQuickfixType(REMOVE_PUBLIC_FROM_BEAN_METHOD); + if (quickfixType != null) { + problem.addQuickfix(new QuickfixData<>( + quickfixType, + createParameters(doc, methodBinding), + "Remove public from @Bean method" + )); + } + problemCollector.accept(problem); + } + } + } + + } + + private static final boolean isOverriding(IMethodBinding binding) { + try { + Field f = binding.getClass().getDeclaredField("binding"); + f.setAccessible(true); + org.eclipse.jdt.internal.compiler.lookup.MethodBinding value = (org.eclipse.jdt.internal.compiler.lookup.MethodBinding) f.get(binding); + return value.isOverriding(); + } catch (Exception e) { + log.error("", e); + } + return false; + } + + public static final boolean isNotOverridingPublicMethod(IMethodBinding methodBinding) { + return !isOverriding(methodBinding) && (methodBinding.getModifiers() & Modifier.PUBLIC) != 0; + } + + public static final List createParameters(IDocument doc, IMethodBinding methodBinding) { + StringBuilder methodPattern = new StringBuilder(methodBinding.getDeclaringClass().getQualifiedName()); + methodPattern.append(' '); + methodPattern.append(methodBinding.getName()); + methodPattern.append('('); + methodPattern.append(Arrays.stream(methodBinding.getParameterTypes()).map(p -> p.getErasure().getQualifiedName()).collect(Collectors.joining(","))); + methodPattern.append(')'); + return List.of(doc.getUri(), methodPattern.toString()); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORAstUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORAstUtils.java index cf9736cec..2773d15ac 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORAstUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORAstUtils.java @@ -292,6 +292,10 @@ public class ORAstUtils { } } + public static Recipe nodeRecipe(JavaVisitor v, Predicate condition) { + return new NodeRecipe((JavaVisitor) v, condition); + } + @SuppressWarnings("unchecked") public static Recipe nodeRecipe(Recipe r, Predicate condition) { return new NodeRecipe((JavaVisitor) getVisitor(r), condition); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java index 57768bb8c..e49a3066f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java @@ -12,7 +12,9 @@ package org.springframework.ide.vscode.boot.java.rewrite; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; @@ -264,4 +266,17 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab return requestor.apply(null); } + public List getCompiulationUnits(IJavaProject project) { + List javaFiles = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> { + try { + return Files.walk(folder.toPath()); + } catch (IOException e) { + logger.error("", e); + } + return Stream.empty(); + }).filter(Files::isRegularFile).filter(p -> p.getFileName().toString().endsWith(".java")).collect(Collectors.toList()); + JavaParser javaParser = loadJavaParser(project); + return ORAstUtils.parse(javaParser, javaFiles); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java index 758509a76..8349d4425 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java @@ -10,24 +10,39 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.rewrite.codeaction; +import java.net.URI; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.CodeActionResolveSupportCapabilities; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.ResourceOperation; import org.eclipse.lsp4j.TextDocumentEdit; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.openrewrite.Recipe; import org.openrewrite.Result; +import org.openrewrite.java.tree.J; import org.springframework.ide.vscode.boot.java.handlers.JavaCodeAction; import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; import org.springframework.ide.vscode.boot.java.rewrite.ORDocUtils; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.util.LspClient; +import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.text.IRegion; +import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; public abstract class AbstractRewriteJavaCodeAction implements JavaCodeAction { @@ -54,17 +69,22 @@ public abstract class AbstractRewriteJavaCodeAction implements JavaCodeAction { return ca; } - protected WorkspaceEdit applyRecipe(Recipe r, TextDocument doc, org.openrewrite.java.tree.J.CompilationUnit cu) { - List results = r.run(List.of(cu)); - if (!results.isEmpty() && results.get(0).getAfter() != null) { - Optional edit = ORDocUtils.computeTextDocEdit(doc, results.get(0)); - return edit.map(e -> { - WorkspaceEdit workspaceEdit = new WorkspaceEdit(); - workspaceEdit.setDocumentChanges(List.of(Either.forLeft(e))); - return workspaceEdit; - }).orElse(null); + protected WorkspaceEdit applyRecipe(Recipe r, IJavaProject project, List cus) { + List results = r.run(cus); + List> edits = results.stream().filter(res -> res.getAfter() != null).map(res -> { + URI docUri = project.getLocationUri().resolve(res.getAfter().getSourcePath().toString()); + TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docUri.toString()); + if (doc == null) { + doc = new TextDocument(docUri.toString(), LanguageId.JAVA, 0, res.getBefore() == null ? "" : res.getBefore().printAll()); + } + return ORDocUtils.computeTextDocEdit(doc, res); + }).filter(e -> e.isPresent()).map(e -> e.get()).map(e -> Either.forLeft(e)).collect(Collectors.toList()); + if (edits.isEmpty()) { + return null; } - return null; + WorkspaceEdit workspaceEdit = new WorkspaceEdit(); + workspaceEdit.setDocumentChanges(edits); + return workspaceEdit; } protected static boolean isResolve(CodeActionCapabilities capabilities, String property) { @@ -77,5 +97,58 @@ public abstract class AbstractRewriteJavaCodeAction implements JavaCodeAction { } return false; } + + protected boolean isSupported(CodeActionCapabilities capabilities, CodeActionContext context) { + // Default case is anything non-quick fix related. + if (isResolve(capabilities, "edit")) { + if (context.getOnly() != null) { + return context.getOnly().contains(CodeActionKind.Refactor); + } else { + if (LspClient.currentClient() == Client.ECLIPSE) { + // Eclipse would have no diagnostics in the context for QuickAssists refactoring. Diagnostics will be around for QuickFix only + return context.getDiagnostics().isEmpty(); + } else { + return true; + } + + } + } + return false; + } + + @Override + final public List> getCodeActions(CodeActionCapabilities capabilities, + CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu, + ASTNode node) { + if (isSupported(capabilities, context)) { + return provideCodeActions(context, doc, region, project, cu, node); + } + return null; + } + + final protected WorkspaceEdit perform(List args, Supplier recipeSupplier) { + String docUri = (String) args.get(0); + boolean projectWide = ((Boolean) args.get(1)).booleanValue(); + + Optional project = projectFinder.find(new TextDocumentIdentifier(docUri)); + + if (project.isPresent()) { + if (projectWide) { + return applyRecipe(recipeSupplier.get(), project.get(), orCuCache.getCompiulationUnits(project.get())); + } else { + return orCuCache.withCompilationUnit(project.get(), URI.create(docUri), cu -> { + if (cu == null) { + throw new IllegalStateException("Cannot parse Java file: " + docUri); + } + return applyRecipe(recipeSupplier.get(), project.get(), List.of(cu)); + }); + } + } + return null; + } + + + protected abstract List> provideCodeActions(CodeActionContext context, TextDocument doc, + IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java new file mode 100644 index 000000000..025231385 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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.rewrite.codeaction; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.CodeActionContext; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.openrewrite.java.spring.BeanMethodsNotPublic; +import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler; +import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; +import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.java.Version; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.text.IRegion; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +public class BeanMethodsNoPublicCodeAction extends AbstractRewriteJavaCodeAction { + + private static final String REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_FILE = "Remove 'public' from @Bean methods in file"; + private static final String REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_PROJECT = "Remove 'public' from @Bean methods in project"; + private static final String CODE_ACTION_ID = "RemovePublicFromBeanMethod"; + + public BeanMethodsNoPublicCodeAction(SimpleLanguageServer server, JavaProjectFinder projectFinder, + RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) { + super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID); + } + + @Override + protected List> provideCodeActions(CodeActionContext context, TextDocument doc, + IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) { + for (; node != null && !(node instanceof MethodDeclaration); node = node.getParent()) { + // nothing + } + if (node instanceof MethodDeclaration) { + MethodDeclaration m = (MethodDeclaration) node; + IMethodBinding binding = m.resolveBinding(); + if (binding != null) { + Optional beanAnnotationPresent = Arrays.stream(binding.getAnnotations()) + .filter(a -> Annotations.BEAN.equals(a.getAnnotationType().getQualifiedName())) + .findFirst(); + if (beanAnnotationPresent.isPresent() && BeanMethodNotPublicReconciler.isNotOverridingPublicMethod(binding)) { + Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT); + if (version.getMajor() >= 2) { + return List.of( + Either.forRight(createCodeAction(REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_FILE, List.of(doc.getUri(), false))), + Either.forRight(createCodeAction(REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_PROJECT, List.of(doc.getUri(), true))) + ); + } + } + } + } + return null; + } + + @Override + public WorkspaceEdit perform(List args) { + return perform(args, () -> new BeanMethodsNotPublic()); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java index cb7f0b0e1..849e9e759 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java @@ -23,7 +23,7 @@ import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.lsp4j.CodeAction; -import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; @@ -35,7 +35,6 @@ import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.util.text.IRegion; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -50,11 +49,9 @@ public class ConvertAutowiredField extends AbstractRewriteJavaCodeAction { @Override public WorkspaceEdit perform(List args) { - SimpleTextDocumentService documents = server.getTextDocumentService(); String docUri = (String) args.get(0); String classFqName = (String) args.get(1); String fieldName = (String) args.get(2); - TextDocument doc = documents.getLatestSnapshot(docUri); Optional project = projectFinder.find(new TextDocumentIdentifier(docUri)); @@ -63,20 +60,15 @@ public class ConvertAutowiredField extends AbstractRewriteJavaCodeAction { if (cu == null) { throw new IllegalStateException("Cannot parse Java file: " + docUri); } - return applyRecipe(new ConvertAutowiredParameterIntoConstructorParameter(classFqName, fieldName), doc, cu); + return applyRecipe(new ConvertAutowiredParameterIntoConstructorParameter(classFqName, fieldName), project.get(), List.of(cu)); }); } return null; } @Override - public List> getCodeActions(CodeActionCapabilities capabilities, TextDocument doc, IRegion region, IJavaProject project, - CompilationUnit cu, ASTNode node) { - // Only supports resolvable code action for now - if (!isResolve(capabilities, "edit")) { - return Collections.emptyList(); - } - + protected List> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, + CompilationUnit cu, ASTNode node) { for (; node != null && !(node instanceof FieldDeclaration); node = node.getParent()) { // nothing } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java index 285f77a7e..17181e674 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java @@ -21,7 +21,7 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.lsp4j.CodeAction; -import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; @@ -34,7 +34,6 @@ import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.util.text.IRegion; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -49,10 +48,8 @@ public class NoRequestMapping extends NoRequestMappings { @Override public WorkspaceEdit perform(List args) { - SimpleTextDocumentService documents = server.getTextDocumentService(); String docUri = (String) args.get(0); String matchStr = (String) args.get(1); - TextDocument doc = documents.getLatestSnapshot(docUri); Optional project = projectFinder.find(new TextDocumentIdentifier(docUri)); @@ -69,19 +66,15 @@ public class NoRequestMapping extends NoRequestMappings { return macther.matches(m.getMethodType()); } return false; - }), doc, cu); + }), project.get(), List.of(cu)); }); } return null; } @Override - public List> getCodeActions(CodeActionCapabilities capabilities, TextDocument doc, IRegion region, IJavaProject project, + protected List> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) { - // Only supports resolvable code action for now - if (!isResolve(capabilities, "edit")) { - return Collections.emptyList(); - } return findAppropriateMethodDeclaration(node).map(method -> { String methodMatcher = "* " + method.getName().getIdentifier() + "(*)"; IMethodBinding methodBinding = method.resolveBinding(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java index ab550db6b..3ea17ca2f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.rewrite.codeaction; -import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -21,9 +20,8 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.lsp4j.CodeAction; -import org.eclipse.lsp4j.CodeActionCapabilities; +import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.Command; -import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.openrewrite.java.spring.NoRequestMappingAnnotation; @@ -33,7 +31,6 @@ import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.util.text.IRegion; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -70,33 +67,16 @@ public class NoRequestMappings extends AbstractRewriteJavaCodeAction { @Override public WorkspaceEdit perform(List args) { - SimpleTextDocumentService documents = server.getTextDocumentService(); - String docUri = (String) args.get(0); - TextDocument doc = documents.getLatestSnapshot(docUri); - - Optional project = projectFinder.find(new TextDocumentIdentifier(docUri)); - - if (project.isPresent()) { - return orCuCache.withCompilationUnit(project.get(), URI.create(docUri), cu -> { - if (cu == null) { - throw new IllegalStateException("Cannot parse Java file: " + docUri); - } - return applyRecipe(new NoRequestMappingAnnotation(), doc, cu); - }); - } - return null; + return perform(args, () -> new NoRequestMappingAnnotation()); } @Override - public List> getCodeActions(CodeActionCapabilities capabilities, TextDocument doc, IRegion region, IJavaProject project, + protected List> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) { - // Only supports resolvable code action for now - if (!isResolve(capabilities, "edit")) { - return Collections.emptyList(); - } - return findAppropriateMethodDeclaration(node).map(m -> createCodeAction("Replace all @RequestMapping with @GetMapping etc.", List.of( - doc.getId().getUri() - ))).map(ca -> List.of(Either.forRight(ca))).orElse(Collections.emptyList()); + return findAppropriateMethodDeclaration(node).map(m -> List.of( + Either.forRight(createCodeAction("Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))), + Either.forRight(createCodeAction("Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true))) + )).orElse(Collections.emptyList()); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java index 7baed1892..ffb2c343a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java @@ -10,19 +10,13 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.rewrite.quickfix; -import java.net.URI; import java.util.List; import java.util.Optional; -import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.openrewrite.Result; import org.openrewrite.java.spring.NoAutowiredOnConstructor; -import org.springframework.ide.vscode.boot.java.rewrite.ORAstUtils; import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; -import org.springframework.ide.vscode.boot.java.rewrite.ORDocUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -58,28 +52,12 @@ public class AutowiredConstructorQuickFixHandler extends RewriteQuickFixHandler } private WorkspaceEdit removeUnnecessaryAutowiredFromConstructor(IJavaProject project, TextDocument doc, String classFqName) { - String docUri = doc.getId().getUri(); - return orCuCache.withCompilationUnit(project, URI.create(docUri), cu -> { - if (cu == null) { - throw new IllegalStateException("Cannot parse Java file: " + docUri); + return applyRecipe(orCuCache, new NoAutowiredOnConstructor(), project, doc, t -> { + if (t instanceof org.openrewrite.java.tree.J.ClassDeclaration) { + org.openrewrite.java.tree.J.ClassDeclaration c = (org.openrewrite.java.tree.J.ClassDeclaration) t; + return c.getType() != null && classFqName.equals(c.getType().getFullyQualifiedName()); } - List results = ORAstUtils.nodeRecipe(new NoAutowiredOnConstructor(), t -> { - if (t instanceof org.openrewrite.java.tree.J.ClassDeclaration) { - org.openrewrite.java.tree.J.ClassDeclaration c = (org.openrewrite.java.tree.J.ClassDeclaration) t; - return c.getType() != null && classFqName.equals(c.getType().getFullyQualifiedName()); - } - return false; - }).run(List.of(cu)); - if (!results.isEmpty() && results.get(0).getAfter() != null) { - Optional edit = ORDocUtils.computeTextDocEdit(doc, results.get(0)); - return edit.map(e -> { - WorkspaceEdit workspaceEdit = new WorkspaceEdit(); - workspaceEdit.setDocumentChanges(List.of(Either.forLeft(e))); - return workspaceEdit; - }).orElse(null); - } - - return null; + return false; }); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/BeanMethodNoPublicQuickFixHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/BeanMethodNoPublicQuickFixHandler.java new file mode 100644 index 000000000..135a79059 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/BeanMethodNoPublicQuickFixHandler.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.rewrite.quickfix; + +import java.util.List; +import java.util.Optional; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceEdit; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.spring.BeanMethodsNotPublic; +import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +public class BeanMethodNoPublicQuickFixHandler extends RewriteQuickFixHandler { + + final private SimpleLanguageServer server; + final private JavaProjectFinder projectFinder; + final private ORCompilationUnitCache orCuCache; + + + public BeanMethodNoPublicQuickFixHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, + ORCompilationUnitCache orCuCache) { + this.server = server; + this.projectFinder = projectFinder; + this.orCuCache = orCuCache; + } + + + @Override + protected WorkspaceEdit perform(List args) { + SimpleTextDocumentService documents = server.getTextDocumentService(); + String docUri = (String) args.get(0); + String methodMatch = (String) args.get(1); + TextDocument doc = documents.getLatestSnapshot(docUri); + + Optional project = projectFinder.find(new TextDocumentIdentifier(docUri)); + + if (project.isPresent()) { + MethodMatcher matcher = new MethodMatcher(methodMatch); + return applyRecipe(orCuCache, new BeanMethodsNotPublic(), project.get(), doc, t -> { + if (t instanceof org.openrewrite.java.tree.J.MethodDeclaration) { + org.openrewrite.java.tree.J.MethodDeclaration m = (org.openrewrite.java.tree.J.MethodDeclaration) t; + return matcher.matches(m.getMethodType()); + } + return false; + }); + } + + return null; + } + + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java index 4ac968dd2..fb43f0119 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java @@ -10,11 +10,26 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.rewrite.quickfix; +import java.net.URI; import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.WorkspaceEdit; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Result; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J; +import org.springframework.ide.vscode.boot.java.rewrite.ORAstUtils; +import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; +import org.springframework.ide.vscode.boot.java.rewrite.ORDocUtils; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixHandler; +import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -34,4 +49,33 @@ public abstract class RewriteQuickFixHandler implements QuickfixHandler { abstract protected WorkspaceEdit perform(List l); + final protected WorkspaceEdit applyRecipe(ORCompilationUnitCache orCuCache, Recipe r, IJavaProject project, TextDocument doc, Predicate nodeFilter) { + return doApplyRecipe(orCuCache, ORAstUtils.nodeRecipe(r, nodeFilter), project, doc); + } + + final protected WorkspaceEdit applyVisitor(ORCompilationUnitCache orCuCache, JavaVisitor v, IJavaProject project, TextDocument doc, Predicate nodeFilter) { + return doApplyRecipe(orCuCache, ORAstUtils.nodeRecipe(v, nodeFilter), project, doc); + } + + final protected WorkspaceEdit doApplyRecipe(ORCompilationUnitCache orCuCache, Recipe r, IJavaProject project, TextDocument doc) { + String docUri = doc.getId().getUri(); + return orCuCache.withCompilationUnit(project, URI.create(docUri), cu -> { + if (cu == null) { + throw new IllegalStateException("Cannot parse Java file: " + docUri); + } + List results = r.run(List.of(cu)); + if (!results.isEmpty() && results.get(0).getAfter() != null) { + Optional edit = ORDocUtils.computeTextDocEdit(doc, results.get(0)); + return edit.map(e -> { + WorkspaceEdit workspaceEdit = new WorkspaceEdit(); + workspaceEdit.setDocumentChanges(List.of(Either.forLeft(e))); + return workspaceEdit; + }).orElse(null); + } + + return null; + }); + + } + }