Skip to content

Commit 881575b

Browse files
committed
GH-1633: cache spring index elements in a way that makes computing stereotype trees run faster
1 parent 4fcc7bf commit 881575b

File tree

11 files changed

+100
-58
lines changed

11 files changed

+100
-58
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ public class ApplicationModulesLabelProvider implements
2727

2828
private final StereotypeCatalog catalog;
2929
private final IJavaProject project;
30-
private final SpringMetamodelIndex springIndex;
30+
private final CachedSpringMetamodelIndex springIndex;
3131
private final ApplicationModules modules;
3232

33-
public ApplicationModulesLabelProvider(StereotypeCatalog catalog, IJavaProject project, SpringMetamodelIndex springIndex, ApplicationModules modules) {
33+
public ApplicationModulesLabelProvider(StereotypeCatalog catalog, IJavaProject project, CachedSpringMetamodelIndex springIndex, ApplicationModules modules) {
3434
this.catalog = catalog;
3535
this.project = project;
3636
this.springIndex = springIndex;

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

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

1818
import org.jmolecules.stereotype.tooling.Grouped;
1919
import org.jmolecules.stereotype.tooling.StructureProvider.GroupingStructureProvider;
20-
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
2120
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
2221
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeMethodElement;
2322
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
@@ -29,7 +28,7 @@ public class ApplicationModulesNamedInterfacesGroupingProvider extends Applicati
2928

3029
private final ApplicationModules modules;
3130

32-
public ApplicationModulesNamedInterfacesGroupingProvider(ApplicationModules modules, IJavaProject project, SpringMetamodelIndex springIndex) {
31+
public ApplicationModulesNamedInterfacesGroupingProvider(ApplicationModules modules, IJavaProject project, CachedSpringMetamodelIndex springIndex) {
3332
super(project, springIndex);
3433
this.modules = modules;
3534
}
@@ -56,14 +55,14 @@ private Collection<StereotypeClassElement> getClassElements(NamedInterface named
5655
.toList();
5756
}
5857

59-
private StereotypeClassElement findClassElement(String className, IJavaProject project, SpringMetamodelIndex springIndex) {
60-
return springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class).stream()
58+
private StereotypeClassElement findClassElement(String className, IJavaProject project, CachedSpringMetamodelIndex springIndex) {
59+
return springIndex.getClassesForProject(project.getElementName()).stream()
6160
.filter(classElement -> classElement.getType().equals(className))
6261
.findAny().orElse(new StereotypeClassElement(className, null, Set.of(), Set.of()));
6362
}
6463

6564
private Collection<StereotypeClassElement> getInternalTypes(ApplicationModule module) {
66-
return springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class).stream()
65+
return springIndex.getClassesForProject(project.getElementName()).stream()
6766
.filter(classElement -> classElement.getType().startsWith(module.getBasePackage()))
6867
.filter(classElement -> !(module.getNamedInterfaces().stream().filter(namedInterface -> namedInterface.getClasses().contains(classElement.getType())).findAny().isPresent()))
6968
.toList();

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import java.util.Collection;
1414

1515
import org.jmolecules.stereotype.tooling.StructureProvider;
16-
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
1716
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
1817
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeMethodElement;
1918
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
@@ -23,9 +22,9 @@ public abstract class ApplicationModulesStructureProvider
2322
implements StructureProvider<ApplicationModules, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement> {
2423

2524
protected final IJavaProject project;
26-
protected final SpringMetamodelIndex springIndex;
25+
protected final CachedSpringMetamodelIndex springIndex;
2726

28-
public ApplicationModulesStructureProvider(IJavaProject project, SpringMetamodelIndex springIndex) {
27+
public ApplicationModulesStructureProvider(IJavaProject project, CachedSpringMetamodelIndex springIndex) {
2928
this.project = project;
3029
this.springIndex = springIndex;
3130
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
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, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.commands;
12+
13+
import java.util.List;
14+
import java.util.concurrent.ConcurrentHashMap;
15+
import java.util.concurrent.ConcurrentMap;
16+
17+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
18+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
19+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
20+
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
21+
22+
public class CachedSpringMetamodelIndex {
23+
24+
private final SpringMetamodelIndex springIndex;
25+
private final ConcurrentMap<String, ProjectCache> cache;
26+
27+
public CachedSpringMetamodelIndex (SpringMetamodelIndex springIndex) {
28+
this.springIndex = springIndex;
29+
this.cache = new ConcurrentHashMap<>();
30+
}
31+
32+
public List<StereotypeClassElement> getClassesForProject(String projectName) {
33+
return this.cache.computeIfAbsent(projectName, pn -> createProjectCache(pn)).classes;
34+
}
35+
36+
public StereotypePackageElement findPackageNode(String packageName, String projectName) {
37+
return this.cache.computeIfAbsent(projectName, pn -> createProjectCache(pn)).packages.get(packageName);
38+
}
39+
40+
private ProjectCache createProjectCache(String projectName) {
41+
var classes = this.springIndex.getNodesOfType(projectName, StereotypeClassElement.class);
42+
43+
var packages = new ConcurrentHashMap<String, StereotypePackageElement>();
44+
List<StereotypePackageElement> packageNodes = this.springIndex.getNodesOfType(projectName, StereotypePackageElement.class);
45+
for (StereotypePackageElement packageNode : packageNodes) {
46+
packages.put(packageNode.getPackageName(), packageNode);
47+
}
48+
49+
return new ProjectCache(classes, packages);
50+
}
51+
52+
public <T extends SpringIndexElement> List<T> getNodesOfType(String projectName, Class<T> type) {
53+
return springIndex.getNodesOfType(projectName, type);
54+
}
55+
56+
private record ProjectCache(List<StereotypeClassElement> classes, ConcurrentMap<String, StereotypePackageElement> packages) {}
57+
58+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
public class JMoleculesStructureView {
2929

3030
private final AbstractStereotypeCatalog catalog;
31-
private final SpringMetamodelIndex springIndex;
31+
private final CachedSpringMetamodelIndex springIndex;
3232

33-
public JMoleculesStructureView(AbstractStereotypeCatalog catalog, SpringMetamodelIndex springIndex) {
33+
public JMoleculesStructureView(AbstractStereotypeCatalog catalog, CachedSpringMetamodelIndex springIndex) {
3434
this.catalog = catalog;
3535
this.springIndex = springIndex;
3636
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626
public class ModulithStructureView {
2727

2828
private final AbstractStereotypeCatalog catalog;
29-
private final SpringMetamodelIndex springIndex;
29+
private final CachedSpringMetamodelIndex springIndex;
3030
private final ModulithService modulithService;
3131

32-
public ModulithStructureView(AbstractStereotypeCatalog catalog, SpringMetamodelIndex springIndex, ModulithService modulithService) {
32+
public ModulithStructureView(AbstractStereotypeCatalog catalog, CachedSpringMetamodelIndex springIndex, ModulithService modulithService) {
3333
this.catalog = catalog;
3434
this.springIndex = springIndex;
3535
this.modulithService = modulithService;

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,21 @@ public class SpringIndexCommands {
3838

3939
private static final Logger log = LoggerFactory.getLogger(SpringIndexCommands.class);
4040

41-
private final SpringMetamodelIndex springIndex;
4241
private final ModulithService modulithService;
4342
private final StereotypeCatalogRegistry stereotypeCatalogRegistry;
4443

4544
public SpringIndexCommands(SimpleLanguageServer server, SpringMetamodelIndex springIndex, ModulithService modulithService,
4645
JavaProjectFinder projectFinder, StereotypeCatalogRegistry stereotypeCatalogRegistry) {
4746

48-
this.springIndex = springIndex;
4947
this.modulithService = modulithService;
5048
this.stereotypeCatalogRegistry = stereotypeCatalogRegistry;
5149

5250
server.onCommand(SPRING_STRUCTURE_CMD, params -> server.getAsync().invoke(() -> {
5351
StructureCommandArgs args = StructureCommandArgs.parseFrom(params);
5452

53+
CachedSpringMetamodelIndex cachedIndex = new CachedSpringMetamodelIndex(springIndex);
5554
return projectFinder.all().stream()
56-
.map(project -> nodeFrom(project, args.updateMetadata, args.selectedGroups))
55+
.map(project -> nodeFrom(project, cachedIndex, args.updateMetadata, args.selectedGroups))
5756
.filter(Objects::nonNull)
5857
.collect(Collectors.toList());
5958
}));
@@ -75,7 +74,7 @@ private Groups getGroups(IJavaProject project) {
7574
return new Groups(project.getElementName(), groups);
7675
}
7776

78-
private Node nodeFrom(IJavaProject project, boolean updateMetadata, List<String> selectedGroups) {
77+
private Node nodeFrom(IJavaProject project, CachedSpringMetamodelIndex springIndex, boolean updateMetadata, List<String> selectedGroups) {
7978
log.info("create structural view tree information for project: " + project.getElementName());
8079

8180
if (updateMetadata) {
@@ -84,7 +83,7 @@ private Node nodeFrom(IJavaProject project, boolean updateMetadata, List<String>
8483
}
8584

8685
var catalog = stereotypeCatalogRegistry.getCatalogOf(project);
87-
var factory = new IndexBasedStereotypeFactory(catalog, springIndex);
86+
var factory = new IndexBasedStereotypeFactory(catalog, project, springIndex);
8887

8988
if (System.getProperty("enable-source-defined-stereotypes") != null) {
9089
factory.registerStereotypeDefinitions();

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

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.jmolecules.stereotype.catalog.StereotypeGroups;
2525
import org.jmolecules.stereotype.catalog.support.AbstractStereotypeCatalog;
2626
import org.jmolecules.stereotype.tooling.LabelUtils;
27-
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
2827
import org.springframework.ide.vscode.boot.java.Annotations;
2928
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement;
3029
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
@@ -91,7 +90,7 @@ public static String abbreviate(StereotypePackageElement mainApplicationPackage,
9190
}
9291
}
9392

94-
public static String getMethodLabel(IJavaProject project, SpringMetamodelIndex springIndex, StereotypeMethodElement method, StereotypeClassElement clazz) {
93+
public static String getMethodLabel(IJavaProject project, CachedSpringMetamodelIndex springIndex, StereotypeMethodElement method, StereotypeClassElement clazz) {
9594
// TODO: special treatment for methods that have specific index elements with specific labels (e.g. mapping methods)
9695

9796
Optional<RequestMappingIndexElement> mapping = springIndex.getNodesOfType(project.getElementName(), RequestMappingIndexElement.class).stream()
@@ -107,8 +106,8 @@ public static String getMethodLabel(IJavaProject project, SpringMetamodelIndex s
107106

108107
}
109108

110-
public static StereotypePackageElement identifyMainApplicationPackage(IJavaProject project, SpringMetamodelIndex springIndex) {
111-
List<StereotypeClassElement> classNodes = springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class);
109+
public static StereotypePackageElement identifyMainApplicationPackage(IJavaProject project, CachedSpringMetamodelIndex springIndex) {
110+
List<StereotypeClassElement> classNodes = springIndex.getClassesForProject(project.getElementName());
112111

113112
Optional<StereotypePackageElement> packageElement = classNodes.stream()
114113
.filter(node -> node.isAnnotatedWith(Annotations.BOOT_APP))
@@ -128,19 +127,9 @@ public static String getPackage(String fullyQualifiedClassName) {
128127
return ModulithService.getPackageNameFromTypeFQName(fullyQualifiedClassName);
129128
}
130129

131-
public static StereotypePackageElement findPackageNode(String packageName, IJavaProject project, SpringMetamodelIndex springIndex) {
132-
List<StereotypePackageElement> packageNodes = springIndex.getNodesOfType(project.getElementName(), StereotypePackageElement.class);
133-
134-
Optional<StereotypePackageElement> found = packageNodes.stream()
135-
.filter(packageNode -> packageNode.getPackageName().equals(packageName))
136-
.findAny();
137-
138-
if (found.isPresent()) {
139-
return found.get();
140-
}
141-
else {
142-
return new StereotypePackageElement(packageName, null);
143-
}
130+
public static StereotypePackageElement findPackageNode(String packageName, IJavaProject project, CachedSpringMetamodelIndex springIndex) {
131+
StereotypePackageElement packageElement = springIndex.findPackageNode(packageName, project.getElementName());
132+
return packageElement != null ? packageElement : new StereotypePackageElement(packageName, null);
144133
}
145134

146135
private static final List<String> EXCLUSIONS = List.of("Application", "Properties", "Mappings", "Hints");

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import java.util.List;
1515

1616
import org.jmolecules.stereotype.tooling.StructureProvider.SimpleStructureProvider;
17-
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
1817
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
1918
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeMethodElement;
2019
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
@@ -23,10 +22,10 @@
2322
public class ToolsStructureProvider implements
2423
SimpleStructureProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement> {
2524

26-
private final SpringMetamodelIndex springIndex;
25+
private final CachedSpringMetamodelIndex springIndex;
2726
private final IJavaProject project;
2827

29-
ToolsStructureProvider(SpringMetamodelIndex springIndex, IJavaProject project) {
28+
public ToolsStructureProvider(CachedSpringMetamodelIndex springIndex, IJavaProject project) {
3029
this.springIndex = springIndex;
3130
this.project = project;
3231
}
@@ -43,7 +42,7 @@ public Collection<StereotypeMethodElement> extractMethods(StereotypeClassElement
4342

4443
@Override
4544
public Collection<StereotypeClassElement> extractTypes(StereotypePackageElement pkg) {
46-
return springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class).stream()
45+
return springIndex.getClassesForProject(project.getElementName()).stream()
4746
.filter(element -> element.getType().startsWith(pkg.getPackageName()))
4847
.toList();
4948
}

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

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

1313
import java.util.ArrayList;
1414
import java.util.Collection;
15-
import java.util.Optional;
1615
import java.util.TreeSet;
1716

1817
import org.jmolecules.stereotype.api.Stereotype;
@@ -23,20 +22,24 @@
2322
import org.jmolecules.stereotype.catalog.support.AbstractStereotypeCatalog;
2423
import org.jmolecules.stereotype.catalog.support.StereotypeDetector.AnalysisLevel;
2524
import org.jmolecules.stereotype.catalog.support.StereotypeMatcher;
26-
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
25+
import org.springframework.ide.vscode.boot.java.commands.CachedSpringMetamodelIndex;
26+
import org.springframework.ide.vscode.commons.java.IJavaProject;
2727

2828
public class IndexBasedStereotypeFactory implements StereotypeFactory<StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement> {
2929

3030
private final AbstractStereotypeCatalog catalog;
31-
private final SpringMetamodelIndex springIndex;
31+
private final IJavaProject project;
32+
33+
private final CachedSpringMetamodelIndex springIndex;
3234

3335
private static final StereotypeMatcher<StereotypeClassElement, StereotypeAnnotatedElement> STEREOTYPE_MATCHER = StereotypeMatcher
3436
.<StereotypeClassElement, StereotypeAnnotatedElement> isAnnotatedWith((element, fqn) -> isAnnotated(element, fqn))
3537
.orImplements((type, fqn) -> doesImplement(type, fqn));
3638

3739

38-
public IndexBasedStereotypeFactory(AbstractStereotypeCatalog catalog, SpringMetamodelIndex springIndex) {
40+
public IndexBasedStereotypeFactory(AbstractStereotypeCatalog catalog, IJavaProject project, CachedSpringMetamodelIndex springIndex) {
3941
this.catalog = catalog;
42+
this.project = project;
4043
this.springIndex = springIndex;
4144
}
4245

@@ -57,7 +60,7 @@ public Stereotypes fromMethod(StereotypeMethodElement method) {
5760
}
5861

5962
public void registerStereotypeDefinitions() {
60-
springIndex.getNodesOfType(StereotypeDefinitionElement.class).stream()
63+
springIndex.getNodesOfType(project.getElementName(), StereotypeDefinitionElement.class).stream()
6164
.forEach(element -> registerStereotype(element));
6265
}
6366

@@ -126,12 +129,7 @@ private static boolean doesImplement(StereotypeClassElement type, String fqn) {
126129

127130
private StereotypePackageElement findPackageFor(StereotypeClassElement type) {
128131
String packageName = type.getType().substring(0, type.getType().lastIndexOf('.'));
129-
130-
Optional<StereotypePackageElement> result = springIndex.getNodesOfType(StereotypePackageElement.class).stream()
131-
.filter(pkg -> pkg.getPackageName().equals(packageName))
132-
.findFirst();
133-
134-
return result.isPresent() ? result.get() : null;
132+
return springIndex.findPackageNode(packageName, this.project.getElementName());
135133
}
136134

137135
private void registerStereotype(StereotypeDefinitionElement element) {

0 commit comments

Comments
 (0)