Serialization polishing

Signed-off-by: aboyko <alex.boyko@broadcom.com>
This commit is contained in:
aboyko
2025-04-16 14:08:47 -04:00
parent fe0c738d1b
commit c56fc65532
7 changed files with 274 additions and 292 deletions

View File

@@ -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:
*
* <pre>{@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;
* }
* }</pre>
*
* <p>Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in
* this drawing a rectangle or a diamond?
* <p>
* Without additional type information, the serialized JSON is ambiguous. Is the
* bottom shape in this drawing a rectangle or a diamond?
*
* <pre>{@code
* {
@@ -77,8 +84,9 @@ import java.util.Map;
* }
* }</pre>
*
* 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:
*
* <pre>{@code
* {
@@ -98,23 +106,22 @@ import java.util.Map;
* }
* }</pre>
*
* 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.
*
* <h2>Registering Types</h2>
*
* 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.
*
* <pre>{@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre>
*
* 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.
*
* <pre>{@code
* shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
@@ -122,27 +129,24 @@ import java.util.Map;
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
* }</pre>
*
* Finally, register the type adapter factory in your application's GSON builder:
* Finally, register the type adapter factory in your application's GSON
* builder:
*
* <pre>{@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory)
* .create();
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(shapeAdapterFactory).create();
* }</pre>
*
* Like {@code GsonBuilder}, this API supports chaining:
*
* <pre>{@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
* .registerSubtype(Rectangle.class).registerSubtype(Circle.class).registerSubtype(Diamond.class);
* }</pre>
*
* <h2>Serialization and deserialization</h2>
*
* 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.
*
* <pre>{@code
* Diamond diamond = new Diamond();
@@ -156,176 +160,199 @@ import java.util.Map;
* }</pre>
*/
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
private final boolean maintainType;
private boolean recognizeSubtypes;
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
private final Map<Class<?>, 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 <T> RuntimeTypeAdapterFactory<T> of(
Class<T> 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 <T> RuntimeTypeAdapterFactory<T> of(Class<T> 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 <T> RuntimeTypeAdapterFactory<T> of(Class<T> 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 <T> RuntimeTypeAdapterFactory<T> of(Class<T> 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 <T> RuntimeTypeAdapterFactory<T> of(Class<T> 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 <T> RuntimeTypeAdapterFactory<T> of(Class<T> 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<T> 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<T> 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<T> registerSubtype(Class<? extends T> 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<T> registerSubtype(Class<? extends T> 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<T> registerSubtype(Class<? extends T> 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<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
@Override
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> 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 <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
if (type == null) {
return null;
}
Class<?> rawType = type.getRawType();
boolean handle =
recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
if (!handle) {
return null;
}
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>();
Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>();
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
final RuntimeTypeAdapterFactory<T> thisAdapter = this;
return new TypeAdapter<R>() {
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>();
Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>();
for (Map.Entry<String, Class<?>> 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<R>() {
@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<R> delegate = (TypeAdapter<R>) 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<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null && recognizeSubtypes) {
try {
delegate = (TypeAdapter<R>) 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<R> delegate = (TypeAdapter<R>) 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<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null && recognizeSubtypes) {
delegate = (TypeAdapter<R>) 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<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
jsonElementAdapter.write(out, clone);
}
}.nullSafe();
}
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
jsonElementAdapter.write(out, clone);
}
}.nullSafe();
}
}

View File

@@ -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<String> getSupertypes() {
return supertypes;
return supertypes == null ? DefaultValues.EMPTY_SUPERTYPES : supertypes;
}
public String getSymbolLabel() {

View File

@@ -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;
}
}

View File

@@ -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<GsonBuilder> 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());
}
}

View File

@@ -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();
}

View File

@@ -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<SpringIndexElement> 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<SpringIndexElement> 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<SpringIndexElement> 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<SpringIndexElement> 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<SpringIndexElement> 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<SpringIndexElement> childrenAfterNewChildAdded = deserializedBean.getChildren();
assertEquals(2, childrenAfterNewChildAdded.size());
}
static class SubType1 extends AbstractSpringIndexElement {
}
static class SubType2 extends AbstractSpringIndexElement {
}
static class Root extends AbstractSpringIndexElement {
}
}

View File

@@ -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,