Skip to content

Commit 0492baa

Browse files
committed
SpringIndexElement serialization. Initial Structure view in VSCode
Signed-off-by: aboyko <[email protected]>
1 parent c56f45c commit 0492baa

File tree

14 files changed

+873
-180
lines changed

14 files changed

+873
-180
lines changed

headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LanguageServerRunner.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.ExecutorService;
2525
import java.util.concurrent.Executors;
2626
import java.util.concurrent.Future;
27+
import java.util.function.Consumer;
2728
import java.util.function.Function;
2829

2930
import org.eclipse.lsp4j.jsonrpc.Launcher;
@@ -38,6 +39,8 @@
3839
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
3940
import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient;
4041

42+
import com.google.gson.GsonBuilder;
43+
4144
/**
4245
* A CommandLineRunner that launches a language server. This meant to be used as a Spring bean
4346
* in a SpringBoot app.
@@ -98,11 +101,14 @@ public void run(String... args) throws Exception {
98101

99102
private Function<MessageConsumer, MessageConsumer> messageConsumer;
100103

101-
public LanguageServerRunner(LanguageServerProperties properties, SimpleLanguageServer languageServer, Function<MessageConsumer, MessageConsumer> messageConsumer) {
104+
private Consumer<GsonBuilder> configureGson;
105+
106+
public LanguageServerRunner(LanguageServerProperties properties, SimpleLanguageServer languageServer, Function<MessageConsumer, MessageConsumer> messageConsumer, Consumer<GsonBuilder> configureGson) {
102107
super();
103108
this.properties = properties;
104109
this.languageServer = languageServer;
105110
this.messageConsumer = messageConsumer;
111+
this.configureGson = configureGson;
106112
}
107113

108114
public void start() throws Exception {
@@ -207,7 +213,7 @@ private <T> Launcher<T> createSocketLauncher(
207213
AsynchronousSocketChannel socketChannel = serverSocket.accept().get();
208214
log.info("Client connected via socket");
209215
return Launcher.createIoLauncher(localService, remoteInterface, Channels.newInputStream(socketChannel),
210-
Channels.newOutputStream(socketChannel), executorService, wrapper);
216+
Channels.newOutputStream(socketChannel), executorService, wrapper, configureGson);
211217
}
212218

213219
private static Connection connectToNode() throws IOException {
@@ -235,12 +241,13 @@ private static Connection connectToNode() throws IOException {
235241
private Future<Void> runAsync(Connection connection) throws Exception {
236242
LanguageServer server = this.languageServer;
237243
ExecutorService executor = createServerThreads();
238-
Launcher<STS4LanguageClient> launcher = Launcher.createLauncher(server,
244+
Launcher<STS4LanguageClient> launcher = Launcher.createIoLauncher(server,
239245
STS4LanguageClient.class,
240246
connection.in,
241247
connection.out,
242248
executor,
243-
messageConsumer
249+
messageConsumer,
250+
configureGson
244251
);
245252

246253
if (server instanceof LanguageClientAware) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/*
2+
* Copyright (C) 2011 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ide.vscode.commons;
18+
19+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
20+
import com.google.gson.Gson;
21+
import com.google.gson.JsonElement;
22+
import com.google.gson.JsonObject;
23+
import com.google.gson.JsonParseException;
24+
import com.google.gson.JsonPrimitive;
25+
import com.google.gson.TypeAdapter;
26+
import com.google.gson.TypeAdapterFactory;
27+
import com.google.gson.reflect.TypeToken;
28+
import com.google.gson.stream.JsonReader;
29+
import com.google.gson.stream.JsonWriter;
30+
import java.io.IOException;
31+
import java.util.LinkedHashMap;
32+
import java.util.Map;
33+
34+
/**
35+
* Adapts values whose runtime type may differ from their declaration type. This is necessary when a
36+
* field's type is not the same type that GSON should create when deserializing that field. For
37+
* example, consider these types:
38+
*
39+
* <pre>{@code
40+
* abstract class Shape {
41+
* int x;
42+
* int y;
43+
* }
44+
* class Circle extends Shape {
45+
* int radius;
46+
* }
47+
* class Rectangle extends Shape {
48+
* int width;
49+
* int height;
50+
* }
51+
* class Diamond extends Shape {
52+
* int width;
53+
* int height;
54+
* }
55+
* class Drawing {
56+
* Shape bottomShape;
57+
* Shape topShape;
58+
* }
59+
* }</pre>
60+
*
61+
* <p>Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in
62+
* this drawing a rectangle or a diamond?
63+
*
64+
* <pre>{@code
65+
* {
66+
* "bottomShape": {
67+
* "width": 10,
68+
* "height": 5,
69+
* "x": 0,
70+
* "y": 0
71+
* },
72+
* "topShape": {
73+
* "radius": 2,
74+
* "x": 4,
75+
* "y": 1
76+
* }
77+
* }
78+
* }</pre>
79+
*
80+
* This class addresses this problem by adding type information to the serialized JSON and honoring
81+
* that type information when the JSON is deserialized:
82+
*
83+
* <pre>{@code
84+
* {
85+
* "bottomShape": {
86+
* "type": "Diamond",
87+
* "width": 10,
88+
* "height": 5,
89+
* "x": 0,
90+
* "y": 0
91+
* },
92+
* "topShape": {
93+
* "type": "Circle",
94+
* "radius": 2,
95+
* "x": 4,
96+
* "y": 1
97+
* }
98+
* }
99+
* }</pre>
100+
*
101+
* Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
102+
* configurable.
103+
*
104+
* <h2>Registering Types</h2>
105+
*
106+
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
107+
* {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
108+
* be used.
109+
*
110+
* <pre>{@code
111+
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
112+
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
113+
* }</pre>
114+
*
115+
* Next register all of your subtypes. Every subtype must be explicitly registered. This protects
116+
* your application from injection attacks. If you don't supply an explicit type label, the type's
117+
* simple name will be used.
118+
*
119+
* <pre>{@code
120+
* shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
121+
* shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
122+
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
123+
* }</pre>
124+
*
125+
* Finally, register the type adapter factory in your application's GSON builder:
126+
*
127+
* <pre>{@code
128+
* Gson gson = new GsonBuilder()
129+
* .registerTypeAdapterFactory(shapeAdapterFactory)
130+
* .create();
131+
* }</pre>
132+
*
133+
* Like {@code GsonBuilder}, this API supports chaining:
134+
*
135+
* <pre>{@code
136+
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
137+
* .registerSubtype(Rectangle.class)
138+
* .registerSubtype(Circle.class)
139+
* .registerSubtype(Diamond.class);
140+
* }</pre>
141+
*
142+
* <h2>Serialization and deserialization</h2>
143+
*
144+
* In order to serialize and deserialize a polymorphic object, you must specify the base type
145+
* explicitly.
146+
*
147+
* <pre>{@code
148+
* Diamond diamond = new Diamond();
149+
* String json = gson.toJson(diamond, Shape.class);
150+
* }</pre>
151+
*
152+
* And then:
153+
*
154+
* <pre>{@code
155+
* Shape shape = gson.fromJson(json, Shape.class);
156+
* }</pre>
157+
*/
158+
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
159+
private final Class<?> baseType;
160+
private final String typeFieldName;
161+
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
162+
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
163+
private final boolean maintainType;
164+
private boolean recognizeSubtypes;
165+
166+
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
167+
if (typeFieldName == null || baseType == null) {
168+
throw new NullPointerException();
169+
}
170+
this.baseType = baseType;
171+
this.typeFieldName = typeFieldName;
172+
this.maintainType = maintainType;
173+
}
174+
175+
/**
176+
* Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type
177+
* field name. Type field names are case sensitive.
178+
*
179+
* @param maintainType true if the type field should be included in deserialized objects
180+
*/
181+
public static <T> RuntimeTypeAdapterFactory<T> of(
182+
Class<T> baseType, String typeFieldName, boolean maintainType) {
183+
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
184+
}
185+
186+
/**
187+
* Creates a new runtime type adapter for {@code baseType} using {@code typeFieldName} as the type
188+
* field name. Type field names are case sensitive.
189+
*/
190+
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
191+
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
192+
}
193+
194+
/**
195+
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type field
196+
* name.
197+
*/
198+
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
199+
return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
200+
}
201+
202+
/**
203+
* Ensures that this factory will handle not just the given {@code baseType}, but any subtype of
204+
* that type.
205+
*/
206+
@CanIgnoreReturnValue
207+
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
208+
this.recognizeSubtypes = true;
209+
return this;
210+
}
211+
212+
/**
213+
* Registers {@code type} identified by {@code label}. Labels are case sensitive.
214+
*
215+
* @throws IllegalArgumentException if either {@code type} or {@code label} have already been
216+
* registered on this type adapter.
217+
*/
218+
@CanIgnoreReturnValue
219+
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
220+
if (type == null || label == null) {
221+
throw new NullPointerException();
222+
}
223+
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
224+
throw new IllegalArgumentException("types and labels must be unique");
225+
}
226+
labelToSubtype.put(label, type);
227+
subtypeToLabel.put(type, label);
228+
return this;
229+
}
230+
231+
/**
232+
* Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are
233+
* case sensitive.
234+
*
235+
* @throws IllegalArgumentException if either {@code type} or its simple name have already been
236+
* registered on this type adapter.
237+
*/
238+
@CanIgnoreReturnValue
239+
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
240+
return registerSubtype(type, type.getSimpleName());
241+
}
242+
243+
@Override
244+
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
245+
if (type == null) {
246+
return null;
247+
}
248+
Class<?> rawType = type.getRawType();
249+
boolean handle =
250+
recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
251+
if (!handle) {
252+
return null;
253+
}
254+
255+
TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
256+
Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>();
257+
Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>();
258+
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
259+
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
260+
labelToDelegate.put(entry.getKey(), delegate);
261+
subtypeToDelegate.put(entry.getValue(), delegate);
262+
}
263+
264+
return new TypeAdapter<R>() {
265+
@Override
266+
public R read(JsonReader in) throws IOException {
267+
JsonElement jsonElement = jsonElementAdapter.read(in);
268+
JsonElement labelJsonElement;
269+
if (maintainType) {
270+
labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
271+
} else {
272+
labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
273+
}
274+
275+
if (labelJsonElement == null) {
276+
throw new JsonParseException(
277+
"cannot deserialize "
278+
+ baseType
279+
+ " because it does not define a field named "
280+
+ typeFieldName);
281+
}
282+
String label = labelJsonElement.getAsString();
283+
@SuppressWarnings("unchecked") // registration requires that subtype extends T
284+
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
285+
if (delegate == null) {
286+
throw new JsonParseException(
287+
"cannot deserialize "
288+
+ baseType
289+
+ " subtype named "
290+
+ label
291+
+ "; did you forget to register a subtype?");
292+
}
293+
return delegate.fromJsonTree(jsonElement);
294+
}
295+
296+
@Override
297+
public void write(JsonWriter out, R value) throws IOException {
298+
Class<?> srcType = value.getClass();
299+
String label = subtypeToLabel.get(srcType);
300+
@SuppressWarnings("unchecked") // registration requires that subtype extends T
301+
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
302+
if (delegate == null) {
303+
throw new JsonParseException(
304+
"cannot serialize " + srcType.getName() + "; did you forget to register a subtype?");
305+
}
306+
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
307+
308+
if (maintainType) {
309+
jsonElementAdapter.write(out, jsonObject);
310+
return;
311+
}
312+
313+
JsonObject clone = new JsonObject();
314+
315+
if (jsonObject.has(typeFieldName)) {
316+
throw new JsonParseException(
317+
"cannot serialize "
318+
+ srcType.getName()
319+
+ " because it already defines a field named "
320+
+ typeFieldName);
321+
}
322+
clone.add(typeFieldName, new JsonPrimitive(label));
323+
324+
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
325+
clone.add(e.getKey(), e.getValue());
326+
}
327+
jsonElementAdapter.write(out, clone);
328+
}
329+
}.nullSafe();
330+
}
331+
}

0 commit comments

Comments
 (0)