aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java
diff options
context:
space:
mode:
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.java463
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
11package tools.refinery.viatra.runtime.rete.matcher;
12
13import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
14import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
15import tools.refinery.viatra.runtime.matchers.backend.IUpdateable;
16import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
17import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
18import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
19import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
20import tools.refinery.viatra.runtime.matchers.tuple.Tuples;
21import tools.refinery.viatra.runtime.matchers.util.Accuracy;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.rete.index.Indexer;
24import tools.refinery.viatra.runtime.rete.index.IterableIndexer;
25import tools.refinery.viatra.runtime.rete.network.Node;
26import tools.refinery.viatra.runtime.rete.network.ProductionNode;
27import tools.refinery.viatra.runtime.rete.network.Receiver;
28import tools.refinery.viatra.runtime.rete.remote.Address;
29import tools.refinery.viatra.runtime.rete.single.CallbackNode;
30import tools.refinery.viatra.runtime.rete.single.TransformerNode;
31import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo;
32
33import java.util.Collection;
34import java.util.List;
35import java.util.Map;
36import java.util.Optional;
37import java.util.stream.Collectors;
38import java.util.stream.Stream;
39
40/**
41 * @author Gabor Bergmann
42 *
43 */
44public 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}