diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredConstructorReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredConstructorReconciler.java deleted file mode 100644 index 9a75ba810..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredConstructorReconciler.java +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * 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.util.List; -import java.util.Optional; - -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.IMemberValuePairBinding; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; -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 AutowiredConstructorReconciler implements AnnotationReconciler { - - public static final String REMOVE_UNNECESSARY_AUTOWIRED_FROM_CONSTRUCTOR = "RemoveUnnecessaryConstructorAutowired"; - - private QuickfixRegistry quickfixRegistry; - - public AutowiredConstructorReconciler(QuickfixRegistry quickfixRegistry) { - this.quickfixRegistry = quickfixRegistry; - } - - @Override - public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector) { - Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT); - - if (version.getMajor() >= 2) { - getSingleAutowiredConstructorDeclaringType(node, typeBinding).ifPresent(type -> { - ReconcileProblemImpl problem = new ReconcileProblemImpl(Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR, "Unnecesary @Autowired", node.getStartPosition(), node.getLength()); - QuickfixType quickfixType = quickfixRegistry.getQuickfixType(AutowiredConstructorReconciler.REMOVE_UNNECESSARY_AUTOWIRED_FROM_CONSTRUCTOR); - if (quickfixType != null) { - problem.addQuickfix(new QuickfixData<>( - quickfixType, - List.of(doc.getUri(), type.getQualifiedName()), - "Remove unnecessary @Autowired" - )); - } - problemCollector.accept(problem); - }); - } - - } - - static Optional getSingleAutowiredConstructorDeclaringType(Annotation a, ITypeBinding type) { - if (type != null && Annotations.AUTOWIRED.equals(type.getQualifiedName())) { - if (a.getParent() instanceof MethodDeclaration) { - MethodDeclaration method = (MethodDeclaration) a.getParent(); - IMethodBinding methodBinding = method.resolveBinding(); - if (methodBinding != null && methodBinding.isConstructor()) { - ITypeBinding declaringType = methodBinding.getDeclaringClass(); - if (declaringType != null && !isBootTestClass(declaringType) && isOnlyOneConstructor(declaringType)) { - return Optional.of(declaringType); - } - } - } - } - return Optional.empty(); - } - - private static boolean isOnlyOneConstructor(ITypeBinding t) { - int numberOfConstructors = 0; - if (!t.isInterface()) { - for (IMethodBinding m : t.getDeclaredMethods()) { - if (m.isConstructor()) { - numberOfConstructors++; - if (numberOfConstructors > 1) { - break; - } - } - } - } - return numberOfConstructors == 1; - } - - private static boolean isBootTestClass(ITypeBinding typeBinding) { - for (IAnnotationBinding annotation : typeBinding.getAnnotations()) { - boolean found = AnnotationHierarchies.findTransitiveSuperAnnotationBindings(annotation).anyMatch(a -> { - if (a.getAnnotationType() != null) { - if ("org.springframework.test.context.BootstrapWith".equals(a.getAnnotationType().getQualifiedName())) { - for (IMemberValuePairBinding pair : a.getAllMemberValuePairs()) { - if (pair.getValue() instanceof ITypeBinding) { - ITypeBinding type = (ITypeBinding) pair.getValue(); - if ("value".equals(pair.getName()) - && "org.springframework.boot.test.context.SpringBootTestContextBootstrapper".equals(type.getQualifiedName())) { - return true; - } - } - } - } - } - return false; - }); - if (found) { - return true; - } - } - return false; - } - - -} 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 deleted file mode 100644 index 50705bdb0..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - * 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.Boot2JavaProblemType; -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( - Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD, "public @Bean method", - modifier.getStartPosition(), modifier.getLength())) - .orElse(new ReconcileProblemImpl( - Boot2JavaProblemType.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/reconcile/NoAutowiredOnConstructorProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java index 1eca2753e..ab1380d77 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 VMware, Inc. + * Copyright (c) 2022, 2023 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 @@ -18,6 +18,7 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.SourceFile; import org.openrewrite.Tree; import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.ClassDeclaration; @@ -32,64 +33,67 @@ import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; -import org.springframework.ide.vscode.commons.rewrite.java.AnnotationHierarchies; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.JavaMarkerVisitor; public class NoAutowiredOnConstructorProblem implements RecipeCodeActionDescriptor { + private static final AnnotationMatcher BOOT_TEST_ANNOTATION_MATCHER = new AnnotationMatcher( + "@org.springframework.boot.test.context.SpringBootTest", true); private static final String ID = "org.openrewrite.java.spring.NoAutowiredOnConstructor"; private static final String LABEL = "Remove Unnecessary @Autowired"; @Override public JavaVisitor getMarkerVisitor(ApplicationContext applicationContext) { return new JavaMarkerVisitor() { - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) { - J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, context); + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, context); + + int constructorCount = 0; + for (Statement s : cd.getBody().getStatements()) { + if (isConstructor(s)) { + constructorCount++; + if (constructorCount > 1) { + return cd; + } + } + } + + FullyQualified type = TypeUtils.asFullyQualified(classDecl.getType()); + if (type != null && isApplicableType(type)) { + return cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), s -> { + if (!isConstructor(s)) { + return s; + } + MethodDeclaration constructor = (MethodDeclaration) s; + String uri = getCursor().firstEnclosing(SourceFile.class).getSourcePath().toUri() + .toASCIIString(); + FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId(), getId()) + .withFix(new FixDescriptor(ID, List.of(uri), LABEL).withRecipeScope(RecipeScope.NODE) + .withRangeScope(getCursor().firstEnclosing(ClassDeclaration.class).getMarkers() + .findFirst(Range.class).get())); + constructor = constructor + .withLeadingAnnotations(ListUtils.map(constructor.getLeadingAnnotations(), a -> { + if (TypeUtils.isOfClassType(a.getType(), Annotations.AUTOWIRED)) { + a = a.withMarkers(a.getMarkers().add(fixAssistMarker)); + } + return a; + })); + return constructor; + }))); + } + return cd; + } - int constructorCount = 0; - for(Statement s : cd.getBody().getStatements()) { - if(isConstructor(s)) { - constructorCount++; - if(constructorCount > 1) { - return cd; - } - } - } - - FullyQualified type = TypeUtils.asFullyQualified(classDecl.getType()); - if (type != null && isApplicableType(type)) { - return cd.withBody(cd.getBody().withStatements( - ListUtils.map(cd.getBody().getStatements(), s -> { - if(!isConstructor(s)) { - return s; - } - MethodDeclaration constructor = (MethodDeclaration) s; - String uri = getCursor().firstEnclosing(SourceFile.class).getSourcePath().toUri().toASCIIString(); - FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId(), getId()) - .withFix( - new FixDescriptor(ID, List.of(uri), LABEL) - .withRecipeScope(RecipeScope.NODE) - .withRangeScope(getCursor().firstEnclosing(ClassDeclaration.class).getMarkers().findFirst(Range.class).get()) - ); - constructor = constructor.withLeadingAnnotations(ListUtils.map(constructor.getLeadingAnnotations(), a -> { - if (TypeUtils.isOfClassType(a.getType(), Annotations.AUTOWIRED)) { - a = a.withMarkers(a.getMarkers().add(fixAssistMarker)); - } - return a; - })); - return constructor; - }) - )); - } - return cd; - } - private boolean isApplicableType(FullyQualified type) { - return !AnnotationHierarchies - .getTransitiveSuperAnnotations(type, fq -> fq.getFullyQualifiedName().startsWith("java.")) - .contains("org.springframework.boot.test.context.SpringBootTest"); + for (FullyQualified annotationType : type.getAnnotations()) { + if (BOOT_TEST_ANNOTATION_MATCHER.matchesAnnotationOrMetaAnnotation(annotationType)) { + return false; + } + } + FullyQualified superType = type.getSupertype(); + return superType == null ? true : isApplicableType(superType); } }; @@ -104,10 +108,9 @@ public class NoAutowiredOnConstructorProblem implements RecipeCodeActionDescript public Boot2JavaProblemType getProblemType() { return Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR; } - - private static boolean isConstructor(Statement s) { - return s instanceof J.MethodDeclaration && ((J.MethodDeclaration)s).isConstructor(); - } + private static boolean isConstructor(Statement s) { + return s instanceof J.MethodDeclaration && ((J.MethodDeclaration) s).isConstructor(); + } }