Index Elements serialization. Structure view initial work

* WIP

Signed-off-by: aboyko <alex.boyko@broadcom.com>

* `SpringIndexElement` serialization. Initial Structure view in VSCode

Signed-off-by: aboyko <alex.boyko@broadcom.com>

* Corrections

Signed-off-by: aboyko <alex.boyko@broadcom.com>

* Serialization polishing

Signed-off-by: aboyko <alex.boyko@broadcom.com>

* Don't include java.lang.Object in supertypes serialization

Signed-off-by: aboyko <alex.boyko@broadcom.com>

---------

Signed-off-by: aboyko <alex.boyko@broadcom.com>
This commit is contained in:
Alex Boyko
2025-04-22 13:43:50 -04:00
committed by GitHub
parent 515f27d556
commit d886efd66a
17 changed files with 884 additions and 151 deletions

View File

@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeActionKind;
@@ -89,6 +90,7 @@ import org.springframework.ide.vscode.boot.properties.completions.SpringProperti
import org.springframework.ide.vscode.boot.xml.SpringXMLCompletionEngine;
import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext;
import org.springframework.ide.vscode.boot.yaml.completions.SpringYamlCompletionEngine;
import org.springframework.ide.vscode.commons.RuntimeTypeAdapterFactory;
import org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner;
import org.springframework.ide.vscode.commons.languageserver.java.FutureProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -98,6 +100,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.LanguageComput
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
import org.springframework.ide.vscode.commons.languageserver.util.ServerCapabilityInitializer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.util.FileObserver;
import org.springframework.ide.vscode.commons.util.LogRedirect;
import org.springframework.ide.vscode.commons.util.text.IDocument;
@@ -118,6 +121,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import reactor.core.publisher.Hooks;
@@ -428,4 +432,10 @@ public class BootLanguageServerBootApp {
return new ResponseModifier();
}
@Bean
Consumer<GsonBuilder> configureGson() {
return builder -> builder
.registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(SpringIndexElement.class, "_internal_node_type")
.recognizeSubtypes());
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2024 Broadcom, Inc.
* Copyright (c) 2024, 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
@@ -12,6 +12,8 @@ package org.springframework.ide.vscode.boot.app;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.commands.SpringIndexCommands;
import org.springframework.ide.vscode.boot.java.commands.WorkspaceBootExecutableProjects;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
@@ -22,5 +24,10 @@ public class CommandsConfig {
@Bean WorkspaceBootExecutableProjects workspaceBootProjects(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex) {
return new WorkspaceBootExecutableProjects(server, projectFinder, symbolIndex);
}
@Bean SpringIndexCommands springIndexCommands(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringMetamodelIndex symbolIndex) {
return new SpringIndexCommands(server, symbolIndex, projectFinder);
}
}

View File

@@ -38,13 +38,9 @@ import java.util.stream.Collectors;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues;
import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint;
import org.springframework.ide.vscode.commons.RuntimeTypeAdapterFactory;
import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement;
import org.springframework.ide.vscode.commons.util.UriUtil;
@@ -569,10 +565,9 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache {
public static Gson createGson() {
return new GsonBuilder()
.registerTypeAdapter(DeltaStorage.class, new DeltaStorageAdapter())
.registerTypeAdapter(Bean.class, new BeanJsonAdapter())
.registerTypeAdapter(InjectionPoint.class, new InjectionPointJsonAdapter())
.registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter())
.registerTypeAdapter(SpringIndexElement.class, new SpringIndexElementAdapter())
.registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(SpringIndexElement.class, "_internal_node_type")
.recognizeSubtypes())
.create();
}
@@ -635,111 +630,5 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache {
}
}
private static class BeanJsonAdapter implements JsonSerializer<Bean>, JsonDeserializer<Bean> {
@Override
public Bean deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String beanName = parsedObject.get("name").getAsString();
String beanType = parsedObject.get("type").getAsString();
JsonElement locationObject = parsedObject.get("location");
Location location = context.deserialize(locationObject, Location.class);
JsonElement injectionPointObject = parsedObject.get("injectionPoints");
InjectionPoint[] injectionPoints = context.deserialize(injectionPointObject, InjectionPoint[].class);
JsonElement supertypesObject = parsedObject.get("supertypes");
Set<String> supertypes = context.deserialize(supertypesObject, Set.class);
JsonElement annotationsObject = parsedObject.get("annotations");
AnnotationMetadata[] annotations = annotationsObject == null ? DefaultValues.EMPTY_ANNOTATIONS : context.deserialize(annotationsObject, AnnotationMetadata[].class);
JsonElement isConfigurationObject = parsedObject.get("isConfiguration");
boolean isConfiguration = context.deserialize(isConfigurationObject, boolean.class);
String symbolLabel = parsedObject.get("symbolLabel").getAsString();
JsonElement childrenObject = parsedObject.get("children");
Type childrenListType = TypeToken.getParameterized(List.class, SpringIndexElement.class).getType();
List<SpringIndexElement> children = context.deserialize(childrenObject, childrenListType);
Bean bean = new Bean(beanName, beanType, location, injectionPoints, supertypes, annotations, isConfiguration, symbolLabel);
for (SpringIndexElement springIndexElement : children) {
bean.addChild(springIndexElement);
}
return bean;
}
@Override
public JsonElement serialize(Bean src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject bean = new JsonObject();
bean.addProperty("name", src.getName());
bean.addProperty("type", src.getType());
bean.add("location", context.serialize(src.getLocation()));
bean.add("injectionPoints", context.serialize(src.getInjectionPoints()));
bean.add("supertypes", context.serialize(src.getSupertypes()));
bean.add("annotations", context.serialize(src.getAnnotations()));
bean.addProperty("isConfiguration", src.isConfiguration());
bean.addProperty("symbolLabel", src.getSymbolLabel());
Type childrenListType = TypeToken.getParameterized(List.class, SpringIndexElement.class).getType();
bean.add("children", context.serialize(src.getChildren(), childrenListType));
bean.addProperty("_internal_node_type", src.getClass().getName());
return bean;
}
}
private static class InjectionPointJsonAdapter implements JsonDeserializer<InjectionPoint> {
@Override
public InjectionPoint deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject parsedObject = json.getAsJsonObject();
String injectionPointName = parsedObject.get("name").getAsString();
String injectionPointType = parsedObject.get("type").getAsString();
JsonElement locationObject = parsedObject.get("location");
Location location = context.deserialize(locationObject, Location.class);
JsonElement annotationsObject = parsedObject.get("annotations");
AnnotationMetadata[] annotations = annotationsObject == null ? DefaultValues.EMPTY_ANNOTATIONS : context.deserialize(annotationsObject, AnnotationMetadata[].class);
return new InjectionPoint(injectionPointName, injectionPointType, location, annotations);
}
}
private static class SpringIndexElementAdapter implements JsonSerializer<SpringIndexElement>, JsonDeserializer<SpringIndexElement> {
@Override
public JsonElement serialize(SpringIndexElement element, Type typeOfSrc, JsonSerializationContext context) {
JsonElement elem = context.serialize(element);
elem.getAsJsonObject().addProperty("_internal_node_type", element.getClass().getName());
return elem;
}
@Override
public SpringIndexElement deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String typeName = jsonObject.get("_internal_node_type").getAsString();
try {
return context.deserialize(jsonObject, (Class<?>) Class.forName(typeName));
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
}
}

View File

@@ -0,0 +1,15 @@
package org.springframework.ide.vscode.boot.java.commands;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
public class SpringIndexCommands {
private static final String SPRING_STRUCTURE_CMD = "sts/spring-boot/structure";
public SpringIndexCommands(SimpleLanguageServer server, SpringMetamodelIndex metamodelIndex, JavaProjectFinder projectFinder) {
server.onCommand(SPRING_STRUCTURE_CMD, params -> server.getAsync().invoke(() -> metamodelIndex.getProjects()));
}
}

View File

@@ -53,10 +53,12 @@ public class JdtCronVisitorUtils {
} else if (e instanceof TextBlock tb) {
value = tb.getLiteralValue();
}
value = value.trim();
if (value.startsWith("#{") || value.startsWith("${")) {
// Either SPEL or Property Holder
return false;
if (value != null) {
value = value.trim();
if (value.startsWith("#{") || value.startsWith("${")) {
// Either SPEL or Property Holder
return false;
}
}
return value != null;
}