diff options
Diffstat (limited to 'subprojects/language-web/src/main/java/tools')
7 files changed, 339 insertions, 21 deletions
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java index 7b48cde8..e98d115e 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebSocketServlet.java | |||
@@ -10,8 +10,10 @@ import org.eclipse.xtext.util.DisposableRegistry; | |||
10 | import jakarta.servlet.ServletException; | 10 | import jakarta.servlet.ServletException; |
11 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; | 11 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; |
12 | 12 | ||
13 | public class ProblemWebSocketServlet extends XtextWebSocketServlet { | 13 | import java.io.Serial; |
14 | 14 | ||
15 | public class ProblemWebSocketServlet extends XtextWebSocketServlet { | ||
16 | @Serial | ||
15 | private static final long serialVersionUID = -7040955470384797008L; | 17 | private static final long serialVersionUID = -7040955470384797008L; |
16 | 18 | ||
17 | private transient DisposableRegistry disposableRegistry; | 19 | private transient DisposableRegistry disposableRegistry; |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java index 56b2cbc1..ba55dc77 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java | |||
@@ -55,7 +55,7 @@ public class SemanticsService extends AbstractCachedService<SemanticsResult> { | |||
55 | } | 55 | } |
56 | var problem = getProblem(doc); | 56 | var problem = getProblem(doc); |
57 | if (problem == null) { | 57 | if (problem == null) { |
58 | return new SemanticsSuccessResult(List.of(), new JsonObject()); | 58 | return new SemanticsSuccessResult(List.of(), List.of(), new JsonObject()); |
59 | } | 59 | } |
60 | var worker = workerProvider.get(); | 60 | var worker = workerProvider.get(); |
61 | worker.setProblem(problem, cancelIndicator); | 61 | worker.setProblem(problem, cancelIndicator); |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java index 15fd4b55..350b0b2b 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java | |||
@@ -6,8 +6,11 @@ | |||
6 | package tools.refinery.language.web.semantics; | 6 | package tools.refinery.language.web.semantics; |
7 | 7 | ||
8 | import com.google.gson.JsonObject; | 8 | import com.google.gson.JsonObject; |
9 | import tools.refinery.language.semantics.metadata.NodeMetadata; | ||
10 | import tools.refinery.language.semantics.metadata.RelationMetadata; | ||
9 | 11 | ||
10 | import java.util.List; | 12 | import java.util.List; |
11 | 13 | ||
12 | public record SemanticsSuccessResult(List<String> nodes, JsonObject partialInterpretation) implements SemanticsResult { | 14 | public record SemanticsSuccessResult(List<NodeMetadata> nodes, List<RelationMetadata> relations, |
15 | JsonObject partialInterpretation) implements SemanticsResult { | ||
13 | } | 16 | } |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java index 43d0238c..108b87dc 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java | |||
@@ -18,6 +18,7 @@ import org.eclipse.xtext.validation.IDiagnosticConverter; | |||
18 | import org.eclipse.xtext.validation.Issue; | 18 | import org.eclipse.xtext.validation.Issue; |
19 | import org.eclipse.xtext.web.server.validation.ValidationResult; | 19 | import org.eclipse.xtext.web.server.validation.ValidationResult; |
20 | import tools.refinery.language.model.problem.Problem; | 20 | import tools.refinery.language.model.problem.Problem; |
21 | import tools.refinery.language.semantics.metadata.MetadataCreator; | ||
21 | import tools.refinery.language.semantics.model.ModelInitializer; | 22 | import tools.refinery.language.semantics.model.ModelInitializer; |
22 | import tools.refinery.language.semantics.model.SemanticsUtils; | 23 | import tools.refinery.language.semantics.model.SemanticsUtils; |
23 | import tools.refinery.language.semantics.model.TracedException; | 24 | import tools.refinery.language.semantics.model.TracedException; |
@@ -34,8 +35,6 @@ import tools.refinery.store.tuple.Tuple; | |||
34 | import tools.refinery.viatra.runtime.CancellationToken; | 35 | import tools.refinery.viatra.runtime.CancellationToken; |
35 | 36 | ||
36 | import java.util.ArrayList; | 37 | import java.util.ArrayList; |
37 | import java.util.Arrays; | ||
38 | import java.util.List; | ||
39 | import java.util.TreeMap; | 38 | import java.util.TreeMap; |
40 | import java.util.concurrent.Callable; | 39 | import java.util.concurrent.Callable; |
41 | 40 | ||
@@ -54,6 +53,9 @@ class SemanticsWorker implements Callable<SemanticsResult> { | |||
54 | @Inject | 53 | @Inject |
55 | private ModelInitializer initializer; | 54 | private ModelInitializer initializer; |
56 | 55 | ||
56 | @Inject | ||
57 | private MetadataCreator metadataCreator; | ||
58 | |||
57 | private Problem problem; | 59 | private Problem problem; |
58 | 60 | ||
59 | private CancellationToken cancellationToken; | 61 | private CancellationToken cancellationToken; |
@@ -78,7 +80,11 @@ class SemanticsWorker implements Callable<SemanticsResult> { | |||
78 | try { | 80 | try { |
79 | var modelSeed = initializer.createModel(problem, builder); | 81 | var modelSeed = initializer.createModel(problem, builder); |
80 | cancellationToken.checkCancelled(); | 82 | cancellationToken.checkCancelled(); |
81 | var nodeTrace = getNodeTrace(initializer); | 83 | metadataCreator.setInitializer(initializer); |
84 | cancellationToken.checkCancelled(); | ||
85 | var nodesMetadata = metadataCreator.getNodesMetadata(); | ||
86 | cancellationToken.checkCancelled(); | ||
87 | var relationsMetadata = metadataCreator.getRelationsMetadata(); | ||
82 | cancellationToken.checkCancelled(); | 88 | cancellationToken.checkCancelled(); |
83 | var store = builder.build(); | 89 | var store = builder.build(); |
84 | cancellationToken.checkCancelled(); | 90 | cancellationToken.checkCancelled(); |
@@ -87,7 +93,7 @@ class SemanticsWorker implements Callable<SemanticsResult> { | |||
87 | cancellationToken.checkCancelled(); | 93 | cancellationToken.checkCancelled(); |
88 | var partialInterpretation = getPartialInterpretation(initializer, model); | 94 | var partialInterpretation = getPartialInterpretation(initializer, model); |
89 | 95 | ||
90 | return new SemanticsSuccessResult(nodeTrace, partialInterpretation); | 96 | return new SemanticsSuccessResult(nodesMetadata, relationsMetadata, partialInterpretation); |
91 | } catch (TracedException e) { | 97 | } catch (TracedException e) { |
92 | return getTracedErrorResult(e.getSourceElement(), e.getMessage()); | 98 | return getTracedErrorResult(e.getSourceElement(), e.getMessage()); |
93 | } catch (TranslationException e) { | 99 | } catch (TranslationException e) { |
@@ -96,16 +102,6 @@ class SemanticsWorker implements Callable<SemanticsResult> { | |||
96 | } | 102 | } |
97 | } | 103 | } |
98 | 104 | ||
99 | private List<String> getNodeTrace(ModelInitializer initializer) { | ||
100 | var nodeTrace = new String[initializer.getNodeCount()]; | ||
101 | for (var entry : initializer.getNodeTrace().keyValuesView()) { | ||
102 | var node = entry.getOne(); | ||
103 | var index = entry.getTwo(); | ||
104 | nodeTrace[index] = semanticsUtils.getName(node).orElse(null); | ||
105 | } | ||
106 | return Arrays.asList(nodeTrace); | ||
107 | } | ||
108 | |||
109 | private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model) { | 105 | private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model) { |
110 | var adapter = model.getAdapter(ReasoningAdapter.class); | 106 | var adapter = model.getAdapter(ReasoningAdapter.class); |
111 | var json = new JsonObject(); | 107 | var json = new JsonObject(); |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java index ff788e94..7c4562bf 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebRequest.java | |||
@@ -5,19 +5,22 @@ | |||
5 | */ | 5 | */ |
6 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
7 | 7 | ||
8 | import com.google.gson.annotations.SerializedName; | ||
9 | |||
8 | import java.util.Map; | 10 | import java.util.Map; |
9 | import java.util.Objects; | 11 | import java.util.Objects; |
10 | 12 | ||
11 | import com.google.gson.annotations.SerializedName; | ||
12 | |||
13 | public class XtextWebRequest { | 13 | public class XtextWebRequest { |
14 | private String id; | 14 | private String id; |
15 | 15 | ||
16 | @SerializedName("request") | 16 | @SerializedName("request") |
17 | private Map<String, String> requestData; | 17 | private Map<String, String> requestData; |
18 | 18 | ||
19 | public XtextWebRequest() { | ||
20 | this(null, null); | ||
21 | } | ||
22 | |||
19 | public XtextWebRequest(String id, Map<String, String> requestData) { | 23 | public XtextWebRequest(String id, Map<String, String> requestData) { |
20 | super(); | ||
21 | this.id = id; | 24 | this.id = id; |
22 | this.requestData = requestData; | 25 | this.requestData = requestData; |
23 | } | 26 | } |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java new file mode 100644 index 00000000..b16cf7df --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/RuntimeTypeAdapterFactory.java | |||
@@ -0,0 +1,304 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2011 Google Inc. | ||
3 | * Copyright (C) 2023 The Refinery Authors <https://refinery.tools/> | ||
4 | * | ||
5 | * SPDX-License-Identifier: Apache-2.0 | ||
6 | * | ||
7 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
8 | * you may not use this file except in compliance with the License. | ||
9 | * You may obtain a copy of the License at | ||
10 | * | ||
11 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
12 | * | ||
13 | * Unless required by applicable law or agreed to in writing, software | ||
14 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
16 | * See the License for the specific language governing permissions and | ||
17 | * limitations under the License. | ||
18 | * | ||
19 | * This file was copied into Refinery according to upstream instructions at | ||
20 | * https://github.com/google/gson/issues/1104#issuecomment-309582470. | ||
21 | * However, we changed the package name below to avoid potential clashes | ||
22 | * with other jars on the classpath. | ||
23 | */ | ||
24 | package tools.refinery.language.web.xtext.servlet; | ||
25 | |||
26 | import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||
27 | import com.google.gson.Gson; | ||
28 | import com.google.gson.JsonElement; | ||
29 | import com.google.gson.JsonObject; | ||
30 | import com.google.gson.JsonParseException; | ||
31 | import com.google.gson.JsonPrimitive; | ||
32 | import com.google.gson.TypeAdapter; | ||
33 | import com.google.gson.TypeAdapterFactory; | ||
34 | import com.google.gson.reflect.TypeToken; | ||
35 | import com.google.gson.stream.JsonReader; | ||
36 | import com.google.gson.stream.JsonWriter; | ||
37 | import java.io.IOException; | ||
38 | import java.util.LinkedHashMap; | ||
39 | import java.util.Map; | ||
40 | |||
41 | /** | ||
42 | * Adapts values whose runtime type may differ from their declaration type. This | ||
43 | * is necessary when a field's type is not the same type that GSON should create | ||
44 | * when deserializing that field. For example, consider these types: | ||
45 | * <pre> {@code | ||
46 | * abstract class Shape { | ||
47 | * int x; | ||
48 | * int y; | ||
49 | * } | ||
50 | * class Circle extends Shape { | ||
51 | * int radius; | ||
52 | * } | ||
53 | * class Rectangle extends Shape { | ||
54 | * int width; | ||
55 | * int height; | ||
56 | * } | ||
57 | * class Diamond extends Shape { | ||
58 | * int width; | ||
59 | * int height; | ||
60 | * } | ||
61 | * class Drawing { | ||
62 | * Shape bottomShape; | ||
63 | * Shape topShape; | ||
64 | * } | ||
65 | * }</pre> | ||
66 | * <p>Without additional type information, the serialized JSON is ambiguous. Is | ||
67 | * the bottom shape in this drawing a rectangle or a diamond? <pre> {@code | ||
68 | * { | ||
69 | * "bottomShape": { | ||
70 | * "width": 10, | ||
71 | * "height": 5, | ||
72 | * "x": 0, | ||
73 | * "y": 0 | ||
74 | * }, | ||
75 | * "topShape": { | ||
76 | * "radius": 2, | ||
77 | * "x": 4, | ||
78 | * "y": 1 | ||
79 | * } | ||
80 | * }}</pre> | ||
81 | * This class addresses this problem by adding type information to the | ||
82 | * serialized JSON and honoring that type information when the JSON is | ||
83 | * deserialized: <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 | * }}</pre> | ||
99 | * Both the type field name ({@code "type"}) and the type labels ({@code | ||
100 | * "Rectangle"}) are configurable. | ||
101 | * | ||
102 | * <h2>Registering Types</h2> | ||
103 | * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field | ||
104 | * name to the {@link #of} factory method. If you don't supply an explicit type | ||
105 | * field name, {@code "type"} will be used. <pre> {@code | ||
106 | * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory | ||
107 | * = RuntimeTypeAdapterFactory.of(Shape.class, "type"); | ||
108 | * }</pre> | ||
109 | * Next register all of your subtypes. Every subtype must be explicitly | ||
110 | * registered. This protects your application from injection attacks. If you | ||
111 | * don't supply an explicit type label, the type's simple name will be used. | ||
112 | * <pre> {@code | ||
113 | * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle"); | ||
114 | * shapeAdapterFactory.registerSubtype(Circle.class, "Circle"); | ||
115 | * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond"); | ||
116 | * }</pre> | ||
117 | * Finally, register the type adapter factory in your application's GSON builder: | ||
118 | * <pre> {@code | ||
119 | * Gson gson = new GsonBuilder() | ||
120 | * .registerTypeAdapterFactory(shapeAdapterFactory) | ||
121 | * .create(); | ||
122 | * }</pre> | ||
123 | * Like {@code GsonBuilder}, this API supports chaining: <pre> {@code | ||
124 | * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class) | ||
125 | * .registerSubtype(Rectangle.class) | ||
126 | * .registerSubtype(Circle.class) | ||
127 | * .registerSubtype(Diamond.class); | ||
128 | * }</pre> | ||
129 | * | ||
130 | * <h2>Serialization and deserialization</h2> | ||
131 | * In order to serialize and deserialize a polymorphic object, | ||
132 | * you must specify the base type explicitly. | ||
133 | * <pre> {@code | ||
134 | * Diamond diamond = new Diamond(); | ||
135 | * String json = gson.toJson(diamond, Shape.class); | ||
136 | * }</pre> | ||
137 | * And then: | ||
138 | * <pre> {@code | ||
139 | * Shape shape = gson.fromJson(json, Shape.class); | ||
140 | * }</pre> | ||
141 | */ | ||
142 | public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory { | ||
143 | private final Class<?> baseType; | ||
144 | private final String typeFieldName; | ||
145 | private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>(); | ||
146 | private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>(); | ||
147 | private final boolean maintainType; | ||
148 | private boolean recognizeSubtypes; | ||
149 | |||
150 | private RuntimeTypeAdapterFactory( | ||
151 | Class<?> baseType, String typeFieldName, boolean maintainType) { | ||
152 | if (typeFieldName == null || baseType == null) { | ||
153 | throw new NullPointerException(); | ||
154 | } | ||
155 | this.baseType = baseType; | ||
156 | this.typeFieldName = typeFieldName; | ||
157 | this.maintainType = maintainType; | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * Creates a new runtime type adapter using for {@code baseType} using {@code | ||
162 | * typeFieldName} as the type field name. Type field names are case sensitive. | ||
163 | * | ||
164 | * @param maintainType true if the type field should be included in deserialized objects | ||
165 | */ | ||
166 | public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) { | ||
167 | return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * Creates a new runtime type adapter using for {@code baseType} using {@code | ||
172 | * typeFieldName} as the type field name. Type field names are case sensitive. | ||
173 | */ | ||
174 | public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) { | ||
175 | return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as | ||
180 | * the type field name. | ||
181 | */ | ||
182 | public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) { | ||
183 | return new RuntimeTypeAdapterFactory<>(baseType, "type", false); | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * Ensures that this factory will handle not just the given {@code baseType}, but any subtype | ||
188 | * of that type. | ||
189 | */ | ||
190 | @CanIgnoreReturnValue | ||
191 | public RuntimeTypeAdapterFactory<T> recognizeSubtypes() { | ||
192 | this.recognizeSubtypes = true; | ||
193 | return this; | ||
194 | } | ||
195 | |||
196 | /** | ||
197 | * Registers {@code type} identified by {@code label}. Labels are case | ||
198 | * sensitive. | ||
199 | * | ||
200 | * @throws IllegalArgumentException if either {@code type} or {@code label} | ||
201 | * have already been registered on this type adapter. | ||
202 | */ | ||
203 | @CanIgnoreReturnValue | ||
204 | public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) { | ||
205 | if (type == null || label == null) { | ||
206 | throw new NullPointerException(); | ||
207 | } | ||
208 | if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { | ||
209 | throw new IllegalArgumentException("types and labels must be unique"); | ||
210 | } | ||
211 | labelToSubtype.put(label, type); | ||
212 | subtypeToLabel.put(type, label); | ||
213 | return this; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * Registers {@code type} identified by its {@link Class#getSimpleName simple | ||
218 | * name}. Labels are case sensitive. | ||
219 | * | ||
220 | * @throws IllegalArgumentException if either {@code type} or its simple name | ||
221 | * have already been registered on this type adapter. | ||
222 | */ | ||
223 | @CanIgnoreReturnValue | ||
224 | public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) { | ||
225 | return registerSubtype(type, type.getSimpleName()); | ||
226 | } | ||
227 | |||
228 | @Override | ||
229 | public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) { | ||
230 | if (type == null) { | ||
231 | return null; | ||
232 | } | ||
233 | Class<?> rawType = type.getRawType(); | ||
234 | boolean handle = | ||
235 | recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType); | ||
236 | if (!handle) { | ||
237 | return null; | ||
238 | } | ||
239 | |||
240 | final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class); | ||
241 | final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>(); | ||
242 | final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>(); | ||
243 | for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) { | ||
244 | TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); | ||
245 | labelToDelegate.put(entry.getKey(), delegate); | ||
246 | subtypeToDelegate.put(entry.getValue(), delegate); | ||
247 | } | ||
248 | |||
249 | return new TypeAdapter<R>() { | ||
250 | @Override public R read(JsonReader in) throws IOException { | ||
251 | JsonElement jsonElement = jsonElementAdapter.read(in); | ||
252 | JsonElement labelJsonElement; | ||
253 | if (maintainType) { | ||
254 | labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); | ||
255 | } else { | ||
256 | labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); | ||
257 | } | ||
258 | |||
259 | if (labelJsonElement == null) { | ||
260 | throw new JsonParseException("cannot deserialize " + baseType | ||
261 | + " because it does not define a field named " + typeFieldName); | ||
262 | } | ||
263 | String label = labelJsonElement.getAsString(); | ||
264 | @SuppressWarnings("unchecked") // registration requires that subtype extends T | ||
265 | TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label); | ||
266 | if (delegate == null) { | ||
267 | throw new JsonParseException("cannot deserialize " + baseType + " subtype named " | ||
268 | + label + "; did you forget to register a subtype?"); | ||
269 | } | ||
270 | return delegate.fromJsonTree(jsonElement); | ||
271 | } | ||
272 | |||
273 | @Override public void write(JsonWriter out, R value) throws IOException { | ||
274 | Class<?> srcType = value.getClass(); | ||
275 | String label = subtypeToLabel.get(srcType); | ||
276 | @SuppressWarnings("unchecked") // registration requires that subtype extends T | ||
277 | TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType); | ||
278 | if (delegate == null) { | ||
279 | throw new JsonParseException("cannot serialize " + srcType.getName() | ||
280 | + "; did you forget to register a subtype?"); | ||
281 | } | ||
282 | JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); | ||
283 | |||
284 | if (maintainType) { | ||
285 | jsonElementAdapter.write(out, jsonObject); | ||
286 | return; | ||
287 | } | ||
288 | |||
289 | JsonObject clone = new JsonObject(); | ||
290 | |||
291 | if (jsonObject.has(typeFieldName)) { | ||
292 | throw new JsonParseException("cannot serialize " + srcType.getName() | ||
293 | + " because it already defines a field named " + typeFieldName); | ||
294 | } | ||
295 | clone.add(typeFieldName, new JsonPrimitive(label)); | ||
296 | |||
297 | for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) { | ||
298 | clone.add(e.getKey(), e.getValue()); | ||
299 | } | ||
300 | jsonElementAdapter.write(out, clone); | ||
301 | } | ||
302 | }.nullSafe(); | ||
303 | } | ||
304 | } | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java index 923fecd6..1fde1be5 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java | |||
@@ -6,6 +6,7 @@ | |||
6 | package tools.refinery.language.web.xtext.servlet; | 6 | package tools.refinery.language.web.xtext.servlet; |
7 | 7 | ||
8 | import com.google.gson.Gson; | 8 | import com.google.gson.Gson; |
9 | import com.google.gson.GsonBuilder; | ||
9 | import com.google.gson.JsonIOException; | 10 | import com.google.gson.JsonIOException; |
10 | import com.google.gson.JsonParseException; | 11 | import com.google.gson.JsonParseException; |
11 | import org.eclipse.jetty.websocket.api.Callback; | 12 | import org.eclipse.jetty.websocket.api.Callback; |
@@ -16,6 +17,7 @@ import org.eclipse.xtext.resource.IResourceServiceProvider; | |||
16 | import org.eclipse.xtext.web.server.ISession; | 17 | import org.eclipse.xtext.web.server.ISession; |
17 | import org.slf4j.Logger; | 18 | import org.slf4j.Logger; |
18 | import org.slf4j.LoggerFactory; | 19 | import org.slf4j.LoggerFactory; |
20 | import tools.refinery.language.semantics.metadata.*; | ||
19 | import tools.refinery.language.web.xtext.server.ResponseHandler; | 21 | import tools.refinery.language.web.xtext.server.ResponseHandler; |
20 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; | 22 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; |
21 | import tools.refinery.language.web.xtext.server.TransactionExecutor; | 23 | import tools.refinery.language.web.xtext.server.TransactionExecutor; |
@@ -28,7 +30,15 @@ import java.io.Reader; | |||
28 | public class XtextWebSocket implements ResponseHandler { | 30 | public class XtextWebSocket implements ResponseHandler { |
29 | private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); | 31 | private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); |
30 | 32 | ||
31 | private final Gson gson = new Gson(); | 33 | private final Gson gson = new GsonBuilder() |
34 | .disableJdkUnsafe() | ||
35 | .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(RelationDetail.class, "type") | ||
36 | .registerSubtype(ClassDetail.class, "class") | ||
37 | .registerSubtype(ReferenceDetail.class, "reference") | ||
38 | .registerSubtype(OppositeReferenceDetail.class, "opposite") | ||
39 | .registerSubtype(PredicateDetail.class, "predicate") | ||
40 | .registerSubtype(BuiltInDetail.class, "builtin")) | ||
41 | .create(); | ||
32 | 42 | ||
33 | private final TransactionExecutor executor; | 43 | private final TransactionExecutor executor; |
34 | 44 | ||