diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/RuntimeTypeAdapterFactory.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/RuntimeTypeAdapterFactory.java index dc0558229..51159d231 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/RuntimeTypeAdapterFactory.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/RuntimeTypeAdapterFactory.java @@ -16,6 +16,11 @@ package org.springframework.ide.vscode.commons; +import java.io.IOException; +import java.lang.reflect.AccessFlag; +import java.util.LinkedHashMap; +import java.util.Map; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -27,39 +32,41 @@ import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; /** - * Adapts values whose runtime type may differ from their declaration type. This is necessary when a - * field's type is not the same type that GSON should create when deserializing that field. For - * example, consider these types: + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: * *
{@code
  * abstract class Shape {
- *   int x;
- *   int y;
+ * 	int x;
+ * 	int y;
  * }
+ * 
  * class Circle extends Shape {
- *   int radius;
+ * 	int radius;
  * }
+ * 
  * class Rectangle extends Shape {
- *   int width;
- *   int height;
+ * 	int width;
+ * 	int height;
  * }
+ * 
  * class Diamond extends Shape {
- *   int width;
- *   int height;
+ * 	int width;
+ * 	int height;
  * }
+ * 
  * class Drawing {
- *   Shape bottomShape;
- *   Shape topShape;
+ * 	Shape bottomShape;
+ * 	Shape topShape;
  * }
  * }
* - *

Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in - * this drawing a rectangle or a diamond? + *

+ * Without additional type information, the serialized JSON is ambiguous. Is the + * bottom shape in this drawing a rectangle or a diamond? * *

{@code
  * {
@@ -77,8 +84,9 @@ import java.util.Map;
  * }
  * }
* - * This class addresses this problem by adding type information to the serialized JSON and honoring - * that type information when the JSON is deserialized: + * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized: * *
{@code
  * {
@@ -98,23 +106,22 @@ import java.util.Map;
  * }
  * }
* - * Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are - * configurable. + * Both the type field name ({@code "type"}) and the type labels + * ({@code "Rectangle"}) are configurable. * *

Registering Types

* - * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the - * {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will - * be used. + * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type + * field name to the {@link #of} factory method. If you don't supply an explicit + * type field name, {@code "type"} will be used. * *
{@code
- * RuntimeTypeAdapterFactory shapeAdapterFactory
- *     = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class, "type");
  * }
* - * Next register all of your subtypes. Every subtype must be explicitly registered. This protects - * your application from injection attacks. If you don't supply an explicit type label, the type's - * simple name will be used. + * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. * *
{@code
  * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
@@ -122,27 +129,24 @@ import java.util.Map;
  * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
  * }
* - * Finally, register the type adapter factory in your application's GSON builder: + * Finally, register the type adapter factory in your application's GSON + * builder: * *
{@code
- * Gson gson = new GsonBuilder()
- *     .registerTypeAdapterFactory(shapeAdapterFactory)
- *     .create();
+ * Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapterFactory).create();
  * }
* * Like {@code GsonBuilder}, this API supports chaining: * *
{@code
  * RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
- *     .registerSubtype(Rectangle.class)
- *     .registerSubtype(Circle.class)
- *     .registerSubtype(Diamond.class);
+ * 		.registerSubtype(Rectangle.class).registerSubtype(Circle.class).registerSubtype(Diamond.class);
  * }
* *

Serialization and deserialization

* - * In order to serialize and deserialize a polymorphic object, you must specify the base type - * explicitly. + * In order to serialize and deserialize a polymorphic object, you must specify + * the base type explicitly. * *
{@code
  * Diamond diamond = new Diamond();
@@ -156,176 +160,199 @@ import java.util.Map;
  * }
*/ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { - private final Class baseType; - private final String typeFieldName; - private final Map> labelToSubtype = new LinkedHashMap<>(); - private final Map, String> subtypeToLabel = new LinkedHashMap<>(); - private final boolean maintainType; - private boolean recognizeSubtypes; + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap<>(); + private final Map, String> subtypeToLabel = new LinkedHashMap<>(); + private final boolean maintainType; + private boolean recognizeSubtypes; - private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { - if (typeFieldName == null || baseType == null) { - throw new NullPointerException(); - } - this.baseType = baseType; - this.typeFieldName = typeFieldName; - this.maintainType = maintainType; - } + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } - /** - * Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type - * field name. Type field names are case sensitive. - * - * @param maintainType true if the type field should be included in deserialized objects - */ - public static RuntimeTypeAdapterFactory of( - Class baseType, String typeFieldName, boolean maintainType) { - return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); - } + /** + * Creates a new runtime type adapter for {@code baseType} using + * {@code typeFieldName} as the type field name. Type field names are case + * sensitive. + * + * @param maintainType true if the type field should be included in deserialized + * objects + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); + } - /** - * Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type - * field name. Type field names are case sensitive. - */ - public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { - return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); - } + /** + * Creates a new runtime type adapter for {@code baseType} using + * {@code typeFieldName} as the type field name. Type field names are case + * sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); + } - /** - * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type field - * name. - */ - public static RuntimeTypeAdapterFactory of(Class baseType) { - return new RuntimeTypeAdapterFactory<>(baseType, "type", false); - } + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} + * as the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory<>(baseType, "type", false); + } - /** - * Ensures that this factory will handle not just the given {@code baseType}, but any subtype of - * that type. - */ - @CanIgnoreReturnValue - public RuntimeTypeAdapterFactory recognizeSubtypes() { - this.recognizeSubtypes = true; - return this; - } + /** + * Ensures that this factory will handle not just the given {@code baseType}, + * but any subtype of that type. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory recognizeSubtypes() { + this.recognizeSubtypes = true; + return this; + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} have + * already been registered on this type + * adapter. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } - /** - * Registers {@code type} identified by {@code label}. Labels are case sensitive. - * - * @throws IllegalArgumentException if either {@code type} or {@code label} have already been - * registered on this type adapter. - */ - @CanIgnoreReturnValue - public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { - if (type == null || label == null) { - throw new NullPointerException(); - } - if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { - throw new IllegalArgumentException("types and labels must be unique"); - } - labelToSubtype.put(label, type); - subtypeToLabel.put(type, label); - return this; - } + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type + * adapter. + */ + @CanIgnoreReturnValue + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } - /** - * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are - * case sensitive. - * - * @throws IllegalArgumentException if either {@code type} or its simple name have already been - * registered on this type adapter. - */ - @CanIgnoreReturnValue - public RuntimeTypeAdapterFactory registerSubtype(Class type) { - return registerSubtype(type, type.getSimpleName()); - } + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type == null) { + return null; + } + Class rawType = type.getRawType(); + boolean handle = recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType); + if (!handle) { + return null; + } - @Override - public TypeAdapter create(Gson gson, TypeToken type) { - if (type == null) { - return null; - } - Class rawType = type.getRawType(); - boolean handle = - recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType); - if (!handle) { - return null; - } + TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); + Map> labelToDelegate = new LinkedHashMap<>(); + Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + final RuntimeTypeAdapterFactory thisAdapter = this; + + return new TypeAdapter() { - TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); - Map> labelToDelegate = new LinkedHashMap<>(); - Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); - for (Map.Entry> entry : labelToSubtype.entrySet()) { - TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); - labelToDelegate.put(entry.getKey(), delegate); - subtypeToDelegate.put(entry.getValue(), delegate); - } + private TypeAdapter findSubtypeDelegate(Class clazz) { + if (clazz.isInterface() || clazz.accessFlags().contains(AccessFlag.ABSTRACT)) { + throw new JsonParseException( + "cannot serialize/deserialize " + clazz.getName() + "; it is abstract"); + } + if (baseType.isAssignableFrom(clazz)) { + TypeAdapter delegate = gson.getDelegateAdapter(thisAdapter, TypeToken.get(clazz)); + labelToDelegate.put(clazz.getName(), delegate); + subtypeToDelegate.put(clazz, delegate); + labelToSubtype.put(clazz.getName(), clazz); + subtypeToLabel.put(clazz, clazz.getName()); + return delegate; + } + return null; + } - return new TypeAdapter() { - @Override - public R read(JsonReader in) throws IOException { - JsonElement jsonElement = jsonElementAdapter.read(in); - JsonElement labelJsonElement; - if (maintainType) { - labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); - } else { - labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); - } + @SuppressWarnings("unchecked") // registration requires that subtype extends T + @Override + public R read(JsonReader in) throws IOException { + JsonElement jsonElement = jsonElementAdapter.read(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + } - if (labelJsonElement == null) { - throw new JsonParseException( - "cannot deserialize " - + baseType - + " because it does not define a field named " - + typeFieldName); - } - String label = labelJsonElement.getAsString(); - @SuppressWarnings("unchecked") // registration requires that subtype extends T - TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); - if (delegate == null) { - throw new JsonParseException( - "cannot deserialize " - + baseType - + " subtype named " - + label - + "; did you forget to register a subtype?"); - } - return delegate.fromJsonTree(jsonElement); - } + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null && recognizeSubtypes) { + try { + delegate = (TypeAdapter) findSubtypeDelegate(Class.forName(label)); + } catch (ClassNotFoundException e) { + // ignore + } + } + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + label + + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } - @Override - public void write(JsonWriter out, R value) throws IOException { - Class srcType = value.getClass(); - String label = subtypeToLabel.get(srcType); - @SuppressWarnings("unchecked") // registration requires that subtype extends T - TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); - if (delegate == null) { - throw new JsonParseException( - "cannot serialize " + srcType.getName() + "; did you forget to register a subtype?"); - } - JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + @Override + public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null && recognizeSubtypes) { + delegate = (TypeAdapter) findSubtypeDelegate(srcType); + } + if (delegate == null) { + throw new JsonParseException( + "cannot serialize " + srcType.getName() + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); - if (maintainType) { - jsonElementAdapter.write(out, jsonObject); - return; - } + if (maintainType) { + jsonElementAdapter.write(out, jsonObject); + return; + } - JsonObject clone = new JsonObject(); + JsonObject clone = new JsonObject(); - if (jsonObject.has(typeFieldName)) { - throw new JsonParseException( - "cannot serialize " - + srcType.getName() - + " because it already defines a field named " - + typeFieldName); - } - clone.add(typeFieldName, new JsonPrimitive(label)); + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + String label = subtypeToLabel.get(srcType); + clone.add(typeFieldName, new JsonPrimitive(label)); - for (Map.Entry e : jsonObject.entrySet()) { - clone.add(e.getKey(), e.getValue()); - } - jsonElementAdapter.write(out, clone); - } - }.nullSafe(); - } + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + jsonElementAdapter.write(out, clone); + } + }.nullSafe(); + } } \ No newline at end of file diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java index e3ba120eb..fd18880cd 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/Bean.java @@ -46,24 +46,21 @@ public class Bean extends AbstractSpringIndexElement implements SymbolElement { this.symbolLabel = symbolLabel; if (injectionPoints != null && injectionPoints.length == 0) { - this.injectionPoints = DefaultValues.EMPTY_INJECTION_POINTS; + this.injectionPoints = null; } else { this.injectionPoints = injectionPoints; } if (supertypes != null && supertypes.size() == 0) { - this.supertypes = DefaultValues.EMPTY_SUPERTYPES; - } - else if (supertypes != null && supertypes.size() == 1 && supertypes.contains("java.lang.Object")) { - this.supertypes = DefaultValues.OBJECT_SUPERTYPE; + this.supertypes = null; } else { this.supertypes = supertypes; } if (annotations != null && annotations.length == 0) { - this.annotations = DefaultValues.EMPTY_ANNOTATIONS; + this.annotations = null; } else { this.annotations = annotations; @@ -83,15 +80,15 @@ public class Bean extends AbstractSpringIndexElement implements SymbolElement { } public InjectionPoint[] getInjectionPoints() { - return injectionPoints; + return injectionPoints == null ? DefaultValues.EMPTY_INJECTION_POINTS : injectionPoints; } public boolean isTypeCompatibleWith(String type) { - return type != null && ((this.type != null && this.type.equals(type)) || (supertypes.contains(type))); + return type != null && ((this.type != null && this.type.equals(type)) || (getSupertypes().contains(type))); } public AnnotationMetadata[] getAnnotations() { - return annotations; + return annotations == null ? DefaultValues.EMPTY_ANNOTATIONS : annotations; } public boolean isConfiguration() { @@ -99,7 +96,7 @@ public class Bean extends AbstractSpringIndexElement implements SymbolElement { } public Set getSupertypes() { - return supertypes; + return supertypes == null ? DefaultValues.EMPTY_SUPERTYPES : supertypes; } public String getSymbolLabel() { diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/InjectionPoint.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/InjectionPoint.java index 57598faab..b3794d0e8 100644 --- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/InjectionPoint.java +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/InjectionPoint.java @@ -29,8 +29,8 @@ public class InjectionPoint { this.type = type; this.location = location; - if (annotations == null || (annotations != null && annotations.length == 0)) { - this.annotations = DefaultValues.EMPTY_ANNOTATIONS; + if (annotations != null && annotations.length == 0) { + this.annotations = null; } else { this.annotations = annotations; @@ -50,7 +50,7 @@ public class InjectionPoint { } public AnnotationMetadata[] getAnnotations() { - return annotations; + return annotations == null ? DefaultValues.EMPTY_ANNOTATIONS : annotations; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index eb083d24f..d281462c2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -46,7 +46,6 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCache; import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDiscDeltaBased; import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid; import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler; -import org.springframework.ide.vscode.boot.java.beans.ConfigPropertyIndexElement; import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider; import org.springframework.ide.vscode.boot.java.beans.NamedDefinitionProvider; import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider; @@ -54,11 +53,8 @@ import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnBeanDefinitionProvider; import org.springframework.ide.vscode.boot.java.conditionals.ConditionalOnResourceDefinitionProvider; import org.springframework.ide.vscode.boot.java.copilot.util.ResponseModifier; -import org.springframework.ide.vscode.boot.java.data.QueryMethodIndexElement; import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider; import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtDataQuerySemanticTokensProvider; -import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement; -import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine; import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler; @@ -78,8 +74,6 @@ import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDa import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler; -import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteElementRangesIndexElement; import org.springframework.ide.vscode.boot.java.spel.SpelDefinitionProvider; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.ValueDefinitionProvider; @@ -106,11 +100,6 @@ 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.AbstractSpringIndexElement; -import org.springframework.ide.vscode.commons.protocol.spring.AotProcessorElement; -import org.springframework.ide.vscode.commons.protocol.spring.BeanMethodContainerElement; -import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; -import org.springframework.ide.vscode.commons.protocol.spring.ProjectElement; 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; @@ -447,18 +436,6 @@ public class BootLanguageServerBootApp { Consumer configureGson() { return builder -> builder .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(SpringIndexElement.class, "_internal_node_type") - .recognizeSubtypes() - .registerSubtype(org.springframework.ide.vscode.commons.protocol.spring.Bean.class) - .registerSubtype(AotProcessorElement.class) - .registerSubtype(BeanMethodContainerElement.class) - .registerSubtype(ConfigPropertyIndexElement.class) - .registerSubtype(DocumentElement.class) - .registerSubtype(EventListenerIndexElement.class) - .registerSubtype(EventPublisherIndexElement.class) - .registerSubtype(ProjectElement.class) - .registerSubtype(QueryMethodIndexElement.class) - .registerSubtype(RequestMappingIndexElement.class) - .registerSubtype(WebfluxRouteElementRangesIndexElement.class) - .registerSubtype(AbstractSpringIndexElement.class)); + .recognizeSubtypes()); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java index 8da2a2aaa..433439145 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/cache/IndexCacheOnDiscDeltaBased.java @@ -40,19 +40,7 @@ import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.beans.ConfigPropertyIndexElement; -import org.springframework.ide.vscode.boot.java.data.QueryMethodIndexElement; -import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement; -import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; -import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteElementRangesIndexElement; import org.springframework.ide.vscode.commons.RuntimeTypeAdapterFactory; -import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; -import org.springframework.ide.vscode.commons.protocol.spring.AotProcessorElement; -import org.springframework.ide.vscode.commons.protocol.spring.Bean; -import org.springframework.ide.vscode.commons.protocol.spring.BeanMethodContainerElement; -import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; -import org.springframework.ide.vscode.commons.protocol.spring.ProjectElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.UriUtil; @@ -579,21 +567,7 @@ public class IndexCacheOnDiscDeltaBased implements IndexCache { .registerTypeAdapter(DeltaStorage.class, new DeltaStorageAdapter()) .registerTypeAdapter(IndexCacheStore.class, new IndexCacheStoreAdapter()) .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(SpringIndexElement.class, "_internal_node_type") - .recognizeSubtypes() - .registerSubtype(Bean.class) - .registerSubtype(AotProcessorElement.class) - .registerSubtype(BeanMethodContainerElement.class) - .registerSubtype(ConfigPropertyIndexElement.class) - .registerSubtype(DocumentElement.class) - .registerSubtype(EventListenerIndexElement.class) - .registerSubtype(EventPublisherIndexElement.class) - .registerSubtype(ProjectElement.class) - .registerSubtype(QueryMethodIndexElement.class) - .registerSubtype(RequestMappingIndexElement.class) - .registerSubtype(WebfluxRouteElementRangesIndexElement.class) - .registerSubtype(AbstractSpringIndexElement.class)) - - + .recognizeSubtypes()) .create(); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java index ae3740a8b..26c37b6ec 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexTest.java @@ -29,14 +29,12 @@ import org.eclipse.lsp4j.Range; import org.junit.jupiter.api.Test; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDiscDeltaBased; -import org.springframework.ide.vscode.boot.java.beans.ConfigPropertyIndexElement; -import org.springframework.ide.vscode.boot.java.data.QueryMethodIndexElement; +import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue; 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.protocol.spring.ProjectElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import com.google.gson.Gson; @@ -271,7 +269,7 @@ public class SpringMetamodelIndexTest { assertEquals("point2", points[1].getName()); assertEquals("point2-type", points[1].getType()); assertEquals(locationForDoc1, points[1].getLocation()); - assertEquals(0, points[1].getAnnotations().length); + assertSame(DefaultValues.EMPTY_ANNOTATIONS, points[1].getAnnotations()); assertTrue(deserializedBean.isTypeCompatibleWith("supertype1")); assertTrue(deserializedBean.isTypeCompatibleWith("supertype2")); @@ -311,7 +309,7 @@ public class SpringMetamodelIndexTest { assertEquals("beanType", deserializedBean.getType()); assertEquals(locationForDoc1, deserializedBean.getLocation()); - assertEquals(0, deserializedBean.getInjectionPoints().length); + assertSame(DefaultValues.EMPTY_INJECTION_POINTS, deserializedBean.getInjectionPoints()); } @Test @@ -403,7 +401,7 @@ public class SpringMetamodelIndexTest { void testBasicSpringIndexStructure() { Bean bean1 = new Bean("beanName1", "beanType1", locationForDoc1, emptyInjectionPoints, Set.of("supertype1", "supertype2"), emptyAnnotations, false, "symbolLabel"); - ConfigPropertyIndexElement child1 = new ConfigPropertyIndexElement("prop1", "java.lang.String", null); + SubType1 child1 = new SubType1(); bean1.addChild(child1); List children2 = bean1.getChildren(); @@ -415,32 +413,32 @@ public class SpringMetamodelIndexTest { void testSpringIndexStructurePolymorphicSerialization() { Gson gson = IndexCacheOnDiscDeltaBased.createGson(); - QueryMethodIndexElement subNode = new QueryMethodIndexElement("find1", "SELECT * FROM All", null); + SubType2 subNode = new SubType2(); - ConfigPropertyIndexElement node1 = new ConfigPropertyIndexElement("prop1", "java.lang.String", null); + SubType1 node1 = new SubType1(); node1.addChild(subNode); - QueryMethodIndexElement node2 = new QueryMethodIndexElement("find", "SELECT * FROM S", null); + SubType2 node2 = new SubType2(); - ProjectElement root = new ProjectElement("my-project"); + Root root = new Root(); root.addChild(node1); root.addChild(node2); String json = gson.toJson(root); - ProjectElement deserializedRoot = gson.fromJson(json, ProjectElement.class); + Root deserializedRoot = gson.fromJson(json, Root.class); List children = deserializedRoot.getChildren(); assertEquals(2, children.size()); - ConfigPropertyIndexElement deserializedNode1 = (ConfigPropertyIndexElement) children.stream().filter(node -> node instanceof ConfigPropertyIndexElement).findAny().get(); - QueryMethodIndexElement deserializedNode2 = (QueryMethodIndexElement) children.stream().filter(node -> node instanceof QueryMethodIndexElement).findAny().get(); + SubType1 deserializedNode1 = (SubType1) children.stream().filter(node -> node instanceof SubType1).findAny().get(); + SubType2 deserializedNode2 = (SubType2) children.stream().filter(node -> node instanceof SubType2).findAny().get(); assertNotNull(deserializedNode1); assertNotNull(deserializedNode2); List deserializedChild2 = deserializedNode1.getChildren(); assertEquals(1, deserializedChild2.size()); - assertTrue(deserializedChild2.get(0) instanceof QueryMethodIndexElement); + assertTrue(deserializedChild2.get(0) instanceof SubType2); } @Test @@ -472,11 +470,11 @@ public class SpringMetamodelIndexTest { Gson gson = IndexCacheOnDiscDeltaBased.createGson(); - QueryMethodIndexElement childOfChild = new QueryMethodIndexElement("find1", "SELECT * FROM All", null); - ConfigPropertyIndexElement child1 = new ConfigPropertyIndexElement("prop1", "java.lang.String", null); + SubType2 childOfChild = new SubType2(); + SubType1 child1 = new SubType1(); child1.addChild(childOfChild); - QueryMethodIndexElement child2 = new QueryMethodIndexElement("find2", "SELECT s2 FROM S", null); + SubType2 child2 = new SubType2(); Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, true, "symbolLabel"); bean1.addChild(child1); bean1.addChild(child2); @@ -487,14 +485,14 @@ public class SpringMetamodelIndexTest { List children = deserializedBean.getChildren(); assertEquals(2, children.size()); - SpringIndexElement deserializedChild1 = children.stream().filter(element -> element instanceof ConfigPropertyIndexElement).findAny().get(); + SpringIndexElement deserializedChild1 = children.stream().filter(element -> element instanceof SubType1).findAny().get(); assertNotNull(deserializedChild1); List childrenOfChild = deserializedChild1.getChildren(); assertEquals(1, childrenOfChild.size()); - assertTrue(childrenOfChild.get(0) instanceof QueryMethodIndexElement); + assertTrue(childrenOfChild.get(0) instanceof SubType2); - SpringIndexElement deserializedChild2 = children.stream().filter(element -> element instanceof QueryMethodIndexElement).findAny().get(); + SpringIndexElement deserializedChild2 = children.stream().filter(element -> element instanceof SubType2).findAny().get(); assertNotNull(deserializedChild2); assertEquals(0, deserializedChild2.getChildren().size()); } @@ -504,18 +502,27 @@ public class SpringMetamodelIndexTest { Gson gson = IndexCacheOnDiscDeltaBased.createGson(); - ConfigPropertyIndexElement child1 = new ConfigPropertyIndexElement("prop1", "java.lang.String", null);; + SubType1 child1 = new SubType1(); Bean bean1 = new Bean("beanName1", "beanType", locationForDoc1, emptyInjectionPoints, emptySupertypes, emptyAnnotations, true, "symbolLabel"); bean1.addChild(child1); String serialized = gson.toJson(bean1); Bean deserializedBean = gson.fromJson(serialized, Bean.class); - QueryMethodIndexElement newChild = new QueryMethodIndexElement("find1", "SELECT * FROM All", null); + SubType2 newChild = new SubType2(); deserializedBean.addChild(newChild); List childrenAfterNewChildAdded = deserializedBean.getChildren(); assertEquals(2, childrenAfterNewChildAdded.size()); } + static class SubType1 extends AbstractSpringIndexElement { + } + + static class SubType2 extends AbstractSpringIndexElement { + } + + static class Root extends AbstractSpringIndexElement { + } + } diff --git a/vscode-extensions/vscode-spring-boot/lib/explorer/structure-tree-manager.ts b/vscode-extensions/vscode-spring-boot/lib/explorer/structure-tree-manager.ts index f9c41be4e..2348c9d0b 100644 --- a/vscode-extensions/vscode-spring-boot/lib/explorer/structure-tree-manager.ts +++ b/vscode-extensions/vscode-spring-boot/lib/explorer/structure-tree-manager.ts @@ -23,11 +23,11 @@ export class StructureManager { private parseNode(json: any): SpringNode | undefined { if (typeof (json._internal_node_type) === 'string') { switch (json._internal_node_type) { - case "ProjectElement": + case "org.springframework.ide.vscode.commons.protocol.spring.ProjectElement": return new ProjectNode(json.projectName as string, this.parseArray(json.children)); - case "DocumentElement": + case "org.springframework.ide.vscode.commons.protocol.spring.DocumentElement": return new DocumentNode(Uri.parse(json.docURI as string), this.parseArray(json.children)); - case "Bean": + case "org.springframework.ide.vscode.commons.protocol.spring.Bean": return new BeanNode( this.parseArray(json.children), json.name, @@ -39,19 +39,19 @@ export class StructureManager { json.isConfiguration, json.symbolLabel ); - case "AotProcessorElement": + case "org.springframework.ide.vscode.commons.protocol.spring.AotProcessorElement": return new AotProcessorNode( this.parseArray(json.children), json.name, Uri.parse(json.docUri) ); - case "BeanMethodContainerElement": + case "org.springframework.ide.vscode.commons.protocol.spring.BeanMethodContainerElement": return new BeanMethodContainerNode( this.parseArray(json.children), json.type, json.location ); - case "BeanRegistrarElement": + case "org.springframework.ide.vscode.commons.protocol.spring.BeanRegistrarElement": return new BeanRegistrarNode( this.parseArray(json.children), json.name, @@ -59,14 +59,14 @@ export class StructureManager { json.location ); - case "ConfigPropertyIndexElement": + case "org.springframework.ide.vscode.boot.java.beans.ConfigPropertyIndexElement": return new ConfigPropertyNode( this.parseArray(json.children), json.name, json.type, json.range ); - case "EventListenerIndexElement": + case "org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement": return new EventListenerNode( this.parseArray(json.children), json.eventType, @@ -74,21 +74,21 @@ export class StructureManager { json.containerBeanType, json.annotations ); - case "EventPublisherIndexElement": + case "org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement": return new EventPublisherNode( this.parseArray(json.children), json.eventType, json.location, json.eventTypesFromHierarchy, ); - case "QueryMethodIndexElement": + case "org.springframework.ide.vscode.boot.java.data.QueryMethodIndexElement": return new QueryMethodNode( this.parseArray(json.children), json.methodName, json.queryString, json.range ); - case "RequestMappingIndexElement": + case "org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement": return new RequestMappingNode( this.parseArray(json.children), json.path, @@ -98,7 +98,7 @@ export class StructureManager { json.symbolLabel, json.range ); - case "WebfluxRouteElementRangesIndexElement": + case "org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteElementRangesIndexElement": return new WebfluxRoutesNode( this.parseArray(json.children), json.path,