aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java')
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java1702
1 files changed, 1702 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java
new file mode 100644
index 00000000..2b5d74b5
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java
@@ -0,0 +1,1702 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, 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 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.core;
10
11import org.apache.log4j.Logger;
12import org.eclipse.emf.common.notify.Notification;
13import org.eclipse.emf.common.notify.Notifier;
14import org.eclipse.emf.common.notify.NotifyingList;
15import org.eclipse.emf.common.util.EList;
16import org.eclipse.emf.ecore.*;
17import org.eclipse.emf.ecore.EStructuralFeature.Setting;
18import org.eclipse.emf.ecore.impl.ENotificationImpl;
19import org.eclipse.emf.ecore.resource.Resource;
20import org.eclipse.emf.ecore.resource.ResourceSet;
21import org.eclipse.emf.ecore.util.EcoreUtil;
22import tools.refinery.viatra.runtime.base.api.*;
23import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEClassProcessor;
24import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEDataTypeProcessor;
25import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter;
26import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter;
27import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension;
28import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor;
29import tools.refinery.viatra.runtime.base.core.EMFBaseIndexInstanceStore.FeatureData;
30import tools.refinery.viatra.runtime.base.core.NavigationHelperVisitor.TraversingVisitor;
31import tools.refinery.viatra.runtime.base.core.profiler.ProfilingNavigationHelperContentAdapter;
32import tools.refinery.viatra.runtime.base.exception.ViatraBaseException;
33import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
34import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
35import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
36import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
37import tools.refinery.viatra.runtime.matchers.util.Preconditions;
38
39import java.lang.reflect.InvocationTargetException;
40import java.util.*;
41import java.util.Map.Entry;
42import java.util.concurrent.Callable;
43import java.util.function.Function;
44import java.util.function.Supplier;
45import java.util.stream.Collectors;
46import java.util.stream.Stream;
47
48import static java.util.function.Function.identity;
49
50/**
51 * @noextend This class is not intended to be subclassed by clients.
52 * @author Gabor Bergmann and Tamas Szabo
53 */
54public class NavigationHelperImpl implements NavigationHelper {
55
56 /**
57 * This is never null.
58 */
59 protected IndexingLevel wildcardMode;
60
61
62 protected Set<Notifier> modelRoots;
63 private boolean expansionAllowed;
64 private boolean traversalDescendsAlongCrossResourceContainment;
65 // protected NavigationHelperVisitor visitor;
66 protected NavigationHelperContentAdapter contentAdapter;
67
68 protected final Logger logger;
69
70 // type object or String id
71 protected Map<Object, IndexingLevel> directlyObservedClasses = new HashMap<Object, IndexingLevel>();
72 // including subclasses; if null, must be recomputed
73 protected Map<Object, IndexingLevel> allObservedClasses = null;
74 protected Map<Object, IndexingLevel> observedDataTypes;
75 protected Map<Object, IndexingLevel> observedFeatures;
76 // ignore RESOLVE for these features, as they are just starting to be observed - see [428458]
77 protected Set<Object> ignoreResolveNotificationFeatures;
78
79 /**
80 * Feature registration and model traversal is delayed while true
81 */
82 protected boolean delayTraversals = false;
83 /**
84 * Classes (or String ID in dynamic mode) to be registered once the coalescing period is over
85 */
86 protected Map<Object, IndexingLevel> delayedClasses = new HashMap<>();
87 /**
88 * EStructuralFeatures (or String ID in dynamic mode) to be registered once the coalescing period is over
89 */
90 protected Map<Object, IndexingLevel> delayedFeatures = new HashMap<>();
91 /**
92 * EDataTypes (or String ID in dynamic mode) to be registered once the coalescing period is over
93 */
94 protected Map<Object, IndexingLevel> delayedDataTypes = new HashMap<>();
95
96 /**
97 * Features per EObject to be resolved later (towards the end of a coalescing period when no Resources are loading)
98 */
99 protected IMultiLookup<EObject, EReference> delayedProxyResolutions = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
100 /**
101 * Reasources that are currently loading, implying the proxy resolution attempts should be delayed
102 */
103 protected Set<Resource> resolutionDelayingResources = new HashSet<Resource>();
104
105 protected Queue<Runnable> traversalCallbacks = new LinkedList<Runnable>();
106
107 /**
108 * These global listeners will be called after updates.
109 */
110 // private final Set<Runnable> afterUpdateCallbacks;
111 private final Set<EMFBaseIndexChangeListener> baseIndexChangeListeners;
112 private final Map<EObject, Set<LightweightEObjectObserver>> lightweightObservers;
113
114 // These are the user subscriptions to notifications
115 private final Map<InstanceListener, Set<EClass>> subscribedInstanceListeners;
116 private final Map<FeatureListener, Set<EStructuralFeature>> subscribedFeatureListeners;
117 private final Map<DataTypeListener, Set<EDataType>> subscribedDataTypeListeners;
118
119 // these are the internal notification tables
120 // (element Type or String id) -> listener -> (subscription types)
121 // if null, must be recomputed from subscriptions
122 // potentially multiple subscription types for each element type because (a) nsURI collisions, (b) multiple
123 // supertypes
124 private Map<Object, Map<InstanceListener, Set<EClass>>> instanceListeners;
125 private Map<Object, Map<FeatureListener, Set<EStructuralFeature>>> featureListeners;
126 private Map<Object, Map<DataTypeListener, Set<EDataType>>> dataTypeListeners;
127
128 private final Set<IEMFIndexingErrorListener> errorListeners;
129 private final BaseIndexOptions baseIndexOptions;
130
131 private EMFModelComprehension comprehension;
132
133 private boolean loggedRegistrationMessage = false;
134
135 EMFBaseIndexMetaStore metaStore;
136 EMFBaseIndexInstanceStore instanceStore;
137 EMFBaseIndexStatisticsStore statsStore;
138
139 <T> Set<T> setMinus(Collection<? extends T> a, Collection<T> b) {
140 Set<T> result = new HashSet<T>(a);
141 result.removeAll(b);
142 return result;
143 }
144
145 @SuppressWarnings("unchecked")
146 <T extends EObject> Set<T> resolveAllInternal(Set<? extends T> a) {
147 if (a == null)
148 a = Collections.emptySet();
149 Set<T> result = new HashSet<T>();
150 for (T t : a) {
151 if (t.eIsProxy()) {
152 result.add((T) EcoreUtil.resolve(t, (ResourceSet) null));
153 } else {
154 result.add(t);
155 }
156 }
157 return result;
158 }
159
160 Set<Object> resolveClassifiersToKey(Set<? extends EClassifier> classes) {
161 Set<? extends EClassifier> resolveds = resolveAllInternal(classes);
162 Set<Object> result = new HashSet<Object>();
163 for (EClassifier resolved : resolveds) {
164 result.add(toKey(resolved));
165 }
166 return result;
167 }
168
169 Set<Object> resolveFeaturesToKey(Set<? extends EStructuralFeature> features) {
170 Set<EStructuralFeature> resolveds = resolveAllInternal(features);
171 Set<Object> result = new HashSet<Object>();
172 for (EStructuralFeature resolved : resolveds) {
173 result.add(toKey(resolved));
174 }
175 return result;
176 }
177
178 @Override
179 public boolean isInWildcardMode() {
180 return isInWildcardMode(IndexingLevel.FULL);
181 }
182
183 @Override
184 public boolean isInWildcardMode(IndexingLevel level) {
185 return wildcardMode.providesLevel(level);
186 }
187
188 @Override
189 public boolean isInDynamicEMFMode() {
190 return baseIndexOptions.isDynamicEMFMode();
191 }
192
193 /**
194 * @return the baseIndexOptions
195 */
196 public BaseIndexOptions getBaseIndexOptions() {
197 return baseIndexOptions.copy();
198 }
199
200 /**
201 * @return the comprehension
202 */
203 public EMFModelComprehension getComprehension() {
204 return comprehension;
205 }
206
207 /**
208 * @throws ViatraQueryRuntimeException
209 */
210 public NavigationHelperImpl(Notifier emfRoot, BaseIndexOptions options, Logger logger) {
211 this.baseIndexOptions = options.copy();
212 this.logger = logger;
213 assert (logger != null);
214
215 this.comprehension = initModelComprehension();
216 this.wildcardMode = baseIndexOptions.getWildcardLevel();
217 this.subscribedInstanceListeners = new HashMap<InstanceListener, Set<EClass>>();
218 this.subscribedFeatureListeners = new HashMap<FeatureListener, Set<EStructuralFeature>>();
219 this.subscribedDataTypeListeners = new HashMap<DataTypeListener, Set<EDataType>>();
220 this.lightweightObservers = CollectionsFactory.createMap();
221 this.observedFeatures = new HashMap<Object, IndexingLevel>();
222 this.ignoreResolveNotificationFeatures = new HashSet<Object>();
223 this.observedDataTypes = new HashMap<Object, IndexingLevel>();
224
225 metaStore = initMetaStore();
226 instanceStore = initInstanceStore();
227 statsStore = initStatStore();
228
229 this.contentAdapter = initContentAdapter();
230 this.baseIndexChangeListeners = new HashSet<EMFBaseIndexChangeListener>();
231 this.errorListeners = new LinkedHashSet<IEMFIndexingErrorListener>();
232
233 this.modelRoots = new HashSet<Notifier>();
234 this.expansionAllowed = false;
235 this.traversalDescendsAlongCrossResourceContainment = false;
236
237 if (emfRoot != null) {
238 addRootInternal(emfRoot);
239 }
240
241 }
242
243 @Override
244 public IndexingLevel getWildcardLevel() {
245 return wildcardMode;
246 }
247
248 @Override
249 public void setWildcardLevel(final IndexingLevel level) {
250 try{
251 IndexingLevel mergedLevel = NavigationHelperImpl.this.wildcardMode.merge(level);
252 if (mergedLevel != NavigationHelperImpl.this.wildcardMode){
253 NavigationHelperImpl.this.wildcardMode = mergedLevel;
254
255 // force traversal upon change of wildcard level
256 final NavigationHelperVisitor visitor = initTraversingVisitor(
257 Collections.<Object, IndexingLevel>emptyMap(), Collections.<Object, IndexingLevel>emptyMap(), Collections.<Object, IndexingLevel>emptyMap(), Collections.<Object, IndexingLevel>emptyMap());
258 coalesceTraversals(() -> traverse(visitor));
259 }
260 } catch (InvocationTargetException ex) {
261 processingFatal(ex.getCause(), "Setting wildcard level: " + level);
262 } catch (Exception ex) {
263 processingFatal(ex, "Setting wildcard level: " + level);
264 }
265 }
266
267 public NavigationHelperContentAdapter getContentAdapter() {
268 return contentAdapter;
269 }
270
271 public Map<Object, IndexingLevel> getObservedFeaturesInternal() {
272 return observedFeatures;
273 }
274
275 public boolean isFeatureResolveIgnored(EStructuralFeature feature) {
276 return ignoreResolveNotificationFeatures.contains(toKey(feature));
277 }
278
279 @Override
280 public void dispose() {
281 ensureNoListenersForDispose();
282 for (Notifier root : modelRoots) {
283 contentAdapter.removeAdapter(root);
284 }
285 }
286
287 @Override
288 public Set<Object> getDataTypeInstances(EDataType type) {
289 Object typeKey = toKey(type);
290 return Collections.unmodifiableSet(instanceStore.getDistinctDataTypeInstances(typeKey));
291 }
292
293 @Override
294 public boolean isInstanceOfDatatype(Object value, EDataType type) {
295 Object typeKey = toKey(type);
296 Set<Object> valMap = instanceStore.getDistinctDataTypeInstances(typeKey);
297 return valMap.contains(value);
298 }
299
300 protected FeatureData featureData(EStructuralFeature feature) {
301 return instanceStore.getFeatureData(toKey(feature));
302 }
303
304 @Override
305 public Set<Setting> findByAttributeValue(Object value_) {
306 Object value = toCanonicalValueRepresentation(value_);
307 return getSettingsForTarget(value);
308 }
309
310 @Override
311 public Set<Setting> findByAttributeValue(Object value_, Collection<EAttribute> attributes) {
312 Object value = toCanonicalValueRepresentation(value_);
313 Set<Setting> retSet = new HashSet<Setting>();
314
315 for (EAttribute attr : attributes) {
316 for (EObject holder : featureData(attr).getDistinctHoldersOfValue(value)) {
317 retSet.add(new NavigationHelperSetting(attr, holder, value));
318 }
319 }
320
321 return retSet;
322 }
323
324 @Override
325 public Set<EObject> findByAttributeValue(Object value_, EAttribute attribute) {
326 Object value = toCanonicalValueRepresentation(value_);
327 final Set<EObject> holders = featureData(attribute).getDistinctHoldersOfValue(value);
328 return Collections.unmodifiableSet(holders);
329 }
330
331 @Override
332 public void processAllFeatureInstances(EStructuralFeature feature, IStructuralFeatureInstanceProcessor processor) {
333 featureData(feature).forEach(processor);
334 }
335
336 @Override
337 public void processDirectInstances(EClass type, IEClassProcessor processor) {
338 Object typeKey = toKey(type);
339 processDirectInstancesInternal(type, processor, typeKey);
340 }
341
342 @Override
343 public void processAllInstances(EClass type, IEClassProcessor processor) {
344 Object typeKey = toKey(type);
345 Set<Object> subTypes = metaStore.getSubTypeMap().get(typeKey);
346 if (subTypes != null) {
347 for (Object subTypeKey : subTypes) {
348 processDirectInstancesInternal(type, processor, subTypeKey);
349 }
350 }
351 processDirectInstancesInternal(type, processor, typeKey);
352 }
353
354 @Override
355 public void processDataTypeInstances(EDataType type, IEDataTypeProcessor processor) {
356 Object typeKey = toKey(type);
357 for (Object value : instanceStore.getDistinctDataTypeInstances(typeKey)) {
358 processor.process(type, value);
359 }
360 }
361
362 protected void processDirectInstancesInternal(EClass type, IEClassProcessor processor, Object typeKey) {
363 final Set<EObject> instances = instanceStore.getInstanceSet(typeKey);
364 if (instances != null) {
365 for (EObject eObject : instances) {
366 processor.process(type, eObject);
367 }
368 }
369 }
370
371 @Override
372 public Set<Setting> getInverseReferences(EObject target) {
373 return getSettingsForTarget(target);
374 }
375
376 protected Set<Setting> getSettingsForTarget(Object target) {
377 Set<Setting> retSet = new HashSet<Setting>();
378 for (Object featureKey : instanceStore.getFeatureKeysPointingTo(target)) {
379 Set<EObject> holders = instanceStore.getFeatureData(featureKey).getDistinctHoldersOfValue(target);
380 for (EObject holder : holders) {
381 EStructuralFeature feature = metaStore.getKnownFeatureForKey(featureKey);
382 retSet.add(new NavigationHelperSetting(feature, holder, target));
383 }
384 }
385 return retSet;
386 }
387
388 @Override
389 public Set<Setting> getInverseReferences(EObject target, Collection<EReference> references) {
390 Set<Setting> retSet = new HashSet<>();
391 for (EReference ref : references) {
392 final Set<EObject> holders = featureData(ref).getDistinctHoldersOfValue(target);
393 for (EObject source : holders) {
394 retSet .add(new NavigationHelperSetting(ref, source, target));
395 }
396 }
397
398 return retSet;
399 }
400
401 @Override
402 public Set<EObject> getInverseReferences(EObject target, EReference reference) {
403 final Set<EObject> holders = featureData(reference).getDistinctHoldersOfValue(target);
404 return Collections.unmodifiableSet(holders);
405 }
406
407 @Override
408 @SuppressWarnings("unchecked")
409 public Set<EObject> getReferenceValues(EObject source, EReference reference) {
410 Set<Object> targets = getFeatureTargets(source, reference);
411 return (Set<EObject>) (Set<?>) targets; // this is known to be safe, as EReferences can only point to EObjects
412 }
413
414 @Override
415 public Set<Object> getFeatureTargets(EObject source, EStructuralFeature _feature) {
416 return Collections.unmodifiableSet(featureData(_feature).getDistinctValuesOfHolder(source));
417 }
418
419 @Override
420 public boolean isFeatureInstance(EObject source, Object target, EStructuralFeature _feature) {
421 return featureData(_feature).isInstance(source, target);
422 }
423
424 @Override
425 public Set<EObject> getDirectInstances(EClass type) {
426 Object typeKey = toKey(type);
427 Set<EObject> valSet = instanceStore.getInstanceSet(typeKey);
428 if (valSet == null) {
429 return Collections.emptySet();
430 } else {
431 return Collections.unmodifiableSet(valSet);
432 }
433 }
434
435 protected Object toKey(EClassifier eClassifier) {
436 return metaStore.toKey(eClassifier);
437 }
438
439 protected Object toKey(EStructuralFeature feature) {
440 return metaStore.toKey(feature);
441 }
442
443 @Override
444 public Object toCanonicalValueRepresentation(Object value) {
445 return metaStore.toInternalValueRepresentation(value);
446 }
447
448 @Override
449 public Set<EObject> getAllInstances(EClass type) {
450 Set<EObject> retSet = new HashSet<EObject>();
451
452 Object typeKey = toKey(type);
453 Set<Object> subTypes = metaStore.getSubTypeMap().get(typeKey);
454 if (subTypes != null) {
455 for (Object subTypeKey : subTypes) {
456 final Set<EObject> instances = instanceStore.getInstanceSet(subTypeKey);
457 if (instances != null) {
458 retSet.addAll(instances);
459 }
460 }
461 }
462 final Set<EObject> instances = instanceStore.getInstanceSet(typeKey);
463 if (instances != null) {
464 retSet.addAll(instances);
465 }
466
467 return retSet;
468 }
469
470 @Override
471 public boolean isInstanceOfUnscoped(EObject object, EClass clazz) {
472 Object candidateTypeKey = toKey(clazz);
473 Object typeKey = toKey(object.eClass());
474
475 return doCalculateInstanceOf(candidateTypeKey, typeKey);
476 }
477
478 @Override
479 public boolean isInstanceOfScoped(EObject object, EClass clazz) {
480 Object typeKey = toKey(object.eClass());
481 if (!doCalculateInstanceOf(toKey(clazz), typeKey)) {
482 return false;
483 }
484 final Set<EObject> instances = instanceStore.getInstanceSet(typeKey);
485 return instances != null && instances.contains(object);
486 }
487
488 protected boolean doCalculateInstanceOf(Object candidateTypeKey, Object typeKey) {
489 if (candidateTypeKey.equals(typeKey)) return true;
490 if (metaStore.getEObjectClassKey().equals(candidateTypeKey)) return true;
491
492 Set<Object> superTypes = metaStore.getSuperTypeMap().get(typeKey);
493 return superTypes.contains(candidateTypeKey);
494 }
495
496 @Override
497 public Set<EObject> findByFeatureValue(Object value_, EStructuralFeature _feature) {
498 Object value = toCanonicalValueRepresentation(value_);
499 return Collections.unmodifiableSet(featureData(_feature).getDistinctHoldersOfValue(value));
500 }
501
502 @Override
503 public Set<EObject> getHoldersOfFeature(EStructuralFeature _feature) {
504 Object feature = toKey(_feature);
505 return Collections.unmodifiableSet(instanceStore.getHoldersOfFeature(feature));
506 }
507 @Override
508 public Set<Object> getValuesOfFeature(EStructuralFeature _feature) {
509 Object feature = toKey(_feature);
510 return Collections.unmodifiableSet(instanceStore.getValuesOfFeature(feature));
511 }
512
513 @Override
514 public void addInstanceListener(Collection<EClass> classes, InstanceListener listener) {
515 Set<EClass> registered = this.subscribedInstanceListeners.computeIfAbsent(listener, l -> new HashSet<>());
516 Set<EClass> delta = setMinus(classes, registered);
517 if (!delta.isEmpty()) {
518 registered.addAll(delta);
519 if (instanceListeners != null) { // if already computed
520 for (EClass subscriptionType : delta) {
521 final Object superElementTypeKey = toKey(subscriptionType);
522 addInstanceListenerInternal(listener, subscriptionType, superElementTypeKey);
523 final Set<Object> subTypeKeys = metaStore.getSubTypeMap().get(superElementTypeKey);
524 if (subTypeKeys != null)
525 for (Object subTypeKey : subTypeKeys) {
526 addInstanceListenerInternal(listener, subscriptionType, subTypeKey);
527 }
528 }
529 }
530 }
531 }
532
533 @Override
534 public void removeInstanceListener(Collection<EClass> classes, InstanceListener listener) {
535 Set<EClass> restriction = this.subscribedInstanceListeners.get(listener);
536 if (restriction != null) {
537 boolean changed = restriction.removeAll(classes);
538 if (restriction.size() == 0) {
539 this.subscribedInstanceListeners.remove(listener);
540 }
541 if (changed)
542 instanceListeners = null; // recompute later on demand
543 }
544 }
545
546 @Override
547 public void addFeatureListener(Collection<? extends EStructuralFeature> features, FeatureListener listener) {
548 Set<EStructuralFeature> registered = this.subscribedFeatureListeners.computeIfAbsent(listener, l -> new HashSet<>());
549 Set<EStructuralFeature> delta = setMinus(features, registered);
550 if (!delta.isEmpty()) {
551 registered.addAll(delta);
552 if (featureListeners != null) { // if already computed
553 for (EStructuralFeature subscriptionType : delta) {
554 addFeatureListenerInternal(listener, subscriptionType, toKey(subscriptionType));
555 }
556 }
557 }
558 }
559
560 @Override
561 public void removeFeatureListener(Collection<? extends EStructuralFeature> features, FeatureListener listener) {
562 Collection<EStructuralFeature> restriction = this.subscribedFeatureListeners.get(listener);
563 if (restriction != null) {
564 boolean changed = restriction.removeAll(features);
565 if (restriction.size() == 0) {
566 this.subscribedFeatureListeners.remove(listener);
567 }
568 if (changed)
569 featureListeners = null; // recompute later on demand
570 }
571 }
572
573 @Override
574 public void addDataTypeListener(Collection<EDataType> types, DataTypeListener listener) {
575 Set<EDataType> registered = this.subscribedDataTypeListeners.computeIfAbsent(listener, l -> new HashSet<>());
576 Set<EDataType> delta = setMinus(types, registered);
577 if (!delta.isEmpty()) {
578 registered.addAll(delta);
579 if (dataTypeListeners != null) { // if already computed
580 for (EDataType subscriptionType : delta) {
581 addDatatypeListenerInternal(listener, subscriptionType, toKey(subscriptionType));
582 }
583 }
584 }
585 }
586
587 @Override
588 public void removeDataTypeListener(Collection<EDataType> types, DataTypeListener listener) {
589 Collection<EDataType> restriction = this.subscribedDataTypeListeners.get(listener);
590 if (restriction != null) {
591 boolean changed = restriction.removeAll(types);
592 if (restriction.size() == 0) {
593 this.subscribedDataTypeListeners.remove(listener);
594 }
595 if (changed)
596 dataTypeListeners = null; // recompute later on demand
597 }
598 }
599
600 /**
601 * @return the observedDataTypes
602 */
603 public Map<Object, IndexingLevel> getObservedDataTypesInternal() {
604 return observedDataTypes;
605 }
606
607 @Override
608 public boolean addLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject) {
609 Set<LightweightEObjectObserver> observers = lightweightObservers.computeIfAbsent(observedObject, CollectionsFactory::emptySet);
610 return observers.add(observer);
611 }
612
613 @Override
614 public boolean removeLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject) {
615 boolean result = false;
616 Set<LightweightEObjectObserver> observers = lightweightObservers.get(observedObject);
617 if (observers != null) {
618 result = observers.remove(observer);
619 if (observers.isEmpty()) {
620 lightweightObservers.remove(observedObject);
621 }
622 }
623 return result;
624 }
625
626 public void notifyBaseIndexChangeListeners() {
627 notifyBaseIndexChangeListeners(instanceStore.isDirty);
628 if (instanceStore.isDirty) {
629 instanceStore.isDirty = false;
630 }
631 }
632
633 /**
634 * This will run after updates.
635 */
636 protected void notifyBaseIndexChangeListeners(boolean baseIndexChanged) {
637 if (!baseIndexChangeListeners.isEmpty()) {
638 for (EMFBaseIndexChangeListener listener : new ArrayList<>(baseIndexChangeListeners)) {
639 try {
640 if (!listener.onlyOnIndexChange() || baseIndexChanged) {
641 listener.notifyChanged(baseIndexChanged);
642 }
643 } catch (Exception ex) {
644 notifyFatalListener("VIATRA Base encountered an error in delivering notifications about changes. ",
645 ex);
646 }
647 }
648 }
649 }
650
651 void notifyDataTypeListeners(final Object typeKey, final Object value, final boolean isInsertion,
652 final boolean firstOrLastOccurrence) {
653 for (final Entry<DataTypeListener, Set<EDataType>> entry : getDataTypeListeners().getOrDefault(typeKey, Collections.emptyMap()).entrySet()) {
654 final DataTypeListener listener = entry.getKey();
655 for (final EDataType subscriptionType : entry.getValue()) {
656 if (isInsertion) {
657 listener.dataTypeInstanceInserted(subscriptionType, value, firstOrLastOccurrence);
658 } else {
659 listener.dataTypeInstanceDeleted(subscriptionType, value, firstOrLastOccurrence);
660 }
661 }
662 }
663 }
664
665 void notifyFeatureListeners(final EObject host, final Object featureKey, final Object value,
666 final boolean isInsertion) {
667 for (final Entry<FeatureListener, Set<EStructuralFeature>> entry : getFeatureListeners().getOrDefault(featureKey, Collections.emptyMap())
668 .entrySet()) {
669 final FeatureListener listener = entry.getKey();
670 for (final EStructuralFeature subscriptionType : entry.getValue()) {
671 if (isInsertion) {
672 listener.featureInserted(host, subscriptionType, value);
673 } else {
674 listener.featureDeleted(host, subscriptionType, value);
675 }
676 }
677 }
678 }
679
680 void notifyInstanceListeners(final Object clazzKey, final EObject instance, final boolean isInsertion) {
681 for (final Entry<InstanceListener, Set<EClass>> entry : getInstanceListeners().getOrDefault(clazzKey, Collections.emptyMap()).entrySet()) {
682 final InstanceListener listener = entry.getKey();
683 for (final EClass subscriptionType : entry.getValue()) {
684 if (isInsertion) {
685 listener.instanceInserted(subscriptionType, instance);
686 } else {
687 listener.instanceDeleted(subscriptionType, instance);
688 }
689 }
690 }
691 }
692
693 void notifyLightweightObservers(final EObject host, final EStructuralFeature feature,
694 final Notification notification) {
695 if (lightweightObservers.containsKey(host)) {
696 Set<LightweightEObjectObserver> observers = lightweightObservers.get(host);
697 for (final LightweightEObjectObserver observer : observers) {
698 observer.notifyFeatureChanged(host, feature, notification);
699 }
700 }
701 }
702
703 @Override
704 public void addBaseIndexChangeListener(EMFBaseIndexChangeListener listener) {
705 Preconditions.checkArgument(listener != null, "Cannot add null listener!");
706 baseIndexChangeListeners.add(listener);
707 }
708
709 @Override
710 public void removeBaseIndexChangeListener(EMFBaseIndexChangeListener listener) {
711 Preconditions.checkArgument(listener != null, "Cannot remove null listener!");
712 baseIndexChangeListeners.remove(listener);
713 }
714
715 @Override
716 public boolean addIndexingErrorListener(IEMFIndexingErrorListener listener) {
717 return errorListeners.add(listener);
718 }
719
720 @Override
721 public boolean removeIndexingErrorListener(IEMFIndexingErrorListener listener) {
722 return errorListeners.remove(listener);
723 }
724
725 protected void processingFatal(final Throwable ex, final String task) {
726 notifyFatalListener(logTaskFormat(task), ex);
727 }
728
729 protected void processingError(final Throwable ex, final String task) {
730 notifyErrorListener(logTaskFormat(task), ex);
731 }
732
733 public void notifyErrorListener(String message, Throwable t) {
734 logger.error(message, t);
735 for (IEMFIndexingErrorListener listener : new ArrayList<>(errorListeners)) {
736 listener.error(message, t);
737 }
738 }
739
740 public void notifyFatalListener(String message, Throwable t) {
741 logger.fatal(message, t);
742 for (IEMFIndexingErrorListener listener : new ArrayList<>(errorListeners)) {
743 listener.fatal(message, t);
744 }
745 }
746
747 protected String logTaskFormat(final String task) {
748 return "VIATRA Query encountered an error in processing the EMF model. " + "This happened while trying to "
749 + task;
750 }
751
752 protected void considerForExpansion(EObject obj) {
753 if (expansionAllowed) {
754 Resource eResource = obj.eResource();
755 if (eResource != null && eResource.getResourceSet() == null) {
756 expandToAdditionalRoot(eResource);
757 }
758 }
759 }
760
761 protected void expandToAdditionalRoot(Notifier root) {
762 if (modelRoots.contains(root))
763 return;
764
765 if (root instanceof ResourceSet) {
766 expansionAllowed = true;
767 } else if (root instanceof Resource) {
768 IBaseIndexResourceFilter resourceFilter = baseIndexOptions.getResourceFilterConfiguration();
769 if (resourceFilter != null && resourceFilter.isResourceFiltered((Resource) root))
770 return;
771 } else { // root instanceof EObject
772 traversalDescendsAlongCrossResourceContainment = true;
773 }
774 final IBaseIndexObjectFilter objectFilter = baseIndexOptions.getObjectFilterConfiguration();
775 if (objectFilter != null && objectFilter.isFiltered(root))
776 return;
777
778 // no veto by filters
779 modelRoots.add(root);
780 contentAdapter.addAdapter(root);
781 notifyBaseIndexChangeListeners();
782 }
783
784 /**
785 * @return the expansionAllowed
786 */
787 public boolean isExpansionAllowed() {
788 return expansionAllowed;
789 }
790
791 public boolean traversalDescendsAlongCrossResourceContainment() {
792 return traversalDescendsAlongCrossResourceContainment;
793 }
794
795 /**
796 * @return the directlyObservedClasses
797 */
798 public Set<Object> getDirectlyObservedClassesInternal() {
799 return directlyObservedClasses.keySet();
800 }
801
802 boolean isObservedInternal(Object clazzKey) {
803 return isInWildcardMode() || getAllObservedClassesInternal().containsKey(clazzKey);
804 }
805
806 /**
807 * Add the given item the map with the given indexing level if it wasn't already added with a higher level.
808 * @param level non-null
809 * @return whether actually changed
810 */
811 protected static <V> boolean putIntoMapMerged(Map<V, IndexingLevel> map, V key, IndexingLevel level) {
812 IndexingLevel l = map.get(key);
813 IndexingLevel merged = level.merge(l);
814 if (merged != l) {
815 map.put(key, merged);
816 return true;
817 } else {
818 return false;
819 }
820 }
821
822 /**
823 * @return true if actually changed
824 */
825 protected boolean addObservedClassesInternal(Object eClassKey, IndexingLevel level) {
826 boolean changed = putIntoMapMerged(allObservedClasses, eClassKey, level);
827 if (!changed) return false;
828
829 final Set<Object> subTypes = metaStore.getSubTypeMap().get(eClassKey);
830 if (subTypes != null) {
831 for (Object subType : subTypes) {
832 /*
833 * It is necessary to check if the class has already been added with a higher indexing level as in case
834 * of multiple inheritance, a subclass may be registered for statistics only but full indexing may be
835 * required via one of its super classes.
836 */
837 putIntoMapMerged(allObservedClasses, subType, level);
838 }
839 }
840 return true;
841 }
842
843 /**
844 * not just the directly observed classes, but also their known subtypes
845 */
846 public Map<Object, IndexingLevel> getAllObservedClassesInternal() {
847 if (allObservedClasses == null) {
848 allObservedClasses = new HashMap<Object, IndexingLevel>();
849 for (Entry<Object, IndexingLevel> entry : directlyObservedClasses.entrySet()) {
850 Object eClassKey = entry.getKey();
851 IndexingLevel level = entry.getValue();
852 addObservedClassesInternal(eClassKey, level);
853 }
854 }
855 return allObservedClasses;
856 }
857
858 /**
859 * @return the instanceListeners
860 */
861 Map<Object, Map<InstanceListener, Set<EClass>>> getInstanceListeners() {
862 if (instanceListeners == null) {
863 instanceListeners = CollectionsFactory.createMap();
864 for (Entry<InstanceListener, Set<EClass>> subscription : subscribedInstanceListeners.entrySet()) {
865 final InstanceListener listener = subscription.getKey();
866 for (EClass subscriptionType : subscription.getValue()) {
867 final Object superElementTypeKey = toKey(subscriptionType);
868 addInstanceListenerInternal(listener, subscriptionType, superElementTypeKey);
869 final Set<Object> subTypeKeys = metaStore.getSubTypeMap().get(superElementTypeKey);
870 if (subTypeKeys != null)
871 for (Object subTypeKey : subTypeKeys) {
872 addInstanceListenerInternal(listener, subscriptionType, subTypeKey);
873 }
874 }
875 }
876 }
877 return instanceListeners;
878 }
879
880 Map<Object, Map<InstanceListener, Set<EClass>>> peekInstanceListeners() {
881 return instanceListeners;
882 }
883
884 void addInstanceListenerInternal(final InstanceListener listener, EClass subscriptionType,
885 final Object elementTypeKey) {
886 Set<EClass> subscriptionTypes = instanceListeners
887 .computeIfAbsent(elementTypeKey, k -> CollectionsFactory.createMap())
888 .computeIfAbsent(listener, k -> CollectionsFactory.createSet());
889 subscriptionTypes.add(subscriptionType);
890 }
891
892 /**
893 * @return the featureListeners
894 */
895 Map<Object, Map<FeatureListener, Set<EStructuralFeature>>> getFeatureListeners() {
896 if (featureListeners == null) {
897 featureListeners = CollectionsFactory.createMap();
898 for (Entry<FeatureListener, Set<EStructuralFeature>> subscription : subscribedFeatureListeners.entrySet()) {
899 final FeatureListener listener = subscription.getKey();
900 for (EStructuralFeature subscriptionType : subscription.getValue()) {
901 final Object elementTypeKey = toKey(subscriptionType);
902 addFeatureListenerInternal(listener, subscriptionType, elementTypeKey);
903 }
904 }
905 }
906 return featureListeners;
907 }
908
909 void addFeatureListenerInternal(final FeatureListener listener, EStructuralFeature subscriptionType,
910 final Object elementTypeKey) {
911 Set<EStructuralFeature> subscriptionTypes = featureListeners
912 .computeIfAbsent(elementTypeKey, k -> CollectionsFactory.createMap())
913 .computeIfAbsent(listener, k -> CollectionsFactory.createSet());
914 subscriptionTypes.add(subscriptionType);
915 }
916
917 /**
918 * @return the dataTypeListeners
919 */
920 Map<Object, Map<DataTypeListener, Set<EDataType>>> getDataTypeListeners() {
921 if (dataTypeListeners == null) {
922 dataTypeListeners = CollectionsFactory.createMap();
923 for (Entry<DataTypeListener, Set<EDataType>> subscription : subscribedDataTypeListeners.entrySet()) {
924 final DataTypeListener listener = subscription.getKey();
925 for (EDataType subscriptionType : subscription.getValue()) {
926 final Object elementTypeKey = toKey(subscriptionType);
927 addDatatypeListenerInternal(listener, subscriptionType, elementTypeKey);
928 }
929 }
930 }
931 return dataTypeListeners;
932 }
933
934 void addDatatypeListenerInternal(final DataTypeListener listener, EDataType subscriptionType,
935 final Object elementTypeKey) {
936 Set<EDataType> subscriptionTypes = dataTypeListeners
937 .computeIfAbsent(elementTypeKey, k -> CollectionsFactory.createMap())
938 .computeIfAbsent(listener, k -> CollectionsFactory.createSet());
939 subscriptionTypes.add(subscriptionType);
940 }
941
942 public void registerObservedTypes(Set<EClass> classes, Set<EDataType> dataTypes,
943 Set<? extends EStructuralFeature> features) {
944 registerObservedTypes(classes, dataTypes, features, IndexingLevel.FULL);
945 }
946
947 @Override
948 public void registerObservedTypes(Set<EClass> classes, Set<EDataType> dataTypes,
949 Set<? extends EStructuralFeature> features, final IndexingLevel level) {
950 if (isRegistrationNecessary(level) && (classes != null || features != null || dataTypes != null)) {
951 final Set<Object> resolvedFeatures = resolveFeaturesToKey(features);
952 final Set<Object> resolvedClasses = resolveClassifiersToKey(classes);
953 final Set<Object> resolvedDatatypes = resolveClassifiersToKey(dataTypes);
954
955 try {
956 coalesceTraversals(() -> {
957 Function<Object, IndexingLevel> f = input -> level;
958 delayedFeatures.putAll(resolvedFeatures.stream().collect(Collectors.toMap(identity(), f)));
959 delayedDataTypes.putAll(resolvedDatatypes.stream().collect(Collectors.toMap(identity(), f)));
960 delayedClasses.putAll(resolvedClasses.stream().collect(Collectors.toMap(identity(), f)));
961 });
962 } catch (InvocationTargetException ex) {
963 processingFatal(ex.getCause(), "register en masse the observed EClasses " + resolvedClasses
964 + " and EDatatypes " + resolvedDatatypes + " and EStructuralFeatures " + resolvedFeatures);
965 } catch (Exception ex) {
966 processingFatal(ex, "register en masse the observed EClasses " + resolvedClasses + " and EDatatypes "
967 + resolvedDatatypes + " and EStructuralFeatures " + resolvedFeatures);
968 }
969 }
970 }
971
972 @Override
973 public void unregisterObservedTypes(Set<EClass> classes, Set<EDataType> dataTypes,
974 Set<? extends EStructuralFeature> features) {
975 unregisterEClasses(classes);
976 unregisterEDataTypes(dataTypes);
977 unregisterEStructuralFeatures(features);
978 }
979
980 @Override
981 public void registerEStructuralFeatures(Set<? extends EStructuralFeature> features, final IndexingLevel level) {
982 if (isRegistrationNecessary(level) && features != null) {
983 final Set<Object> resolved = resolveFeaturesToKey(features);
984
985 try {
986 coalesceTraversals(() -> resolved.forEach(o -> delayedFeatures.put(o, level)));
987 } catch (InvocationTargetException ex) {
988 processingFatal(ex.getCause(), "register the observed EStructuralFeatures: " + resolved);
989 } catch (Exception ex) {
990 processingFatal(ex, "register the observed EStructuralFeatures: " + resolved);
991 }
992 }
993 }
994
995 @Override
996 public void unregisterEStructuralFeatures(Set<? extends EStructuralFeature> features) {
997 if (isRegistrationNecessary(IndexingLevel.FULL) && features != null) {
998 final Set<Object> resolved = resolveFeaturesToKey(features);
999 ensureNoListeners(resolved, getFeatureListeners());
1000 observedFeatures.keySet().removeAll(resolved);
1001 delayedFeatures.keySet().removeAll(resolved);
1002 for (Object f : resolved) {
1003 instanceStore.forgetFeature(f);
1004 statsStore.removeType(f);
1005 }
1006 }
1007 }
1008
1009 @Override
1010 public void registerEClasses(Set<EClass> classes, final IndexingLevel level) {
1011 if (isRegistrationNecessary(level) && classes != null) {
1012 final Set<Object> resolvedClasses = resolveClassifiersToKey(classes);
1013
1014 try {
1015 coalesceTraversals(() -> resolvedClasses.forEach(o -> delayedClasses.put(o, level)));
1016 } catch (InvocationTargetException ex) {
1017 processingFatal(ex.getCause(), "register the observed EClasses: " + resolvedClasses);
1018 } catch (Exception ex) {
1019 processingFatal(ex, "register the observed EClasses: " + resolvedClasses);
1020 }
1021 }
1022 }
1023
1024 /**
1025 * @return true if there is an actual change in the transitively computed observation levels,
1026 * warranting an actual traversal
1027 */
1028 protected boolean startObservingClasses(Map<Object, IndexingLevel> requestedClassObservations) {
1029 boolean warrantsTraversal = false;
1030 getAllObservedClassesInternal(); // pre-populate
1031 for (Entry<Object, IndexingLevel> request : requestedClassObservations.entrySet()) {
1032 if (putIntoMapMerged(directlyObservedClasses, request.getKey(), request.getValue())) {
1033 // maybe already observed for the sake of a supertype?
1034 if (addObservedClassesInternal(request.getKey(), request.getValue())) {
1035 warrantsTraversal = true;
1036 };
1037 }
1038 }
1039 return warrantsTraversal;
1040 }
1041
1042 @Override
1043 public void unregisterEClasses(Set<EClass> classes) {
1044 if (isRegistrationNecessary(IndexingLevel.FULL) && classes != null) {
1045 final Set<Object> resolved = resolveClassifiersToKey(classes);
1046 ensureNoListeners(resolved, getInstanceListeners());
1047 directlyObservedClasses.keySet().removeAll(resolved);
1048 allObservedClasses = null;
1049 delayedClasses.keySet().removeAll(resolved);
1050 for (Object c : resolved) {
1051 instanceStore.removeInstanceSet(c);
1052 statsStore.removeType(c);
1053 }
1054 }
1055 }
1056
1057 @Override
1058 public void registerEDataTypes(Set<EDataType> dataTypes, final IndexingLevel level) {
1059 if (isRegistrationNecessary(level) && dataTypes != null) {
1060 final Set<Object> resolved = resolveClassifiersToKey(dataTypes);
1061
1062 try {
1063 coalesceTraversals(() -> resolved.forEach(o -> delayedDataTypes.put(o, level)));
1064 } catch (InvocationTargetException ex) {
1065 processingFatal(ex.getCause(), "register the observed EDataTypes: " + resolved);
1066 } catch (Exception ex) {
1067 processingFatal(ex, "register the observed EDataTypes: " + resolved);
1068 }
1069 }
1070 }
1071
1072 @Override
1073 public void unregisterEDataTypes(Set<EDataType> dataTypes) {
1074 if (isRegistrationNecessary(IndexingLevel.FULL) && dataTypes != null) {
1075 final Set<Object> resolved = resolveClassifiersToKey(dataTypes);
1076 ensureNoListeners(resolved, getDataTypeListeners());
1077 observedDataTypes.keySet().removeAll(resolved);
1078 delayedDataTypes.keySet().removeAll(resolved);
1079 for (Object dataType : resolved) {
1080 instanceStore.removeDataTypeMap(dataType);
1081 statsStore.removeType(dataType);
1082 }
1083 }
1084 }
1085
1086 @Override
1087 public boolean isCoalescing() {
1088 return delayTraversals;
1089 }
1090
1091 public void coalesceTraversals(final Runnable runnable) throws InvocationTargetException {
1092 coalesceTraversals(() -> {
1093 runnable.run();
1094 return null;
1095 });
1096 }
1097
1098 @Override
1099 public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException {
1100 V finalResult = null;
1101
1102 if (delayTraversals) { // reentrant case, no special action needed
1103 try {
1104 finalResult = callable.call();
1105 } catch (Exception e) {
1106 throw new InvocationTargetException(e);
1107 }
1108 return finalResult;
1109 }
1110
1111 boolean firstRun = true;
1112 while (callable != null) { // repeat if post-processing needed
1113
1114 try {
1115 delayTraversals = true;
1116
1117 V result = callable.call();
1118 if (firstRun) {
1119 firstRun = false;
1120 finalResult = result;
1121 }
1122
1123 // are there proxies left to be resolved? are we allowed to resolve them now?
1124 while ((!delayedProxyResolutions.isEmpty()) && resolutionDelayingResources.isEmpty()) {
1125 // pop first entry
1126 EObject toResolveObject = delayedProxyResolutions.distinctKeys().iterator().next();
1127 EReference toResolveReference = delayedProxyResolutions.lookup(toResolveObject).iterator().next();
1128 delayedProxyResolutions.removePair(toResolveObject, toResolveReference);
1129
1130 // see if we can resolve proxies
1131 comprehension.tryResolveReference(toResolveObject, toResolveReference);
1132 }
1133
1134 delayTraversals = false;
1135 callable = considerRevisit();
1136 } catch (Exception e) {
1137 // since this is a fatal error, it is OK if delayTraversals remains true,
1138 // hence no need for a try-finally block
1139
1140 notifyFatalListener(
1141 "VIATRA Base encountered an error while traversing the EMF model to gather new information. ",
1142 e);
1143 throw new InvocationTargetException(e);
1144 }
1145 }
1146 executeTraversalCallbacks();
1147 return finalResult;
1148 }
1149
1150 protected <V> Callable<V> considerRevisit() {
1151 // has there been any requests for a retraversal at all?
1152 if (!delayedClasses.isEmpty() || !delayedFeatures.isEmpty() || !delayedDataTypes.isEmpty()) {
1153 // make copies of requested types so that
1154 // (a) original accumulators can be cleaned for the next cycle, also
1155 // (b) to remove entries that are already covered, or
1156 // (c) for the rare case that a coalesced traversal is invoked during visitation,
1157 // e.g. by a derived feature implementation
1158 // initialize the collections empty (but with capacity), fill with new entries
1159 final Map<Object, IndexingLevel> toGatherClasses = new HashMap<Object, IndexingLevel>(delayedClasses.size());
1160 final Map<Object, IndexingLevel> toGatherFeatures = new HashMap<Object, IndexingLevel>(delayedFeatures.size());
1161 final Map<Object, IndexingLevel> toGatherDataTypes = new HashMap<Object, IndexingLevel>(delayedDataTypes.size());
1162
1163 for (Entry<Object, IndexingLevel> requested : delayedFeatures.entrySet()) {
1164 Object typekey = requested.getKey();
1165 IndexingLevel old = observedFeatures.get(typekey);
1166 IndexingLevel merged = requested.getValue().merge(old);
1167 if (merged != old) toGatherFeatures.put(typekey, merged);
1168 }
1169 for (Entry<Object, IndexingLevel> requested : delayedClasses.entrySet()) {
1170 Object typekey = requested.getKey();
1171 IndexingLevel old = directlyObservedClasses.get(typekey);
1172 IndexingLevel merged = requested.getValue().merge(old);
1173 if (merged != old) toGatherClasses.put(typekey, merged);
1174 }
1175 for (Entry<Object, IndexingLevel> requested : delayedDataTypes.entrySet()) {
1176 Object typekey = requested.getKey();
1177 IndexingLevel old = observedDataTypes.get(typekey);
1178 IndexingLevel merged = requested.getValue().merge(old);
1179 if (merged != old) toGatherDataTypes.put(typekey, merged);
1180 }
1181
1182 delayedClasses.clear();
1183 delayedFeatures.clear();
1184 delayedDataTypes.clear();
1185
1186 // check if the filtered request sets are empty
1187 // - could be false alarm if we already observe all of them
1188 if (!toGatherClasses.isEmpty() || !toGatherFeatures.isEmpty() || !toGatherDataTypes.isEmpty()) {
1189 final HashMap<Object, IndexingLevel> oldClasses = new HashMap<Object, IndexingLevel>(
1190 directlyObservedClasses);
1191
1192 /* Instance indexing would add extra entries to the statistics store, so we have to clean the
1193 * appropriate entries. If no re-traversal is required, it is detected earlier; at this point we
1194 * only have to consider the target indexing level. See bug
1195 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=518356 for more details.
1196 *
1197 * This has to be executed before the old observed types are updated to check whether the indexing level increased.
1198 *
1199 * Technically, the statsStore cleanup seems only necessary for EDataTypes; otherwise everything
1200 * works as expected, but it seems a better idea to do the cleanup for all types in the same way */
1201 toGatherClasses.forEach((key, value) -> {
1202 IndexingLevel oldIndexingLevel = getIndexingLevel(metaStore.getKnownClassifierForKey(key));
1203 if (value.hasInstances() && oldIndexingLevel.hasStatistics() && !oldIndexingLevel.hasInstances()) {
1204 statsStore.removeType(key);
1205 }
1206
1207 });
1208 toGatherFeatures.forEach((key, value) -> {
1209 IndexingLevel oldIndexingLevel = getIndexingLevel(metaStore.getKnownFeatureForKey(key));
1210 if (value.hasInstances() && oldIndexingLevel.hasStatistics() && !oldIndexingLevel.hasInstances()) {
1211 statsStore.removeType(key);
1212 }
1213
1214 });
1215 toGatherDataTypes.forEach((key, value) -> {
1216 IndexingLevel oldIndexingLevel = getIndexingLevel(metaStore.getKnownClassifierForKey(key));
1217 if (value.hasInstances() && oldIndexingLevel.hasStatistics() && !oldIndexingLevel.hasInstances()) {
1218 statsStore.removeType(key);
1219 }
1220
1221 });
1222
1223 // Are there new classes to be observed that are not available via superclasses?
1224 // (at sufficient level)
1225 // if yes, model traversal needed
1226 // if not, index can be updated without retraversal
1227 boolean classesWarrantTraversal = startObservingClasses(toGatherClasses);
1228 observedDataTypes.putAll(toGatherDataTypes);
1229 observedFeatures.putAll(toGatherFeatures);
1230
1231
1232 // So, is an actual traversal needed, or are we done?
1233 if (classesWarrantTraversal || !toGatherFeatures.isEmpty() || !toGatherDataTypes.isEmpty()) {
1234 // repeat the cycle with this visit
1235 final NavigationHelperVisitor visitor = initTraversingVisitor(toGatherClasses, toGatherFeatures, toGatherDataTypes, oldClasses);
1236
1237 return new Callable<V>() {
1238 @Override
1239 public V call() throws Exception {
1240 // temporarily ignoring RESOLVE on these features, as they were not observed before
1241 ignoreResolveNotificationFeatures.addAll(toGatherFeatures.keySet());
1242 try {
1243 traverse(visitor);
1244 } finally {
1245 ignoreResolveNotificationFeatures.removeAll(toGatherFeatures.keySet());
1246 }
1247 return null;
1248 }
1249 };
1250
1251 }
1252 }
1253 }
1254
1255 return null; // no callable -> no further action
1256 }
1257
1258 protected void executeTraversalCallbacks() throws InvocationTargetException{
1259 final Runnable[] callbacks = traversalCallbacks.toArray(new Runnable[traversalCallbacks.size()]);
1260 traversalCallbacks.clear();
1261 if (callbacks.length > 0){
1262 coalesceTraversals(() -> Arrays.stream(callbacks).forEach(Runnable::run));
1263 }
1264 }
1265
1266 protected void traverse(final NavigationHelperVisitor visitor) {
1267 // Cloning model roots avoids a concurrent modification exception
1268 for (Notifier root : new HashSet<Notifier>(modelRoots)) {
1269 comprehension.traverseModel(visitor, root);
1270 }
1271 notifyBaseIndexChangeListeners();
1272 }
1273
1274 /**
1275 * Returns a stream of model roots registered to the navigation helper instance
1276 * @since 2.3
1277 */
1278 protected Stream<Notifier> getModelRoots() {
1279 return modelRoots.stream();
1280 }
1281
1282 @Override
1283 public void addRoot(Notifier emfRoot) {
1284 addRootInternal(emfRoot);
1285 }
1286
1287 /**
1288 * Supports removing model roots
1289 * </p>
1290 * Note: for now this API is considered experimental thus it is not added to the {@link NavigationHelper} interface.
1291 * @since 2.3
1292 */
1293 protected void removeRoot(Notifier root) {
1294 if (!((root instanceof EObject) || (root instanceof Resource) || (root instanceof ResourceSet))) {
1295 throw new ViatraBaseException(ViatraBaseException.INVALID_EMFROOT);
1296 }
1297
1298 if (!modelRoots.contains(root))
1299 return;
1300
1301 if (root instanceof Resource) {
1302 IBaseIndexResourceFilter resourceFilter = getBaseIndexOptions().getResourceFilterConfiguration();
1303 if (resourceFilter != null && resourceFilter.isResourceFiltered((Resource) root))
1304 return;
1305 }
1306 final IBaseIndexObjectFilter objectFilter = getBaseIndexOptions().getObjectFilterConfiguration();
1307 if (objectFilter != null && objectFilter.isFiltered(root))
1308 return;
1309
1310 // no veto by filters
1311 modelRoots.remove(root);
1312 contentAdapter.removeAdapter(root);
1313 notifyBaseIndexChangeListeners();
1314 }
1315
1316 @Override
1317 public <T extends EObject> void cheapMoveTo(T element, EList<T> targetContainmentReferenceList) {
1318 if (element.eAdapters().contains(contentAdapter)
1319 && targetContainmentReferenceList instanceof NotifyingList<?>) {
1320 final Object listNotifier = ((NotifyingList<?>) targetContainmentReferenceList).getNotifier();
1321 if (listNotifier instanceof Notifier && ((Notifier) listNotifier).eAdapters().contains(contentAdapter)) {
1322 contentAdapter.ignoreInsertionAndDeletion = element;
1323 try {
1324 targetContainmentReferenceList.add(element);
1325 } finally {
1326 contentAdapter.ignoreInsertionAndDeletion = null;
1327 }
1328 } else {
1329 targetContainmentReferenceList.add(element);
1330 }
1331 } else {
1332 targetContainmentReferenceList.add(element);
1333 }
1334 }
1335
1336 @SuppressWarnings({ "unchecked", "rawtypes" })
1337 @Override
1338 public void cheapMoveTo(EObject element, EObject parent, EReference containmentFeature) {
1339 metaStore.maintainMetamodel(containmentFeature);
1340 if (containmentFeature.isMany())
1341 cheapMoveTo(element, (EList) parent.eGet(containmentFeature));
1342 else if (element.eAdapters().contains(contentAdapter) && parent.eAdapters().contains(contentAdapter)) {
1343 contentAdapter.ignoreInsertionAndDeletion = element;
1344 try {
1345 parent.eSet(containmentFeature, element);
1346 } finally {
1347 contentAdapter.ignoreInsertionAndDeletion = null;
1348 }
1349 } else {
1350 parent.eSet(containmentFeature, element);
1351 }
1352 }
1353
1354 protected void addRootInternal(Notifier emfRoot) {
1355 if (!((emfRoot instanceof EObject) || (emfRoot instanceof Resource) || (emfRoot instanceof ResourceSet))) {
1356 throw new ViatraBaseException(ViatraBaseException.INVALID_EMFROOT);
1357 }
1358 expandToAdditionalRoot(emfRoot);
1359 }
1360
1361 @Override
1362 public Set<EClass> getAllCurrentClasses() {
1363 return instanceStore.getAllCurrentClasses();
1364 }
1365
1366 protected boolean isRegistrationNecessary(IndexingLevel level) {
1367 boolean inWildcardMode = isInWildcardMode(level);
1368 if (inWildcardMode && !loggedRegistrationMessage) {
1369 loggedRegistrationMessage = true;
1370 logger.warn("Type registration/unregistration not required in wildcard mode. This message will not be repeated for future occurences.");
1371 }
1372 return !inWildcardMode;
1373 }
1374
1375 protected <X, Y> void ensureNoListeners(Set<Object> unobservedTypes,
1376 final Map<Object, Map<X, Set<Y>>> listenerRegistry) {
1377 if (!Collections.disjoint(unobservedTypes, listenerRegistry.keySet()))
1378 throw new IllegalStateException("Cannot unregister observed types for which there are active listeners");
1379 }
1380
1381 protected void ensureNoListenersForDispose() {
1382 if (!(baseIndexChangeListeners.isEmpty() && subscribedFeatureListeners.isEmpty()
1383 && subscribedDataTypeListeners.isEmpty() && subscribedInstanceListeners.isEmpty()))
1384 throw new IllegalStateException("Cannot dispose while there are active listeners");
1385 }
1386
1387 /**
1388 * Resamples the values of not well-behaving derived features if those features are also indexed.
1389 */
1390 @Override
1391 public void resampleDerivedFeatures() {
1392 // otherwise notifications are delivered anyway
1393 if (!baseIndexOptions.isTraverseOnlyWellBehavingDerivedFeatures()) {
1394 // get all required classes
1395 Set<EClass> allCurrentClasses = instanceStore.getAllCurrentClasses();
1396 Set<EStructuralFeature> featuresToSample = new HashSet<>();
1397 // collect features to sample
1398 for (EClass cls : allCurrentClasses) {
1399 EList<EStructuralFeature> features = cls.getEAllStructuralFeatures();
1400 for (EStructuralFeature f : features) {
1401 // is feature only sampled?
1402 if (comprehension.onlySamplingFeature(f)) {
1403 featuresToSample.add(f);
1404 }
1405 }
1406 }
1407
1408 final EMFVisitor removalVisitor = contentAdapter.getVisitorForChange(false);
1409 final EMFVisitor insertionVisitor = contentAdapter.getVisitorForChange(true);
1410
1411 // iterate on instances
1412 for (final EStructuralFeature f : featuresToSample) {
1413 EClass containingClass = f.getEContainingClass();
1414 processAllInstances(containingClass, (type, instance) ->
1415 resampleFeatureValueForHolder(instance, f, insertionVisitor, removalVisitor));
1416 }
1417 notifyBaseIndexChangeListeners();
1418 }
1419 }
1420
1421 protected void resampleFeatureValueForHolder(EObject source, EStructuralFeature feature,
1422 EMFVisitor insertionVisitor, EMFVisitor removalVisitor) {
1423 // traverse features and update value
1424 Object newValue = source.eGet(feature);
1425 Set<Object> oldValues = instanceStore.getOldValuesForHolderAndFeature(source, toKey(feature));
1426 if (feature.isMany()) {
1427 resampleManyFeatureValueForHolder(source, feature, newValue, oldValues, insertionVisitor, removalVisitor);
1428 } else {
1429 resampleSingleFeatureValueForHolder(source, feature, newValue, oldValues, insertionVisitor, removalVisitor);
1430 }
1431
1432 }
1433
1434 protected void resampleManyFeatureValueForHolder(EObject source, EStructuralFeature feature, Object newValue,
1435 Set<Object> oldValues, EMFVisitor insertionVisitor, EMFVisitor removalVisitor) {
1436 InternalEObject internalEObject = (InternalEObject) source;
1437 Collection<?> newValues = (Collection<?>) newValue;
1438 // add those that are in new but not in old
1439 Set<Object> newValueSet = new HashSet<Object>(newValues);
1440 newValueSet.removeAll(oldValues);
1441 // remove those that are in old but not in new
1442 oldValues.removeAll(newValues);
1443 if (!oldValues.isEmpty()) {
1444 for (Object ov : oldValues) {
1445 comprehension.traverseFeature(removalVisitor, source, feature, ov, null);
1446 }
1447 ENotificationImpl removeNotification = new ENotificationImpl(internalEObject, Notification.REMOVE_MANY,
1448 feature, oldValues, null);
1449 notifyLightweightObservers(source, feature, removeNotification);
1450 }
1451 if (!newValueSet.isEmpty()) {
1452 for (Object nv : newValueSet) {
1453 comprehension.traverseFeature(insertionVisitor, source, feature, nv, null);
1454 }
1455 ENotificationImpl addNotification = new ENotificationImpl(internalEObject, Notification.ADD_MANY, feature,
1456 null, newValueSet);
1457 notifyLightweightObservers(source, feature, addNotification);
1458 }
1459 }
1460
1461 protected void resampleSingleFeatureValueForHolder(EObject source, EStructuralFeature feature, Object newValue,
1462 Set<Object> oldValues, EMFVisitor insertionVisitor, EMFVisitor removalVisitor) {
1463 InternalEObject internalEObject = (InternalEObject) source;
1464 Object oldValue = oldValues.stream().findFirst().orElse(null);
1465 if (!Objects.equals(oldValue, newValue)) {
1466 // value changed
1467 comprehension.traverseFeature(removalVisitor, source, feature, oldValue, null);
1468 comprehension.traverseFeature(insertionVisitor, source, feature, newValue, null);
1469 ENotificationImpl notification = new ENotificationImpl(internalEObject, Notification.SET, feature, oldValue,
1470 newValue);
1471 notifyLightweightObservers(source, feature, notification);
1472 }
1473 }
1474
1475 @Override
1476 public int countAllInstances(EClass type) {
1477 int result = 0;
1478
1479 Object typeKey = toKey(type);
1480 Set<Object> subTypes = metaStore.getSubTypeMap().get(typeKey);
1481 if (subTypes != null) {
1482 for (Object subTypeKey : subTypes) {
1483 result += statsStore.countInstances(subTypeKey);
1484 }
1485 }
1486 result += statsStore.countInstances(typeKey);
1487
1488 return result;
1489 }
1490
1491 @Override
1492 public int countDataTypeInstances(EDataType dataType) {
1493 return statsStore.countInstances(toKey(dataType));
1494 }
1495
1496 @Override
1497 public int countFeatureTargets(EObject seedSource, EStructuralFeature feature) {
1498 return featureData(feature).getDistinctValuesOfHolder(seedSource).size();
1499 }
1500
1501 @Override
1502 public int countFeatures(EStructuralFeature feature) {
1503 return statsStore.countFeatures(toKey(feature));
1504 }
1505
1506 protected IndexingLevel getIndexingLevel(Object type) {
1507 if (type instanceof EClass) {
1508 return getIndexingLevel((EClass)type);
1509 } else if (type instanceof EDataType) {
1510 return getIndexingLevel((EDataType)type);
1511 } else if (type instanceof EStructuralFeature) {
1512 return getIndexingLevel((EStructuralFeature)type);
1513 } else {
1514 throw new IllegalArgumentException("Unexpected type descriptor " + type.toString());
1515 }
1516 }
1517
1518 @Override
1519 public IndexingLevel getIndexingLevel(EClass type) {
1520 Object key = toKey(type);
1521 IndexingLevel level = directlyObservedClasses.get(key);
1522 if (level == null) {
1523 level = delayedClasses.get(key);
1524 }
1525 // Wildcard mode is never null
1526 return wildcardMode.merge(level);
1527 }
1528
1529 @Override
1530 public IndexingLevel getIndexingLevel(EDataType type) {
1531 Object key = toKey(type);
1532 IndexingLevel level = observedDataTypes.get(key);
1533 if (level == null) {
1534 level = delayedDataTypes.get(key);
1535 }
1536 // Wildcard mode is never null
1537 return wildcardMode.merge(level);
1538 }
1539
1540 @Override
1541 public IndexingLevel getIndexingLevel(EStructuralFeature feature) {
1542 Object key = toKey(feature);
1543 IndexingLevel level = observedFeatures.get(key);
1544 if (level == null) {
1545 level = delayedFeatures.get(key);
1546 }
1547 // Wildcard mode is never null
1548 return wildcardMode.merge(level);
1549 }
1550
1551 @Override
1552 public void executeAfterTraversal(final Runnable traversalCallback) throws InvocationTargetException {
1553 coalesceTraversals(() -> traversalCallbacks.add(traversalCallback));
1554 }
1555
1556 /**
1557 * Records a non-exception incident such as faulty notifications.
1558 * Depending on the strictness setting {@link BaseIndexOptions#isStrictNotificationMode()} and log levels,
1559 * this may be treated as a fatal error, merely logged, or just ignored.
1560 *
1561 * @param msgProvider message supplier that only invoked if the message actually gets logged.
1562 *
1563 * @since 2.3
1564 */
1565 protected void logIncident(Supplier<String> msgProvider) {
1566 if (baseIndexOptions.isStrictNotificationMode()) {
1567 // This will cause e.g. query engine to become tainted
1568 String msg = msgProvider.get();
1569 notifyFatalListener(msg, new IllegalStateException(msg));
1570 } else {
1571 if (notificationErrorReported) {
1572 if (logger.isDebugEnabled()) {
1573 String msg = msgProvider.get();
1574 logger.debug(msg);
1575 }
1576 } else {
1577 notificationErrorReported = true;
1578 String msg = msgProvider.get();
1579 logger.error(msg);
1580 }
1581 }
1582 }
1583 boolean notificationErrorReported = false;
1584
1585
1586// DESIGNATED CUSTOMIZATION POINTS FOR SUBCLASSES
1587
1588 /**
1589 * Point of customization, called by constructor
1590 * @since 2.3
1591 */
1592 protected NavigationHelperContentAdapter initContentAdapter() {
1593 switch (baseIndexOptions.getIndexerProfilerMode()) {
1594 case START_DISABLED:
1595 return new ProfilingNavigationHelperContentAdapter(this, false);
1596 case START_ENABLED:
1597 return new ProfilingNavigationHelperContentAdapter(this, true);
1598 case OFF:
1599 default:
1600 return new NavigationHelperContentAdapter(this);
1601 }
1602 }
1603
1604 /**
1605 * Point of customization, called by constructor
1606 * @since 2.3
1607 */
1608 protected EMFBaseIndexStatisticsStore initStatStore() {
1609 return new EMFBaseIndexStatisticsStore(this, logger);
1610 }
1611
1612 /**
1613 * Point of customization, called by constructor
1614 * @since 2.3
1615 */
1616 protected EMFBaseIndexInstanceStore initInstanceStore() {
1617 return new EMFBaseIndexInstanceStore(this, logger);
1618 }
1619
1620 /**
1621 * Point of customization, called by constructor
1622 * @since 2.3
1623 */
1624 protected EMFBaseIndexMetaStore initMetaStore() {
1625 return new EMFBaseIndexMetaStore(this);
1626 }
1627
1628 /**
1629 * Point of customization, called by constructor
1630 * @since 2.3
1631 */
1632 protected EMFModelComprehension initModelComprehension() {
1633 return new EMFModelComprehension(baseIndexOptions);
1634 }
1635
1636 /**
1637 * Point of customization, called at runtime
1638 * @since 2.3
1639 */
1640 protected TraversingVisitor initTraversingVisitor(final Map<Object, IndexingLevel> toGatherClasses,
1641 final Map<Object, IndexingLevel> toGatherFeatures, final Map<Object, IndexingLevel> toGatherDataTypes,
1642 final Map<Object, IndexingLevel> oldClasses) {
1643 return new NavigationHelperVisitor.TraversingVisitor(this,
1644 toGatherFeatures, toGatherClasses, oldClasses, toGatherDataTypes);
1645 }
1646
1647
1648
1649 /**
1650 * Point of customization, e.g. override to suppress
1651 * @since 2.3
1652 */
1653 protected void logIncidentAdapterRemoval(final Notifier notifier) {
1654 logIncident(() -> String.format("Erroneous removal of unattached notification adapter from notifier %s", notifier));
1655 }
1656
1657 /**
1658 * Point of customization, e.g. override to suppress
1659 * @since 2.3
1660 */
1661 protected void logIncidentFeatureTupleInsertion(final Object value, final EObject holder, Object featureKey) {
1662 logIncident(() -> String.format(
1663 "Error: trying to add duplicate value %s to the unique feature %s of host object %s. This indicates some errors in underlying model representation.",
1664 value, featureKey, holder));
1665 }
1666
1667 /**
1668 * Point of customization, e.g. override to suppress
1669 * @since 2.3
1670 */
1671 protected void logIncidentFeatureTupleRemoval(final Object value, final EObject holder, Object featureKey) {
1672 logIncident(() -> String.format(
1673 "Error: trying to remove duplicate value %s from the unique feature %s of host object %s. This indicates some errors in underlying model representation.",
1674 value, featureKey, holder));
1675 }
1676
1677 /**
1678 * Point of customization, e.g. override to suppress
1679 * @since 2.3
1680 */
1681 protected void logIncidentInstanceInsertion(final Object keyClass, final EObject value) {
1682 logIncident(() -> String.format("Notification received to index %s as a %s, but it already exists in the index. This indicates some errors in underlying model representation.", value, keyClass));
1683 }
1684
1685 /**
1686 * Point of customization, e.g. override to suppress
1687 * @since 2.3
1688 */
1689 protected void logIncidentInstanceRemoval(final Object keyClass, final EObject value) {
1690 logIncident(() -> String.format("Notification received to remove %s as a %s, but it is missing from the index. This indicates some errors in underlying model representation.", value, keyClass));
1691 }
1692
1693 /**
1694 * Point of customization, e.g. override to suppress
1695 * @since 2.3
1696 */
1697 protected void logIncidentStatRemoval(Object key) {
1698 logIncident(() -> String.format("No instances of %s is registered before calling removeInstance method.", key));
1699 }
1700
1701
1702}