Skip to content

Commit 56f6d95

Browse files
committed
integrate method nodes into jmolecules structural view
1 parent 0f16e42 commit 56f6d95

File tree

11 files changed

+243
-53
lines changed

11 files changed

+243
-53
lines changed

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

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.commands;
1212

13-
import java.util.Collection;
1413
import java.util.List;
1514
import java.util.Objects;
1615
import java.util.Optional;
@@ -21,7 +20,6 @@
2120
import org.jmolecules.stereotype.tooling.LabelUtils;
2221
import org.jmolecules.stereotype.tooling.ProjectTree;
2322
import org.jmolecules.stereotype.tooling.SimpleLabelProvider;
24-
import org.jmolecules.stereotype.tooling.StructureProvider.SimpleStructureProvider;
2523
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
2624
import org.springframework.ide.vscode.boot.java.Annotations;
2725
import org.springframework.ide.vscode.boot.java.commands.ToolsJsonNodeHandler.Node;
@@ -54,38 +52,20 @@ private Node nodeFrom(StereotypeCatalogRegistry stereotypeCatalogRegistry, Sprin
5452

5553
StereotypePackageElement mainApplicationPackage = identifyMainApplicationPackage(project, springIndex);
5654

57-
var labels = new SimpleLabelProvider<>(StereotypePackageElement::getPackageName, StereotypePackageElement::getPackageName, StereotypeClassElement::getType,
55+
var labelProvider = new SimpleLabelProvider<>(StereotypePackageElement::getPackageName, StereotypePackageElement::getPackageName, StereotypeClassElement::getType,
5856
(StereotypeMethodElement m, StereotypeClassElement __) -> m.getMethodName(), Object::toString)
59-
.withTypeLabel(it -> abbreviate(mainApplicationPackage, it));
57+
.withTypeLabel(it -> abbreviate(mainApplicationPackage, it))
58+
.withMethodLabel((m, c) -> getMethodLabel(m, c));
6059

61-
SimpleStructureProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement> structureProvider =
62-
new SimpleStructureProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement>() {
63-
64-
@Override
65-
public Collection<StereotypePackageElement> extractPackages(StereotypePackageElement pkg) {
66-
return List.of(pkg);
67-
}
68-
69-
@Override
70-
public Collection<StereotypeMethodElement> extractMethods(StereotypeClassElement type) {
71-
return List.of();
72-
}
73-
74-
@Override
75-
public Collection<StereotypeClassElement> extractTypes(StereotypePackageElement pkg) {
76-
return springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class).stream()
77-
.filter(element -> element.getType().startsWith(pkg.getPackageName()))
78-
.toList();
79-
}
80-
};
60+
var structureProvider = new ToolsStructureProvider(springIndex, project);
8161

8262
// json output
8363
BiConsumer<Node, Object> consumer = (node, c) -> {
84-
node.withAttribute(HierarchicalNodeHandler.TEXT, labels.getCustomLabel(c))
64+
node.withAttribute(HierarchicalNodeHandler.TEXT, labelProvider.getCustomLabel(c))
8565
.withAttribute("icon", "fa-named-interface");
8666
};
8767

88-
var jsonHandler = new ToolsJsonNodeHandler(labels, consumer);
68+
var jsonHandler = new ToolsJsonNodeHandler(labelProvider, consumer);
8969

9070
var jsonTree = new ProjectTree<>(factory, catalog, jsonHandler)
9171
.withStructureProvider(structureProvider)
@@ -106,6 +86,11 @@ private String abbreviate(StereotypePackageElement mainApplicationPackage, Stere
10686
}
10787
}
10888

89+
private String getMethodLabel(StereotypeMethodElement method, StereotypeClassElement clazz) {
90+
// TODO: special treatment for methods that have specific index elements with specific labels (e.g. mapping methods)
91+
return method.getMethodLabel();
92+
}
93+
10994
public StereotypePackageElement identifyMainApplicationPackage(IJavaProject project, SpringMetamodelIndex springIndex) {
11095
List<StereotypeClassElement> classNodes = springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class);
11196

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,23 @@ public void handleType(StereotypeClassElement type, NodeContext context) {
8787
);
8888
}
8989

90-
public Node createNested() {
91-
return new Node(this.current);
92-
}
93-
9490
@Override
9591
public void handleMethod(StereotypeMethodElement method, MethodNodeContext<StereotypeClassElement> context) {
96-
addChildFoo(node -> node.withAttribute("title", labels.getMethodLabel(method, context.getContextualType())));
92+
addChildFoo(node -> node
93+
.withAttribute(TEXT, labels.getMethodLabel(method, context.getContextualType()))
94+
.withAttribute(LOCATION, method.getLocation())
95+
);
9796
}
9897

9998
@Override
10099
public void handleCustom(Object custom, NodeContext context) {
101100
addChild(node -> customHandler.accept(node, custom));
102101
}
103102

103+
public Node createNested() {
104+
return new Node(this.current);
105+
}
106+
104107
@Override
105108
public void postGroup() {
106109
this.current = this.current.parent;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.Collection;
14+
import java.util.List;
15+
16+
import org.jmolecules.stereotype.tooling.StructureProvider.SimpleStructureProvider;
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.StereotypeMethodElement;
20+
import org.springframework.ide.vscode.boot.java.stereotypes.StereotypePackageElement;
21+
import org.springframework.ide.vscode.commons.java.IJavaProject;
22+
23+
public class ToolsStructureProvider implements
24+
SimpleStructureProvider<StereotypePackageElement, StereotypePackageElement, StereotypeClassElement, StereotypeMethodElement> {
25+
26+
private final SpringMetamodelIndex springIndex;
27+
private final IJavaProject project;
28+
29+
ToolsStructureProvider(SpringMetamodelIndex springIndex, IJavaProject project) {
30+
this.springIndex = springIndex;
31+
this.project = project;
32+
}
33+
34+
@Override
35+
public Collection<StereotypePackageElement> extractPackages(StereotypePackageElement pkg) {
36+
return List.of(pkg);
37+
}
38+
39+
@Override
40+
public Collection<StereotypeMethodElement> extractMethods(StereotypeClassElement type) {
41+
return type.getMethods();
42+
}
43+
44+
@Override
45+
public Collection<StereotypeClassElement> extractTypes(StereotypePackageElement pkg) {
46+
return springIndex.getNodesOfType(project.getElementName(), StereotypeClassElement.class).stream()
47+
.filter(element -> element.getType().startsWith(pkg.getPackageName()))
48+
.toList();
49+
}
50+
}

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

Lines changed: 1 addition & 2 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.List;
1615
import java.util.Optional;
1716
import java.util.TreeSet;
1817

@@ -67,7 +66,7 @@ public Stereotypes fromType(StereotypeClassElement type) {
6766

6867
@Override
6968
public Stereotypes fromMethod(StereotypeMethodElement method) {
70-
return new Stereotypes(List.of());
69+
return new Stereotypes(fromAnnotatedElement(method));
7170
}
7271

7372
private <T extends StereotypeAnnotatedElement> Collection<Stereotype> fromAnnotatedElement(StereotypeAnnotatedElement element) {

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

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

1616
import org.eclipse.lsp4j.Location;
17+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
1718
import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement;
1819

1920
public class StereotypeClassElement extends AbstractSpringIndexElement implements StereotypeAnnotatedElement {
@@ -52,5 +53,9 @@ public boolean doesImplement(String fqn) {
5253
public List<String> getAnnotationTypes() {
5354
return annotationTypes;
5455
}
56+
57+
public List<StereotypeMethodElement> getMethods() {
58+
return SpringMetamodelIndex.getNodesOfType(StereotypeMethodElement.class, List.of(this));
59+
}
5560

5661
}

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,46 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.stereotypes;
1212

13+
import java.util.List;
14+
15+
import org.eclipse.lsp4j.Location;
1316
import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement;
1417

15-
public class StereotypeMethodElement extends AbstractSpringIndexElement {
18+
public class StereotypeMethodElement extends AbstractSpringIndexElement implements StereotypeAnnotatedElement {
1619

1720
private final String methodName;
21+
private final String methodLabel;
22+
private final String methodSignature;
23+
24+
private final Location location;
25+
private List<String> annotationTypes;
1826

19-
public StereotypeMethodElement(String methodName) {
27+
public StereotypeMethodElement(String methodName, String methodLabel, String methodSignature, Location location, List<String> annotationTypes) {
2028
this.methodName = methodName;
29+
this.methodLabel = methodLabel;
30+
this.methodSignature = methodSignature;
31+
this.location = location;
32+
this.annotationTypes = annotationTypes;
2133
}
2234

2335
public String getMethodName() {
2436
return methodName;
2537
}
38+
39+
public String getMethodLabel() {
40+
return methodLabel;
41+
}
42+
43+
public String getMethodSignature() {
44+
return methodSignature;
45+
}
46+
47+
public Location getLocation() {
48+
return location;
49+
}
50+
51+
public List<String> getAnnotationTypes() {
52+
return annotationTypes;
53+
}
2654

2755
}

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

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@ public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITy
7171
}
7272
}
7373

74-
@Override
75-
public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
76-
// TODO
77-
}
78-
7974
@Override
8075
public void addSymbols(RecordDeclaration recordDeclaration, SpringIndexerJavaContext context, TextDocument doc) {
8176
try {
@@ -153,6 +148,53 @@ private void createStereotypeElementForType(AbstractTypeDeclaration typeDeclarat
153148
}
154149

155150
Collection<Annotation> annotations = ASTUtils.getAnnotations(typeDeclaration);
151+
List<String> annotationTypes = getAnnotationTypes(annotationHierarchies, superTypeAnnotations, annotations);
152+
153+
SimpleName astNodeForLocation = typeDeclaration.getName();
154+
Location location = new Location(doc.getUri(), doc.toRange(astNodeForLocation.getStartPosition(), astNodeForLocation.getLength()));
155+
156+
StereotypeClassElement indexElement = new StereotypeClassElement(qualifiedName, location, supertypes, annotationTypes);
157+
158+
indexMethods(indexElement, typeDeclaration, annotationHierarchies, doc);
159+
160+
context.getBeans().add(new CachedBean(context.getDocURI(), indexElement));
161+
}
162+
163+
private void indexMethods(StereotypeClassElement indexElement, AbstractTypeDeclaration typeDeclaration, AnnotationHierarchies annotationHierarchies, TextDocument doc) throws BadLocationException {
164+
MethodDeclaration[] methods = null;
165+
166+
if (typeDeclaration instanceof TypeDeclaration) {
167+
methods = ((TypeDeclaration) typeDeclaration).getMethods();
168+
}
169+
else if (typeDeclaration instanceof RecordDeclaration) {
170+
methods = ((RecordDeclaration) typeDeclaration).getMethods();
171+
}
172+
173+
if (methods == null) {
174+
return;
175+
}
176+
177+
for (MethodDeclaration method : methods) {
178+
String methodName = method.getName().getFullyQualifiedName();
179+
180+
Collection<Annotation> annotations = ASTUtils.getAnnotations(method);
181+
List<String> annotationTypes = getAnnotationTypes(annotationHierarchies, List.of(), annotations);
182+
183+
if (annotationTypes.size() > 0) { // only index annotated methods to avoid creating all those useless index elements for each and every method
184+
SimpleName astNodeForLocation = method.getName();
185+
Location location = new Location(doc.getUri(), doc.toRange(astNodeForLocation.getStartPosition(), astNodeForLocation.getLength()));
186+
187+
String methodSignature = ASTUtils.getMethodSignature(method, true);
188+
String methodLabel = ASTUtils.getMethodSignature(method, false);
189+
190+
StereotypeMethodElement methodElement = new StereotypeMethodElement(methodName, methodLabel, methodSignature, location, annotationTypes);
191+
indexElement.addChild(methodElement);
192+
}
193+
}
194+
}
195+
196+
private List<String> getAnnotationTypes(AnnotationHierarchies annotationHierarchies,
197+
List<IAnnotationBinding> superTypeAnnotations, Collection<Annotation> annotations) {
156198
Stream<IAnnotationBinding> annotationBindings = annotations.stream()
157199
.map(annotation -> annotation.resolveAnnotationBinding())
158200
.filter(binding -> binding != null)
@@ -163,19 +205,8 @@ private void createStereotypeElementForType(AbstractTypeDeclaration typeDeclarat
163205
.distinct()
164206
.map(binding -> binding.getAnnotationType().getQualifiedName())
165207
.toList();
166-
167-
SimpleName astNodeForLocation = typeDeclaration.getName();
168-
Location location = new Location(doc.getUri(), doc.toRange(astNodeForLocation.getStartPosition(), astNodeForLocation.getLength()));
169-
170-
StereotypeClassElement indexElement = new StereotypeClassElement(qualifiedName, location, supertypes, annotationTypes);
171-
context.getBeans().add(new CachedBean(context.getDocURI(), indexElement));
172-
}
173-
174-
private boolean isStereotype(TypeDeclaration typeDeclaration, ITypeBinding binding) {
175-
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(typeDeclaration);
176-
boolean isStereotype = annotationHierarchies.isAnnotatedWith(binding, Annotations.JMOLECULES_STEREOTYPE);
177208

178-
return isStereotype;
209+
return annotationTypes;
179210
}
180211

181212
private Collection<IAnnotationBinding> getMetaAnnotations(AnnotationHierarchies annotationHierarchies, IAnnotationBinding annotationBinding) {
@@ -187,4 +218,13 @@ private Collection<IAnnotationBinding> getMetaAnnotations(AnnotationHierarchies
187218
return result;
188219
}
189220

221+
private boolean isStereotype(TypeDeclaration typeDeclaration, ITypeBinding binding) {
222+
AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(typeDeclaration);
223+
boolean isStereotype = annotationHierarchies.isAnnotatedWith(binding, Annotations.JMOLECULES_STEREOTYPE);
224+
225+
return isStereotype;
226+
}
227+
228+
229+
190230
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.eclipse.jdt.core.dom.IAnnotationBinding;
3838
import org.eclipse.jdt.core.dom.IBinding;
3939
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
40+
import org.eclipse.jdt.core.dom.IMethodBinding;
4041
import org.eclipse.jdt.core.dom.ITypeBinding;
4142
import org.eclipse.jdt.core.dom.IVariableBinding;
4243
import org.eclipse.jdt.core.dom.MemberValuePair;
@@ -861,5 +862,41 @@ public static class MemberValuePairAndType {
861862
public String[] values;
862863
public ITypeBinding dereferencedType;
863864
}
865+
866+
public static String getMethodSignature(MethodDeclaration method, boolean fullyQualifiedTypeNames) {
867+
StringBuilder result = new StringBuilder();
868+
869+
IMethodBinding binding = method.resolveBinding();
870+
871+
// class name
872+
String className = fullyQualifiedTypeNames ? binding.getDeclaringClass().getBinaryName() : binding.getDeclaringClass().getName();
873+
result.append(className);
874+
result.append('.');
875+
876+
// method name
877+
String methodName = binding.getName();
878+
result.append(methodName);
879+
880+
// params
881+
result.append('(');
882+
883+
ITypeBinding[] parameterTypes = binding.getParameterTypes();
884+
String[] parameterTypeNames = new String[parameterTypes.length];
885+
886+
for (int i = 0; i < parameterTypes.length; i++) {
887+
parameterTypeNames[i] = fullyQualifiedTypeNames ? parameterTypes[i].getBinaryName() : parameterTypes[i].getName();
888+
}
889+
result.append(String.join(", ", parameterTypeNames));
890+
891+
result.append(") : ");
892+
893+
// return type
894+
String returnTypeName = fullyQualifiedTypeNames ? binding.getReturnType().getBinaryName() : binding.getReturnType().getName();
895+
result.append(returnTypeName);
896+
897+
return result.toString();
898+
}
899+
900+
864901

865902
}

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
@@ -94,7 +94,7 @@ public class SpringIndexerJava implements SpringIndexer {
9494

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

100100
private static final String SYMBOL_KEY = "symbols";

0 commit comments

Comments
 (0)