diff options
Diffstat (limited to 'subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java')
-rw-r--r-- | subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java new file mode 100644 index 00000000..38fe7c2f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java | |||
@@ -0,0 +1,463 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro | ||
3 | * Copyright (c) 2023 The Refinery Authors <https://refinery.tools> | ||
4 | * This program and the accompanying materials are made available under the | ||
5 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
6 | * http://www.eclipse.org/legal/epl-v20.html. | ||
7 | * | ||
8 | * SPDX-License-Identifier: EPL-2.0 | ||
9 | *******************************************************************************/ | ||
10 | |||
11 | package tools.refinery.viatra.runtime.rete.matcher; | ||
12 | |||
13 | import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; | ||
14 | import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; | ||
15 | import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; | ||
16 | import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; | ||
17 | import tools.refinery.viatra.runtime.matchers.tuple.ITuple; | ||
18 | import tools.refinery.viatra.runtime.matchers.tuple.Tuple; | ||
19 | import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; | ||
20 | import tools.refinery.viatra.runtime.matchers.tuple.Tuples; | ||
21 | import tools.refinery.viatra.runtime.matchers.util.Accuracy; | ||
22 | import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; | ||
23 | import tools.refinery.viatra.runtime.rete.index.Indexer; | ||
24 | import tools.refinery.viatra.runtime.rete.index.IterableIndexer; | ||
25 | import tools.refinery.viatra.runtime.rete.network.Node; | ||
26 | import tools.refinery.viatra.runtime.rete.network.ProductionNode; | ||
27 | import tools.refinery.viatra.runtime.rete.network.Receiver; | ||
28 | import tools.refinery.viatra.runtime.rete.remote.Address; | ||
29 | import tools.refinery.viatra.runtime.rete.single.CallbackNode; | ||
30 | import tools.refinery.viatra.runtime.rete.single.TransformerNode; | ||
31 | import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; | ||
32 | |||
33 | import java.util.Collection; | ||
34 | import java.util.List; | ||
35 | import java.util.Map; | ||
36 | import java.util.Optional; | ||
37 | import java.util.stream.Collectors; | ||
38 | import java.util.stream.Stream; | ||
39 | |||
40 | /** | ||
41 | * @author Gabor Bergmann | ||
42 | * | ||
43 | */ | ||
44 | public class RetePatternMatcher extends TransformerNode implements IQueryResultProvider { | ||
45 | |||
46 | protected ReteEngine engine; | ||
47 | protected IQueryRuntimeContext context; | ||
48 | protected ProductionNode productionNode; | ||
49 | protected RecipeTraceInfo productionNodeTrace; | ||
50 | protected Map<String, Integer> posMapping; | ||
51 | protected Map<Object, Receiver> taggedChildren = CollectionsFactory.createMap(); | ||
52 | protected boolean connected = false; // is rete-wise connected to the | ||
53 | // production node? | ||
54 | |||
55 | /** | ||
56 | * @param productionNode | ||
57 | * a production node that matches this pattern without any parameter bindings | ||
58 | * @pre: Production must be local to the head container | ||
59 | */ | ||
60 | public RetePatternMatcher(ReteEngine engine, RecipeTraceInfo productionNodeTrace) { | ||
61 | super(engine.getReteNet().getHeadContainer()); | ||
62 | this.engine = engine; | ||
63 | this.context = engine.getRuntimeContext(); | ||
64 | this.productionNodeTrace = productionNodeTrace; | ||
65 | final Address<? extends Node> productionAddress = reteContainer.getProvisioner() | ||
66 | .getOrCreateNodeByRecipe(productionNodeTrace); | ||
67 | if (!reteContainer.isLocal(productionAddress)) | ||
68 | throw new IllegalArgumentException("@pre: Production must be local to the head container"); | ||
69 | this.productionNode = (ProductionNode) reteContainer.resolveLocal(productionAddress); | ||
70 | this.posMapping = this.productionNode.getPosMapping(); | ||
71 | this.reteContainer.getCommunicationTracker().registerDependency(this.productionNode, this); | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * @since 1.6 | ||
76 | */ | ||
77 | public ProductionNode getProductionNode() { | ||
78 | return productionNode; | ||
79 | } | ||
80 | |||
81 | public Tuple matchOneRandomly(Object[] inputMapping, boolean[] fixed) { | ||
82 | List<Tuple> allMatches = matchAll(inputMapping, fixed).collect(Collectors.toList()); | ||
83 | if (allMatches == null || allMatches.isEmpty()) | ||
84 | return null; | ||
85 | else | ||
86 | return allMatches.get((int) (Math.random() * allMatches.size())); | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * @since 2.0 | ||
91 | */ | ||
92 | public Stream<Tuple> matchAll(Object[] inputMapping, boolean[] fixed) { | ||
93 | // retrieving the projection | ||
94 | TupleMask mask = TupleMask.fromKeepIndicators(fixed); | ||
95 | Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); | ||
96 | |||
97 | return matchAll(mask, inputSignature); | ||
98 | |||
99 | } | ||
100 | |||
101 | /** | ||
102 | * @since 2.0 | ||
103 | */ | ||
104 | public Stream<Tuple> matchAll(TupleMask mask, ITuple inputSignature) { | ||
105 | AllMatchFetcher fetcher = new AllMatchFetcher(engine.accessProjection(productionNodeTrace, mask), | ||
106 | context.wrapTuple(inputSignature.toImmutable())); | ||
107 | engine.reteNet.waitForReteTermination(fetcher); | ||
108 | return fetcher.getMatches(); | ||
109 | |||
110 | } | ||
111 | |||
112 | /** | ||
113 | * @since 2.0 | ||
114 | */ | ||
115 | public Optional<Tuple> matchOne(Object[] inputMapping, boolean[] fixed) { | ||
116 | // retrieving the projection | ||
117 | TupleMask mask = TupleMask.fromKeepIndicators(fixed); | ||
118 | Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); | ||
119 | |||
120 | return matchOne(mask, inputSignature); | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * @since 2.0 | ||
125 | */ | ||
126 | public Optional<Tuple> matchOne(TupleMask mask, ITuple inputSignature) { | ||
127 | SingleMatchFetcher fetcher = new SingleMatchFetcher(engine.accessProjection(productionNodeTrace, mask), | ||
128 | context.wrapTuple(inputSignature.toImmutable())); | ||
129 | engine.reteNet.waitForReteTermination(fetcher); | ||
130 | return Optional.ofNullable(fetcher.getMatch()); | ||
131 | } | ||
132 | |||
133 | /** | ||
134 | * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true. | ||
135 | * | ||
136 | * @return the number of occurrences | ||
137 | */ | ||
138 | public int count(Object[] inputMapping, boolean[] fixed) { | ||
139 | TupleMask mask = TupleMask.fromKeepIndicators(fixed); | ||
140 | Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); | ||
141 | |||
142 | return count(mask, inputSignature); | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true. | ||
147 | * | ||
148 | * @return the number of occurrences | ||
149 | * @since 1.7 | ||
150 | */ | ||
151 | public int count(TupleMask mask, ITuple inputSignature) { | ||
152 | CountFetcher fetcher = new CountFetcher(engine.accessProjection(productionNodeTrace, mask), | ||
153 | context.wrapTuple(inputSignature.toImmutable())); | ||
154 | engine.reteNet.waitForReteTermination(fetcher); | ||
155 | |||
156 | return fetcher.getCount(); | ||
157 | } | ||
158 | |||
159 | /** | ||
160 | * Counts the number of distinct tuples attainable from the match set by projecting match tuples according to the given mask. | ||
161 | * | ||
162 | * | ||
163 | * @return the size of the projection | ||
164 | * @since 2.1 | ||
165 | */ | ||
166 | public int projectionSize(TupleMask groupMask) { | ||
167 | ProjectionSizeFetcher fetcher = new ProjectionSizeFetcher( | ||
168 | (IterableIndexer) engine.accessProjection(productionNodeTrace, groupMask)); | ||
169 | engine.reteNet.waitForReteTermination(fetcher); | ||
170 | |||
171 | return fetcher.getSize(); | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * Connects a new external receiver that will receive update notifications from now on. The receiver will | ||
176 | * practically connect to the production node, the added value is unwrapping the updates for external use. | ||
177 | * | ||
178 | * @param synchronize | ||
179 | * if true, the contents of the production node will be inserted into the receiver after the connection | ||
180 | * is established. | ||
181 | */ | ||
182 | public synchronized void connect(Receiver receiver, boolean synchronize) { | ||
183 | if (!connected) { // connect to the production node as a RETE-child | ||
184 | reteContainer.connect(productionNode, this); | ||
185 | connected = true; | ||
186 | } | ||
187 | if (synchronize) | ||
188 | reteContainer.connectAndSynchronize(this, receiver); | ||
189 | else | ||
190 | reteContainer.connect(this, receiver); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * Connects a new external receiver that will receive update notifications from now on. The receiver will | ||
195 | * practically connect to the production node, the added value is unwrapping the updates for external use. | ||
196 | * | ||
197 | * The external receiver will be disconnectable later based on its tag. | ||
198 | * | ||
199 | * @param tag | ||
200 | * an identifier to recognize the child node by. | ||
201 | * | ||
202 | * @param synchronize | ||
203 | * if true, the contents of the production node will be inserted into the receiver after the connection | ||
204 | * is established. | ||
205 | * | ||
206 | */ | ||
207 | public synchronized void connect(Receiver receiver, Object tag, boolean synchronize) { | ||
208 | taggedChildren.put(tag, receiver); | ||
209 | connect(receiver, synchronize); | ||
210 | } | ||
211 | |||
212 | /** | ||
213 | * Disconnects a child node. | ||
214 | */ | ||
215 | public synchronized void disconnect(Receiver receiver) { | ||
216 | reteContainer.disconnect(this, receiver); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * Disconnects the child node that was connected by specifying the given tag. | ||
221 | * | ||
222 | * @return if a child node was found registered with this tag. | ||
223 | */ | ||
224 | public synchronized boolean disconnectByTag(Object tag) { | ||
225 | final Receiver receiver = taggedChildren.remove(tag); | ||
226 | final boolean found = receiver != null; | ||
227 | if (found) | ||
228 | disconnect(receiver); | ||
229 | return found; | ||
230 | } | ||
231 | |||
232 | @Override | ||
233 | protected Tuple transform(Tuple input) { | ||
234 | return context.unwrapTuple(input); | ||
235 | } | ||
236 | |||
237 | abstract class AbstractMatchFetcher implements Runnable { | ||
238 | Indexer indexer; | ||
239 | Tuple signature; | ||
240 | |||
241 | public AbstractMatchFetcher(Indexer indexer, Tuple signature) { | ||
242 | super(); | ||
243 | this.indexer = indexer; | ||
244 | this.signature = signature; | ||
245 | } | ||
246 | |||
247 | @Override | ||
248 | public void run() { | ||
249 | fetch(indexer.get(signature)); | ||
250 | } | ||
251 | |||
252 | protected abstract void fetch(Collection<Tuple> matches); | ||
253 | |||
254 | } | ||
255 | |||
256 | class AllMatchFetcher extends AbstractMatchFetcher { | ||
257 | |||
258 | public AllMatchFetcher(Indexer indexer, Tuple signature) { | ||
259 | super(indexer, signature); | ||
260 | } | ||
261 | |||
262 | Stream<Tuple> matches = null; | ||
263 | |||
264 | public Stream<Tuple> getMatches() { | ||
265 | return matches; | ||
266 | } | ||
267 | |||
268 | @Override | ||
269 | protected void fetch(Collection<Tuple> matches) { | ||
270 | if (matches == null) | ||
271 | this.matches = Stream.of(); | ||
272 | else { | ||
273 | this.matches = matches.stream().map(context::unwrapTuple); | ||
274 | } | ||
275 | |||
276 | } | ||
277 | |||
278 | } | ||
279 | |||
280 | class SingleMatchFetcher extends AbstractMatchFetcher { | ||
281 | |||
282 | public SingleMatchFetcher(Indexer indexer, Tuple signature) { | ||
283 | super(indexer, signature); | ||
284 | } | ||
285 | |||
286 | Tuple match = null; | ||
287 | |||
288 | public Tuple getMatch() { | ||
289 | return match; | ||
290 | } | ||
291 | |||
292 | @Override | ||
293 | protected void fetch(Collection<Tuple> matches) { | ||
294 | if (matches != null && !matches.isEmpty()) | ||
295 | match = context.unwrapTuple(matches.iterator().next()); | ||
296 | } | ||
297 | |||
298 | // public void run() { | ||
299 | // Collection<Tuple> unscopedMatches = indexer.get(signature); | ||
300 | // | ||
301 | // // checking scopes | ||
302 | // if (unscopedMatches != null) { | ||
303 | // for (Tuple um : /* productionNode */unscopedMatches) { | ||
304 | // match = inputConnector.unwrapTuple(um); | ||
305 | // return; | ||
306 | // | ||
307 | // // Tuple ps = inputConnector.unwrapTuple(um); | ||
308 | // // boolean ok = true; | ||
309 | // // if (!ignoreScope) for (int k = 0; (k < ps.getSize()) && ok; k++) { | ||
310 | // // if (pcs[k].getParameterMode() == ParameterMode.INPUT) { | ||
311 | // // // ok = ok && (inputMapping[k]==ps.elements[k]); | ||
312 | // // // should now be true | ||
313 | // // } else // ParameterMode.OUTPUT | ||
314 | // // { | ||
315 | // // IEntity scopeParent = (IEntity) pcs[k].getParameterScope().getParent(); | ||
316 | // // Integer containmentMode = pcs[k].getParameterScope().getContainmentMode(); | ||
317 | // // if (containmentMode == Scope.BELOW) | ||
318 | // // ok = ok && ((IModelElement) ps.get(k)).isBelowNamespace(scopeParent); | ||
319 | // // else | ||
320 | // // /* case Scope.IN: */ | ||
321 | // // ok = ok && scopeParent.equals(((IModelElement) ps.get(k)).getNamespace()); | ||
322 | // // // note: getNamespace returns null instead of the | ||
323 | // // // (imaginary) modelspace root entity for top level | ||
324 | // // // elements; | ||
325 | // // // this is not a problem here as Scope.IN implies | ||
326 | // // // scopeParent != root. | ||
327 | // // | ||
328 | // // } | ||
329 | // // } | ||
330 | // // | ||
331 | // // if (ok) { | ||
332 | // // reteMatching = new ReteMatching(ps, posMapping); | ||
333 | // // return; | ||
334 | // // } | ||
335 | // } | ||
336 | // } | ||
337 | // | ||
338 | // } | ||
339 | |||
340 | } | ||
341 | |||
342 | class CountFetcher extends AbstractMatchFetcher { | ||
343 | |||
344 | public CountFetcher(Indexer indexer, Tuple signature) { | ||
345 | super(indexer, signature); | ||
346 | } | ||
347 | |||
348 | int count = 0; | ||
349 | |||
350 | public int getCount() { | ||
351 | return count; | ||
352 | } | ||
353 | |||
354 | @Override | ||
355 | protected void fetch(Collection<Tuple> matches) { | ||
356 | count = matches == null ? 0 : matches.size(); | ||
357 | } | ||
358 | |||
359 | } | ||
360 | |||
361 | class ProjectionSizeFetcher implements Runnable { | ||
362 | IterableIndexer indexer; | ||
363 | int size = 0; | ||
364 | |||
365 | public ProjectionSizeFetcher(IterableIndexer indexer) { | ||
366 | super(); | ||
367 | this.indexer = indexer; | ||
368 | } | ||
369 | |||
370 | @Override | ||
371 | public void run() { | ||
372 | size = indexer.getBucketCount(); | ||
373 | } | ||
374 | |||
375 | public int getSize() { | ||
376 | return size; | ||
377 | } | ||
378 | |||
379 | } | ||
380 | |||
381 | private boolean[] notNull(Object[] parameters) { | ||
382 | boolean[] notNull = new boolean[parameters.length]; | ||
383 | for (int i = 0; i < parameters.length; ++i) | ||
384 | notNull[i] = parameters[i] != null; | ||
385 | return notNull; | ||
386 | } | ||
387 | |||
388 | |||
389 | |||
390 | @Override | ||
391 | public boolean hasMatch(Object[] parameters) { | ||
392 | return countMatches(parameters) > 0; | ||
393 | } | ||
394 | |||
395 | @Override | ||
396 | public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) { | ||
397 | return count(parameterSeedMask, parameters) > 0; | ||
398 | } | ||
399 | |||
400 | @Override | ||
401 | public int countMatches(Object[] parameters) { | ||
402 | return count(parameters, notNull(parameters)); | ||
403 | } | ||
404 | |||
405 | @Override | ||
406 | public int countMatches(TupleMask parameterSeedMask, ITuple parameters) { | ||
407 | return count(parameterSeedMask, parameters); | ||
408 | } | ||
409 | |||
410 | |||
411 | @Override | ||
412 | public Optional<Long> estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) { | ||
413 | return Optional.of((long)projectionSize(groupMask)); // always accurate | ||
414 | } | ||
415 | |||
416 | @Override | ||
417 | public Optional<Tuple> getOneArbitraryMatch(Object[] parameters) { | ||
418 | return matchOne(parameters, notNull(parameters)); | ||
419 | } | ||
420 | |||
421 | @Override | ||
422 | public Optional<Tuple> getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) { | ||
423 | return matchOne(parameterSeedMask, parameters); | ||
424 | } | ||
425 | |||
426 | @Override | ||
427 | public Stream<Tuple> getAllMatches(Object[] parameters) { | ||
428 | return matchAll(parameters, notNull(parameters)); | ||
429 | } | ||
430 | |||
431 | @Override | ||
432 | public Stream<Tuple> getAllMatches(TupleMask parameterSeedMask, ITuple parameters) { | ||
433 | return matchAll(parameterSeedMask, parameters); | ||
434 | } | ||
435 | |||
436 | @Override | ||
437 | public IQueryBackend getQueryBackend() { | ||
438 | return engine; | ||
439 | } | ||
440 | |||
441 | @Override | ||
442 | public void addUpdateListener(final IUpdateable listener, final Object listenerTag, boolean fireNow) { | ||
443 | // As a listener is added as a delayed command, they should be executed to make sure everything is consistent on | ||
444 | // return, see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=562369 | ||
445 | engine.constructionWrapper(() -> { | ||
446 | final CallbackNode callbackNode = new CallbackNode(this.reteContainer, listener); | ||
447 | connect(callbackNode, listenerTag, fireNow); | ||
448 | return null; | ||
449 | }); | ||
450 | } | ||
451 | |||
452 | @Override | ||
453 | public void removeUpdateListener(Object listenerTag) { | ||
454 | engine.constructionWrapper(() -> { | ||
455 | disconnectByTag(listenerTag); | ||
456 | return null; | ||
457 | }); | ||
458 | } | ||
459 | |||
460 | public Indexer getInternalIndexer(TupleMask mask) { | ||
461 | return engine.accessProjection(productionNodeTrace, mask); | ||
462 | } | ||
463 | } | ||