aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-17 02:32:26 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2023-08-17 02:43:55 +0200
commiteb5da232b5954895b449957c73e35d0b36e3a902 (patch)
treea8714116cfe3102659e9c5e5b90131e7ec248492 /subprojects/language-web
parentchore(deps): bump dependencies (diff)
downloadrefinery-eb5da232b5954895b449957c73e35d0b36e3a902.tar.gz
refinery-eb5da232b5954895b449957c73e35d0b36e3a902.tar.zst
refinery-eb5da232b5954895b449957c73e35d0b36e3a902.zip
feat: basic semantics mapping and visualization
Diffstat (limited to 'subprojects/language-web')
-rw-r--r--subprojects/language-web/build.gradle.kts2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java9
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java11
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java155
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java6
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java2
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java12
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java36
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java8
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java16
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java2
-rw-r--r--subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java4
14 files changed, 240 insertions, 38 deletions
diff --git a/subprojects/language-web/build.gradle.kts b/subprojects/language-web/build.gradle.kts
index 562a1bd9..20e5780b 100644
--- a/subprojects/language-web/build.gradle.kts
+++ b/subprojects/language-web/build.gradle.kts
@@ -17,6 +17,8 @@ val webapp: Configuration by configurations.creating {
17dependencies { 17dependencies {
18 implementation(project(":refinery-language")) 18 implementation(project(":refinery-language"))
19 implementation(project(":refinery-language-ide")) 19 implementation(project(":refinery-language-ide"))
20 implementation(project(":refinery-language-semantics"))
21 implementation(project(":refinery-store-query-viatra"))
20 implementation(libs.jetty.server) 22 implementation(libs.jetty.server)
21 implementation(libs.jetty.servlet) 23 implementation(libs.jetty.servlet)
22 implementation(libs.jetty.websocket.api) 24 implementation(libs.jetty.websocket.api)
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java
new file mode 100644
index 00000000..ce34ef6c
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsErrorResult.java
@@ -0,0 +1,9 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8public record SemanticsErrorResult(String error) implements SemanticsResult {
9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
new file mode 100644
index 00000000..92639578
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsResult.java
@@ -0,0 +1,11 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import org.eclipse.xtext.web.server.IServiceResult;
9
10public sealed interface SemanticsResult extends IServiceResult permits SemanticsSuccessResult, SemanticsErrorResult {
11}
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
new file mode 100644
index 00000000..483d24f6
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsService.java
@@ -0,0 +1,155 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import com.google.gson.JsonArray;
9import com.google.gson.JsonObject;
10import com.google.inject.Inject;
11import com.google.inject.Provider;
12import com.google.inject.Singleton;
13import org.eclipse.xtext.service.OperationCanceledManager;
14import org.eclipse.xtext.util.CancelIndicator;
15import org.eclipse.xtext.web.server.model.AbstractCachedService;
16import org.eclipse.xtext.web.server.model.IXtextWebDocument;
17import org.eclipse.xtext.web.server.model.XtextWebDocument;
18import org.eclipse.xtext.web.server.validation.ValidationService;
19import org.jetbrains.annotations.Nullable;
20import org.slf4j.Logger;
21import org.slf4j.LoggerFactory;
22import tools.refinery.language.model.problem.Problem;
23import tools.refinery.language.semantics.model.ModelInitializer;
24import tools.refinery.language.semantics.model.SemanticsUtils;
25import tools.refinery.store.model.Model;
26import tools.refinery.store.model.ModelStore;
27import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
28import tools.refinery.store.reasoning.ReasoningAdapter;
29import tools.refinery.store.reasoning.ReasoningStoreAdapter;
30import tools.refinery.store.reasoning.literal.Concreteness;
31import tools.refinery.store.reasoning.representation.PartialRelation;
32import tools.refinery.store.representation.TruthValue;
33import tools.refinery.store.tuple.Tuple;
34
35import java.util.Arrays;
36import java.util.List;
37import java.util.TreeMap;
38
39@Singleton
40public class SemanticsService extends AbstractCachedService<SemanticsResult> {
41 private static final Logger LOG = LoggerFactory.getLogger(SemanticsService.class);
42
43 @Inject
44 private SemanticsUtils semanticsUtils;
45
46 @Inject
47 private ValidationService validationService;
48
49 @Inject
50 private Provider<ModelInitializer> initializerProvider;
51
52 @Inject
53 private OperationCanceledManager operationCanceledManager;
54
55 @Override
56 public SemanticsResult compute(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
57 long start = System.currentTimeMillis();
58 Problem problem = getProblem(doc, cancelIndicator);
59 if (problem == null) {
60 return null;
61 }
62 var initializer = initializerProvider.get();
63 var builder = ModelStore.builder()
64 .with(ViatraModelQueryAdapter.builder());
65 operationCanceledManager.checkCanceled(cancelIndicator);
66 try {
67 var modelSeed = initializer.createModel(problem, builder);
68 operationCanceledManager.checkCanceled(cancelIndicator);
69 var nodeTrace = getNodeTrace(initializer);
70 operationCanceledManager.checkCanceled(cancelIndicator);
71 var store = builder.build();
72 operationCanceledManager.checkCanceled(cancelIndicator);
73 var model = store.getAdapter(ReasoningStoreAdapter.class).createInitialModel(modelSeed);
74 operationCanceledManager.checkCanceled(cancelIndicator);
75 var partialInterpretation = getPartialInterpretation(initializer, model, cancelIndicator);
76 long end = System.currentTimeMillis();
77 LOG.info("Computed semantics for {} ({}) in {}ms", doc.getResourceId(), doc.getStateId(), end - start);
78 return new SemanticsSuccessResult(nodeTrace, partialInterpretation);
79 } catch (RuntimeException e) {
80 LOG.error("Error while computing semantics", e);
81 return new SemanticsErrorResult(e.toString());
82 }
83 }
84
85 @Nullable
86 private Problem getProblem(IXtextWebDocument doc, CancelIndicator cancelIndicator) {
87 if (!(doc instanceof XtextWebDocument webDoc)) {
88 throw new IllegalArgumentException("Unexpected IXtextWebDocument: " + doc);
89 }
90 var validationResult = webDoc.getCachedServiceResult(validationService, cancelIndicator, true);
91 boolean hasError = validationResult.getIssues().stream()
92 .anyMatch(issue -> "error".equals(issue.getSeverity()));
93 if (hasError) {
94 return null;
95 }
96 var contents = doc.getResource().getContents();
97 if (contents.isEmpty()) {
98 return null;
99 }
100 var model = contents.get(0);
101 if (!(model instanceof Problem problem)) {
102 return null;
103 }
104 return problem;
105 }
106
107 private List<String> getNodeTrace(ModelInitializer initializer) {
108 var nodeTrace = new String[initializer.getNodeCount()];
109 for (var entry : initializer.getNodeTrace().keyValuesView()) {
110 var node = entry.getOne();
111 var index = entry.getTwo();
112 nodeTrace[index] = semanticsUtils.getName(node).orElse(null);
113 }
114 return Arrays.asList(nodeTrace);
115 }
116
117 private JsonObject getPartialInterpretation(ModelInitializer initializer, Model model,
118 CancelIndicator cancelIndicator) {
119 var adapter = model.getAdapter(ReasoningAdapter.class);
120 var json = new JsonObject();
121 for (var entry : initializer.getRelationTrace().entrySet()) {
122 var relation = entry.getKey();
123 var partialSymbol = entry.getValue();
124 var tuples = getTuplesJson(adapter, partialSymbol);
125 var name = semanticsUtils.getName(relation).orElse(partialSymbol.name());
126 json.add(name, tuples);
127 operationCanceledManager.checkCanceled(cancelIndicator);
128 }
129 return json;
130 }
131
132 private static JsonArray getTuplesJson(ReasoningAdapter adapter, PartialRelation partialSymbol) {
133 var interpretation = adapter.getPartialInterpretation(Concreteness.PARTIAL, partialSymbol);
134 var cursor = interpretation.getAll();
135 var map = new TreeMap<Tuple, TruthValue>();
136 while (cursor.move()) {
137 map.put(cursor.getKey(), cursor.getValue());
138 }
139 var tuples = new JsonArray();
140 for (var entry : map.entrySet()) {
141 tuples.add(toArray(entry.getKey(), entry.getValue()));
142 }
143 return tuples;
144 }
145
146 private static JsonArray toArray(Tuple tuple, TruthValue value) {
147 int arity = tuple.getSize();
148 var json = new JsonArray(arity + 1);
149 for (int i = 0; i < arity; i++) {
150 json.add(tuple.get(i));
151 }
152 json.add(value.toString());
153 return json;
154 }
155}
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
new file mode 100644
index 00000000..15fd4b55
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsSuccessResult.java
@@ -0,0 +1,13 @@
1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics;
7
8import com.google.gson.JsonObject;
9
10import java.util.List;
11
12public record SemanticsSuccessResult(List<String> nodes, JsonObject partialInterpretation) implements SemanticsResult {
13}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
index 0135d8f5..2c0e9329 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/TransactionExecutor.java
@@ -134,7 +134,7 @@ public class TransactionExecutor implements IDisposable, PrecomputationListener
134 * @throws UnknownLanguageException if the Xtext language cannot be determined 134 * @throws UnknownLanguageException if the Xtext language cannot be determined
135 */ 135 */
136 protected Injector getInjector(IServiceContext context) { 136 protected Injector getInjector(IServiceContext context) {
137 IResourceServiceProvider resourceServiceProvider = null; 137 IResourceServiceProvider resourceServiceProvider;
138 var resourceName = context.getParameter("resource"); 138 var resourceName = context.getParameter("resource");
139 if (resourceName == null) { 139 if (resourceName == null) {
140 resourceName = ""; 140 resourceName = "";
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java
index 73527ee5..c3379329 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebOkResponse.java
@@ -5,12 +5,11 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
7 7
8import java.util.Objects; 8import com.google.gson.annotations.SerializedName;
9
10import org.eclipse.xtext.web.server.IServiceResult; 9import org.eclipse.xtext.web.server.IServiceResult;
11import org.eclipse.xtext.web.server.IUnwrappableServiceResult; 10import org.eclipse.xtext.web.server.IUnwrappableServiceResult;
12 11
13import com.google.gson.annotations.SerializedName; 12import java.util.Objects;
14 13
15public final class XtextWebOkResponse implements XtextWebResponse { 14public final class XtextWebOkResponse implements XtextWebResponse {
16 private String id; 15 private String id;
@@ -19,7 +18,6 @@ public final class XtextWebOkResponse implements XtextWebResponse {
19 private Object responseData; 18 private Object responseData;
20 19
21 public XtextWebOkResponse(String id, Object responseData) { 20 public XtextWebOkResponse(String id, Object responseData) {
22 super();
23 this.id = id; 21 this.id = id;
24 this.responseData = responseData; 22 this.responseData = responseData;
25 } 23 }
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java
index 61444c99..c370fb56 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/message/XtextWebResponse.java
@@ -5,5 +5,5 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.message; 6package tools.refinery.language.web.xtext.server.message;
7 7
8public sealed interface XtextWebResponse permits XtextWebOkResponse,XtextWebErrorResponse,XtextWebPushMessage { 8public sealed interface XtextWebResponse permits XtextWebOkResponse, XtextWebErrorResponse, XtextWebPushMessage {
9} 9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
index 4c9135c8..d4a8c433 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushServiceDispatcher.java
@@ -5,16 +5,28 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
7 7
8import com.google.inject.Inject;
8import org.eclipse.xtext.web.server.IServiceContext; 9import org.eclipse.xtext.web.server.IServiceContext;
9import org.eclipse.xtext.web.server.XtextServiceDispatcher; 10import org.eclipse.xtext.web.server.XtextServiceDispatcher;
11import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry;
10import org.eclipse.xtext.web.server.model.XtextWebDocument; 12import org.eclipse.xtext.web.server.model.XtextWebDocument;
11 13
12import com.google.inject.Singleton; 14import com.google.inject.Singleton;
13 15
16import tools.refinery.language.web.semantics.SemanticsService;
14import tools.refinery.language.web.xtext.server.SubscribingServiceContext; 17import tools.refinery.language.web.xtext.server.SubscribingServiceContext;
15 18
16@Singleton 19@Singleton
17public class PushServiceDispatcher extends XtextServiceDispatcher { 20public class PushServiceDispatcher extends XtextServiceDispatcher {
21 @Inject
22 private SemanticsService semanticsService;
23
24 @Override
25 @Inject
26 protected void registerPreComputedServices(PrecomputedServiceRegistry registry) {
27 super.registerPreComputedServices(registry);
28 registry.addPrecomputedService(semanticsService);
29 }
18 30
19 @Override 31 @Override
20 protected XtextWebDocument getFullTextDocument(String fullText, String resourceId, IServiceContext context) { 32 protected XtextWebDocument getFullTextDocument(String fullText, String resourceId, IServiceContext context) {
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
index 56fd12c9..dfbd4878 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocument.java
@@ -5,11 +5,7 @@
5 */ 5 */
6package tools.refinery.language.web.xtext.server.push; 6package tools.refinery.language.web.xtext.server.push;
7 7
8import java.util.ArrayList; 8import com.google.common.collect.ImmutableList;
9import java.util.HashMap;
10import java.util.List;
11import java.util.Map;
12
13import org.eclipse.xtext.util.CancelIndicator; 9import org.eclipse.xtext.util.CancelIndicator;
14import org.eclipse.xtext.web.server.IServiceResult; 10import org.eclipse.xtext.web.server.IServiceResult;
15import org.eclipse.xtext.web.server.model.AbstractCachedService; 11import org.eclipse.xtext.web.server.model.AbstractCachedService;
@@ -17,11 +13,13 @@ import org.eclipse.xtext.web.server.model.DocumentSynchronizer;
17import org.eclipse.xtext.web.server.model.XtextWebDocument; 13import org.eclipse.xtext.web.server.model.XtextWebDocument;
18import org.slf4j.Logger; 14import org.slf4j.Logger;
19import org.slf4j.LoggerFactory; 15import org.slf4j.LoggerFactory;
20
21import com.google.common.collect.ImmutableList;
22
23import tools.refinery.language.web.xtext.server.ResponseHandlerException; 16import tools.refinery.language.web.xtext.server.ResponseHandlerException;
24 17
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.List;
21import java.util.Map;
22
25public class PushWebDocument extends XtextWebDocument { 23public class PushWebDocument extends XtextWebDocument {
26 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class); 24 private static final Logger LOG = LoggerFactory.getLogger(PushWebDocument.class);
27 25
@@ -36,37 +34,31 @@ public class PushWebDocument extends XtextWebDocument {
36 } 34 }
37 } 35 }
38 36
39 public boolean addPrecomputationListener(PrecomputationListener listener) { 37 public void addPrecomputationListener(PrecomputationListener listener) {
40 synchronized (precomputationListeners) { 38 synchronized (precomputationListeners) {
41 if (precomputationListeners.contains(listener)) { 39 if (precomputationListeners.contains(listener)) {
42 return false; 40 return;
43 } 41 }
44 precomputationListeners.add(listener); 42 precomputationListeners.add(listener);
45 listener.onSubscribeToPrecomputationEvents(getResourceId(), this); 43 listener.onSubscribeToPrecomputationEvents(getResourceId(), this);
46 return true;
47 } 44 }
48 } 45 }
49 46
50 public boolean removePrecomputationListener(PrecomputationListener listener) { 47 public void removePrecomputationListener(PrecomputationListener listener) {
51 synchronized (precomputationListeners) { 48 synchronized (precomputationListeners) {
52 return precomputationListeners.remove(listener); 49 precomputationListeners.remove(listener);
53 } 50 }
54 } 51 }
55 52
56 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName, 53 public <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, String serviceName,
57 CancelIndicator cancelIndicator, boolean logCacheMiss) { 54 CancelIndicator cancelIndicator, boolean logCacheMiss) {
58 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
59 if (result == null) {
60 LOG.error("{} service returned null result", serviceName);
61 return;
62 }
63 var serviceClass = service.getClass(); 55 var serviceClass = service.getClass();
64 var previousResult = precomputedServices.get(serviceClass); 56 var previousResult = precomputedServices.get(serviceClass);
65 if (previousResult != null && previousResult.equals(result)) { 57 var result = getCachedServiceResult(service, cancelIndicator, logCacheMiss);
66 return;
67 }
68 precomputedServices.put(serviceClass, result); 58 precomputedServices.put(serviceClass, result);
69 notifyPrecomputationListeners(serviceName, result); 59 if (result != null && !result.equals(previousResult)) {
60 notifyPrecomputationListeners(serviceName, result);
61 }
70 } 62 }
71 63
72 private <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) { 64 private <T extends IServiceResult> void notifyPrecomputationListeners(String serviceName, T result) {
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
index d9e548cd..c72e8e67 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/server/push/PushWebDocumentAccess.java
@@ -18,6 +18,7 @@ import org.eclipse.xtext.web.server.syntaxcoloring.HighlightingService;
18import org.eclipse.xtext.web.server.validation.ValidationService; 18import org.eclipse.xtext.web.server.validation.ValidationService;
19 19
20import com.google.inject.Inject; 20import com.google.inject.Inject;
21import tools.refinery.language.web.semantics.SemanticsService;
21 22
22public class PushWebDocumentAccess extends XtextWebDocumentAccess { 23public class PushWebDocumentAccess extends XtextWebDocumentAccess {
23 24
@@ -49,7 +50,7 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
49 precomputeServiceResult(service, false); 50 precomputeServiceResult(service, false);
50 } 51 }
51 } 52 }
52 53
53 protected <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, boolean logCacheMiss) { 54 protected <T extends IServiceResult> void precomputeServiceResult(AbstractCachedService<T> service, boolean logCacheMiss) {
54 var serviceName = getPrecomputedServiceName(service); 55 var serviceName = getPrecomputedServiceName(service);
55 readOnly(new CancelableUnitOfWork<Void, IXtextWebDocument>() { 56 readOnly(new CancelableUnitOfWork<Void, IXtextWebDocument>() {
@@ -60,7 +61,7 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
60 } 61 }
61 }); 62 });
62 } 63 }
63 64
64 protected String getPrecomputedServiceName(AbstractCachedService<? extends IServiceResult> service) { 65 protected String getPrecomputedServiceName(AbstractCachedService<? extends IServiceResult> service) {
65 if (service instanceof ValidationService) { 66 if (service instanceof ValidationService) {
66 return "validate"; 67 return "validate";
@@ -68,6 +69,9 @@ public class PushWebDocumentAccess extends XtextWebDocumentAccess {
68 if (service instanceof HighlightingService) { 69 if (service instanceof HighlightingService) {
69 return "highlight"; 70 return "highlight";
70 } 71 }
72 if (service instanceof SemanticsService) {
73 return "semantics";
74 }
71 throw new IllegalArgumentException("Unknown precomputed service: " + service); 75 throw new IllegalArgumentException("Unknown precomputed service: " + service);
72 } 76 }
73} 77}
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
index 927eeab1..99ca5420 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java
@@ -93,7 +93,7 @@ class ProblemWebSocketServletIntegrationTest {
93 clientSocket.waitForTestResult(); 93 clientSocket.waitForTestResult();
94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL)); 94 assertThat(clientSocket.getCloseStatusCode(), equalTo(StatusCode.NORMAL));
95 var responses = clientSocket.getResponses(); 95 var responses = clientSocket.getResponses();
96 assertThat(responses, hasSize(5)); 96 assertThat(responses, hasSize(7));
97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}")); 97 assertThat(responses.get(0), equalTo("{\"id\":\"foo\",\"response\":{\"stateId\":\"-80000000\"}}"));
98 assertThat(responses.get(1), startsWith( 98 assertThat(responses.get(1), startsWith(
99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," + 99 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"highlight\"," +
@@ -101,10 +101,16 @@ class ProblemWebSocketServletIntegrationTest {
101 assertThat(responses.get(2), equalTo( 101 assertThat(responses.get(2), equalTo(
102 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\"," + 102 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"validate\"," +
103 "\"push\":{\"issues\":[]}}")); 103 "\"push\":{\"issues\":[]}}"));
104 assertThat(responses.get(3), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}")); 104 assertThat(responses.get(3), startsWith(
105 assertThat(responses.get(4), startsWith( 105 "{\"resource\":\"test.problem\",\"stateId\":\"-80000000\",\"service\":\"semantics\"," +
106 "\"push\":{"));
107 assertThat(responses.get(4), equalTo("{\"id\":\"bar\",\"response\":{\"stateId\":\"-7fffffff\"}}"));
108 assertThat(responses.get(5), startsWith(
106 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," + 109 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"highlight\"," +
107 "\"push\":{\"regions\":[")); 110 "\"push\":{\"regions\":["));
111 assertThat(responses.get(6), startsWith(
112 "{\"resource\":\"test.problem\",\"stateId\":\"-7fffffff\",\"service\":\"semantics\"," +
113 "\"push\":{"));
108 } 114 }
109 115
110 @WebSocket 116 @WebSocket
@@ -117,14 +123,14 @@ class ProblemWebSocketServletIntegrationTest {
117 "\"fullText\":\"class Person.\n\"}}", 123 "\"fullText\":\"class Person.\n\"}}",
118 Callback.NOOP 124 Callback.NOOP
119 ); 125 );
120 case 3 -> //noinspection TextBlockMigration 126 case 4 -> //noinspection TextBlockMigration
121 session.sendText( 127 session.sendText(
122 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\"," + 128 "{\"id\":\"bar\",\"request\":{\"resource\":\"test.problem\",\"serviceType\":\"update\"," +
123 "\"requiredStateId\":\"-80000000\",\"deltaText\":\"indiv q.\nnode(q).\n\"," + 129 "\"requiredStateId\":\"-80000000\",\"deltaText\":\"indiv q.\nnode(q).\n\"," +
124 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}", 130 "\"deltaOffset\":\"0\",\"deltaReplaceLength\":\"0\"}}",
125 Callback.NOOP 131 Callback.NOOP
126 ); 132 );
127 case 5 -> session.close(); 133 case 7 -> session.close();
128 } 134 }
129 } 135 }
130 } 136 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
index 09079aa8..991ff114 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java
@@ -35,7 +35,7 @@ public class RestartableCachedThreadPool implements ExecutorService {
35 public void waitForTermination() { 35 public void waitForTermination() {
36 boolean result = false; 36 boolean result = false;
37 try { 37 try {
38 result = delegate.awaitTermination(1, TimeUnit.SECONDS); 38 result = delegate.awaitTermination(10, TimeUnit.SECONDS);
39 } catch (InterruptedException e) { 39 } catch (InterruptedException e) {
40 LOG.warn("Interrupted while waiting for delegate executor to stop", e); 40 LOG.warn("Interrupted while waiting for delegate executor to stop", e);
41 } 41 }
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
index 841bacd3..b7142506 100644
--- a/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
+++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/xtext/servlet/TransactionExecutorTest.java
@@ -95,7 +95,7 @@ class TransactionExecutorTest {
95 "0"))); 95 "0")));
96 96
97 var captor = newCaptor(); 97 var captor = newCaptor();
98 verify(responseHandler, times(2)).onResponse(captor.capture()); 98 verify(responseHandler, times(3)).onResponse(captor.capture());
99 var newStateId = getStateId("bar", captor.getAllValues().get(0)); 99 var newStateId = getStateId("bar", captor.getAllValues().get(0));
100 assertHighlightingResponse(newStateId, captor.getAllValues().get(1)); 100 assertHighlightingResponse(newStateId, captor.getAllValues().get(1));
101 } 101 }
@@ -126,7 +126,7 @@ class TransactionExecutorTest {
126 private String updateFullText(ArgumentCaptor<XtextWebResponse> captor) throws ResponseHandlerException { 126 private String updateFullText(ArgumentCaptor<XtextWebResponse> captor) throws ResponseHandlerException {
127 var responseHandler = sendRequestAndWaitForAllResponses(new XtextWebRequest("foo", UPDATE_FULL_TEXT_PARAMS)); 127 var responseHandler = sendRequestAndWaitForAllResponses(new XtextWebRequest("foo", UPDATE_FULL_TEXT_PARAMS));
128 128
129 verify(responseHandler, times(3)).onResponse(captor.capture()); 129 verify(responseHandler, times(4)).onResponse(captor.capture());
130 return getStateId("foo", captor.getAllValues().get(0)); 130 return getStateId("foo", captor.getAllValues().get(0));
131 } 131 }
132 132