diff options
Diffstat (limited to 'subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java')
-rw-r--r-- | subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java new file mode 100644 index 00000000..7809cd24 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java | |||
@@ -0,0 +1,839 @@ | |||
1 | /******************************************************************************* | ||
2 | * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro | ||
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.emf; | ||
10 | |||
11 | import java.lang.reflect.InvocationTargetException; | ||
12 | import java.util.Collection; | ||
13 | import java.util.Collections; | ||
14 | import java.util.EnumSet; | ||
15 | import java.util.HashMap; | ||
16 | import java.util.HashSet; | ||
17 | import java.util.Map; | ||
18 | import java.util.Optional; | ||
19 | import java.util.Set; | ||
20 | import java.util.concurrent.Callable; | ||
21 | import java.util.function.Function; | ||
22 | import java.util.stream.Collectors; | ||
23 | |||
24 | import org.apache.log4j.Logger; | ||
25 | import org.eclipse.emf.ecore.EClass; | ||
26 | import org.eclipse.emf.ecore.EDataType; | ||
27 | import org.eclipse.emf.ecore.EObject; | ||
28 | import org.eclipse.emf.ecore.EStructuralFeature; | ||
29 | import tools.refinery.viatra.runtime.base.api.DataTypeListener; | ||
30 | import tools.refinery.viatra.runtime.base.api.FeatureListener; | ||
31 | import tools.refinery.viatra.runtime.base.api.IndexingLevel; | ||
32 | import tools.refinery.viatra.runtime.base.api.InstanceListener; | ||
33 | import tools.refinery.viatra.runtime.base.api.NavigationHelper; | ||
34 | import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; | ||
35 | import tools.refinery.viatra.runtime.emf.types.EClassUnscopedTransitiveInstancesKey; | ||
36 | import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; | ||
37 | import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; | ||
38 | import tools.refinery.viatra.runtime.matchers.context.AbstractQueryRuntimeContext; | ||
39 | import tools.refinery.viatra.runtime.matchers.context.IInputKey; | ||
40 | import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; | ||
41 | import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; | ||
42 | import tools.refinery.viatra.runtime.matchers.context.IndexingService; | ||
43 | import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; | ||
44 | import tools.refinery.viatra.runtime.matchers.tuple.ITuple; | ||
45 | import tools.refinery.viatra.runtime.matchers.tuple.Tuple; | ||
46 | import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; | ||
47 | import tools.refinery.viatra.runtime.matchers.tuple.Tuples; | ||
48 | import tools.refinery.viatra.runtime.matchers.util.Accuracy; | ||
49 | |||
50 | /** | ||
51 | * The EMF-based runtime query context, backed by an IQBase NavigationHelper. | ||
52 | * | ||
53 | * @author Bergmann Gabor | ||
54 | * | ||
55 | * <p> TODO: {@link #ensureIndexed(EClass)} may be inefficient if supertype already cached. | ||
56 | * @since 1.4 | ||
57 | */ | ||
58 | public class EMFQueryRuntimeContext extends AbstractQueryRuntimeContext { | ||
59 | protected final NavigationHelper baseIndex; | ||
60 | //private BaseIndexListener listener; | ||
61 | |||
62 | protected final Map<EClass, EnumSet<IndexingService>> indexedClasses = new HashMap<>(); | ||
63 | protected final Map<EDataType, EnumSet<IndexingService>> indexedDataTypes = new HashMap<>(); | ||
64 | protected final Map<EStructuralFeature, EnumSet<IndexingService>> indexedFeatures = new HashMap<>(); | ||
65 | |||
66 | protected final EMFQueryMetaContext metaContext; | ||
67 | |||
68 | protected Logger logger; | ||
69 | |||
70 | private EMFScope emfScope; | ||
71 | |||
72 | public EMFQueryRuntimeContext(NavigationHelper baseIndex, Logger logger, EMFScope emfScope) { | ||
73 | this.baseIndex = baseIndex; | ||
74 | this.logger = logger; | ||
75 | this.metaContext = new EMFQueryMetaContext(emfScope); | ||
76 | this.emfScope = emfScope; | ||
77 | } | ||
78 | |||
79 | public EMFScope getEmfScope() { | ||
80 | return emfScope; | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Utility method to add an indexing service to a given key. Returns true if the requested service was | ||
85 | * not present before this call. | ||
86 | * @param map | ||
87 | * @param key | ||
88 | * @param service | ||
89 | * @return | ||
90 | */ | ||
91 | private static <K> boolean addIndexingService(Map<K, EnumSet<IndexingService>> map, K key, IndexingService service){ | ||
92 | EnumSet<IndexingService> current = map.get(key); | ||
93 | if (current == null){ | ||
94 | current = EnumSet.of(service); | ||
95 | map.put(key, current); | ||
96 | return true; | ||
97 | }else{ | ||
98 | return current.add(service); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | public void dispose() { | ||
103 | //baseIndex.removeFeatureListener(indexedFeatures, listener); | ||
104 | indexedFeatures.clear(); | ||
105 | //baseIndex.removeInstanceListener(indexedClasses, listener); | ||
106 | indexedClasses.clear(); | ||
107 | //baseIndex.removeDataTypeListener(indexedDataTypes, listener); | ||
108 | indexedDataTypes.clear(); | ||
109 | |||
110 | // No need to remove listeners, as NavHelper will be disposed imminently. | ||
111 | } | ||
112 | |||
113 | @Override | ||
114 | public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException { | ||
115 | return baseIndex.coalesceTraversals(callable); | ||
116 | } | ||
117 | |||
118 | @Override | ||
119 | public boolean isCoalescing() { | ||
120 | return baseIndex.isCoalescing(); | ||
121 | } | ||
122 | |||
123 | @Override | ||
124 | public IQueryMetaContext getMetaContext() { | ||
125 | return metaContext; | ||
126 | } | ||
127 | |||
128 | @Override | ||
129 | public void ensureIndexed(IInputKey key, IndexingService service) { | ||
130 | ensureEnumerableKey(key); | ||
131 | if (key instanceof EClassTransitiveInstancesKey) { | ||
132 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
133 | ensureIndexed(eClass, service); | ||
134 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
135 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
136 | ensureIndexed(dataType, service); | ||
137 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
138 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
139 | ensureIndexed(feature, service); | ||
140 | } else { | ||
141 | illegalInputKey(key); | ||
142 | } | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Retrieve the current registered indexing services for the given key. May not return null, | ||
147 | * returns an empty set if no indexing is registered. | ||
148 | * | ||
149 | * @since 1.4 | ||
150 | */ | ||
151 | protected EnumSet<IndexingService> getCurrentIndexingServiceFor(IInputKey key){ | ||
152 | ensureEnumerableKey(key); | ||
153 | if (key instanceof EClassTransitiveInstancesKey) { | ||
154 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
155 | EnumSet<IndexingService> is = indexedClasses.get(eClass); | ||
156 | return is == null ? EnumSet.noneOf(IndexingService.class) : is; | ||
157 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
158 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
159 | EnumSet<IndexingService> is = indexedDataTypes.get(dataType); | ||
160 | return is == null ? EnumSet.noneOf(IndexingService.class) : is; | ||
161 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
162 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
163 | EnumSet<IndexingService> is = indexedFeatures.get(feature); | ||
164 | return is == null ? EnumSet.noneOf(IndexingService.class) : is; | ||
165 | } else { | ||
166 | illegalInputKey(key); | ||
167 | return EnumSet.noneOf(IndexingService.class); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | @Override | ||
172 | public boolean isIndexed(IInputKey key, IndexingService service) { | ||
173 | return getCurrentIndexingServiceFor(key).contains(service); | ||
174 | } | ||
175 | |||
176 | @Override | ||
177 | public boolean containsTuple(IInputKey key, ITuple seed) { | ||
178 | ensureValidKey(key); | ||
179 | if (key instanceof JavaTransitiveInstancesKey) { | ||
180 | Class<?> instanceClass = forceGetWrapperInstanceClass((JavaTransitiveInstancesKey) key); | ||
181 | return instanceClass != null && instanceClass.isInstance(seed.get(0)); | ||
182 | } else if (key instanceof EClassUnscopedTransitiveInstancesKey) { | ||
183 | EClass emfKey = ((EClassUnscopedTransitiveInstancesKey) key).getEmfKey(); | ||
184 | Object candidateInstance = seed.get(0); | ||
185 | return candidateInstance instanceof EObject | ||
186 | && baseIndex.isInstanceOfUnscoped((EObject) candidateInstance, emfKey); | ||
187 | } else { | ||
188 | ensureIndexed(key, IndexingService.INSTANCES); | ||
189 | if (key instanceof EClassTransitiveInstancesKey) { | ||
190 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
191 | // instance check not enough to satisfy scoping, must lookup from index | ||
192 | Object candidateInstance = seed.get(0); | ||
193 | return candidateInstance instanceof EObject | ||
194 | && baseIndex.isInstanceOfScoped((EObject) candidateInstance, eClass); | ||
195 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
196 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
197 | return baseIndex.isInstanceOfDatatype(seed.get(0), dataType); | ||
198 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
199 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
200 | Object sourceCandidate = seed.get(0); | ||
201 | return sourceCandidate instanceof EObject | ||
202 | && baseIndex.isFeatureInstance((EObject) sourceCandidate, seed.get(1), feature); | ||
203 | } else { | ||
204 | illegalInputKey(key); | ||
205 | return false; | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | |||
210 | private Class<?> forceGetWrapperInstanceClass(JavaTransitiveInstancesKey key) { | ||
211 | Class<?> instanceClass; | ||
212 | try { | ||
213 | instanceClass = key.forceGetWrapperInstanceClass(); | ||
214 | } catch (ClassNotFoundException e) { | ||
215 | logger.error("Could not load instance class for type constraint " + key.getWrappedKey(), e); | ||
216 | instanceClass = null; | ||
217 | } | ||
218 | return instanceClass; | ||
219 | } | ||
220 | |||
221 | @Override | ||
222 | public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
223 | ensureIndexed(key, IndexingService.INSTANCES); | ||
224 | final Collection<Tuple> result = new HashSet<Tuple>(); | ||
225 | |||
226 | if (key instanceof EClassTransitiveInstancesKey) { | ||
227 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
228 | |||
229 | if (seedMask.indices.length == 0) { // unseeded | ||
230 | return baseIndex.getAllInstances(eClass).stream().map(wrapUnary).collect(Collectors.toSet()); | ||
231 | } else { // fully seeded | ||
232 | Object seedInstance = seedMask.getValue(seed, 0); | ||
233 | if (containsTuple(key, seed)) | ||
234 | result.add(Tuples.staticArityFlatTupleOf(seedInstance)); | ||
235 | } | ||
236 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
237 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
238 | |||
239 | if (seedMask.indices.length == 0) { // unseeded | ||
240 | return baseIndex.getDataTypeInstances(dataType).stream().map(wrapUnary).collect(Collectors.toSet()); | ||
241 | } else { // fully seeded | ||
242 | Object seedInstance = seedMask.getValue(seed, 0); | ||
243 | if (containsTuple(key, seed)) | ||
244 | result.add(Tuples.staticArityFlatTupleOf(seedInstance)); | ||
245 | } | ||
246 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
247 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
248 | |||
249 | boolean isSourceBound = false; | ||
250 | int sourceIndex = -1; | ||
251 | boolean isTargetBound = false; | ||
252 | int targetIndex = -1; | ||
253 | for (int i = 0; i < seedMask.getSize(); i++) { | ||
254 | int index = seedMask.indices[i]; | ||
255 | if (index == 0) { | ||
256 | isSourceBound = true; | ||
257 | sourceIndex = i; | ||
258 | } else if (index == 1) { | ||
259 | isTargetBound = true; | ||
260 | targetIndex = i; | ||
261 | } | ||
262 | } | ||
263 | |||
264 | if (!isSourceBound && isTargetBound) { | ||
265 | final Object seedTarget = seed.get(targetIndex); | ||
266 | final Set<EObject> results = baseIndex.findByFeatureValue(seedTarget, feature); | ||
267 | return results.stream().map(obj -> Tuples.staticArityFlatTupleOf(obj, seedTarget)).collect(Collectors.toSet()); | ||
268 | } else if (isSourceBound && isTargetBound) { // fully seeded | ||
269 | final Object seedSource = seed.get(sourceIndex); | ||
270 | final Object seedTarget = seed.get(targetIndex); | ||
271 | if (containsTuple(key, seed)) | ||
272 | result.add(Tuples.staticArityFlatTupleOf(seedSource, seedTarget)); | ||
273 | } else if (!isSourceBound && !isTargetBound) { // fully unseeded | ||
274 | baseIndex.processAllFeatureInstances(feature, (source, target) -> result.add(Tuples.staticArityFlatTupleOf(source, target))); | ||
275 | } else if (isSourceBound && !isTargetBound) { | ||
276 | final Object seedSource = seed.get(sourceIndex); | ||
277 | final Set<Object> results = baseIndex.getFeatureTargets((EObject) seedSource, feature); | ||
278 | return results.stream().map(obj -> Tuples.staticArityFlatTupleOf(seedSource, obj)).collect(Collectors.toSet()); | ||
279 | } | ||
280 | } else { | ||
281 | illegalInputKey(key); | ||
282 | } | ||
283 | |||
284 | |||
285 | return result; | ||
286 | } | ||
287 | |||
288 | private static Function<Object, Tuple> wrapUnary = Tuples::staticArityFlatTupleOf; | ||
289 | |||
290 | @Override | ||
291 | public Iterable<? extends Object> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
292 | ensureIndexed(key, IndexingService.INSTANCES); | ||
293 | |||
294 | if (key instanceof EClassTransitiveInstancesKey) { | ||
295 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
296 | |||
297 | if (seedMask.indices.length == 0) { // unseeded | ||
298 | return baseIndex.getAllInstances(eClass); | ||
299 | } else { | ||
300 | // must be unseeded, this is enumerateValues after all! | ||
301 | illegalEnumerateValues(seed.toImmutable()); | ||
302 | } | ||
303 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
304 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
305 | |||
306 | if (seedMask.indices.length == 0) { // unseeded | ||
307 | return baseIndex.getDataTypeInstances(dataType); | ||
308 | } else { | ||
309 | // must be unseeded, this is enumerateValues after all! | ||
310 | illegalEnumerateValues(seed.toImmutable()); | ||
311 | } | ||
312 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
313 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
314 | |||
315 | boolean isSourceBound = false; | ||
316 | int sourceIndex = -1; | ||
317 | boolean isTargetBound = false; | ||
318 | int targetIndex = -1; | ||
319 | for (int i = 0; i < seedMask.getSize(); i++) { | ||
320 | int index = seedMask.indices[i]; | ||
321 | if (index == 0) { | ||
322 | isSourceBound = true; | ||
323 | sourceIndex = i; | ||
324 | } else if (index == 1) { | ||
325 | isTargetBound = true; | ||
326 | targetIndex = i; | ||
327 | } | ||
328 | } | ||
329 | |||
330 | if (!isSourceBound && isTargetBound) { | ||
331 | Object seedTarget = seed.get(targetIndex); | ||
332 | return baseIndex.findByFeatureValue(seedTarget, feature); | ||
333 | } else if (isSourceBound && !isTargetBound) { | ||
334 | Object seedSource = seed.get(sourceIndex); | ||
335 | return baseIndex.getFeatureTargets((EObject) seedSource, feature); | ||
336 | } else { | ||
337 | // must be singly unseeded, this is enumerateValues after all! | ||
338 | illegalEnumerateValues(seed.toImmutable()); | ||
339 | } | ||
340 | } else { | ||
341 | illegalInputKey(key); | ||
342 | } | ||
343 | return null; | ||
344 | } | ||
345 | |||
346 | @Override | ||
347 | public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { | ||
348 | ensureIndexed(key, IndexingService.STATISTICS); | ||
349 | |||
350 | if (key instanceof EClassTransitiveInstancesKey) { | ||
351 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
352 | |||
353 | if (seedMask.indices.length == 0) { // unseeded | ||
354 | return baseIndex.countAllInstances(eClass); | ||
355 | } else { // fully seeded | ||
356 | return (containsTuple(key, seed)) ? 1 : 0; | ||
357 | } | ||
358 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
359 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
360 | |||
361 | if (seedMask.indices.length == 0) { // unseeded | ||
362 | return baseIndex.countDataTypeInstances(dataType); | ||
363 | } else { // fully seeded | ||
364 | return (containsTuple(key, seed)) ? 1 : 0; | ||
365 | } | ||
366 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
367 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
368 | |||
369 | boolean isSourceBound = false; | ||
370 | int sourceIndex = -1; | ||
371 | boolean isTargetBound = false; | ||
372 | int targetIndex = -1; | ||
373 | for (int i = 0; i < seedMask.getSize(); i++) { | ||
374 | int index = seedMask.indices[i]; | ||
375 | if (index == 0) { | ||
376 | isSourceBound = true; | ||
377 | sourceIndex = i; | ||
378 | } else if (index == 1) { | ||
379 | isTargetBound = true; | ||
380 | targetIndex = i; | ||
381 | } | ||
382 | } | ||
383 | |||
384 | if (!isSourceBound && isTargetBound) { | ||
385 | final Object seedTarget = seed.get(targetIndex); | ||
386 | return baseIndex.findByFeatureValue(seedTarget, feature).size(); | ||
387 | } else if (isSourceBound && isTargetBound) { // fully seeded | ||
388 | return (containsTuple(key, seed)) ? 1 : 0; | ||
389 | } else if (!isSourceBound && !isTargetBound) { // fully unseeded | ||
390 | return baseIndex.countFeatures(feature); | ||
391 | } else if (isSourceBound && !isTargetBound) { | ||
392 | final Object seedSource = seed.get(sourceIndex); | ||
393 | return baseIndex.countFeatureTargets((EObject) seedSource, feature); | ||
394 | } | ||
395 | } else { | ||
396 | illegalInputKey(key); | ||
397 | } | ||
398 | return 0; | ||
399 | } | ||
400 | |||
401 | |||
402 | /** | ||
403 | * @since 2.1 | ||
404 | */ | ||
405 | @Override | ||
406 | public Optional<Long> estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { | ||
407 | |||
408 | if (key instanceof EClassTransitiveInstancesKey) { | ||
409 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
410 | |||
411 | if (isIndexed(key, IndexingService.STATISTICS)) { // exact answer known | ||
412 | if (groupMask.indices.length == 0) { // empty projection | ||
413 | return (0 != baseIndex.countAllInstances(eClass)) ? Optional.of(1L) : Optional.of(0L); | ||
414 | } else { // unprojected | ||
415 | return Optional.of((long)baseIndex.countAllInstances(eClass)); | ||
416 | } | ||
417 | } else return Optional.empty(); // TODO use known supertype counts as upper, subtypes as lower bounds | ||
418 | |||
419 | } else if (key instanceof EClassUnscopedTransitiveInstancesKey) { | ||
420 | EClass eClass = ((EClassUnscopedTransitiveInstancesKey) key).getEmfKey(); | ||
421 | |||
422 | // can give only lower bound based on the scoped key | ||
423 | if (Accuracy.BEST_LOWER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { | ||
424 | return estimateCardinality(new EClassTransitiveInstancesKey(eClass), groupMask, requiredAccuracy); | ||
425 | } else return Optional.empty(); | ||
426 | |||
427 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
428 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
429 | |||
430 | if (isIndexed(key, IndexingService.STATISTICS)) { | ||
431 | if (groupMask.indices.length == 0) { // empty projection | ||
432 | return (0 != baseIndex.countDataTypeInstances(dataType)) ? Optional.of(1L) : Optional.of(0L); | ||
433 | } else { // unprojected | ||
434 | return Optional.of((long)baseIndex.countDataTypeInstances(dataType)); | ||
435 | } | ||
436 | } else return Optional.empty(); | ||
437 | |||
438 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
439 | EStructuralFeatureInstancesKey featureKey = (EStructuralFeatureInstancesKey) key; | ||
440 | EStructuralFeature feature = featureKey.getEmfKey(); | ||
441 | |||
442 | |||
443 | boolean isSourceSelected = false; | ||
444 | boolean isTargetSelected = false; | ||
445 | for (int i = 0; i < groupMask.getSize(); i++) { | ||
446 | int index = groupMask.indices[i]; | ||
447 | if (index == 0) { | ||
448 | isSourceSelected = true; | ||
449 | } else if (index == 1) { | ||
450 | isTargetSelected = true; | ||
451 | } | ||
452 | } | ||
453 | |||
454 | Optional<Long> sourceTypeUpperEstimate = | ||
455 | estimateCardinality(metaContext.getSourceTypeKey(featureKey), | ||
456 | TupleMask.identity(1), Accuracy.BEST_UPPER_BOUND); | ||
457 | Optional<Long> targetTypeUpperEstimate = | ||
458 | estimateCardinality(metaContext.getTargetTypeKey(featureKey), | ||
459 | TupleMask.identity(1), Accuracy.BEST_UPPER_BOUND); | ||
460 | |||
461 | if (!isSourceSelected && !isTargetSelected) { // empty projection | ||
462 | if (isIndexed(key, IndexingService.STATISTICS)) { // we have exact node counts | ||
463 | return (0 == baseIndex.countFeatures(feature)) ? Optional.of(0L) : Optional.of(1L); | ||
464 | } else { // we can still say 0 in a few cases | ||
465 | if (0 == sourceTypeUpperEstimate.orElse(-1L)) | ||
466 | return Optional.of(0L); | ||
467 | |||
468 | if (0 == targetTypeUpperEstimate.orElse(-1L)) | ||
469 | return Optional.of(0L); | ||
470 | |||
471 | return Optional.empty(); | ||
472 | } | ||
473 | |||
474 | } else if (isSourceSelected && !isTargetSelected) { // count sources | ||
475 | if (isIndexed(key, IndexingService.INSTANCES)) { // we have instances, therefore feature end counts | ||
476 | return Optional.of((long)(baseIndex.getHoldersOfFeature(feature).size())); | ||
477 | } else if (metaContext.isFeatureMultiplicityToOne(feature) && | ||
478 | isIndexed(key, IndexingService.STATISTICS)) { // count of edges = count of sources due to func. dep. | ||
479 | return Optional.of((long)(baseIndex.countFeatures(feature))); | ||
480 | } else if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { | ||
481 | // upper bound by source type | ||
482 | Optional<Long> estimate = sourceTypeUpperEstimate; | ||
483 | // total edge counts are another upper bound (even if instances are unindexed) | ||
484 | if (isIndexed(key, IndexingService.STATISTICS)) { | ||
485 | estimate = Optional.of(Math.min( | ||
486 | baseIndex.countFeatures(feature), | ||
487 | estimate.orElse(Long.MAX_VALUE))); | ||
488 | } | ||
489 | return estimate; | ||
490 | } else return Optional.empty(); | ||
491 | |||
492 | } else if (!isSourceSelected /*&& isTargetSelected*/) { // count targets | ||
493 | if (isIndexed(key, IndexingService.INSTANCES)) { // we have instances, therefore feature end counts | ||
494 | return Optional.of((long)(baseIndex.getValuesOfFeature(feature).size())); | ||
495 | } else if (metaContext.isFeatureMultiplicityOneTo(feature) && | ||
496 | isIndexed(key, IndexingService.STATISTICS)) { // count of edges = count of targets due to func. dep. | ||
497 | return Optional.of((long)(baseIndex.countFeatures(feature))); | ||
498 | } else if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // upper bound by target type | ||
499 | // upper bound by target type | ||
500 | Optional<Long> estimate = targetTypeUpperEstimate; | ||
501 | // total edge counts are another upper bound (even if instances are unindexed) | ||
502 | if (isIndexed(key, IndexingService.STATISTICS)) { | ||
503 | estimate = Optional.of(Math.min( | ||
504 | baseIndex.countFeatures(feature), | ||
505 | estimate.orElse(Long.MAX_VALUE))); | ||
506 | } | ||
507 | return estimate; | ||
508 | } else return Optional.empty(); | ||
509 | |||
510 | } else { // (isSourceSelected && isTargetSelected) // count edges | ||
511 | if (isIndexed(key, IndexingService.STATISTICS)) { // we have exact edge counts | ||
512 | return Optional.of((long)baseIndex.countFeatures(feature)); | ||
513 | } else if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // overestimates may still be available | ||
514 | Optional<Long> estimate = // trivial upper bound: product of source & target type sizes (if available) | ||
515 | (sourceTypeUpperEstimate.isPresent() && targetTypeUpperEstimate.isPresent()) ? | ||
516 | Optional.of( | ||
517 | ((long)sourceTypeUpperEstimate.get()) * targetTypeUpperEstimate.get() | ||
518 | ) : Optional.empty(); | ||
519 | |||
520 | if (metaContext.isFeatureMultiplicityToOne(feature) && sourceTypeUpperEstimate.isPresent()) { | ||
521 | // upper bounded by source type due to func. dep. | ||
522 | estimate = Optional.of(Math.min( | ||
523 | sourceTypeUpperEstimate.get(), | ||
524 | estimate.orElse(Long.MAX_VALUE))); | ||
525 | } | ||
526 | if (metaContext.isFeatureMultiplicityOneTo(feature) && targetTypeUpperEstimate.isPresent()) { | ||
527 | // upper bounded by target type due to func. dep. | ||
528 | estimate = Optional.of(Math.min( | ||
529 | targetTypeUpperEstimate.get(), | ||
530 | estimate.orElse(Long.MAX_VALUE))); | ||
531 | } | ||
532 | |||
533 | return estimate; | ||
534 | } else return Optional.empty(); | ||
535 | } | ||
536 | |||
537 | } else { | ||
538 | return Optional.empty(); | ||
539 | } | ||
540 | } | ||
541 | |||
542 | /** | ||
543 | * @since 2.1 | ||
544 | */ | ||
545 | @Override | ||
546 | public Optional<Double> estimateAverageBucketSize(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { | ||
547 | // smart handling of special cases | ||
548 | if (key instanceof EStructuralFeatureInstancesKey) { | ||
549 | EStructuralFeatureInstancesKey featureKey = (EStructuralFeatureInstancesKey) key; | ||
550 | EStructuralFeature feature = featureKey.getEmfKey(); | ||
551 | |||
552 | // special treatment for edge navigation | ||
553 | if (1 == groupMask.getSize()) { | ||
554 | if (0 == groupMask.indices[0] && metaContext.isFeatureMultiplicityToOne(feature)) { // count targets per source | ||
555 | return Optional.of(1.0); | ||
556 | } else if (1 == groupMask.indices[0] && metaContext.isFeatureMultiplicityOneTo(feature)) { // count sources per target | ||
557 | return Optional.of(1.0); | ||
558 | } | ||
559 | } | ||
560 | } | ||
561 | |||
562 | // keep the default behaviour | ||
563 | return super.estimateAverageBucketSize(key, groupMask, requiredAccuracy); | ||
564 | } | ||
565 | |||
566 | |||
567 | public void ensureEnumerableKey(IInputKey key) { | ||
568 | ensureValidKey(key); | ||
569 | if (! metaContext.isEnumerable(key)) | ||
570 | throw new IllegalArgumentException("Key is not enumerable: " + key); | ||
571 | |||
572 | } | ||
573 | |||
574 | public void ensureValidKey(IInputKey key) { | ||
575 | metaContext.ensureValidKey(key); | ||
576 | } | ||
577 | public void illegalInputKey(IInputKey key) { | ||
578 | metaContext.illegalInputKey(key); | ||
579 | } | ||
580 | public void illegalEnumerateValues(Tuple seed) { | ||
581 | throw new IllegalArgumentException("Must have exactly one unseeded element in enumerateValues() invocation, received instead: " + seed); | ||
582 | } | ||
583 | |||
584 | /** | ||
585 | * @since 1.4 | ||
586 | */ | ||
587 | public void ensureIndexed(EClass eClass, IndexingService service) { | ||
588 | if (addIndexingService(indexedClasses, eClass, service)) { | ||
589 | final Set<EClass> newClasses = Collections.singleton(eClass); | ||
590 | IndexingLevel level = IndexingLevel.toLevel(service); | ||
591 | if (!baseIndex.getIndexingLevel(eClass).providesLevel(level)) { | ||
592 | baseIndex.registerEClasses(newClasses, level); | ||
593 | } | ||
594 | //baseIndex.addInstanceListener(newClasses, listener); | ||
595 | } | ||
596 | } | ||
597 | |||
598 | /** | ||
599 | * @since 1.4 | ||
600 | */ | ||
601 | public void ensureIndexed(EDataType eDataType, IndexingService service) { | ||
602 | if (addIndexingService(indexedDataTypes, eDataType, service)) { | ||
603 | final Set<EDataType> newDataTypes = Collections.singleton(eDataType); | ||
604 | IndexingLevel level = IndexingLevel.toLevel(service); | ||
605 | if (!baseIndex.getIndexingLevel(eDataType).providesLevel(level)) { | ||
606 | baseIndex.registerEDataTypes(newDataTypes, level); | ||
607 | } | ||
608 | //baseIndex.addDataTypeListener(newDataTypes, listener); | ||
609 | } | ||
610 | } | ||
611 | |||
612 | /** | ||
613 | * @since 1.4 | ||
614 | */ | ||
615 | public void ensureIndexed(EStructuralFeature feature, IndexingService service) { | ||
616 | if (addIndexingService(indexedFeatures, feature, service)) { | ||
617 | final Set<EStructuralFeature> newFeatures = Collections.singleton(feature); | ||
618 | IndexingLevel level = IndexingLevel.toLevel(service); | ||
619 | if (!baseIndex.getIndexingLevel(feature).providesLevel(level)) { | ||
620 | baseIndex.registerEStructuralFeatures(newFeatures, level); | ||
621 | } | ||
622 | //baseIndex.addFeatureListener(newFeatures, listener); | ||
623 | } | ||
624 | } | ||
625 | |||
626 | |||
627 | |||
628 | // UPDATE HANDLING SECTION | ||
629 | |||
630 | /** | ||
631 | * Abstract internal listener wrapper for a {@link IQueryRuntimeContextListener}. | ||
632 | * Due to the overridden equals/hashCode(), it is safe to create a new instance for the same listener. | ||
633 | * | ||
634 | * @author Bergmann Gabor | ||
635 | */ | ||
636 | private abstract static class ListenerAdapter { | ||
637 | IQueryRuntimeContextListener listener; | ||
638 | Tuple seed; | ||
639 | /** | ||
640 | * @param listener | ||
641 | * @param seed must be non-null | ||
642 | */ | ||
643 | public ListenerAdapter(IQueryRuntimeContextListener listener, Object... seed) { | ||
644 | this.listener = listener; | ||
645 | this.seed = Tuples.flatTupleOf(seed); | ||
646 | } | ||
647 | |||
648 | @Override | ||
649 | public int hashCode() { | ||
650 | final int prime = 31; | ||
651 | int result = 1; | ||
652 | result = prime * result | ||
653 | + ((listener == null) ? 0 : listener.hashCode()); | ||
654 | result = prime * result + ((seed == null) ? 0 : seed.hashCode()); | ||
655 | return result; | ||
656 | } | ||
657 | |||
658 | @Override | ||
659 | public boolean equals(Object obj) { | ||
660 | if (this == obj) | ||
661 | return true; | ||
662 | if (obj == null) | ||
663 | return false; | ||
664 | if (!(obj.getClass().equals(this.getClass()))) | ||
665 | return false; | ||
666 | ListenerAdapter other = (ListenerAdapter) obj; | ||
667 | if (listener == null) { | ||
668 | if (other.listener != null) | ||
669 | return false; | ||
670 | } else if (!listener.equals(other.listener)) | ||
671 | return false; | ||
672 | if (seed == null) { | ||
673 | if (other.seed != null) | ||
674 | return false; | ||
675 | } else if (!seed.equals(other.seed)) | ||
676 | return false; | ||
677 | return true; | ||
678 | } | ||
679 | |||
680 | |||
681 | @Override | ||
682 | public String toString() { | ||
683 | return "Wrapped<Seed:" + seed + ">#" + listener; | ||
684 | } | ||
685 | |||
686 | |||
687 | } | ||
688 | private static class EClassTransitiveInstancesAdapter extends ListenerAdapter implements InstanceListener { | ||
689 | private Object seedInstance; | ||
690 | public EClassTransitiveInstancesAdapter(IQueryRuntimeContextListener listener, Object seedInstance) { | ||
691 | super(listener, seedInstance); | ||
692 | this.seedInstance = seedInstance; | ||
693 | } | ||
694 | @Override | ||
695 | public void instanceInserted(EClass clazz, EObject instance) { | ||
696 | if (seedInstance != null && !seedInstance.equals(instance)) return; | ||
697 | listener.update(new EClassTransitiveInstancesKey(clazz), | ||
698 | Tuples.staticArityFlatTupleOf(instance), true); | ||
699 | } | ||
700 | @Override | ||
701 | public void instanceDeleted(EClass clazz, EObject instance) { | ||
702 | if (seedInstance != null && !seedInstance.equals(instance)) return; | ||
703 | listener.update(new EClassTransitiveInstancesKey(clazz), | ||
704 | Tuples.staticArityFlatTupleOf(instance), false); | ||
705 | } | ||
706 | } | ||
707 | private static class EDataTypeInSlotsAdapter extends ListenerAdapter implements DataTypeListener { | ||
708 | private Object seedValue; | ||
709 | public EDataTypeInSlotsAdapter(IQueryRuntimeContextListener listener, Object seedValue) { | ||
710 | super(listener, seedValue); | ||
711 | this.seedValue = seedValue; | ||
712 | } | ||
713 | @Override | ||
714 | public void dataTypeInstanceInserted(EDataType type, Object instance, | ||
715 | boolean firstOccurrence) { | ||
716 | if (firstOccurrence) { | ||
717 | if (seedValue != null && !seedValue.equals(instance)) return; | ||
718 | listener.update(new EDataTypeInSlotsKey(type), | ||
719 | Tuples.staticArityFlatTupleOf(instance), true); | ||
720 | } | ||
721 | } | ||
722 | @Override | ||
723 | public void dataTypeInstanceDeleted(EDataType type, Object instance, | ||
724 | boolean lastOccurrence) { | ||
725 | if (lastOccurrence) { | ||
726 | if (seedValue != null && !seedValue.equals(instance)) return; | ||
727 | listener.update(new EDataTypeInSlotsKey(type), | ||
728 | Tuples.staticArityFlatTupleOf(instance), false); | ||
729 | } | ||
730 | } | ||
731 | } | ||
732 | private static class EStructuralFeatureInstancesKeyAdapter extends ListenerAdapter implements FeatureListener { | ||
733 | private Object seedHost; | ||
734 | private Object seedValue; | ||
735 | public EStructuralFeatureInstancesKeyAdapter(IQueryRuntimeContextListener listener, Object seedHost, Object seedValue) { | ||
736 | super(listener, seedHost, seedValue); | ||
737 | this.seedHost = seedHost; | ||
738 | this.seedValue = seedValue; | ||
739 | } | ||
740 | @Override | ||
741 | public void featureInserted(EObject host, EStructuralFeature feature, | ||
742 | Object value) { | ||
743 | if (seedHost != null && !seedHost.equals(host)) return; | ||
744 | if (seedValue != null && !seedValue.equals(value)) return; | ||
745 | listener.update(new EStructuralFeatureInstancesKey(feature), | ||
746 | Tuples.staticArityFlatTupleOf(host, value), true); | ||
747 | } | ||
748 | @Override | ||
749 | public void featureDeleted(EObject host, EStructuralFeature feature, | ||
750 | Object value) { | ||
751 | if (seedHost != null && !seedHost.equals(host)) return; | ||
752 | if (seedValue != null && !seedValue.equals(value)) return; | ||
753 | listener.update(new EStructuralFeatureInstancesKey(feature), | ||
754 | Tuples.staticArityFlatTupleOf(host, value), false); | ||
755 | } | ||
756 | } | ||
757 | |||
758 | @Override | ||
759 | public void addUpdateListener(IInputKey key, Tuple seed /* TODO ignored */, IQueryRuntimeContextListener listener) { | ||
760 | // stateless, so NOP | ||
761 | if (key instanceof JavaTransitiveInstancesKey) return; | ||
762 | |||
763 | ensureIndexed(key, IndexingService.INSTANCES); | ||
764 | if (key instanceof EClassTransitiveInstancesKey) { | ||
765 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
766 | baseIndex.addInstanceListener(Collections.singleton(eClass), | ||
767 | new EClassTransitiveInstancesAdapter(listener, seed.get(0))); | ||
768 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
769 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
770 | baseIndex.addDataTypeListener(Collections.singleton(dataType), | ||
771 | new EDataTypeInSlotsAdapter(listener, seed.get(0))); | ||
772 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
773 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
774 | baseIndex.addFeatureListener(Collections.singleton(feature), | ||
775 | new EStructuralFeatureInstancesKeyAdapter(listener, seed.get(0), seed.get(1))); | ||
776 | } else { | ||
777 | illegalInputKey(key); | ||
778 | } | ||
779 | } | ||
780 | @Override | ||
781 | public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { | ||
782 | // stateless, so NOP | ||
783 | if (key instanceof JavaTransitiveInstancesKey) return; | ||
784 | |||
785 | ensureIndexed(key, IndexingService.INSTANCES); | ||
786 | if (key instanceof EClassTransitiveInstancesKey) { | ||
787 | EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); | ||
788 | baseIndex.removeInstanceListener(Collections.singleton(eClass), | ||
789 | new EClassTransitiveInstancesAdapter(listener, seed.get(0))); | ||
790 | } else if (key instanceof EDataTypeInSlotsKey) { | ||
791 | EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); | ||
792 | baseIndex.removeDataTypeListener(Collections.singleton(dataType), | ||
793 | new EDataTypeInSlotsAdapter(listener, seed.get(0))); | ||
794 | } else if (key instanceof EStructuralFeatureInstancesKey) { | ||
795 | EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); | ||
796 | baseIndex.removeFeatureListener(Collections.singleton(feature), | ||
797 | new EStructuralFeatureInstancesKeyAdapter(listener, seed.get(0), seed.get(1))); | ||
798 | } else { | ||
799 | illegalInputKey(key); | ||
800 | } | ||
801 | } | ||
802 | |||
803 | // TODO wrap / unwrap enum literals | ||
804 | // TODO use this in all other public methods (maybe wrap & delegate?) | ||
805 | |||
806 | @Override | ||
807 | public Object unwrapElement(Object internalElement) { | ||
808 | return internalElement; | ||
809 | } | ||
810 | @Override | ||
811 | public Tuple unwrapTuple(Tuple internalElements) { | ||
812 | return internalElements; | ||
813 | } | ||
814 | @Override | ||
815 | public Object wrapElement(Object externalElement) { | ||
816 | return externalElement; | ||
817 | } | ||
818 | @Override | ||
819 | public Tuple wrapTuple(Tuple externalElements) { | ||
820 | return externalElements; | ||
821 | } | ||
822 | |||
823 | /** | ||
824 | * @since 1.4 | ||
825 | */ | ||
826 | @Override | ||
827 | public void ensureWildcardIndexing(IndexingService service) { | ||
828 | baseIndex.setWildcardLevel(IndexingLevel.toLevel(service)); | ||
829 | } | ||
830 | |||
831 | /** | ||
832 | * @since 1.4 | ||
833 | */ | ||
834 | @Override | ||
835 | public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { | ||
836 | baseIndex.executeAfterTraversal(runnable); | ||
837 | } | ||
838 | } | ||
839 | |||