From d86e43f89804bddc777d0d802d8ef8f70f3f5e94 Mon Sep 17 00:00:00 2001 From: aboyko Date: Mon, 31 Mar 2025 12:33:25 -0400 Subject: [PATCH 1/4] Move methods from ReconcileUtils into ASTUtils Signed-off-by: aboyko --- .../java/beans/ComponentSymbolProvider.java | 3 +- ...anPostProcessingIgnoreInAotReconciler.java | 4 +- .../NotRegisteredBeansReconciler.java | 3 +- .../boot/java/reconcilers/ReconcileUtils.java | 28 ---- .../ide/vscode/boot/java/utils/ASTUtils.java | 130 ++++++++++-------- 5 files changed, 80 insertions(+), 88 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java index a7f59aa500..65154df7df 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java @@ -45,7 +45,6 @@ import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.reconcilers.NotRegisteredBeansReconciler; -import org.springframework.ide.vscode.boot.java.reconcilers.ReconcileUtils; import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException; import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexer; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; @@ -364,7 +363,7 @@ private void indexAotProcessors(TypeDeclaration typeDeclaration, SpringIndexerJa ITypeBinding typeBinding = typeDeclaration.resolveBinding(); if (typeBinding == null) return; - if (ReconcileUtils.implementsAnyType(NotRegisteredBeansReconciler.AOT_BEANS, typeBinding)) { + if (ASTUtils.isAnyTypeInHierarchy(typeBinding, NotRegisteredBeansReconciler.AOT_BEANS)) { String type = typeBinding.getQualifiedName(); String docUri = context.getDocURI(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java index f976dd9075..865f3a4f61 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java @@ -14,6 +14,7 @@ import java.net.URI; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.core.dom.ASTVisitor; @@ -23,6 +24,7 @@ import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; @@ -108,7 +110,7 @@ public boolean visit(ReturnStatement node) { } private static boolean isApplicable(ITypeBinding type) { - return ReconcileUtils.implementsType(RUNTIME_BEAN_POST_PROCESSOR, type) && ReconcileUtils.implementsType(COMPILE_BEAN_POST_PROCESSOR, type); + return ASTUtils.areAllTypesInHierarchy(type, Set.of(RUNTIME_BEAN_POST_PROCESSOR, COMPILE_BEAN_POST_PROCESSOR)); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java index 46fbe7bfef..379435ce0b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java @@ -30,6 +30,7 @@ import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; @@ -83,7 +84,7 @@ public boolean visit(TypeDeclaration node) { if (!node.isInterface() && !Modifier.isAbstract(node.getModifiers())) { ITypeBinding type = node.resolveBinding(); - if (type != null && ReconcileUtils.implementsAnyType(AOT_BEANS, type)) { + if (type != null && ASTUtils.isAnyTypeInHierarchy(type, AOT_BEANS)) { // // reconcile AOT Proceesor itself diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcileUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcileUtils.java index 7066bec23e..273b043f93 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcileUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcileUtils.java @@ -148,34 +148,6 @@ public boolean visit(SimpleType node) { return typeUsed.get(); } - public static boolean implementsType(String fqName, ITypeBinding type) { - if (fqName.equals(type.getQualifiedName())) { - return true; - } else { - for (ITypeBinding t : type.getInterfaces()) { - if (implementsType(fqName, t)) { - return true; - } - } - } - return false; - } - - public static boolean implementsAnyType(Collection fqNames, ITypeBinding type) { - if (fqNames.contains(type.getQualifiedName())) { - return true; - } else { - for (ITypeBinding t : type.getInterfaces()) { - if (implementsAnyType(fqNames, t)) { - return true; - } - } - } - return false; - } - - - public static String getSimpleName(String fqName) { int idx = fqName.lastIndexOf('.'); if (idx >= 0 && idx < fqName.length() - 1) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index 5f4c20ee5c..bf123b23bd 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -10,13 +10,18 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; +import java.util.Queue; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Stream; @@ -394,36 +399,14 @@ public static boolean isAbstractClass(TypeDeclaration typeDeclaration) { } public static ITypeBinding findInTypeHierarchy(ITypeBinding resolvedType, Set typesToCheck) { - ITypeBinding[] interfaces = resolvedType.getInterfaces(); - - for (ITypeBinding resolvedInterface : interfaces) { - String simplifiedType = null; - - if (resolvedInterface.isParameterizedType()) { - simplifiedType = resolvedInterface.getBinaryName(); - } - else { - simplifiedType = resolvedInterface.getQualifiedName(); - } - - if (typesToCheck.contains(simplifiedType)) { - return resolvedInterface; + for (Iterator itr = getSuperTypesIterator(resolvedType); itr.hasNext();) { + ITypeBinding b = itr.next(); + String fqn = b.isParameterizedType() ? b.getBinaryName() : b.getQualifiedName(); + if (typesToCheck.contains(fqn)) { + return b; } - else { - ITypeBinding result = findInTypeHierarchy(resolvedInterface, typesToCheck); - if (result != null) { - return result; - } - } - } - - ITypeBinding superclass = resolvedType.getSuperclass(); - if (superclass != null) { - return findInTypeHierarchy(superclass, typesToCheck); - } - else { - return null; } + return null; } public static Optional getImportsEdit(CompilationUnit cu, Collection imprts, IDocument doc) { @@ -463,40 +446,75 @@ public static Optional getImportsEdit(CompilationUnit cu, Collect // } // public static void findSupertypes(ITypeBinding binding, Set supertypesCollector) { - - // interfaces - ITypeBinding[] interfaces = binding.getInterfaces(); - for (ITypeBinding resolvedInterface : interfaces) { - String simplifiedType = null; - if (resolvedInterface.isParameterizedType()) { - simplifiedType = resolvedInterface.getBinaryName(); - } - else { - simplifiedType = resolvedInterface.getQualifiedName(); - } - - if (simplifiedType != null) { - supertypesCollector.add(simplifiedType); - findSupertypes(resolvedInterface, supertypesCollector); + for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext();) { + supertypesCollector.add(itr.next()); + } + } + + public static boolean isAnyTypeInHierarchy(ITypeBinding binding, Collection typeFqns) { + for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext();) { + String fqn = itr.next(); + if (typeFqns.contains(fqn)) { + return true; } } - - // superclasses - ITypeBinding superclass = binding.getSuperclass(); - if (superclass != null) { - String simplifiedType = null; - if (superclass.isParameterizedType()) { - simplifiedType = superclass.getBinaryName(); + return false; + } + + public static boolean areAllTypesInHierarchy(ITypeBinding binding, Collection typeFqns) { + HashSet notFound = new HashSet<>(typeFqns); + for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext() && !notFound.isEmpty();) { + notFound.remove(itr.next()); + } + return notFound.isEmpty(); + } + + public static Iterator getSuperTypesIterator(ITypeBinding binding) { + final Queue q = new ArrayDeque<>(10); + q.add(binding); + return new Iterator() { + + @Override + public boolean hasNext() { + return !q.isEmpty(); } - else { - simplifiedType = superclass.getQualifiedName(); + + @Override + public ITypeBinding next() { + ITypeBinding t = q.poll(); + if (t == null) { + throw new NoSuchElementException(); + } + for (ITypeBinding b : t.getInterfaces()) { + if (b != null) { + q.add(b); + } + } + if (t.getSuperclass() != null) { + q.add(t.getSuperclass()); + } + return t; } - if (simplifiedType != null) { - supertypesCollector.add(simplifiedType); - findSupertypes(superclass, supertypesCollector); + }; + } + + public static Iterator getSuperTypesFqNamesIterator(ITypeBinding binding) { + Iterator itr = getSuperTypesIterator(binding); + return new Iterator() { + + @Override + public boolean hasNext() { + return itr.hasNext(); } - } + + @Override + public String next() { + ITypeBinding b = itr.next(); + return b.isParameterizedType() ? b.getBinaryName() : b.getQualifiedName(); + } + + }; } public static InjectionPoint[] findInjectionPoints(MethodDeclaration method, TextDocument doc) throws BadLocationException { From 5a0ac21324e4e1308890fb2ef71e04a4288af247 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Tue, 1 Apr 2025 12:30:59 +0200 Subject: [PATCH 2/4] added test case for new ASTUtil methods to iterate through type hierarchies Signed-off-by: aboyko --- .../boot/java/utils/test/ASTUtilsTest.java | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java new file mode 100644 index 0000000000..698b3dcdda --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java @@ -0,0 +1,286 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom, 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: + * Broadcom, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FileASTRequestor; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; + +public class ASTUtilsTest { + + private List createdFiles = new ArrayList<>(); + + private final String projectName = "test-spring-validations"; + + private MavenJavaProject project; + + private Path mySimpleMain; + private Path myComponent; + + + @BeforeEach + void setup() throws Exception { + this.project = ProjectsHarness.INSTANCE.mavenProject(projectName); + createTestFiles(); + } + + @AfterEach + void tearDown() { + clearTestFiles(); + } + + @Test + void testTypeHierarchyIteratorSimpleClass() throws Exception { + runTestsAgainstTypeDeclaration(mySimpleMain, (type) -> { + Iterator iter = ASTUtils.getSuperTypesIterator(type.resolveBinding()); + assertNotNull(iter); + + assertEquals("test.MySimpleMain", iter.next().getQualifiedName()); + assertEquals("java.lang.Object", iter.next().getQualifiedName()); + assertFalse(iter.hasNext()); + }); + } + + @Test + void testSupertypesForSimpleClass() throws Exception { + runTestsAgainstTypeDeclaration(mySimpleMain, (type) -> { + Set supertypes = new HashSet<>(); + ASTUtils.findSupertypes(type.resolveBinding(), supertypes); + + assertEquals(1, supertypes.size()); + assertTrue(supertypes.contains("java.lang.Object")); + }); + } + + @Test + void testIsAnyTypeInHierarchyForSimpleClass() throws Exception { + runTestsAgainstTypeDeclaration(mySimpleMain, (type) -> { + assertTrue(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of("java.lang.Object"))); + assertTrue(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of("java.lang.Object", "java.io.Serializable"))); + assertFalse(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of("java.io.Serializable"))); + assertFalse(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of())); + + assertTrue(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of("test.MySimpleMain"))); + }); + } + + @Test + void testAreAllTypesInHierarchyForSimpleClass() throws Exception { + runTestsAgainstTypeDeclaration(mySimpleMain, (type) -> { + assertTrue(ASTUtils.areAllTypesInHierarchy(type.resolveBinding(), List.of("java.lang.Object"))); + assertFalse(ASTUtils.areAllTypesInHierarchy(type.resolveBinding(), List.of("java.lang.Object", "java.io.Serializable"))); + assertTrue(ASTUtils.areAllTypesInHierarchy(type.resolveBinding(), List.of())); + + assertTrue(ASTUtils.areAllTypesInHierarchy(type.resolveBinding(), List.of("test.MySimpleMain"))); + }); + } + + @Test + void testTypeHierarchyIteratorWithSuperclassesAndInterfaces() throws Exception { + runTestsAgainstTypeDeclaration(myComponent, (type) -> { + Iterator iter = ASTUtils.getSuperTypesIterator(type.resolveBinding()); + assertNotNull(iter); + + assertEquals("test.MyComponent", iter.next().getQualifiedName()); + assertEquals("test.MyInterface", iter.next().getQualifiedName()); + assertEquals("test.MySuperclass", iter.next().getQualifiedName()); + assertEquals("test.MySuperInterface", iter.next().getQualifiedName()); + assertEquals("test.MySuperclassInterface", iter.next().getQualifiedName()); + assertEquals("java.lang.Object", iter.next().getQualifiedName()); + assertFalse(iter.hasNext()); + }); + } + + @Test + void testTypeHierarchyIteratorWithFullyQualifiedTypeNames() throws Exception { + runTestsAgainstTypeDeclaration(myComponent, (type) -> { + Iterator iter = ASTUtils.getSuperTypesFqNamesIterator(type.resolveBinding()); + assertNotNull(iter); + + assertEquals("test.MyComponent", iter.next()); + assertEquals("test.MyInterface", iter.next()); + assertEquals("test.MySuperclass", iter.next()); + assertEquals("test.MySuperInterface", iter.next()); + assertEquals("test.MySuperclassInterface", iter.next()); + assertEquals("java.lang.Object", iter.next()); + assertFalse(iter.hasNext()); + }); + } + + @Test + void testCircularTypeHierarchy() throws Exception { + createFile(projectName, "test", "Start.java", """ + package test; + public class Start extends Third { + } + """); + + createFile(projectName, "test", "Second.java", """ + package test; + public class Second extends Start { + } + """); + + Path third = createFile(projectName, "test", "Third.java", """ + package test; + public class Third extends Second { + } + """); + + runTestsAgainstTypeDeclaration(third, (type) -> { + assertFalse(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of("java.io.Serializable"))); + assertTrue(ASTUtils.isAnyTypeInHierarchy(type.resolveBinding(), List.of("test.Start"))); + assertTrue(ASTUtils.areAllTypesInHierarchy(type.resolveBinding(), List.of("test.Start", "test.Second", "test.Third"))); + }); + + } + + @Test + void testInterfaceAppearsMultipleTimesInHierarchy() throws Exception { + createFile(projectName, "test", "Start.java", """ + package test; + public class Start implements TestInterface { + } + """); + + Path second = createFile(projectName, "test", "Second.java", """ + package test; + public class Second extends Start implements TestInterface { + } + """); + + createFile(projectName, "test", "TestInterface.java", """ + package test; + public interface TestInterface { + } + """); + + runTestsAgainstTypeDeclaration(second, (type) -> { + Iterator iter = ASTUtils.getSuperTypesFqNamesIterator(type.resolveBinding()); + assertNotNull(iter); + + assertEquals("test.Second", iter.next()); + assertEquals("test.TestInterface", iter.next()); + assertEquals("test.Start", iter.next()); +// assertEquals("test.TestInterface", iter.next()); + assertEquals("java.lang.Object", iter.next()); + assertFalse(iter.hasNext()); + }); + + } + + private void runTestsAgainstTypeDeclaration(Path file, Consumer test) throws Exception { + SpringIndexerJava.createParser(this.project, new AnnotationHierarchies(), true).createASTs(new String[] { file.toFile().toString() }, null, new String[0], new FileASTRequestor() { + @Override + public void acceptAST(String sourceFilePath, CompilationUnit cu) { + cu.accept(new ASTVisitor() { + + @Override + public boolean visit(TypeDeclaration type) { + test.accept(type); + return super.visit(type); + } + + }); + } + }, null); + } + + private void createTestFiles() throws Exception { + this.mySimpleMain = createFile(projectName, "test", "MySimpleMain.java", """ + package test; + public class MySimpleMain { + } + """); + + createFile(projectName, "test", "MySuperclass.java", """ + package test; + public class MySuperclass implements MySuperclassInterface { + } + """); + + createFile(projectName, "test", "MySuperclassInterface.java", """ + package test; + public interface MySuperclassInterface { + } + """); + + createFile(projectName, "test", "MyInterface.java", """ + package test; + public interface MyInterface extends MySuperInterface { + } + """); + + createFile(projectName, "test", "MySuperInterface.java", """ + package test; + public interface MySuperInterface { + } + """); + + this.myComponent = createFile(projectName, "test", "MyComponent.java", """ + package test; + import org.springframework.boot.autoconfigure.SpringBootApplication; + + @SpringBootApplication + public class MyComponent extends MySuperclass implements MyInterface { + } + """); + } + + private Path createFile(String projectName, String packageName, String name, String content) throws Exception { + Path projectPath = Paths.get(getClass().getResource("/test-projects/" + projectName).toURI()); + Path filePath = projectPath.resolve("src/main/java").resolve(packageName.replace('.', '/')).resolve(name); + Files.createDirectories(filePath.getParent()); + createdFiles.add(Files.createFile(filePath)); + Files.write(filePath, content.getBytes(StandardCharsets.UTF_8)); + return filePath; + } + + private void clearTestFiles() { + for (Iterator itr = createdFiles.iterator(); itr.hasNext();) { + Path path = itr.next(); + try { + Files.delete(path); + itr.remove(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + +} From a2348e04c1e1109b24d0f7c4ec2421f71eba5ed3 Mon Sep 17 00:00:00 2001 From: aboyko Date: Tue, 1 Apr 2025 10:07:54 -0400 Subject: [PATCH 3/4] Traversal excludes type itself. Is in hierarchy includes type itself Signed-off-by: aboyko --- .../ide/vscode/boot/java/utils/ASTUtils.java | 26 ++++++++++++------- .../boot/java/utils/test/ASTUtilsTest.java | 10 +++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index bf123b23bd..7afd7613d3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -452,6 +452,9 @@ public static void findSupertypes(ITypeBinding binding, Set supertypesCo } public static boolean isAnyTypeInHierarchy(ITypeBinding binding, Collection typeFqns) { + if (typeFqns.contains(binding.isParameterizedType() ? binding.getBinaryName() : binding.getQualifiedName())) { + return true; + } for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext();) { String fqn = itr.next(); if (typeFqns.contains(fqn)) { @@ -463,15 +466,27 @@ public static boolean isAnyTypeInHierarchy(ITypeBinding binding, Collection typeFqns) { HashSet notFound = new HashSet<>(typeFqns); + notFound.remove(binding.isParameterizedType() ? binding.getBinaryName() : binding.getQualifiedName()); for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext() && !notFound.isEmpty();) { notFound.remove(itr.next()); } return notFound.isEmpty(); } + private static void enqueueSuperTypes(Queue q, ITypeBinding t) { + for (ITypeBinding b : t.getInterfaces()) { + if (b != null) { + q.add(b); + } + } + if (t.getSuperclass() != null) { + q.add(t.getSuperclass()); + } + } + public static Iterator getSuperTypesIterator(ITypeBinding binding) { final Queue q = new ArrayDeque<>(10); - q.add(binding); + enqueueSuperTypes(q, binding); return new Iterator() { @Override @@ -485,14 +500,7 @@ public ITypeBinding next() { if (t == null) { throw new NoSuchElementException(); } - for (ITypeBinding b : t.getInterfaces()) { - if (b != null) { - q.add(b); - } - } - if (t.getSuperclass() != null) { - q.add(t.getSuperclass()); - } + enqueueSuperTypes(q, t); return t; } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java index 698b3dcdda..3ba4613376 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java @@ -70,7 +70,7 @@ void testTypeHierarchyIteratorSimpleClass() throws Exception { Iterator iter = ASTUtils.getSuperTypesIterator(type.resolveBinding()); assertNotNull(iter); - assertEquals("test.MySimpleMain", iter.next().getQualifiedName()); +// assertEquals("test.MySimpleMain", iter.next().getQualifiedName()); assertEquals("java.lang.Object", iter.next().getQualifiedName()); assertFalse(iter.hasNext()); }); @@ -116,7 +116,7 @@ void testTypeHierarchyIteratorWithSuperclassesAndInterfaces() throws Exception { Iterator iter = ASTUtils.getSuperTypesIterator(type.resolveBinding()); assertNotNull(iter); - assertEquals("test.MyComponent", iter.next().getQualifiedName()); +// assertEquals("test.MyComponent", iter.next().getQualifiedName()); assertEquals("test.MyInterface", iter.next().getQualifiedName()); assertEquals("test.MySuperclass", iter.next().getQualifiedName()); assertEquals("test.MySuperInterface", iter.next().getQualifiedName()); @@ -132,7 +132,7 @@ void testTypeHierarchyIteratorWithFullyQualifiedTypeNames() throws Exception { Iterator iter = ASTUtils.getSuperTypesFqNamesIterator(type.resolveBinding()); assertNotNull(iter); - assertEquals("test.MyComponent", iter.next()); +// assertEquals("test.MyComponent", iter.next()); assertEquals("test.MyInterface", iter.next()); assertEquals("test.MySuperclass", iter.next()); assertEquals("test.MySuperInterface", iter.next()); @@ -194,10 +194,10 @@ public interface TestInterface { Iterator iter = ASTUtils.getSuperTypesFqNamesIterator(type.resolveBinding()); assertNotNull(iter); - assertEquals("test.Second", iter.next()); +// assertEquals("test.Second", iter.next()); assertEquals("test.TestInterface", iter.next()); assertEquals("test.Start", iter.next()); -// assertEquals("test.TestInterface", iter.next()); + assertEquals("test.TestInterface", iter.next()); assertEquals("java.lang.Object", iter.next()); assertFalse(iter.hasNext()); }); From 759058cc1162b2663e4c152ad71bc389fb44d496 Mon Sep 17 00:00:00 2001 From: aboyko Date: Tue, 1 Apr 2025 11:03:07 -0400 Subject: [PATCH 4/4] Polish up Signed-off-by: aboyko --- .../vscode/boot/java/beans/BeansIndexer.java | 4 +-- .../boot/java/beans/BeansSymbolProvider.java | 4 +-- .../java/beans/ComponentSymbolProvider.java | 13 +++----- ...ConfigurationPropertiesSymbolProvider.java | 4 +-- .../java/beans/FeignClientSymbolProvider.java | 4 +-- .../data/DataRepositorySymbolProvider.java | 4 +-- .../ide/vscode/boot/java/utils/ASTUtils.java | 30 +++++++++++-------- .../boot/java/utils/test/ASTUtilsTest.java | 20 ++++++------- 8 files changed, 35 insertions(+), 48 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java index 5d3597497b..34cddedbe2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -78,8 +77,7 @@ public static void indexBeanMethod(SpringIndexElement parentNode, Annotation nod InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc); - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(beanType, supertypes); + Set supertypes = ASTUtils.findSupertypes(beanType); Collection annotationsOnMethod = ASTUtils.getAnnotations(method); AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java index d5abeae006..342a760957 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Collection; -import java.util.HashSet; import java.util.Set; import org.eclipse.jdt.core.dom.ASTNode; @@ -101,8 +100,7 @@ private void indexFunctionBeans(TypeDeclaration typeDeclaration, SpringIndexerJa context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); ITypeBinding concreteBeanType = typeDeclaration.resolveBinding(); - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(concreteBeanType, supertypes); + Set supertypes = ASTUtils.findSupertypes(concreteBeanType); Collection annotationsOnTypeDeclaration = ASTUtils.getAnnotations(typeDeclaration); AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnTypeDeclaration, doc); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java index 65154df7df..f7dea414f7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -113,8 +112,7 @@ private void createSymbol(TypeDeclaration type, Annotation node, ITypeBinding an InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc); - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(beanType, supertypes); + Set supertypes = ASTUtils.findSupertypes(beanType); Collection annotationsOnType = ASTUtils.getAnnotations(type); @@ -176,8 +174,7 @@ private void createSymbol(RecordDeclaration record, Annotation node, ITypeBindin InjectionPoint[] injectionPoints = DefaultValues.EMPTY_INJECTION_POINTS; - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(beanType, supertypes); + Set supertypes = ASTUtils.findSupertypes(beanType); Collection annotationsOnType = ASTUtils.getAnnotations(record); @@ -321,8 +318,7 @@ public boolean visit(MethodInvocation methodInvocation) { Location location; location = new Location(doc.getUri(), nodeRegion.asRange()); - Set typesFromhierarchy = new HashSet<>(); - ASTUtils.findSupertypes(eventTypeBinding, typesFromhierarchy); + Set typesFromhierarchy = ASTUtils.findSupertypes(eventTypeBinding); EventPublisherIndexElement eventPublisherIndexElement = new EventPublisherIndexElement(eventTypeBinding.getQualifiedName(), location, typesFromhierarchy); component.addChild(eventPublisherIndexElement); @@ -593,8 +589,7 @@ public void createBean(SpringIndexElement parentNode, String beanName, String be context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); InjectionPoint[] injectionPoints = DefaultValues.EMPTY_INJECTION_POINTS; - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(beanTypeBinding, supertypes); + Set supertypes = ASTUtils.findSupertypes(beanTypeBinding); AnnotationMetadata[] annotations = DefaultValues.EMPTY_ANNOTATIONS; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java index 490768424e..71f9b9f975 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java @@ -12,7 +12,6 @@ import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -96,8 +95,7 @@ protected void createSymbolForType(AbstractTypeDeclaration type, Annotation node InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc); - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(typeBinding, supertypes); + Set supertypes = ASTUtils.findSupertypes(typeBinding); Collection annotationsOnType = ASTUtils.getAnnotations(type); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java index 597e9fd29a..56664ed33b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java @@ -12,7 +12,6 @@ import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -82,8 +81,7 @@ private Two createSymbol(Annotation node, ITypeBinding an InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(type, doc); - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(beanType, supertypes); + Set supertypes = ASTUtils.findSupertypes(beanType); Collection annotationsOnType = ASTUtils.getAnnotations(type); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java index 0a878c6b24..26373b8c75 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -81,8 +80,7 @@ public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext ITypeBinding concreteBeanTypeBindung = typeDeclaration.resolveBinding(); - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(concreteBeanTypeBindung, supertypes); + Set supertypes = ASTUtils.findSupertypes(concreteBeanTypeBindung); String concreteRepoType = concreteBeanTypeBindung.getQualifiedName(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java index 7afd7613d3..3371bc0bd9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ASTUtils.java @@ -399,7 +399,7 @@ public static boolean isAbstractClass(TypeDeclaration typeDeclaration) { } public static ITypeBinding findInTypeHierarchy(ITypeBinding resolvedType, Set typesToCheck) { - for (Iterator itr = getSuperTypesIterator(resolvedType); itr.hasNext();) { + for (Iterator itr = getHierarchyTypesBreadthFirstIterator(resolvedType); itr.hasNext();) { ITypeBinding b = itr.next(); String fqn = b.isParameterizedType() ? b.getBinaryName() : b.getQualifiedName(); if (typesToCheck.contains(fqn)) { @@ -445,17 +445,22 @@ public static Optional getImportsEdit(CompilationUnit cu, Collect // return result; // } // - public static void findSupertypes(ITypeBinding binding, Set supertypesCollector) { - for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext();) { + /** + * Returns only the super types without the type itself. + * @param binding + * @return + */ + public static Set findSupertypes(ITypeBinding binding) { + Set supertypesCollector = new HashSet<>(); + for (Iterator itr = getHierarchyTypesFqNamesBreadthFirstIterator(binding); itr.hasNext();) { supertypesCollector.add(itr.next()); } + supertypesCollector.remove(binding.isParameterizedType() ? binding.getBinaryName() : binding.getQualifiedName()); + return supertypesCollector; } public static boolean isAnyTypeInHierarchy(ITypeBinding binding, Collection typeFqns) { - if (typeFqns.contains(binding.isParameterizedType() ? binding.getBinaryName() : binding.getQualifiedName())) { - return true; - } - for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext();) { + for (Iterator itr = getHierarchyTypesFqNamesBreadthFirstIterator(binding); itr.hasNext();) { String fqn = itr.next(); if (typeFqns.contains(fqn)) { return true; @@ -466,8 +471,7 @@ public static boolean isAnyTypeInHierarchy(ITypeBinding binding, Collection typeFqns) { HashSet notFound = new HashSet<>(typeFqns); - notFound.remove(binding.isParameterizedType() ? binding.getBinaryName() : binding.getQualifiedName()); - for (Iterator itr = getSuperTypesFqNamesIterator(binding); itr.hasNext() && !notFound.isEmpty();) { + for (Iterator itr = getHierarchyTypesFqNamesBreadthFirstIterator(binding); itr.hasNext() && !notFound.isEmpty();) { notFound.remove(itr.next()); } return notFound.isEmpty(); @@ -484,9 +488,9 @@ private static void enqueueSuperTypes(Queue q, ITypeBinding t) { } } - public static Iterator getSuperTypesIterator(ITypeBinding binding) { + public static Iterator getHierarchyTypesBreadthFirstIterator(ITypeBinding binding) { final Queue q = new ArrayDeque<>(10); - enqueueSuperTypes(q, binding); + q.add(binding); return new Iterator() { @Override @@ -507,8 +511,8 @@ public ITypeBinding next() { }; } - public static Iterator getSuperTypesFqNamesIterator(ITypeBinding binding) { - Iterator itr = getSuperTypesIterator(binding); + public static Iterator getHierarchyTypesFqNamesBreadthFirstIterator(ITypeBinding binding) { + Iterator itr = getHierarchyTypesBreadthFirstIterator(binding); return new Iterator() { @Override diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java index 3ba4613376..ada92c546e 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/ASTUtilsTest.java @@ -21,7 +21,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -67,10 +66,10 @@ void tearDown() { @Test void testTypeHierarchyIteratorSimpleClass() throws Exception { runTestsAgainstTypeDeclaration(mySimpleMain, (type) -> { - Iterator iter = ASTUtils.getSuperTypesIterator(type.resolveBinding()); + Iterator iter = ASTUtils.getHierarchyTypesBreadthFirstIterator(type.resolveBinding()); assertNotNull(iter); -// assertEquals("test.MySimpleMain", iter.next().getQualifiedName()); + assertEquals("test.MySimpleMain", iter.next().getQualifiedName()); assertEquals("java.lang.Object", iter.next().getQualifiedName()); assertFalse(iter.hasNext()); }); @@ -79,8 +78,7 @@ void testTypeHierarchyIteratorSimpleClass() throws Exception { @Test void testSupertypesForSimpleClass() throws Exception { runTestsAgainstTypeDeclaration(mySimpleMain, (type) -> { - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(type.resolveBinding(), supertypes); + Set supertypes = ASTUtils.findSupertypes(type.resolveBinding()); assertEquals(1, supertypes.size()); assertTrue(supertypes.contains("java.lang.Object")); @@ -113,10 +111,10 @@ void testAreAllTypesInHierarchyForSimpleClass() throws Exception { @Test void testTypeHierarchyIteratorWithSuperclassesAndInterfaces() throws Exception { runTestsAgainstTypeDeclaration(myComponent, (type) -> { - Iterator iter = ASTUtils.getSuperTypesIterator(type.resolveBinding()); + Iterator iter = ASTUtils.getHierarchyTypesBreadthFirstIterator(type.resolveBinding()); assertNotNull(iter); -// assertEquals("test.MyComponent", iter.next().getQualifiedName()); + assertEquals("test.MyComponent", iter.next().getQualifiedName()); assertEquals("test.MyInterface", iter.next().getQualifiedName()); assertEquals("test.MySuperclass", iter.next().getQualifiedName()); assertEquals("test.MySuperInterface", iter.next().getQualifiedName()); @@ -129,10 +127,10 @@ void testTypeHierarchyIteratorWithSuperclassesAndInterfaces() throws Exception { @Test void testTypeHierarchyIteratorWithFullyQualifiedTypeNames() throws Exception { runTestsAgainstTypeDeclaration(myComponent, (type) -> { - Iterator iter = ASTUtils.getSuperTypesFqNamesIterator(type.resolveBinding()); + Iterator iter = ASTUtils.getHierarchyTypesFqNamesBreadthFirstIterator(type.resolveBinding()); assertNotNull(iter); -// assertEquals("test.MyComponent", iter.next()); + assertEquals("test.MyComponent", iter.next()); assertEquals("test.MyInterface", iter.next()); assertEquals("test.MySuperclass", iter.next()); assertEquals("test.MySuperInterface", iter.next()); @@ -191,10 +189,10 @@ public interface TestInterface { """); runTestsAgainstTypeDeclaration(second, (type) -> { - Iterator iter = ASTUtils.getSuperTypesFqNamesIterator(type.resolveBinding()); + Iterator iter = ASTUtils.getHierarchyTypesFqNamesBreadthFirstIterator(type.resolveBinding()); assertNotNull(iter); -// assertEquals("test.Second", iter.next()); + assertEquals("test.Second", iter.next()); assertEquals("test.TestInterface", iter.next()); assertEquals("test.Start", iter.next()); assertEquals("test.TestInterface", iter.next());