Skip to content

Commit 49b4bca

Browse files
committed
GH-1675: specifically index spring boot application annotated type declarations to identify root packages exactly
1 parent 2548d85 commit 49b4bca

File tree

11 files changed

+260
-57
lines changed

11 files changed

+260
-57
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,9 @@ JavaDefinitionHandler javaDefinitionHandler(SimpleLanguageServer server, Compila
430430

431431
@Bean
432432
ModulithService modulithService(SimpleLanguageServer server, JavaProjectFinder projectFinder,
433-
ProjectObserver projectObserver, SpringSymbolIndex springIndex,
434-
BootJavaReconcileEngine reconciler,
435-
BootJavaConfig config) {
436-
return new ModulithService(server, projectFinder, projectObserver, springIndex, reconciler, config);
433+
ProjectObserver projectObserver, SpringSymbolIndex springIndexer, SpringMetamodelIndex springIndex,
434+
BootJavaReconcileEngine reconciler, BootJavaConfig config) {
435+
return new ModulithService(server, projectFinder, projectObserver, springIndexer, springIndex, reconciler, config);
437436
}
438437

439438
@Bean

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.jdt.core.dom.ASTVisitor;
2323
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
2424
import org.eclipse.jdt.core.dom.Annotation;
25+
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
2526
import org.eclipse.jdt.core.dom.Block;
2627
import org.eclipse.jdt.core.dom.Expression;
2728
import org.eclipse.jdt.core.dom.IMethodBinding;
@@ -81,6 +82,9 @@ public void addSymbols(Annotation node, ITypeBinding annotationType, Collection<
8182
else if (node != null && node.getParent() != null && node.getParent() instanceof RecordDeclaration record) {
8283
createSymbol(record, node, annotationType, metaAnnotations, context, doc);
8384
}
85+
else if (node != null && node.getParent() != null && node.getParent() instanceof AnnotationTypeDeclaration annotationDeclaration) {
86+
createSymbol(annotationDeclaration, node, annotationType, metaAnnotations, context, doc);
87+
}
8488
else if (Annotations.NAMED_ANNOTATIONS.contains(annotationType.getQualifiedName())) {
8589
WorkspaceSymbol symbol = DefaultSymbolProvider.provideDefaultSymbol(node, doc);
8690
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
@@ -150,6 +154,8 @@ private void createSymbol(TypeDeclaration type, Annotation node, ITypeBinding an
150154
indexConfigurationProperties(beanDefinition, type, context, doc);
151155
indexBeanRegistrarImplementation(beanDefinition, type, context, doc);
152156
indexWebConfig(beanDefinition, type, context, doc);
157+
158+
SpringBootApplicationIndexer.createIndexElement(type, annotationType, metaAnnotations, context);
153159

154160
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol));
155161
context.getGeneratedIndexElements().add(new CachedIndexElement(context.getDocURI(), beanDefinition));
@@ -195,6 +201,12 @@ private void createSymbol(RecordDeclaration record, Annotation node, ITypeBindin
195201
context.getGeneratedIndexElements().add(new CachedIndexElement(context.getDocURI(), beanDefinition));
196202
}
197203

204+
private void createSymbol(AnnotationTypeDeclaration annotationDeclaration, Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations,
205+
SpringIndexerJavaContext context, TextDocument doc) {
206+
207+
SpringBootApplicationIndexer.createIndexElement(annotationDeclaration, annotationType, metaAnnotations, context);
208+
}
209+
198210
private void indexConfigurationProperties(Bean beanDefinition, AbstractTypeDeclaration type, SpringIndexerJavaContext context, TextDocument doc) {
199211
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type);
200212

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.beans;
12+
13+
import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement;
14+
15+
public class SpringBootApplicationIndexElement extends AbstractSpringIndexElement {
16+
17+
private final String packageName;
18+
private final String typeName;
19+
private final boolean isClassDeclaration;
20+
private final boolean isAnnotationDeclaration;
21+
22+
public SpringBootApplicationIndexElement(String packageName, String typeName, boolean isClassDeclaration, boolean isAnnotationDeclaration) {
23+
this.packageName = packageName;
24+
this.typeName = typeName;
25+
this.isClassDeclaration = isClassDeclaration;
26+
this.isAnnotationDeclaration = isAnnotationDeclaration;
27+
}
28+
29+
public String getPackageName() {
30+
return packageName;
31+
}
32+
33+
public String getTypeName() {
34+
return typeName;
35+
}
36+
37+
public boolean isClassDeclaration() {
38+
return isClassDeclaration;
39+
}
40+
41+
public boolean isAnnotationDeclaration() {
42+
return isAnnotationDeclaration;
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.beans;
12+
13+
import java.util.Collection;
14+
15+
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
16+
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
17+
import org.eclipse.jdt.core.dom.ITypeBinding;
18+
import org.eclipse.jdt.core.dom.TypeDeclaration;
19+
import org.springframework.ide.vscode.boot.java.Annotations;
20+
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
21+
22+
public class SpringBootApplicationIndexer {
23+
24+
private static boolean isSpringBootApplicationType(ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations) {
25+
if (Annotations.BOOT_APP.equals(annotationType.getQualifiedName())) {
26+
return true;
27+
}
28+
else {
29+
return metaAnnotations.stream()
30+
.map(type -> type.getQualifiedName())
31+
.filter(typeName -> Annotations.BOOT_APP.equals(typeName))
32+
.findAny().isPresent();
33+
}
34+
}
35+
36+
public static void createIndexElement(AbstractTypeDeclaration type, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context) {
37+
38+
if (isSpringBootApplicationType(annotationType, metaAnnotations)) {
39+
ITypeBinding binding = type.resolveBinding();
40+
String packageName = binding.getPackage().getName();
41+
String typeName = binding.getName();
42+
43+
boolean classDef = type instanceof TypeDeclaration;
44+
boolean annotationDef = type instanceof AnnotationTypeDeclaration;
45+
46+
SpringBootApplicationIndexElement mainIndexElement = new SpringBootApplicationIndexElement(packageName, typeName, classDef, annotationDef);
47+
context.getGeneratedIndexElements().add(new CachedIndexElement(context.getDocURI(), mainIndexElement));
48+
}
49+
}
50+
51+
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/commands/CachedSpringMetamodelIndex.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.concurrent.ConcurrentMap;
1616

1717
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
18+
import org.springframework.ide.vscode.boot.java.beans.SpringBootApplicationIndexElement;
1819
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
1920
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
2021
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
@@ -38,6 +39,10 @@ public StereotypePackageElement findPackageNode(String packageName, String proje
3839
return this.cache.computeIfAbsent(projectName, pn -> createProjectCache(pn)).packages.get(packageName);
3940
}
4041

42+
public List<SpringBootApplicationIndexElement> getSpringBootApplicationElementsForProject(String projectName) {
43+
return this.cache.computeIfAbsent(projectName, pn -> createProjectCache(pn)).springBootAppElements;
44+
}
45+
4146
private ProjectCache createProjectCache(String projectName) {
4247
var classes = this.springIndex.getNodesOfType(projectName, StereotypeClassElement.class);
4348

@@ -47,7 +52,9 @@ private ProjectCache createProjectCache(String projectName) {
4752
packages.put(packageNode.getPackageName(), packageNode);
4853
}
4954

50-
return new ProjectCache(classes, packages);
55+
var springBootAppElements = this.springIndex.getNodesOfType(projectName, SpringBootApplicationIndexElement.class);
56+
57+
return new ProjectCache(classes, packages, springBootAppElements);
5158
}
5259

5360
public <T extends SpringIndexElement> List<T> getNodesOfType(String projectName, Class<T> type) {
@@ -58,6 +65,7 @@ public Bean[] getBeansOfDocument(String docUri) {
5865
return springIndex.getBeansOfDocument(docUri);
5966
}
6067

61-
private record ProjectCache(List<StereotypeClassElement> classes, ConcurrentMap<String, StereotypePackageElement> packages) {}
68+
private record ProjectCache(List<StereotypeClassElement> classes, ConcurrentMap<String, StereotypePackageElement> packages,
69+
List<SpringBootApplicationIndexElement> springBootAppElements) {}
6270

6371
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/commands/StructureViewUtil.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import java.util.ArrayList;
1414
import java.util.Collection;
15+
import java.util.Comparator;
1516
import java.util.List;
1617
import java.util.Optional;
1718
import java.util.function.Function;
@@ -26,6 +27,7 @@
2627
import org.jmolecules.stereotype.catalog.support.AbstractStereotypeCatalog;
2728
import org.jmolecules.stereotype.tooling.LabelUtils;
2829
import org.springframework.ide.vscode.boot.java.Annotations;
30+
import org.springframework.ide.vscode.boot.java.beans.SpringBootApplicationIndexElement;
2931
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement;
3032
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
3133
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeMethodElement;
@@ -108,11 +110,22 @@ public static String getMethodLabel(IJavaProject project, CachedSpringMetamodelI
108110
}
109111

110112
public static StereotypePackageElement identifyMainApplicationPackage(IJavaProject project, CachedSpringMetamodelIndex springIndex) {
111-
List<StereotypeClassElement> classNodes = springIndex.getClassesForProject(project.getElementName());
113+
List<SpringBootApplicationIndexElement> mainAppNodes = springIndex.getSpringBootApplicationElementsForProject(project.getElementName());
112114

113-
Optional<StereotypePackageElement> packageElement = classNodes.stream()
114-
.filter(node -> node.isAnnotatedWith(Annotations.BOOT_APP))
115-
.map(node -> getPackage(node.getType()))
115+
Optional<StereotypePackageElement> packageElement = mainAppNodes.stream()
116+
.sorted(new Comparator<SpringBootApplicationIndexElement>() {
117+
@Override
118+
public int compare(SpringBootApplicationIndexElement o1, SpringBootApplicationIndexElement o2) {
119+
if (o1.isClassDeclaration() && o2.isClassDeclaration()) return 0;
120+
if (o1.isAnnotationDeclaration() && o2.isAnnotationDeclaration()) return 0;
121+
122+
if (o1.isClassDeclaration() && o2.isAnnotationDeclaration()) return -1;
123+
if (o1.isAnnotationDeclaration() && o2.isClassDeclaration()) return 1;
124+
125+
return 0;
126+
}
127+
})
128+
.map(element -> element.getPackageName())
116129
.map(packageName -> findPackageNode(packageName, project, springIndex))
117130
.findFirst();
118131

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class SpringIndexerJava implements SpringIndexer {
9595

9696
// whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid,
9797
// we need to change the generation - this will result in a re-indexing due to no up-to-date cache data being found
98-
private static final String GENERATION = "GEN-32";
98+
private static final String GENERATION = "GEN-33";
9999
private static final String INDEX_FILES_TASK_ID = "index-java-source-files-task-";
100100

101101
private static final String SYMBOL_KEY = "symbols";

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/modulith/ModulithService.java

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import java.nio.file.Paths;
2020
import java.time.Duration;
2121
import java.util.ArrayList;
22-
import java.util.Arrays;
2322
import java.util.Collections;
24-
import java.util.HashSet;
2523
import java.util.List;
2624
import java.util.Map;
2725
import java.util.Objects;
@@ -42,8 +40,9 @@
4240
import org.slf4j.LoggerFactory;
4341
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
4442
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
45-
import org.springframework.ide.vscode.boot.java.Annotations;
43+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
4644
import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType;
45+
import org.springframework.ide.vscode.boot.java.beans.SpringBootApplicationIndexElement;
4746
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
4847
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
4948
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -53,7 +52,6 @@
5352
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemCategory.Toggle.Option;
5453
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
5554
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
56-
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
5755
import org.springframework.ide.vscode.commons.protocol.spring.BeansParams;
5856
import org.springframework.ide.vscode.commons.util.text.TextDocument;
5957

@@ -80,10 +78,11 @@ public class ModulithService {
8078
private final ProjectObserver projectObserver;
8179
private final JavaProjectFinder projectFinder;
8280

83-
private SimpleLanguageServer server;
84-
private SpringSymbolIndex springIndex;
85-
private BootJavaReconcileEngine reconciler;
86-
private BootJavaConfig config;
81+
private final SimpleLanguageServer server;
82+
private final SpringSymbolIndex springIndexer;
83+
private final SpringMetamodelIndex springIndex;
84+
private final BootJavaReconcileEngine reconciler;
85+
private final BootJavaConfig config;
8786
private boolean autoTrackingProjects;
8887

8988
private Map<URI, AppModules> cache;
@@ -94,7 +93,8 @@ public ModulithService(
9493
SimpleLanguageServer server,
9594
JavaProjectFinder projectFinder,
9695
ProjectObserver projectObserver,
97-
SpringSymbolIndex springIndex,
96+
SpringSymbolIndex springIndexer,
97+
SpringMetamodelIndex springIndex,
9898
BootJavaReconcileEngine reconciler,
9999
BootJavaConfig config
100100
) {
@@ -105,6 +105,7 @@ public ModulithService(
105105
this.metadataRequested = new ConcurrentHashMap<>();
106106
this.classFilesListeners = new ConcurrentHashMap<>();
107107
this.server = server;
108+
this.springIndexer = springIndexer;
108109
this.springIndex = springIndex;
109110
this.reconciler = reconciler;
110111
this.executor = Executors.newCachedThreadPool();
@@ -313,7 +314,7 @@ private void validate(IJavaProject project) {
313314
}
314315
}
315316
String[] uris = fileUriToUpdate.toArray(new String[fileUriToUpdate.size()]);
316-
springIndex.deleteDocuments(uris).thenAccept(v -> springIndex.updateDocuments(uris, "Modulith Metadata Changed"));
317+
springIndexer.deleteDocuments(uris).thenAccept(v -> springIndexer.updateDocuments(uris, "Modulith Metadata Changed"));
317318
}
318319
}
319320

@@ -378,31 +379,57 @@ private List<AppModule> computeAppModules(String projectName, String javaCmd,
378379
return Collections.emptyList();
379380
}
380381

382+
// private CompletableFuture<Set<String>> findRootPackages(IJavaProject project, Duration delay) {
383+
// return CompletableFuture.supplyAsync(() -> {
384+
// Set<String> rootPackages = springIndex.getNodesOfType(project.getElementName(), SpringBootApplicationIndexElement.class)
385+
// .stream()
386+
// .map(element -> element.getPackageName())
387+
// .collect(Collectors.toSet());
388+
//
389+
// return rootPackages;
390+
// }, CompletableFuture.delayedExecutor(delay.toSeconds(), TimeUnit.SECONDS, executor));
391+
// }
392+
393+
381394
private CompletableFuture<Set<String>> findRootPackages(IJavaProject project, Duration delay) {
382395
return CompletableFuture.supplyAsync(() -> {
383396
BeansParams params = new BeansParams();
384397
params.setProjectName(project.getElementName());
385398
return params;
386399
}, CompletableFuture.delayedExecutor(delay.toSeconds(), TimeUnit.SECONDS, executor))
387-
.thenComposeAsync(params -> springIndex.beans(params), executor)
400+
.thenComposeAsync(params -> springIndexer.beans(params), executor)
388401
.thenApply(beansOfProject -> {
389-
HashSet<String> packages = new HashSet<>();
390-
if (beansOfProject != null) {
391-
for (Bean bean : beansOfProject) {
392-
String beanType = bean.getType();
393-
if (beanType != null) {
394-
if (Arrays.stream(bean.getAnnotations())
395-
.map(annotation -> annotation.getAnnotationType())
396-
.anyMatch(Annotations.BOOT_APP::equals)) {
397-
packages.add(getPackageNameFromTypeFQName(beanType));
398-
}
399-
}
400-
}
401-
}
402-
return packages;
402+
// HashSet<String> packages = new HashSet<>();
403+
// if (beansOfProject != null) {
404+
// for (Bean bean : beansOfProject) {
405+
// String beanType = bean.getType();
406+
// if (beanType != null) {
407+
// if (Arrays.stream(bean.getAnnotations())
408+
// .map(annotation -> annotation.getAnnotationType())
409+
// .anyMatch(Annotations.BOOT_APP::equals)) {
410+
// packages.add(getPackageNameFromTypeFQName(beanType));
411+
// }
412+
// }
413+
// }
414+
// }
415+
// return packages;
416+
417+
Set<String> rootPackages = springIndex.getNodesOfType(project.getElementName(), SpringBootApplicationIndexElement.class)
418+
.stream()
419+
.map(element -> element.getPackageName())
420+
.collect(Collectors.toSet());
421+
422+
return rootPackages;
423+
403424
});
404425
}
405426

427+
428+
429+
430+
431+
432+
406433
public static String getPackageNameFromTypeFQName(String fqn) {
407434
int idx = 0;
408435
for (; idx < fqn.length() - 1; idx++) {

0 commit comments

Comments
 (0)