diff options
Diffstat (limited to 'subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java')
-rw-r--r-- | subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java new file mode 100644 index 00000000..8b1944c1 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java | |||
@@ -0,0 +1,485 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. | ||
3 | * This program and the accompanying materials are made available under the | ||
4 | * terms of the Eclipse Public License v. 2.0 which is available at | ||
5 | * http://www.eclipse.org/legal/epl-v20.html. | ||
6 | * | ||
7 | * SPDX-License-Identifier: EPL-2.0 | ||
8 | *******************************************************************************/ | ||
9 | package tools.refinery.viatra.runtime.matchers.util; | ||
10 | |||
11 | import java.util.Collections; | ||
12 | import java.util.Iterator; | ||
13 | import java.util.NoSuchElementException; | ||
14 | import java.util.Objects; | ||
15 | import java.util.stream.Stream; | ||
16 | import java.util.stream.StreamSupport; | ||
17 | |||
18 | import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedSet; | ||
19 | |||
20 | /** | ||
21 | * Specialized multimap implementation that saves memory | ||
22 | * by storing singleton value objects (multiplicity 1) instead of multiset buckets | ||
23 | * whenever there is only one value associated with a key. | ||
24 | * | ||
25 | * <p> See specialized {@link ToSetsAbstract}, {@link ToMultisetsAbstract} for various bucket types. | ||
26 | * | ||
27 | * <p> Implemented as a Key->Object map with invariant: <ul> | ||
28 | * <li> key maps to null if associated with no values; | ||
29 | * <li> key maps to a single Value iff it is associated with a single value of multiplicity +1; | ||
30 | * <li> key maps to Bucket otherwise | ||
31 | * </ul> | ||
32 | * | ||
33 | * Note that due to the above invariant, handling +1 and -1 are asymmetric in case of delta maps. | ||
34 | * | ||
35 | * <p> Not intended as an API, but rather as a 'base class' for implementors. | ||
36 | * Realized as an interface with default implementations, instead of an abstract class, | ||
37 | * to ensure that implementors can easily choose a base class such as UnifiedMap to augment. | ||
38 | * | ||
39 | * <p> Implementor should inherit from a Map<Key, Object>-like class (primitive map possible) | ||
40 | * and bind the lowLevel* methods accordingly. | ||
41 | * | ||
42 | * @noreference This interface is not intended to be referenced by clients. | ||
43 | * @noimplement This interface is not intended to be implemented by clients. | ||
44 | * | ||
45 | * @author Gabor Bergmann | ||
46 | * @since 2.0 | ||
47 | * | ||
48 | * | ||
49 | */ | ||
50 | public interface IMultiLookupAbstract<Key, Value, Bucket extends MarkedMemory<Value>> extends IMultiLookup<Key, Value> { | ||
51 | |||
52 | // the following methods must be bound to a concrete Map<Key,Object>-like structure (primitive implementation allowed) | ||
53 | |||
54 | /** | ||
55 | * Implementor shall bind to the low-level get() or equivalent of the underlying Key-to-Object map | ||
56 | */ | ||
57 | abstract Object lowLevelGet(Key key); | ||
58 | |||
59 | /** | ||
60 | * Implementor shall bind to the low-level get() or equivalent of the underlying Key-to-Object map | ||
61 | */ | ||
62 | abstract Object lowLevelGetUnsafe(Object key); | ||
63 | |||
64 | /** | ||
65 | * Implementor shall bind to the low-level remove() or equivalent of the underlying Key-to-Object map | ||
66 | */ | ||
67 | abstract Object lowLevelRemove(Key key); | ||
68 | |||
69 | /** | ||
70 | * Implementor shall bind to the low-level putIfAbsent() or equivalent of the underlying Key-to-Object map | ||
71 | */ | ||
72 | abstract Object lowLevelPutIfAbsent(Key key, Value value); | ||
73 | |||
74 | /** | ||
75 | * Implementor shall bind to the low-level put() or equivalent of the underlying Key-to-Object map | ||
76 | */ | ||
77 | abstract void lowLevelPut(Key key, Object valueOrBucket); | ||
78 | |||
79 | /** | ||
80 | * Implementor shall bind to the low-level values() or equivalent of the underlying Key-to-Object map | ||
81 | */ | ||
82 | abstract Iterable<Object> lowLevelValues(); | ||
83 | |||
84 | /** | ||
85 | * Implementor shall bind to the low-level keySet() or equivalent of the underlying Key-to-Object map | ||
86 | */ | ||
87 | abstract Iterable<Key> lowLevelKeySet(); | ||
88 | |||
89 | /** | ||
90 | * Implementor shall bind to the low-level size() or equivalent of the underlying Key-to-Object map | ||
91 | */ | ||
92 | abstract int lowLevelSize(); | ||
93 | |||
94 | |||
95 | // generic multi-lookup logic | ||
96 | |||
97 | @Override | ||
98 | default boolean lookupExists(Key key) { | ||
99 | Object object = lowLevelGet(key); | ||
100 | return null != object; | ||
101 | } | ||
102 | |||
103 | @Override | ||
104 | public default IMemoryView<Value> lookup(Key key) { | ||
105 | Object object = lowLevelGet(key); | ||
106 | if (object == null) return null; | ||
107 | if (object instanceof MarkedMemory) return (Bucket) object; | ||
108 | return yieldSingleton((Value)object); | ||
109 | } | ||
110 | |||
111 | @Override | ||
112 | default IMemoryView<Value> lookupAndRemoveAll(Key key) { | ||
113 | Object object = lowLevelRemove(key); | ||
114 | if (object == null) return EmptyMemory.instance(); | ||
115 | if (object instanceof MarkedMemory) return (Bucket) object; | ||
116 | return yieldSingleton((Value)object); | ||
117 | } | ||
118 | |||
119 | @Override | ||
120 | public default IMemoryView<Value> lookupUnsafe(Object key) { | ||
121 | Object object = lowLevelGetUnsafe(key); | ||
122 | if (object == null) return null; | ||
123 | if (object instanceof MarkedMemory) return (Bucket) object; | ||
124 | return yieldSingleton((Value)object); | ||
125 | } | ||
126 | |||
127 | @Override | ||
128 | public default ChangeGranularity addPair(Key key, Value value) { | ||
129 | return addPairInternal(key, value, true); | ||
130 | } | ||
131 | |||
132 | @Override | ||
133 | default ChangeGranularity addPairOrNop(Key key, Value value) { | ||
134 | return addPairInternal(key, value, false); | ||
135 | } | ||
136 | |||
137 | public default ChangeGranularity addPairInternal(Key key, Value value, boolean throwIfImpossible) { | ||
138 | Object old = lowLevelPutIfAbsent(key, value); | ||
139 | boolean keyChange = (old == null); | ||
140 | |||
141 | if (keyChange) { // key was not present | ||
142 | return ChangeGranularity.KEY; | ||
143 | } else { // key was already present | ||
144 | Bucket bucket; | ||
145 | if (old instanceof MarkedMemory) { // ... as collection | ||
146 | bucket = (Bucket) old; | ||
147 | } else { // ... as singleton | ||
148 | if (!this.duplicatesAllowed() && Objects.equals(value, old)) { | ||
149 | if (throwIfImpossible) | ||
150 | throw new IllegalStateException(); | ||
151 | else | ||
152 | return ChangeGranularity.DUPLICATE; | ||
153 | } | ||
154 | bucket = createSingletonBucket((Value) old); | ||
155 | lowLevelPut(key, bucket); | ||
156 | } | ||
157 | // will throw if forbidden duplicate, return false if allowed duplicate | ||
158 | if (addToBucket(bucket, value, throwIfImpossible)) { | ||
159 | // deltas may become empty or a singleton after addition! | ||
160 | if (negativesAllowed()) { | ||
161 | if (bucket.isEmpty()) { | ||
162 | lowLevelRemove(key); | ||
163 | return ChangeGranularity.KEY; | ||
164 | } else { | ||
165 | handleSingleton(key, bucket); | ||
166 | return ChangeGranularity.VALUE; | ||
167 | } | ||
168 | } else return ChangeGranularity.VALUE; | ||
169 | } else return ChangeGranularity.DUPLICATE; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | @Override | ||
174 | // TODO deltas not supproted yet | ||
175 | default ChangeGranularity addPairPositiveMultiplicity(Key key, Value value, int count) { | ||
176 | if (count == 1) return addPair(key, value); | ||
177 | // count > 1, always end up with non-singleton bucket | ||
178 | |||
179 | Object old = lowLevelGet(key); | ||
180 | boolean keyChange = (old == null); | ||
181 | |||
182 | Bucket bucket; | ||
183 | if (keyChange) { // ... nothing associated to key yet | ||
184 | bucket = createSingletonBucket(value); | ||
185 | lowLevelPut(key, bucket); | ||
186 | --count; // one less to increment later | ||
187 | } else if (old instanceof MarkedMemory) { // ... as collection | ||
188 | bucket = (Bucket) old; | ||
189 | } else { // ... as singleton | ||
190 | bucket = createSingletonBucket((Value) old); | ||
191 | lowLevelPut(key, bucket); | ||
192 | } | ||
193 | |||
194 | boolean newValue = bucket.addSigned(value, count); | ||
195 | |||
196 | if (keyChange) return ChangeGranularity.KEY; | ||
197 | else if (newValue) return ChangeGranularity.VALUE; | ||
198 | else return ChangeGranularity.DUPLICATE; | ||
199 | } | ||
200 | |||
201 | @Override | ||
202 | public default ChangeGranularity removePair(Key key, Value value) { | ||
203 | return removePairInternal(key, value, true); | ||
204 | } | ||
205 | |||
206 | @Override | ||
207 | default ChangeGranularity removePairOrNop(Key key, Value value) { | ||
208 | return removePairInternal(key, value, false); | ||
209 | } | ||
210 | |||
211 | public default ChangeGranularity removePairInternal(Key key, Value value, boolean throwIfImpossible) { | ||
212 | Object old = lowLevelGet(key); | ||
213 | if (old instanceof MarkedMemory) { // ... as collection | ||
214 | @SuppressWarnings("unchecked") | ||
215 | Bucket bucket = (Bucket) old; | ||
216 | // will throw if removing non-existent, return false if removing duplicate | ||
217 | boolean valueChange = removeFromBucket(bucket, value, throwIfImpossible); | ||
218 | handleSingleton(key, bucket); | ||
219 | if (valueChange) | ||
220 | return ChangeGranularity.VALUE; | ||
221 | else | ||
222 | return ChangeGranularity.DUPLICATE; | ||
223 | } else if (value.equals(old)) { // matching singleton | ||
224 | lowLevelRemove(key); | ||
225 | return ChangeGranularity.KEY; | ||
226 | } else { // different singleton, will produce a delta if possible | ||
227 | if (negativesAllowed()) { | ||
228 | Bucket deltaBucket = createDeltaBucket((Value) old, value); // will throw if no deltas supported | ||
229 | lowLevelPut(key, deltaBucket); | ||
230 | return ChangeGranularity.VALUE; // no key change | ||
231 | } else { | ||
232 | if (throwIfImpossible) | ||
233 | throw new IllegalStateException(); | ||
234 | else | ||
235 | return ChangeGranularity.DUPLICATE; | ||
236 | } | ||
237 | } | ||
238 | } | ||
239 | |||
240 | public default void handleSingleton(Key key, Bucket bucket) { | ||
241 | Value remainingSingleton = asSingleton(bucket); | ||
242 | if (remainingSingleton != null) { // only one remains | ||
243 | lowLevelPut(key, remainingSingleton); | ||
244 | } | ||
245 | } | ||
246 | |||
247 | @Override | ||
248 | public default Iterable<Value> distinctValues() { | ||
249 | return new Iterable<Value>() { | ||
250 | private final Iterator<Value> EMPTY_ITERATOR = Collections.<Value>emptySet().iterator(); | ||
251 | @Override | ||
252 | public Iterator<Value> iterator() { | ||
253 | return new Iterator<Value>() { | ||
254 | Iterator<Object> bucketIterator = lowLevelValues().iterator(); | ||
255 | Iterator<Value> elementIterator = EMPTY_ITERATOR; | ||
256 | |||
257 | @Override | ||
258 | public boolean hasNext() { | ||
259 | return (elementIterator.hasNext() || bucketIterator.hasNext()); | ||
260 | } | ||
261 | |||
262 | @Override | ||
263 | public Value next() { | ||
264 | if (elementIterator.hasNext()) | ||
265 | return elementIterator.next(); | ||
266 | else if (bucketIterator.hasNext()) { | ||
267 | Object bucket = bucketIterator.next(); | ||
268 | if (bucket instanceof MarkedMemory) { | ||
269 | elementIterator = | ||
270 | ((MarkedMemory) bucket).distinctValues().iterator(); | ||
271 | return elementIterator.next(); | ||
272 | } else { | ||
273 | elementIterator = EMPTY_ITERATOR; | ||
274 | return (Value) bucket; | ||
275 | } | ||
276 | } else | ||
277 | throw new NoSuchElementException(); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Not implemented | ||
282 | */ | ||
283 | @Override | ||
284 | public void remove() { | ||
285 | throw new UnsupportedOperationException(); | ||
286 | } | ||
287 | |||
288 | }; | ||
289 | } | ||
290 | }; | ||
291 | } | ||
292 | |||
293 | @Override | ||
294 | default Stream<Value> distinctValuesStream() { | ||
295 | return StreamSupport.stream(distinctValues().spliterator(), false); | ||
296 | } | ||
297 | |||
298 | @Override | ||
299 | default Iterable<Key> distinctKeys() { | ||
300 | return lowLevelKeySet(); | ||
301 | } | ||
302 | |||
303 | @Override | ||
304 | default Stream<Key> distinctKeysStream() { | ||
305 | return StreamSupport.stream(distinctKeys().spliterator(), false); | ||
306 | } | ||
307 | |||
308 | @Override | ||
309 | default int countKeys() { | ||
310 | return lowLevelSize(); | ||
311 | } | ||
312 | |||
313 | // the following methods are customized for bucket type | ||
314 | |||
315 | /** | ||
316 | * @return iff negative multiplicites are allowed | ||
317 | */ | ||
318 | abstract boolean negativesAllowed(); | ||
319 | |||
320 | /** | ||
321 | * @return iff larger-than-1 multiplicites are allowed | ||
322 | * @since 2.3 | ||
323 | */ | ||
324 | abstract boolean duplicatesAllowed(); | ||
325 | |||
326 | /** | ||
327 | * Increases the multiplicity of the value in the bucket. | ||
328 | * @return true iff non-duplicate | ||
329 | * @throws IllegalStateException if disallowed duplication and throwIfImpossible is specified | ||
330 | */ | ||
331 | abstract boolean addToBucket(Bucket bucket, Value value, boolean throwIfImpossible); | ||
332 | |||
333 | /** | ||
334 | * Decreases the multiplicity of the value in the bucket. | ||
335 | * @return false if removing duplicate value | ||
336 | * @throws IllegalStateException if removing non-existing value (unless delta map) and throwIfImpossible is specified | ||
337 | */ | ||
338 | abstract boolean removeFromBucket(Bucket bucket, Value value, boolean throwIfImpossible); | ||
339 | |||
340 | /** | ||
341 | * Checks whether the bucket is a singleton, i.e. it contains a single value with multiplicity +1 | ||
342 | * @return the singleton value, or null if the bucket is not singleton | ||
343 | */ | ||
344 | abstract Value asSingleton(Bucket bucket); | ||
345 | |||
346 | /** | ||
347 | * @return a new bucket consisting of a sole value | ||
348 | */ | ||
349 | abstract Bucket createSingletonBucket(Value value); | ||
350 | /** | ||
351 | * @return a read-only bucket consisting of a sole value, to be returned to the user | ||
352 | */ | ||
353 | default IMemoryView<Value> yieldSingleton(Value value) { | ||
354 | return new SingletonMemoryView<>(value); | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * @param positive the previously existing value, or null if the delta is to contain a single negative tuple | ||
359 | * @return a new bucket consisting of a delta of two values | ||
360 | * @throws IllegalStateException if deltas not supported | ||
361 | */ | ||
362 | abstract Bucket createDeltaBucket(Value positive, Value negative); | ||
363 | |||
364 | /** | ||
365 | * A multi-lookup whose buckets are sets. | ||
366 | * | ||
367 | * <p> Not intended as an API, but rather as a 'base class' for implementors. | ||
368 | * Realized as an interface with default implementations, instead of an abstract class, | ||
369 | * to ensure that implementors can easily choose a base class such as UnifiedMap to augment. | ||
370 | * | ||
371 | * <p> Implementor should inherit from a Map<Key, Object>-like class (primitive map possible) | ||
372 | * and bind the lowLevel* methods accordingly. | ||
373 | * | ||
374 | * @noreference This interface is not intended to be referenced by clients. | ||
375 | * @noimplement This interface is not intended to be implemented by clients. | ||
376 | * @author Gabor Bergmann | ||
377 | */ | ||
378 | public static interface ToSetsAbstract<Key, Value> extends IMultiLookupAbstract<Key, Value, MarkedMemory.MarkedSet<Value>> { | ||
379 | /** | ||
380 | * @return a fresh, empty marked set | ||
381 | */ | ||
382 | public MarkedSet<Value> createMarkedSet(); | ||
383 | |||
384 | @Override | ||
385 | public default boolean negativesAllowed() { | ||
386 | return false; | ||
387 | } | ||
388 | @Override | ||
389 | default boolean duplicatesAllowed() { | ||
390 | return false; | ||
391 | } | ||
392 | |||
393 | @Override | ||
394 | public default boolean addToBucket(MarkedSet<Value> bucket, Value value, boolean throwIfImpossible) { | ||
395 | if (bucket.addOne(value)) return true; | ||
396 | else if (throwIfImpossible) throw new IllegalStateException(); | ||
397 | else return false; | ||
398 | } | ||
399 | |||
400 | @Override | ||
401 | public default boolean removeFromBucket(MarkedSet<Value> bucket, Value value, boolean throwIfImpossible) { | ||
402 | return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value); | ||
403 | } | ||
404 | |||
405 | @Override | ||
406 | public default Value asSingleton(MarkedSet<Value> bucket) { | ||
407 | return bucket.size() == 1 ? bucket.iterator().next() : null; | ||
408 | } | ||
409 | |||
410 | @Override | ||
411 | public default MarkedSet<Value> createSingletonBucket(Value value) { | ||
412 | MarkedSet<Value> result = createMarkedSet(); | ||
413 | result.addOne(value); | ||
414 | return result; | ||
415 | } | ||
416 | |||
417 | @Override | ||
418 | public default MarkedSet<Value> createDeltaBucket(Value positive, Value negative) { | ||
419 | throw new IllegalStateException(); | ||
420 | } | ||
421 | } | ||
422 | |||
423 | /** | ||
424 | * A multi-lookup whose buckets are multisets. | ||
425 | * | ||
426 | * <p> Not intended as an API, but rather as a 'base class' for implementors. | ||
427 | * Realized as an interface with default implementations, instead of an abstract class, | ||
428 | * to ensure that implementors can easily choose a base class such as UnifiedMap to augment. | ||
429 | * | ||
430 | * <p> Implementor should inherit from a Map<Key, Object>-like class (primitive map possible) | ||
431 | * and bind the lowLevel* methods accordingly. | ||
432 | * | ||
433 | * @noreference This interface is not intended to be referenced by clients. | ||
434 | * @noimplement This interface is not intended to be implemented by clients. | ||
435 | * @author Gabor Bergmann | ||
436 | */ | ||
437 | public static interface ToMultisetsAbstract<Key, Value> extends IMultiLookupAbstract<Key, Value, MarkedMemory.MarkedMultiset<Value>> { | ||
438 | /** | ||
439 | * @return a fresh, empty marked multiset | ||
440 | */ | ||
441 | public MarkedMemory.MarkedMultiset<Value> createMarkedMultiset(); | ||
442 | |||
443 | @Override | ||
444 | public default boolean negativesAllowed() { | ||
445 | return false; | ||
446 | } | ||
447 | @Override | ||
448 | default boolean duplicatesAllowed() { | ||
449 | return true; | ||
450 | } | ||
451 | |||
452 | @Override | ||
453 | public default boolean addToBucket(MarkedMemory.MarkedMultiset<Value> bucket, Value value, boolean throwIfImpossible) { | ||
454 | return bucket.addOne(value); | ||
455 | } | ||
456 | |||
457 | @Override | ||
458 | public default boolean removeFromBucket(MarkedMemory.MarkedMultiset<Value> bucket, Value value, boolean throwIfImpossible) { | ||
459 | return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value); | ||
460 | } | ||
461 | |||
462 | @Override | ||
463 | public default Value asSingleton(MarkedMemory.MarkedMultiset<Value> bucket) { | ||
464 | if (bucket.size() != 1) return null; | ||
465 | Value candidate = bucket.iterator().next(); | ||
466 | return bucket.getCount(candidate) == 1 ? candidate : null; | ||
467 | } | ||
468 | |||
469 | @Override | ||
470 | public default MarkedMemory.MarkedMultiset<Value> createSingletonBucket(Value value) { | ||
471 | MarkedMemory.MarkedMultiset<Value> result = createMarkedMultiset(); | ||
472 | result.addOne(value); | ||
473 | return result; | ||
474 | } | ||
475 | |||
476 | @Override | ||
477 | public default MarkedMemory.MarkedMultiset<Value> createDeltaBucket(Value positive, Value negative) { | ||
478 | throw new IllegalStateException(); | ||
479 | } | ||
480 | } | ||
481 | |||
482 | |||
483 | // TODO add ToDeltaBagsAbstract | ||
484 | |||
485 | } \ No newline at end of file | ||