diff options
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.java | 1702 |
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 | *******************************************************************************/ | ||
9 | package tools.refinery.viatra.runtime.base.core; | ||
10 | |||
11 | import org.apache.log4j.Logger; | ||
12 | import org.eclipse.emf.common.notify.Notification; | ||
13 | import org.eclipse.emf.common.notify.Notifier; | ||
14 | import org.eclipse.emf.common.notify.NotifyingList; | ||
15 | import org.eclipse.emf.common.util.EList; | ||
16 | import org.eclipse.emf.ecore.*; | ||
17 | import org.eclipse.emf.ecore.EStructuralFeature.Setting; | ||
18 | import org.eclipse.emf.ecore.impl.ENotificationImpl; | ||
19 | import org.eclipse.emf.ecore.resource.Resource; | ||
20 | import org.eclipse.emf.ecore.resource.ResourceSet; | ||
21 | import org.eclipse.emf.ecore.util.EcoreUtil; | ||
22 | import tools.refinery.viatra.runtime.base.api.*; | ||
23 | import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEClassProcessor; | ||
24 | import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEDataTypeProcessor; | ||
25 | import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter; | ||
26 | import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter; | ||
27 | import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension; | ||
28 | import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor; | ||
29 | import tools.refinery.viatra.runtime.base.core.EMFBaseIndexInstanceStore.FeatureData; | ||
30 | import tools.refinery.viatra.runtime.base.core.NavigationHelperVisitor.TraversingVisitor; | ||
31 | import tools.refinery.viatra.runtime.base.core.profiler.ProfilingNavigationHelperContentAdapter; | ||
32 | import tools.refinery.viatra.runtime.base.exception.ViatraBaseException; | ||
33 | import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; | ||
34 | import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; | ||
35 | import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; | ||
36 | import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; | ||
37 | import tools.refinery.viatra.runtime.matchers.util.Preconditions; | ||
38 | |||
39 | import java.lang.reflect.InvocationTargetException; | ||
40 | import java.util.*; | ||
41 | import java.util.Map.Entry; | ||
42 | import java.util.concurrent.Callable; | ||
43 | import java.util.function.Function; | ||
44 | import java.util.function.Supplier; | ||
45 | import java.util.stream.Collectors; | ||
46 | import java.util.stream.Stream; | ||
47 | |||
48 | import 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 | */ | ||
54 | public 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 | } | ||