Serialization polishing
Signed-off-by: aboyko <alex.boyko@broadcom.com>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user