Skip to content

Commit c6e5a39

Browse files
committed
Simple jmolecules stereotype view
1 parent cf59c8a commit c6e5a39

File tree

9 files changed

+355
-15
lines changed

9 files changed

+355
-15
lines changed

headless-services/spring-boot-language-server/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
<artifactId>org.json</artifactId>
110110
<version>1.0.0-SNAPSHOT</version>
111111
</dependency>
112+
<dependency>
113+
<groupId>com.jayway.jsonpath</groupId>
114+
<artifactId>json-path</artifactId>
115+
</dependency>
112116
<dependency>
113117
<groupId>org.springframework.ide.vscode</groupId>
114118
<artifactId>java-properties</artifactId>

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
1616
import org.springframework.ide.vscode.boot.java.commands.SpringIndexCommands;
1717
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects;
18+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeCatalogRegistry;
1819
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
1920
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
2021

@@ -25,9 +26,10 @@ public class CommandsConfig {
2526
return new WorkspaceBootExecutableProjects(server, projectFinder, symbolIndex);
2627
}
2728

28-
@Bean SpringIndexCommands springIndexCommands(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringMetamodelIndex symbolIndex) {
29-
return new SpringIndexCommands(server, symbolIndex, projectFinder);
30-
}
31-
29+
@Bean
30+
SpringIndexCommands springIndexCommands(SimpleLanguageServer server, JavaProjectFinder projectFinder,
31+
SpringMetamodelIndex symbolIndex, StereotypeCatalogRegistry stereotypeCatalogRegistry) {
32+
return new SpringIndexCommands(server, symbolIndex, projectFinder, stereotypeCatalogRegistry);
33+
}
3234

3335
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ public <T extends SpringIndexElement> List<T> getNodesOfType(Class<T> type) {
8181
return getNodesOfType(type, rootNodes);
8282
}
8383

84+
public <T extends SpringIndexElement> List<T> getNodesOfType(String projectName, Class<T> type) {
85+
ProjectElement project = this.projectRootElements.get(projectName);
86+
return project == null ? List.of() : getNodesOfType(type, List.of(project));
87+
}
88+
8489
public Bean[] getBeans() {
8590
List<SpringIndexElement> rootNodes = new ArrayList<SpringIndexElement>(this.projectRootElements.values());
8691
return getNodesOfType(Bean.class, rootNodes).toArray(Bean[]::new);
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,93 @@
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+
*******************************************************************************/
111
package org.springframework.ide.vscode.boot.java.commands;
212

13+
import java.util.Collection;
14+
import java.util.List;
15+
import java.util.Objects;
16+
import java.util.function.BiConsumer;
17+
import java.util.stream.Collectors;
18+
19+
import org.jmolecules.stereotype.tooling.HierarchicalNodeHandler;
20+
import org.jmolecules.stereotype.tooling.ProjectTree;
21+
import org.jmolecules.stereotype.tooling.SimpleLabelProvider;
22+
import org.jmolecules.stereotype.tooling.StructureProvider.SimpleStructureProvider;
323
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
24+
import org.springframework.ide.vscode.boot.java.commands.ToolsJsonNodeHandler.Node;
25+
import org.springframework.ide.vscode.boot.java.stereotypes.IndexBasedStereotypeFactory;
26+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeCatalogRegistry;
27+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
28+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeMethodElement;
29+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
30+
import org.springframework.ide.vscode.commons.java.IJavaProject;
431
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
532
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
633

734
public class SpringIndexCommands {
835

936
private static final String SPRING_STRUCTURE_CMD = "sts/spring-boot/structure";
37+
private static final String SPRING_STRUCTURE_CMD_2 = "sts/spring-boot/structure2";
1038

11-
public SpringIndexCommands(SimpleLanguageServer server, SpringMetamodelIndex metamodelIndex, JavaProjectFinder projectFinder) {
12-
server.onCommand(SPRING_STRUCTURE_CMD, params -> server.getAsync().invoke(() -> metamodelIndex.getProjects()));
39+
public SpringIndexCommands(SimpleLanguageServer server, SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, StereotypeCatalogRegistry stereotypeCatalogRegistry) {
40+
server.onCommand(SPRING_STRUCTURE_CMD, params -> server.getAsync().invoke(() -> springIndex.getProjects()));
41+
server.onCommand(SPRING_STRUCTURE_CMD_2, params -> server.getAsync().invoke(() -> {
42+
return projectFinder.all().stream().map(project -> nodeFrom(stereotypeCatalogRegistry, springIndex, project)).filter(Objects::nonNull).collect(Collectors.toList());
43+
}));
1344
}
45+
46+
private Node nodeFrom(StereotypeCatalogRegistry stereotypeCatalogRegistry, SpringMetamodelIndex springIndex, IJavaProject project) {
47+
var catalog = stereotypeCatalogRegistry.getCatalogOf(project);
48+
var factory = new IndexBasedStereotypeFactory(catalog, springIndex);
49+
factory.registerStereotypeDefinitions();
50+
51+
var labels = SimpleLabelProvider.forPackage(StereotypePackageElement::getPackageName, StereotypeClassElement::getType,
52+
(StereotypeMethodElement m, StereotypeClassElement __) -> m.getMethodName(), Object::toString);
53+
54+
SimpleStructureProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement> structureProvider =
55+
new SimpleStructureProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement>() {
56+
57+
@Override
58+
public Collection<StereotypePackageElement> extractPackages(StereotypePackageElement pkg) {
59+
return springIndex.getNodesOfType(project.getElementName(), StereotypePackageElement.class);
60+
}
1461

62+
@Override
63+
public Collection<StereotypeMethodElement> extractMethods(StereotypeClassElement type) {
64+
return List.of();
65+
}
66+
67+
@Override
68+
public Collection<StereotypeClassElement> extractTypes(StereotypePackageElement pkg) {
69+
return springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class).stream()
70+
.filter(element -> element.getType().startsWith(pkg.getPackageName()))
71+
.toList();
72+
}
73+
};
74+
75+
// json output
76+
BiConsumer<Node, Object> consumer = (node, c) -> {
77+
node.withAttribute(HierarchicalNodeHandler.TEXT, labels.getCustomLabel(c))
78+
.withAttribute("icon", "fa-named-interface");
79+
};
80+
81+
var jsonHandler = new ToolsJsonNodeHandler(labels, consumer);
82+
83+
var jsonTree = new ProjectTree<>(factory, catalog, jsonHandler)
84+
.withStructureProvider(structureProvider)
85+
.withGrouper("org.jmolecules.architecture")
86+
.withGrouper("org.jmolecules.ddd", "org.jmolecules.event", "spring", "jpa", "java");
87+
88+
jsonTree.process(new StereotypePackageElement(project.getElementName(), null));
89+
90+
return jsonHandler.getRoot();
91+
}
92+
1593
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ide.vscode.boot.java.commands;
18+
19+
import java.util.ArrayList;
20+
import java.util.LinkedHashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.function.BiConsumer;
24+
import java.util.function.Consumer;
25+
26+
import org.eclipse.lsp4j.Location;
27+
import org.eclipse.lsp4j.Range;
28+
import org.jmolecules.stereotype.api.Stereotype;
29+
import org.jmolecules.stereotype.tooling.LabelProvider;
30+
import org.jmolecules.stereotype.tooling.MethodNodeContext;
31+
import org.jmolecules.stereotype.tooling.NodeContext;
32+
import org.jmolecules.stereotype.tooling.NodeHandler;
33+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeClassElement;
34+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypeMethodElement;
35+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
36+
37+
import com.google.gson.Gson;
38+
import com.google.gson.GsonBuilder;
39+
40+
/**
41+
* @author Oliver Drotbohm
42+
* @author Martin Lippert
43+
*/
44+
class ToolsJsonNodeHandler implements NodeHandler<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement, Object> {
45+
46+
public static final String ICON = "icon";
47+
public static final String TEXT = "text";
48+
49+
private final Node root;
50+
private final LabelProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement, Object> labels;
51+
private final BiConsumer<Node, Object> customHandler;
52+
private Node current;
53+
54+
public ToolsJsonNodeHandler(LabelProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement, Object> labels, BiConsumer<Node, Object> customHandler) {
55+
this.labels = labels;
56+
this.root = new Node(null);
57+
this.customHandler = customHandler;
58+
this.current = root;
59+
}
60+
61+
/*
62+
* (non-Javadoc)
63+
* @see org.jmolecules.stereotype.tooling.NodeHandler#handleApplication(java.lang.Object)
64+
*/
65+
@Override
66+
public void handleApplication(StereotypePackageElement application) {
67+
this.root
68+
.withAttribute(TEXT, labels.getApplicationLabel(application))
69+
.withAttribute(ICON, "fa-application");
70+
}
71+
72+
/*
73+
* (non-Javadoc)
74+
* @see org.jmolecules.stereotype.tooling.NodeHandler#handlePackage(java.lang.Object, org.jmolecules.stereotype.tooling.NodeContext)
75+
*/
76+
@Override
77+
public void handlePackage(StereotypePackageElement pkg, NodeContext context) {
78+
79+
addChild(node -> node
80+
.withAttribute(ICON, "fa-package")
81+
.withAttribute(TEXT, labels.getPackageLabel(pkg)));
82+
}
83+
84+
/*
85+
* (non-Javadoc)
86+
* @see org.jmolecules.stereotype.tooling.NodeHandler#handleStereotype(org.jmolecules.stereotype.api.Stereotype, org.jmolecules.stereotype.tooling.NestingLevel)
87+
*/
88+
@Override
89+
public void handleStereotype(Stereotype stereotype, NodeContext context) {
90+
91+
addChild(node -> node.withAttribute(ICON, "fa-stereotype")
92+
.withAttribute(TEXT, labels.getSterotypeLabel(stereotype)));
93+
}
94+
95+
/*
96+
* (non-Javadoc)
97+
* @see org.jmolecules.stereotype.tooling.NodeHandler#handleType(java.lang.Object, org.jmolecules.stereotype.tooling.NestingLevel)
98+
*/
99+
@Override
100+
public void handleType(StereotypeClassElement type, NodeContext context) {
101+
addChild(node -> node
102+
.withAttribute(TEXT, labels.getTypeLabel(type))
103+
.withAttribute("location", new Location("sampleUri", new Range()))
104+
);
105+
}
106+
107+
public Node createNested() {
108+
return new Node(this.current);
109+
}
110+
111+
/*
112+
* (non-Javadoc)
113+
* @see org.jmolecules.stereotype.tooling.NodeHandler#handleMethod(java.lang.Object, org.jmolecules.stereotype.tooling.NestingLevel, boolean)
114+
*/
115+
@Override
116+
public void handleMethod(StereotypeMethodElement method, MethodNodeContext<StereotypeClassElement> context) {
117+
addChildFoo(node -> node.withAttribute("title", labels.getMethodLabel(method, context.getContextualType())));
118+
}
119+
120+
/*
121+
* (non-Javadoc)
122+
* @see org.jmolecules.stereotype.tooling.NodeHandler#handleCustom(java.lang.Object, org.jmolecules.stereotype.tooling.NodeContext)
123+
*/
124+
@Override
125+
public void handleCustom(Object custom, NodeContext context) {
126+
addChild(node -> customHandler.accept(node, custom));
127+
}
128+
129+
/*
130+
* (non-Javadoc)
131+
* @see org.jmolecules.stereotype.tooling.NodeHandler#postGroup()
132+
*/
133+
@Override
134+
public void postGroup() {
135+
this.current = this.current.parent;
136+
}
137+
138+
private void addChild(Consumer<Node> consumer) {
139+
this.current = addChildFoo(consumer);
140+
}
141+
142+
private Node addChildFoo(Consumer<Node> consumer) {
143+
144+
var node = new Node(this.current);
145+
consumer.accept(node);
146+
147+
this.current.children.add(node);
148+
149+
return node;
150+
}
151+
152+
@Override
153+
public String toString() {
154+
Gson gson = new GsonBuilder().setPrettyPrinting().create();
155+
return gson.toJson(root);
156+
}
157+
158+
Node getRoot() {
159+
return root;
160+
}
161+
162+
static class Node {
163+
164+
transient final Node parent;
165+
final Map<String, Object> attributes;
166+
final List<Node> children;
167+
168+
Node(Node parent) {
169+
this.parent = parent;
170+
this.attributes = new LinkedHashMap<>();
171+
this.children = new ArrayList<>();
172+
}
173+
174+
public Node withAttribute(String key, Object value) {
175+
this.attributes.put(key, value);
176+
return this;
177+
}
178+
}
179+
}

vscode-extensions/vscode-spring-boot/lib/Main.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,10 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
178178
],
179179
180180
*/
181-
// const structureManager = new StructureManager();
182-
// const explorerTreeProvider = new ExplorerTreeProvider(structureManager);
183-
// context.subscriptions.push(window.createTreeView('explorer.spring', { treeDataProvider: explorerTreeProvider, showCollapseAll: true }));
184-
// context.subscriptions.push(commands.registerCommand("vscode-spring-boot.structure.refresh", () => structureManager.refresh()));
181+
const structureManager = new StructureManager();
182+
const explorerTreeProvider = new ExplorerTreeProvider(structureManager);
183+
context.subscriptions.push(window.createTreeView('explorer.spring', { treeDataProvider: explorerTreeProvider, showCollapseAll: true }));
184+
context.subscriptions.push(commands.registerCommand("vscode-spring-boot.structure.refresh", () => structureManager.refresh()));
185185

186186
context.subscriptions.push(commands.registerCommand('vscode-spring-boot.ls.start', () => client.start().then(() => {
187187
// Boot LS is fully started
@@ -213,7 +213,7 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
213213

214214
const api = new ApiManager(client).api
215215

216-
// context.subscriptions.push(api.getSpringIndex().onSpringIndexUpdated(e => structureManager.refresh()));
216+
context.subscriptions.push(api.getSpringIndex().onSpringIndexUpdated(e => structureManager.refresh()));
217217

218218
return api;
219219
});

0 commit comments

Comments
 (0)