diff options
author | 2023-08-17 02:32:26 +0200 | |
---|---|---|
committer | 2023-08-17 02:43:55 +0200 | |
commit | eb5da232b5954895b449957c73e35d0b36e3a902 (patch) | |
tree | a8714116cfe3102659e9c5e5b90131e7ec248492 /subprojects/language-web | |
parent | chore(deps): bump dependencies (diff) | |
download | refinery-eb5da232b5954895b449957c73e35d0b36e3a902.tar.gz refinery-eb5da232b5954895b449957c73e35d0b36e3a902.tar.zst refinery-eb5da232b5954895b449957c73e35d0b36e3a902.zip |
feat: basic semantics mapping and visualization
Diffstat (limited to 'subprojects/language-web')
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 { | |||
17 | dependencies { | 17 | dependencies { |
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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | public 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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | import org.eclipse.xtext.web.server.IServiceResult; | ||
9 | |||
10 | public 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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | import com.google.gson.JsonArray; | ||
9 | import com.google.gson.JsonObject; | ||
10 | import com.google.inject.Inject; | ||
11 | import com.google.inject.Provider; | ||
12 | import com.google.inject.Singleton; | ||
13 | import org.eclipse.xtext.service.OperationCanceledManager; | ||
14 | import org.eclipse.xtext.util.CancelIndicator; | ||
15 | import org.eclipse.xtext.web.server.model.AbstractCachedService; | ||
16 | import org.eclipse.xtext.web.server.model.IXtextWebDocument; | ||
17 | import org.eclipse.xtext.web.server.model.XtextWebDocument; | ||
18 | import org.eclipse.xtext.web.server.validation.ValidationService; | ||
19 | import org.jetbrains.annotations.Nullable; | ||
20 | import org.slf4j.Logger; | ||
21 | import org.slf4j.LoggerFactory; | ||
22 | import tools.refinery.language.model.problem.Problem; | ||
23 | import tools.refinery.language.semantics.model.ModelInitializer; | ||
24 | import tools.refinery.language.semantics.model.SemanticsUtils; | ||
25 | import tools.refinery.store.model.Model; | ||
26 | import tools.refinery.store.model.ModelStore; | ||
27 | import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; | ||
28 | import tools.refinery.store.reasoning.ReasoningAdapter; | ||
29 | import tools.refinery.store.reasoning.ReasoningStoreAdapter; | ||
30 | import tools.refinery.store.reasoning.literal.Concreteness; | ||
31 | import tools.refinery.store.reasoning.representation.PartialRelation; | ||
32 | import tools.refinery.store.representation.TruthValue; | ||
33 | import tools.refinery.store.tuple.Tuple; | ||
34 | |||
35 | import java.util.Arrays; | ||
36 | import java.util.List; | ||
37 | import java.util.TreeMap; | ||
38 | |||
39 | @Singleton | ||
40 | public 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 | */ | ||
6 | package tools.refinery.language.web.semantics; | ||
7 | |||
8 | import com.google.gson.JsonObject; | ||
9 | |||
10 | import java.util.List; | ||
11 | |||
12 | public 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 | */ |
6 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
7 | 7 | ||
8 | import java.util.Objects; | 8 | import com.google.gson.annotations.SerializedName; |
9 | |||
10 | import org.eclipse.xtext.web.server.IServiceResult; | 9 | import org.eclipse.xtext.web.server.IServiceResult; |
11 | import org.eclipse.xtext.web.server.IUnwrappableServiceResult; | 10 | import org.eclipse.xtext.web.server.IUnwrappableServiceResult; |
12 | 11 | ||
13 | import com.google.gson.annotations.SerializedName; | 12 | import java.util.Objects; |
14 | 13 | ||
15 | public final class XtextWebOkResponse implements XtextWebResponse { | 14 | public 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 | */ |
6 | package tools.refinery.language.web.xtext.server.message; | 6 | package tools.refinery.language.web.xtext.server.message; |
7 | 7 | ||
8 | public sealed interface XtextWebResponse permits XtextWebOkResponse,XtextWebErrorResponse,XtextWebPushMessage { | 8 | public 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 | */ |
6 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
7 | 7 | ||
8 | import com.google.inject.Inject; | ||
8 | import org.eclipse.xtext.web.server.IServiceContext; | 9 | import org.eclipse.xtext.web.server.IServiceContext; |
9 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; | 10 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; |
11 | import org.eclipse.xtext.web.server.model.PrecomputedServiceRegistry; | ||
10 | import org.eclipse.xtext.web.server.model.XtextWebDocument; | 12 | import org.eclipse.xtext.web.server.model.XtextWebDocument; |
11 | 13 | ||
12 | import com.google.inject.Singleton; | 14 | import com.google.inject.Singleton; |
13 | 15 | ||
16 | import tools.refinery.language.web.semantics.SemanticsService; | ||
14 | import tools.refinery.language.web.xtext.server.SubscribingServiceContext; | 17 | import tools.refinery.language.web.xtext.server.SubscribingServiceContext; |
15 | 18 | ||
16 | @Singleton | 19 | @Singleton |
17 | public class PushServiceDispatcher extends XtextServiceDispatcher { | 20 | public 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 | */ |
6 | package tools.refinery.language.web.xtext.server.push; | 6 | package tools.refinery.language.web.xtext.server.push; |
7 | 7 | ||
8 | import java.util.ArrayList; | 8 | import com.google.common.collect.ImmutableList; |
9 | import java.util.HashMap; | ||
10 | import java.util.List; | ||
11 | import java.util.Map; | ||
12 | |||
13 | import org.eclipse.xtext.util.CancelIndicator; | 9 | import org.eclipse.xtext.util.CancelIndicator; |
14 | import org.eclipse.xtext.web.server.IServiceResult; | 10 | import org.eclipse.xtext.web.server.IServiceResult; |
15 | import org.eclipse.xtext.web.server.model.AbstractCachedService; | 11 | import org.eclipse.xtext.web.server.model.AbstractCachedService; |
@@ -17,11 +13,13 @@ import org.eclipse.xtext.web.server.model.DocumentSynchronizer; | |||
17 | import org.eclipse.xtext.web.server.model.XtextWebDocument; | 13 | import org.eclipse.xtext.web.server.model.XtextWebDocument; |
18 | import org.slf4j.Logger; | 14 | import org.slf4j.Logger; |
19 | import org.slf4j.LoggerFactory; | 15 | import org.slf4j.LoggerFactory; |
20 | |||
21 | import com.google.common.collect.ImmutableList; | ||
22 | |||
23 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; | 16 | import tools.refinery.language.web.xtext.server.ResponseHandlerException; |
24 | 17 | ||
18 | import java.util.ArrayList; | ||
19 | import java.util.HashMap; | ||
20 | import java.util.List; | ||
21 | import java.util.Map; | ||
22 | |||
25 | public class PushWebDocument extends XtextWebDocument { | 23 | public 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; | |||
18 | import org.eclipse.xtext.web.server.validation.ValidationService; | 18 | import org.eclipse.xtext.web.server.validation.ValidationService; |
19 | 19 | ||
20 | import com.google.inject.Inject; | 20 | import com.google.inject.Inject; |
21 | import tools.refinery.language.web.semantics.SemanticsService; | ||
21 | 22 | ||
22 | public class PushWebDocumentAccess extends XtextWebDocumentAccess { | 23 | public 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 | ||