aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-base
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime-base')
-rw-r--r--subprojects/viatra-runtime-base/about.html26
-rw-r--r--subprojects/viatra-runtime-base/build.gradle.kts19
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java46
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java395
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java44
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java33
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java48
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java25
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java22
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java63
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java45
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java19
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java113
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java41
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java32
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java74
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java885
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java322
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java210
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java26
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java180
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java38
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java30
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java27
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java46
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java79
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java22
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java356
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java145
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java154
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java28
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java451
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java380
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java71
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java137
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java750
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java1702
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java73
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java14
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java441
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java153
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java155
-rw-r--r--subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java25
43 files changed, 7945 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-base/about.html b/subprojects/viatra-runtime-base/about.html
new file mode 100644
index 00000000..d1d5593a
--- /dev/null
+++ b/subprojects/viatra-runtime-base/about.html
@@ -0,0 +1,26 @@
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
2<html>
3<!--
4 Copyright (c) 2017, Eclipse.org Foundation, Inc.
5
6 SPDX-License-Identifier: LicenseRef-EPL-Steward
7-->
8<head>
9<title>About</title>
10<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
11</head>
12<body lang="EN-US">
13<h2>About This Content</h2>
14
15<p>March 18, 2019</p>
16<h3>License</h3>
17
18<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
19Eclipse Public License Version 2.0 (&quot;EPL&quot;). A copy of the EPL is available at <a href="http://www.eclipse.org/org/documents/epl-v20.php">http://www.eclipse.org/legal/epl-v20.html</a>.
20For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
21
22<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
23apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
24indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
25</body>
26</html>
diff --git a/subprojects/viatra-runtime-base/build.gradle.kts b/subprojects/viatra-runtime-base/build.gradle.kts
new file mode 100644
index 00000000..55c6acb8
--- /dev/null
+++ b/subprojects/viatra-runtime-base/build.gradle.kts
@@ -0,0 +1,19 @@
1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7plugins {
8 id("tools.refinery.gradle.java-library")
9}
10
11dependencies {
12 api(project(":refinery-viatra-runtime-base-itc"))
13 api(project(":refinery-viatra-runtime-matchers"))
14 implementation(libs.eclipse)
15 implementation(libs.ecore)
16 implementation(libs.emf)
17 implementation(libs.osgi)
18 implementation(libs.slf4j.log4j)
19}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java
new file mode 100644
index 00000000..96b40825
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java
@@ -0,0 +1,46 @@
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
10package tools.refinery.viatra.runtime.base;
11
12import org.eclipse.core.runtime.Plugin;
13import tools.refinery.viatra.runtime.base.comprehension.WellbehavingDerivedFeatureRegistry;
14import org.osgi.framework.BundleContext;
15
16public class ViatraBasePlugin extends Plugin {
17
18 // The shared instance
19 private static ViatraBasePlugin plugin;
20
21 public static final String PLUGIN_ID = "tools.refinery.viatra.runtime.base";
22 public static final String WELLBEHAVING_DERIVED_FEATURE_EXTENSION_POINT_ID = "tools.refinery.viatra.runtime.base.wellbehaving.derived.features";
23
24 @Override
25 public void start(BundleContext context) throws Exception {
26 super.start(context);
27 plugin = this;
28 WellbehavingDerivedFeatureRegistry.initRegistry();
29 }
30
31 @Override
32 public void stop(BundleContext context) throws Exception {
33 plugin = null;
34 super.stop(context);
35 }
36
37 /**
38 * Returns the shared instance
39 *
40 * @return the shared instance
41 */
42 public static ViatraBasePlugin getDefault() {
43 return plugin;
44 }
45
46}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java
new file mode 100644
index 00000000..92663400
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java
@@ -0,0 +1,395 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11import java.util.Objects;
12
13import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexFeatureFilter;
14import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter;
15import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter;
16import tools.refinery.viatra.runtime.base.api.profiler.ProfilerMode;
17
18/**
19 * The base index options indicate how the indices are built.
20 *
21 * <p>
22 * One of the options is to build indices in <em>wildcard mode</em>, meaning that all EClasses, EDataTypes, EReferences
23 * and EAttributes are indexed. This is convenient, but comes at a high memory cost. To save memory, one can disable
24 * <em>wildcard mode</em> and manually register those EClasses, EDataTypes, EReferences and EAttributes that should be
25 * indexed.
26 * </p>
27 *
28 * <p>
29 * Another choice is whether to build indices in <em>dynamic EMF mode</em>, meaning that types are identified by the
30 * String IDs that are ultimately derived from the nsURI of the EPackage. Multiple types with the same ID are treated as
31 * the same. This is useful if dynamic EMF is used, where there can be multiple copies (instantiations) of the same
32 * EPackage, representing essentially the same metamodel. If one disables <em>dynamic EMF mode</em>, an error is logged
33 * if duplicate EPackages with the same nsURI are encountered.
34 * </p>
35 *
36 * @author Abel Hegedus
37 * @noimplement This class is not intended to be subclasses outside of VIATRA runtime
38 *
39 */
40public class BaseIndexOptions {
41
42 /**
43 *
44 * By default, base indices will be constructed with wildcard mode set as false.
45 */
46 protected static final IndexingLevel WILDCARD_MODE_DEFAULT = IndexingLevel.NONE;
47 /**
48 *
49 * By default, base indices will be constructed with only well-behaving features traversed.
50 */
51 private static final boolean TRAVERSE_ONLY_WELLBEHAVING_DERIVED_FEATURES_DEFAULT = true;
52 /**
53 *
54 * By default, base indices will be constructed with dynamic EMF mode set as false.
55 */
56 protected static final boolean DYNAMIC_EMF_MODE_DEFAULT = false;
57
58 /**
59 *
60 * By default, the scope will make the assumption that it is free from dangling edges.
61 * @since 1.6
62 */
63 protected static final boolean DANGLING_FREE_ASSUMPTION_DEFAULT = true;
64
65 /**
66 * By default, duplicate notifications are only logged.
67 *
68 * @since 1.6
69 */
70 protected static final boolean STRICT_NOTIFICATION_MODE_DEFAULT = true;
71
72 /**
73 * @since 2.3
74 */
75 protected static final ProfilerMode INDEX_PROFILER_MODE_DEFAULT = ProfilerMode.OFF;
76
77 /**
78 * @since 1.6
79 */
80 protected boolean danglingFreeAssumption = DANGLING_FREE_ASSUMPTION_DEFAULT;
81 protected boolean dynamicEMFMode = DYNAMIC_EMF_MODE_DEFAULT;
82 protected boolean traverseOnlyWellBehavingDerivedFeatures = TRAVERSE_ONLY_WELLBEHAVING_DERIVED_FEATURES_DEFAULT;
83 protected IndexingLevel wildcardMode = WILDCARD_MODE_DEFAULT;
84 protected IBaseIndexObjectFilter notifierFilterConfiguration;
85 protected IBaseIndexResourceFilter resourceFilterConfiguration;
86 /**
87 * @since 1.5
88 */
89 protected IBaseIndexFeatureFilter featureFilterConfiguration;
90
91 /**
92 * If strict notification mode is turned on, errors related to inconsistent notifications, e.g. duplicate deletions
93 * cause the entire Base index to be considered invalid, e.g. the query engine on top of the index should become
94 * tainted.
95 *
96 * @since 1.6
97 */
98 protected boolean strictNotificationMode = STRICT_NOTIFICATION_MODE_DEFAULT;
99
100 /**
101 * Returns whether base index profiling should be turned on.
102 *
103 * @since 2.3
104 */
105 protected ProfilerMode indexerProfilerMode = INDEX_PROFILER_MODE_DEFAULT;
106
107 /**
108 * Creates a base index options with the default values.
109 */
110 public BaseIndexOptions() {
111 }
112
113 /**
114 * Creates a base index options using the provided values for dynamic EMF mode and wildcard mode.
115 * @since 1.4
116 */
117 public BaseIndexOptions(boolean dynamicEMFMode, IndexingLevel wildcardMode) {
118 this.dynamicEMFMode = dynamicEMFMode;
119 this.wildcardMode = wildcardMode;
120 }
121
122 /**
123 *
124 * @param dynamicEMFMode
125 * @since 0.9
126 */
127 public BaseIndexOptions withDynamicEMFMode(boolean dynamicEMFMode) {
128 BaseIndexOptions result = copy();
129 result.dynamicEMFMode = dynamicEMFMode;
130 return result;
131 }
132
133 /**
134 * Sets the dangling edge handling property of the index option. If not set explicitly, it is considered as `true`.
135 * @param danglingFreeAssumption if true,
136 * the base index will assume that there are no dangling references
137 * (pointing out of scope or to proxies)
138 * @since 1.6
139 */
140 public BaseIndexOptions withDanglingFreeAssumption(boolean danglingFreeAssumption) {
141 BaseIndexOptions result = copy();
142 result.danglingFreeAssumption = danglingFreeAssumption;
143 return result;
144 }
145
146 /**
147 * Adds an object-level filter to the indexer configuration. Warning - object-level indexing can increase indexing time
148 * noticeably. If possibly, use {@link #withResourceFilterConfiguration(IBaseIndexResourceFilter)} instead.
149 *
150 * @param filter
151 * @since 0.9
152 */
153 public BaseIndexOptions withObjectFilterConfiguration(IBaseIndexObjectFilter filter) {
154 BaseIndexOptions result = copy();
155 result.notifierFilterConfiguration = filter;
156 return result;
157 }
158
159 /**
160 * @return the selected object filter configuration, or null if not set
161 */
162 public IBaseIndexObjectFilter getObjectFilterConfiguration() {
163 return notifierFilterConfiguration;
164 }
165
166 /**
167 * Returns a copy of the configuration with a specified resource filter
168 *
169 * @param filter
170 * @since 0.9
171 */
172 public BaseIndexOptions withResourceFilterConfiguration(IBaseIndexResourceFilter filter) {
173 BaseIndexOptions result = copy();
174 result.resourceFilterConfiguration = filter;
175 return result;
176 }
177
178 /**
179 * @return the selected resource filter, or null if not set
180 */
181 public IBaseIndexResourceFilter getResourceFilterConfiguration() {
182 return resourceFilterConfiguration;
183 }
184
185
186 /**
187 * Returns a copy of the configuration with a specified feature filter
188 *
189 * @param filter
190 * @since 1.5
191 */
192 public BaseIndexOptions withFeatureFilterConfiguration(IBaseIndexFeatureFilter filter) {
193 BaseIndexOptions result = copy();
194 result.featureFilterConfiguration = filter;
195 return result;
196 }
197
198 /**
199 * @return the selected feature filter, or null if not set
200 * @since 1.5
201 */
202 public IBaseIndexFeatureFilter getFeatureFilterConfiguration() {
203 return featureFilterConfiguration;
204 }
205
206
207 /**
208 * @return whether the base index option has dynamic EMF mode set
209 */
210 public boolean isDynamicEMFMode() {
211 return dynamicEMFMode;
212 }
213
214 /**
215 * @return whether the base index makes the assumption that there can be no dangling edges
216 * @since 1.6
217 */
218 public boolean isDanglingFreeAssumption() {
219 return danglingFreeAssumption;
220 }
221
222 /**
223 * @return whether the base index option has traverse only well-behaving derived features set
224 */
225 public boolean isTraverseOnlyWellBehavingDerivedFeatures() {
226 return traverseOnlyWellBehavingDerivedFeatures;
227 }
228
229 /**
230 *
231 * @param wildcardMode
232 * @since 1.4
233 */
234 public BaseIndexOptions withWildcardLevel(IndexingLevel wildcardLevel) {
235 BaseIndexOptions result = copy();
236 result.wildcardMode = wildcardLevel;
237 return result;
238 }
239
240 /**
241 * @since 1.6
242 */
243 public BaseIndexOptions withStrictNotificationMode(boolean strictNotificationMode) {
244 BaseIndexOptions result = copy();
245 result.strictNotificationMode = strictNotificationMode;
246 return result;
247 }
248
249 /**
250 * @since 2.3
251 */
252 public BaseIndexOptions withIndexProfilerMode(ProfilerMode indexerProfilerMode) {
253 BaseIndexOptions result = copy();
254 result.indexerProfilerMode = indexerProfilerMode;
255 return result;
256 }
257
258 /**
259 * @return whether the base index option has wildcard mode set
260 */
261 public boolean isWildcardMode() {
262 return wildcardMode == IndexingLevel.FULL;
263 }
264
265 /**
266 * @return the wildcardMode level
267 * @since 1.4
268 */
269 public IndexingLevel getWildcardLevel() {
270 return wildcardMode;
271 }
272
273 /**
274 * If strict notification mode is turned on, errors related to inconsistent notifications, e.g. duplicate deletions
275 * cause the entire Base index to be considered invalid, e.g. the query engine on top of the index should become
276 * tainted.
277 *
278 * @since 1.6
279 */
280 public boolean isStrictNotificationMode() {
281 return strictNotificationMode;
282 }
283
284 /**
285 * Returns whether base indexer profiling is enabled. The profiling causes some slowdown, but provides information
286 * about how much time the base indexer takes for initialization and updates.
287 *
288 * @since 2.3
289 */
290 public ProfilerMode getIndexerProfilerMode() {
291 return indexerProfilerMode;
292 }
293
294 /**
295 * Creates an independent copy of itself. The values of each option will be the same as this options. This method is
296 * used when a provided option must be copied to avoid external option changes afterward.
297 *
298 * @return the copy of this options
299 */
300 public BaseIndexOptions copy() {
301 BaseIndexOptions baseIndexOptions = new BaseIndexOptions(this.dynamicEMFMode, this.wildcardMode);
302 baseIndexOptions.danglingFreeAssumption = this.danglingFreeAssumption;
303 baseIndexOptions.traverseOnlyWellBehavingDerivedFeatures = this.traverseOnlyWellBehavingDerivedFeatures;
304 baseIndexOptions.notifierFilterConfiguration = this.notifierFilterConfiguration;
305 baseIndexOptions.resourceFilterConfiguration = this.resourceFilterConfiguration;
306 baseIndexOptions.featureFilterConfiguration = this.featureFilterConfiguration;
307 baseIndexOptions.strictNotificationMode = this.strictNotificationMode;
308 baseIndexOptions.indexerProfilerMode = this.indexerProfilerMode;
309 return baseIndexOptions;
310 }
311
312 @Override
313 public int hashCode() {
314 return Objects.hash(dynamicEMFMode, notifierFilterConfiguration, resourceFilterConfiguration,
315 featureFilterConfiguration, traverseOnlyWellBehavingDerivedFeatures, wildcardMode, strictNotificationMode,
316 danglingFreeAssumption, indexerProfilerMode);
317 }
318
319 @Override
320 public boolean equals(Object obj) {
321 if (this == obj)
322 return true;
323 if (obj == null)
324 return false;
325 if (!(obj instanceof BaseIndexOptions))
326 return false;
327 BaseIndexOptions other = (BaseIndexOptions) obj;
328 if (dynamicEMFMode != other.dynamicEMFMode)
329 return false;
330 if (danglingFreeAssumption != other.danglingFreeAssumption)
331 return false;
332 if (notifierFilterConfiguration == null) {
333 if (other.notifierFilterConfiguration != null)
334 return false;
335 } else if (!notifierFilterConfiguration
336 .equals(other.notifierFilterConfiguration))
337 return false;
338 if (resourceFilterConfiguration == null) {
339 if (other.resourceFilterConfiguration != null)
340 return false;
341 } else if (!resourceFilterConfiguration
342 .equals(other.resourceFilterConfiguration)){
343 return false;
344 }
345
346 if (featureFilterConfiguration == null) {
347 if (other.featureFilterConfiguration != null)
348 return false;
349 } else if (!featureFilterConfiguration
350 .equals(other.featureFilterConfiguration)){
351 return false;
352 }
353
354 if (traverseOnlyWellBehavingDerivedFeatures != other.traverseOnlyWellBehavingDerivedFeatures)
355 return false;
356 if (wildcardMode != other.wildcardMode)
357 return false;
358 if (strictNotificationMode != other.strictNotificationMode) {
359 return false;
360 }
361 if (indexerProfilerMode != other.indexerProfilerMode) {
362 return false;
363 }
364 return true;
365 }
366
367
368 @Override
369 public String toString() {
370 StringBuilder sb = new StringBuilder();
371 appendModifier(sb, dynamicEMFMode, DYNAMIC_EMF_MODE_DEFAULT, "dynamicEMF");
372 appendModifier(sb, wildcardMode, WILDCARD_MODE_DEFAULT, "wildcard");
373 appendModifier(sb, danglingFreeAssumption, DANGLING_FREE_ASSUMPTION_DEFAULT, "danglingFreeAssumption");
374 appendModifier(sb, traverseOnlyWellBehavingDerivedFeatures, TRAVERSE_ONLY_WELLBEHAVING_DERIVED_FEATURES_DEFAULT, "wellBehavingOnly");
375 appendModifier(sb, strictNotificationMode, STRICT_NOTIFICATION_MODE_DEFAULT, "strictNotificationMode");
376 appendModifier(sb, indexerProfilerMode, INDEX_PROFILER_MODE_DEFAULT, "indexerProfilerMode");
377 appendModifier(sb, notifierFilterConfiguration, null, "notifierFilter=");
378 appendModifier(sb, resourceFilterConfiguration, null, "resourceFilter=");
379 appendModifier(sb, featureFilterConfiguration, null, "featureFilterConfiguration=");
380 final String result = sb.toString();
381 return result.isEmpty() ? "defaults" : result;
382 }
383
384 private static void appendModifier(StringBuilder sb, Object actualValue, Object expectedValue, String switchName) {
385 if (Objects.equals(expectedValue, actualValue)) {
386 // silent
387 } else {
388 sb.append(Boolean.FALSE.equals(actualValue) ? '-' : '+');
389 sb.append(switchName);
390 if (! (actualValue instanceof Boolean))
391 sb.append(actualValue);
392 }
393 }
394
395}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java
new file mode 100644
index 00000000..3d30df5e
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java
@@ -0,0 +1,44 @@
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.api;
10
11import org.eclipse.emf.ecore.EDataType;
12
13/**
14 * Interface for observing insertion and deletion of instances of data types.
15 *
16 * @author Tamas Szabo
17 *
18 */
19public interface DataTypeListener {
20
21 /**
22 * Called when an instance of the given type is inserted.
23 *
24 * @param type
25 * the {@link EDataType}
26 * @param instance
27 * the instance of the data type
28 * @param firstOccurrence
29 * true if this value was not previously present in the model
30 */
31 public void dataTypeInstanceInserted(EDataType type, Object instance, boolean firstOccurrence);
32
33 /**
34 * Called when an instance of the given type is deleted.
35 *
36 * @param type
37 * the {@link EDataType}
38 * @param instance
39 * the instance of the data type
40 * @param lastOccurrence
41 * true if this value is no longer present in the model
42 */
43 public void dataTypeInstanceDeleted(EDataType type, Object instance, boolean lastOccurrence);
44}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java
new file mode 100644
index 00000000..5a2486f9
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11/**
12 * Listener interface for change notifications from the VIATRA Base index.
13 *
14 * @author Abel Hegedus
15 *
16 */
17public interface EMFBaseIndexChangeListener {
18
19 /**
20 * NOTE: it is possible that this method is called only ONCE! Consider returning a constant value that is set in the constructor.
21 *
22 * @return true, if the listener should be notified only after index changes, false if notification is needed after each model change
23 */
24 public boolean onlyOnIndexChange();
25
26 /**
27 * Called after a model change is handled by the VIATRA Base index and if <code>indexChanged == onlyOnIndexChange()</code>.
28 *
29 * @param indexChanged true, if the model change also affected the contents of the base index
30 */
31 public void notifyChanged(boolean indexChanged);
32
33}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java
new file mode 100644
index 00000000..fa2d679e
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java
@@ -0,0 +1,48 @@
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.api;
10
11import org.eclipse.emf.ecore.EAttribute;
12import org.eclipse.emf.ecore.EObject;
13import org.eclipse.emf.ecore.EReference;
14import org.eclipse.emf.ecore.EStructuralFeature;
15
16/**
17 * Interface for observing insertion and deletion of structural feature values ("settings"). (Works both for
18 * single-valued and many-valued features.)
19 *
20 * @author Tamas Szabo
21 *
22 */
23public interface FeatureListener {
24
25 /**
26 * Called when the given value is inserted into the given feature of the given host EObject.
27 *
28 * @param host
29 * the host (holder) of the feature
30 * @param feature
31 * the {@link EAttribute} or {@link EReference} instance
32 * @param value
33 * the target of the feature
34 */
35 public void featureInserted(EObject host, EStructuralFeature feature, Object value);
36
37 /**
38 * Called when the given value is removed from the given feature of the given host EObject.
39 *
40 * @param host
41 * the host (holder) of the feature
42 * @param feature
43 * the {@link EAttribute} or {@link EReference} instance
44 * @param value
45 * the target of the feature
46 */
47 public void featureDeleted(EObject host, EStructuralFeature feature, Object value);
48}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java
new file mode 100644
index 00000000..aaa98918
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11import org.eclipse.emf.ecore.EClass;
12import org.eclipse.emf.ecore.EDataType;
13import org.eclipse.emf.ecore.EObject;
14
15/**
16 * @author Abel Hegedus
17 *
18 */
19public interface IEClassifierProcessor<ClassType, InstanceType> {
20
21 void process(ClassType type, InstanceType instance);
22
23 public interface IEClassProcessor extends IEClassifierProcessor<EClass, EObject>{}
24 public interface IEDataTypeProcessor extends IEClassifierProcessor<EDataType, Object>{}
25}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java
new file mode 100644
index 00000000..1dc3291b
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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.api;
10
11/**
12 *
13 * This interface contains callbacks for various internal errors from the {@link NavigationHelper base index}.
14 *
15 * @author Zoltan Ujhelyi
16 *
17 */
18public interface IEMFIndexingErrorListener {
19
20 void error(String description, Throwable t);
21 void fatal(String description, Throwable t);
22}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java
new file mode 100644
index 00000000..717bad4b
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java
@@ -0,0 +1,63 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Abel Hegedus, 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.api;
10
11/**
12 * Setter interface for query result multimaps that allow modifications of the model through the multimap.
13 *
14 * <p>
15 * The model modifications should ensure that the multimap changes exactly as required (i.e. a put results in only one
16 * new key-value pair and remove results in only one removed pair).
17 *
18 * <p>
19 * The input parameters of both put and remove can be validated by implementing the {@link #validate(Object, Object)}
20 * method.
21 *
22 * @author Abel Hegedus
23 *
24 * @param <KeyType>
25 * @param <ValueType>
26 */
27public interface IQueryResultSetter<KeyType, ValueType> {
28 /**
29 * Modify the underlying model of the query in order to have the given key-value pair as a new result of the query.
30 *
31 * @param key
32 * the key for which a new value is added to the query results
33 * @param value
34 * the new value that should be added to the query results for the given key
35 * @return true, if the query result changed
36 */
37 boolean put(KeyType key, ValueType value);
38
39 /**
40 * Modify the underlying model of the query in order to remove the given key-value pair from the results of the
41 * query.
42 *
43 * @param key
44 * the key for which the value is removed from the query results
45 * @param value
46 * the value that should be removed from the query results for the given key
47 * @return true, if the query result changed
48 */
49 boolean remove(KeyType key, ValueType value);
50
51 /**
52 * Validates a given key-value pair for the query result. The validation has to ensure that (1) if the pair does not
53 * exist in the result, it can be added safely (2) if the pair already exists in the result, it can be removed
54 * safely
55 *
56 * @param key
57 * the key of the pair that is validated
58 * @param value
59 * the value of the pair that is validated
60 * @return true, if the pair does not exists but can be added or the pair exists and can be removed
61 */
62 boolean validate(KeyType key, ValueType value);
63} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java
new file mode 100644
index 00000000..5addfd78
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2012, Abel Hegedus, 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.api;
10
11/**
12 * Listener interface for receiving notification from {@link QueryResultMultimap}
13 *
14 * @author Abel Hegedus
15 *
16 * @param <KeyType>
17 * @param <ValueType>
18 */
19public interface IQueryResultUpdateListener<KeyType, ValueType> {
20 /**
21 * This method is called by the query result multimap when a new key-value pair is put into the multimap
22 *
23 * <p>
24 * Only invoked if the contents of the multimap changed!
25 *
26 * @param key
27 * the key of the newly inserted pair
28 * @param value
29 * the value of the newly inserted pair
30 */
31 void notifyPut(KeyType key, ValueType value);
32
33 /**
34 * This method is called by the query result multimap when key-value pair is removed from the multimap
35 *
36 * <p>
37 * Only invoked if the contents of the multimap changed!
38 *
39 * @param key
40 * the key of the removed pair
41 * @param value
42 * the value of the removed pair
43 */
44 void notifyRemove(KeyType key, ValueType value);
45} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java
new file mode 100644
index 00000000..208ad761
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java
@@ -0,0 +1,19 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.api;
10
11import org.eclipse.emf.ecore.EObject;
12
13/**
14 * @author Gabor Bergmann
15 * @since 1.7
16 */
17public interface IStructuralFeatureInstanceProcessor {
18 void process(EObject source, Object target);
19}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java
new file mode 100644
index 00000000..df5c59f5
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java
@@ -0,0 +1,113 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs
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.api;
10
11import tools.refinery.viatra.runtime.matchers.context.IndexingService;
12
13import java.util.Set;
14
15/**
16 * The values of this enum denotes the level of indexing the base indexer is capable of.
17 *
18 * @author Grill Balázs
19 * @since 1.4
20 *
21 */
22public enum IndexingLevel {
23
24 /**
25 * No indexing is performed
26 */
27 NONE,
28
29 /**
30 * Only cardinality information is stored. This indexing level makes possible to calculate
31 * results of {@link NavigationHelper#countAllInstances(org.eclipse.emf.ecore.EClass)}, {@link NavigationHelper#countFeatures(org.eclipse.emf.ecore.EStructuralFeature)}
32 * and {@link NavigationHelper#countDataTypeInstances(org.eclipse.emf.ecore.EDataType)} with minimal memory footprint.
33 */
34 STATISTICS,
35
36 /**
37 * Notifications are dispatched about the changes
38 */
39 NOTIFICATIONS,
40
41 /**
42 * Cardinality information is stored and live notifications are dispatched
43 */
44 BOTH,
45
46 /**
47 * Full indexing is performed, set of instances is available
48 */
49 FULL
50
51 ;
52
53 private static final IndexingLevel[][] mergeTable = {
54 /* NONE STATISTICS NOTIFICATIONS BOTH FULL*/
55 /* NONE */{ NONE, STATISTICS, NOTIFICATIONS, BOTH, FULL},
56 /* STATISTICS */{ STATISTICS, STATISTICS, BOTH, BOTH, FULL},
57 /* NOTIFICATIONS */{ NOTIFICATIONS, BOTH, NOTIFICATIONS, BOTH, FULL},
58 /* BOTH */{ BOTH, BOTH, BOTH, BOTH, FULL},
59 /* FULL */{ FULL, FULL, FULL, FULL, FULL}
60 };
61
62 public static IndexingLevel toLevel(IndexingService service){
63 switch(service){
64 case INSTANCES:
65 return IndexingLevel.FULL;
66 case NOTIFICATIONS:
67 return IndexingLevel.NOTIFICATIONS;
68 case STATISTICS:
69 return IndexingLevel.STATISTICS;
70 default:
71 return IndexingLevel.NONE;
72 }
73 }
74
75 public static IndexingLevel toLevel(Set<IndexingService> services){
76 IndexingLevel result = NONE;
77 for(IndexingService service : services){
78 result = result.merge(toLevel(service));
79 }
80 return result;
81 }
82
83 /**
84 * Merge this level with the given other level, The resulting indexing level will provide the
85 * functionality which conforms to both given levels.
86 */
87 public IndexingLevel merge(IndexingLevel other){
88 if (other == null) return this;
89 return mergeTable[this.ordinal()][other.ordinal()];
90 }
91
92 /**
93 * Tells whether the indexer shall perform separate statistics calculation for this level
94 */
95 public boolean hasStatistics() {
96 return this == IndexingLevel.BOTH || this == IndexingLevel.STATISTICS || this == IndexingLevel.FULL;
97 }
98
99 /**
100 * Tells whether the indexer shall perform instance indexing
101 */
102 public boolean hasInstances(){
103 return this == IndexingLevel.FULL;
104 }
105
106 /**
107 * Returns whether the current indexing level includes all features from the parameter level
108 * @since 1.5
109 */
110 public boolean providesLevel(IndexingLevel level) {
111 return this.merge(level) == this;
112 }
113}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java
new file mode 100644
index 00000000..6339545d
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java
@@ -0,0 +1,41 @@
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.api;
10
11import org.eclipse.emf.ecore.EClass;
12import org.eclipse.emf.ecore.EObject;
13
14/**
15 * Interface for observing insertion / deletion of instances of EClass.
16 *
17 * @author Tamas Szabo
18 *
19 */
20public interface InstanceListener {
21
22 /**
23 * Called when the given instance was added to the model.
24 *
25 * @param clazz
26 * an EClass registered for this listener, for which a new instance (possibly an instance of a subclass) was inserted into the model
27 * @param instance
28 * an EObject instance that was inserted into the model
29 */
30 public void instanceInserted(EClass clazz, EObject instance);
31
32 /**
33 * Called when the given instance was removed from the model.
34 *
35 * @param clazz
36 * an EClass registered for this listener, for which an instance (possibly an instance of a subclass) was removed from the model
37 * @param instance
38 * an EObject instance that was removed from the model
39 */
40 public void instanceDeleted(EClass clazz, EObject instance);
41}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java
new file mode 100644
index 00000000..f0245b5d
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11import org.eclipse.emf.common.notify.Notification;
12import org.eclipse.emf.ecore.EObject;
13import org.eclipse.emf.ecore.EStructuralFeature;
14
15
16/**
17 * Listener interface for lightweight observation on EObject feature value changes.
18 *
19 * @author Abel Hegedus
20 *
21 */
22public interface LightweightEObjectObserver {
23
24 /**
25 *
26 * @param host
27 * @param feature
28 * @param notification
29 */
30 void notifyFeatureChanged(EObject host, EStructuralFeature feature, Notification notification);
31
32}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java
new file mode 100644
index 00000000..bcdb8ff4
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument;
12
13import java.util.Collection;
14import java.util.HashSet;
15
16import org.eclipse.emf.common.notify.Notification;
17import org.eclipse.emf.ecore.EObject;
18import org.eclipse.emf.ecore.EStructuralFeature;
19
20/**
21 * Adapter class for lightweight observer which filters feature updates to a selected set of features.
22 *
23 * @author Abel Hegedus
24 *
25 */
26public abstract class LightweightEObjectObserverAdapter implements LightweightEObjectObserver {
27
28 private Collection<EStructuralFeature> observedFeatures;
29
30 /**
31 * Creates a new adapter with the given set of observed features.
32 */
33 public LightweightEObjectObserverAdapter(Collection<EStructuralFeature> observedFeatures) {
34 checkArgument(observedFeatures != null, "List of observed features must not be null!");
35 this.observedFeatures = new HashSet<>(observedFeatures);
36 }
37
38 public void observeAdditionalFeature(EStructuralFeature observedFeature) {
39 checkArgument(observedFeature != null, "Cannot observe null feature!");
40 this.observedFeatures.add(observedFeature);
41 }
42
43 public void observeAdditionalFeatures(Collection<EStructuralFeature> observedFeatures) {
44 checkArgument(observedFeatures != null, "List of additional observed features must not be null!");
45 this.observedFeatures.addAll(observedFeatures);
46 }
47
48 public void removeObservedFeature(EStructuralFeature observedFeature) {
49 checkArgument(observedFeature != null, "Cannot remove null observed feature!");
50 this.observedFeatures.remove(observedFeature);
51 }
52
53 public void removeObservedFeatures(Collection<EStructuralFeature> observedFeatures) {
54 checkArgument(observedFeatures != null, "List of observed features to remove must not be null!");
55 this.observedFeatures.removeAll(observedFeatures);
56 }
57
58 @Override
59 public void notifyFeatureChanged(EObject host, EStructuralFeature feature, Notification notification) {
60 if(this.observedFeatures.contains(feature)) {
61 observedFeatureUpdate(host, feature, notification);
62 }
63 }
64
65 /**
66 * This method is called when the feature that changed is among the observed features of the adapter.
67 *
68 * @param host
69 * @param feature
70 * @param notification
71 */
72 public abstract void observedFeatureUpdate(EObject host, EStructuralFeature feature, Notification notification);
73
74}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java
new file mode 100644
index 00000000..6a9f704b
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java
@@ -0,0 +1,885 @@
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
10package tools.refinery.viatra.runtime.base.api;
11
12import org.eclipse.emf.common.notify.Notifier;
13import org.eclipse.emf.common.util.EList;
14import org.eclipse.emf.common.util.Enumerator;
15import org.eclipse.emf.ecore.*;
16import org.eclipse.emf.ecore.EStructuralFeature.Setting;
17import org.eclipse.emf.ecore.resource.Resource;
18import org.eclipse.emf.ecore.resource.ResourceSet;
19import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEClassProcessor;
20import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEDataTypeProcessor;
21import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
22
23import java.lang.reflect.InvocationTargetException;
24import java.util.Collection;
25import java.util.ConcurrentModificationException;
26import java.util.Set;
27import java.util.concurrent.Callable;
28
29/**
30 *
31 * Using an index of the EMF model, this interface exposes useful query functionality, such as:
32 * <ul>
33 * <li>
34 * Getting all the (direct or descendant) instances of a given {@link EClass}
35 * <li>
36 * Inverse navigation along arbitrary {@link EReference} instances (heterogenous paths too)
37 * <li>
38 * Finding model elements by attribute value (i.e. inverse navigation along {@link EAttribute})
39 * <li>
40 * Querying instances of given data types, or structural features.
41 * </ul>
42 * As queries are served from an index, results are always instantaneous.
43 *
44 * <p>
45 * Such indices will be built on an EMF model rooted at an {@link EObject}, {@link Resource} or {@link ResourceSet}.
46 * The boundaries of the model are defined by the containment (sub)tree.
47 * The indices will be <strong>maintained incrementally</strong> on changes to the model; these updates can also be
48 * observed by registering listeners.
49 * </p>
50 *
51 * <p>
52 * One of the options is to build indices in <em>wildcard mode</em>, meaning that all EClasses, EDataTypes, EReferences
53 * and EAttributes are indexed. This is convenient, but comes at a high memory cost. To save memory, one can disable
54 * <em>wildcard mode</em> and manually register those EClasses, EDataTypes, EReferences and EAttributes that should be
55 * indexed.
56 * </p>
57 *
58 * <p>
59 * Another choice is whether to build indices in <em>dynamic EMF mode</em>, meaning that types are identified by the String IDs
60 * that are ultimately derived from the nsURI of the EPackage. Multiple types with the same ID are treated as the same.
61 * This is useful if dynamic EMF is used, where there can be multiple copies (instantiations) of the same EPackage,
62 * representing essentially the same metamodel. If one disables <em>dynamic EMF mode</em>, an error is logged if
63 * duplicate EPackages with the same nsURI are encountered.
64 * </p>
65 *
66 * <p>
67 * Note that none of the defined query methods return null upon empty result sets. All query methods return either a copy of
68 * the result sets (where {@link Setting} is instantiated) or an unmodifiable collection of the result view.
69 *
70 * <p>
71 * Instantiate using {@link ViatraBaseFactory}
72 *
73 * @author Tamas Szabo
74 * @noimplement This interface is not intended to be implemented by clients.
75 *
76 */
77public interface NavigationHelper {
78
79 /**
80 * Indicates whether indexing is performed in <em>wildcard mode</em>, where every aspect of the EMF model is
81 * automatically indexed.
82 *
83 * @return true if everything is indexed, false if manual registration of interesting EClassifiers and
84 * EStructuralFeatures is required.
85 */
86 public boolean isInWildcardMode();
87
88 /**
89 * Indicates whether indexing is performed in <em>wildcard mode</em> for a selected indexing level
90 *
91 * @return true if everything is indexed, false if manual registration of interesting EClassifiers and
92 * EStructuralFeatures is required.
93 * @since 1.5
94 */
95 public boolean isInWildcardMode(IndexingLevel level);
96
97 /**
98 * Returns the current {@link IndexingLevel} applied to all model elements. For specific types it is possible to request a higher indexing levels, but cannot be lowered.
99 * @return the current level of index specified
100 * @since 1.4
101 */
102 public IndexingLevel getWildcardLevel();
103
104 /**
105 * Starts wildcard indexing at the given level. After this call, no registration is required for this {@link IndexingLevel}.
106 * a previously set wildcard level cannot be lowered, only extended.
107 *
108 * @since 1.4
109 */
110 public void setWildcardLevel(IndexingLevel level);
111
112 /**
113 * Indicates whether indexing is performed in <em>dynamic EMF mode</em>, i.e. EPackage nsURI collisions are
114 * tolerated and EPackages with the same URI are automatically considered as equal.
115 *
116 * @return true if multiple EPackages with the same nsURI are treated as the same,
117 * false if an error is logged instead in this case.
118 */
119 public boolean isInDynamicEMFMode();
120
121 /**
122 * For a given attribute value <code>value</code>, find each {@link EAttribute} and host {@link EObject}
123 * such that this attribute of the the host object takes the given value. The method will
124 * return a set of {@link Setting}s, one for each such host object - EAttribute - value triplet.
125 *
126 * <p>
127 * <strong>Precondition:</strong> Unset / null attribute values are not indexed, so <code>value!=null</code>
128 *
129 * <p>
130 * <strong>Precondition:</strong> Will only find those EAttributes that have already been registered using
131 * {@link #registerEStructuralFeatures(Set)}, unless running in <em>wildcard mode</em> (see
132 * {@link #isInWildcardMode()}).
133 *
134 * @param value
135 * the value of the attribute
136 * @return a set of {@link Setting}s, one for each EObject and EAttribute that have the given value
137 * @see #findByAttributeValue(Object)
138 */
139 public Set<Setting> findByAttributeValue(Object value);
140
141 /**
142 * For given <code>attributes</code> and an attribute value <code>value</code>, find each host {@link EObject}
143 * such that any of these attributes of the the host object takes the given value. The method will
144 * return a set of {@link Setting}s, one for each such host object - EAttribute - value triplet.
145 *
146 * <p>
147 * <strong>Precondition:</strong> Unset / null attribute values are not indexed, so <code>value!=null</code>
148 *
149 * <p>
150 * <strong>Precondition:</strong> Will only find those EAttributes that have already been registered using
151 * {@link #registerEStructuralFeatures(Set)}, unless running in <em>wildcard mode</em> (see
152 * {@link #isInWildcardMode()}).
153 *
154 * @param value
155 * the value of the attribute
156 * @param attributes
157 * the collection of attributes that should take the given value
158 * @return a set of {@link Setting}s, one for each EObject and attribute that have the given value
159 */
160 public Set<Setting> findByAttributeValue(Object value, Collection<EAttribute> attributes);
161
162 /**
163 * Find all {@link EObject}s for which the given <code>attribute</code> takes the given <code>value</code>.
164 *
165 * <p>
166 * <strong>Precondition:</strong> Unset / null attribute values are not indexed, so <code>value!=null</code>
167 *
168 * <p>
169 * <strong>Precondition:</strong> Results will be returned only if either (a) the EAttribute has already been
170 * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in <em>wildcard mode</em> (see
171 * {@link #isInWildcardMode()}).
172 *
173 * @param value
174 * the value of the attribute
175 * @param attribute
176 * the EAttribute that should take the given value
177 * @return the set of {@link EObject}s for which the given attribute has the given value
178 */
179 public Set<EObject> findByAttributeValue(Object value, EAttribute attribute);
180
181 /**
182 * Returns the set of instances for the given {@link EDataType} that can be found in the model.
183 *
184 * <p>
185 * <strong>Precondition:</strong> Results will be returned only if either (a) the EDataType has already been
186 * registered using {@link #registerEDataTypes(Set)}, or (b) running in <em>wildcard mode</em> (see
187 * {@link #isInWildcardMode()}).
188 *
189 * @param type
190 * the data type
191 * @return the set of all attribute values found in the model that are of the given data type
192 */
193 public Set<Object> getDataTypeInstances(EDataType type);
194
195 /**
196 * Returns whether an object is an instance for the given {@link EDataType} that can be found in the current scope.
197 * <p>
198 * <strong>Precondition:</strong> Result will be true only if either (a) the EDataType has already been registered
199 * using {@link #registerEDataTypes(Set)}, or (b) running in <em>wildcard mode</em> (see
200 * {@link #isInWildcardMode()}).
201 *
202 * @param value a non-null value to decide whether it is available as an EDataType instance
203 * @param type a non-null EDataType
204 * @return true, if a corresponding instance was found
205 * @since 1.7
206 */
207 public boolean isInstanceOfDatatype(Object value, EDataType type);
208
209 /**
210 * Find all {@link EObject}s that are the target of the EReference <code>reference</code> from the given
211 * <code>source</code> {@link EObject}.
212 *
213 * <p>
214 * Unset / null-valued references are not indexed, and will not be included in the results.
215 *
216 * <p>
217 * <strong>Precondition:</strong> Results will be returned only if either (a) the reference has already been
218 * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in <em>wildcard mode</em> (see
219 * {@link #isInWildcardMode()}).
220 *
221 * @param source the host object
222 * @param reference an EReference of the host object
223 * @return the set of {@link EObject}s that the given reference points to, from the given source object
224 */
225 public Set<EObject> getReferenceValues(EObject source, EReference reference);
226
227 /**
228 * Find all {@link Object}s that are the target of the EStructuralFeature <code>feature</code> from the given
229 * <code>source</code> {@link EObject}.
230 *
231 * <p>
232 * Unset / null-valued features are not indexed, and will not be included in the results.
233 *
234 * <p>
235 * <strong>Precondition:</strong> Results will be returned only if either (a) the feature has already been
236 * registered, or (b) running in <em>wildcard mode</em> (see
237 * {@link #isInWildcardMode()}).
238 *
239 * @param source the host object
240 * @param feature an EStructuralFeature of the host object
241 * @return the set of values that the given feature takes at the given source object
242 *
243 * @see #getReferenceValues(EObject, EReference)
244 */
245 public Set<Object> getFeatureTargets(EObject source, EStructuralFeature feature);
246
247 /**
248 * Decides whether the given non-null source and target objects are connected via a specific, indexed EStructuralFeature instance.
249 *
250 * <p>
251 * Unset / null-valued features are not indexed, and will not be included in the results.
252 *
253 * <p>
254 * <strong>Precondition:</strong> Result will be true only if either (a) the feature has already been
255 * registered, or (b) running in <em>wildcard mode</em> (see
256 * {@link #isInWildcardMode()}).
257 * @since 1.7
258 */
259 public boolean isFeatureInstance(EObject source, Object target, EStructuralFeature feature);
260
261 /**
262 * For a given {@link EObject} <code>target</code>, find each {@link EReference} and source {@link EObject}
263 * such that this reference (list) of the the host object points to the given target object. The method will
264 * return a set of {@link Setting}s, one for each such source object - EReference - target triplet.
265 *
266 * <p>
267 * <strong>Precondition:</strong> Unset / null reference values are not indexed, so <code>target!=null</code>
268 *
269 * <p>
270 * <strong>Precondition:</strong> Results will be returned only for those references that have already been
271 * registered using {@link #registerEStructuralFeatures(Set)}, or all references if running in
272 * <em>wildcard mode</em> (see {@link #isInWildcardMode()}).
273 *
274 * @param target
275 * the EObject pointed to by the references
276 * @return a set of {@link Setting}s, one for each source EObject and reference that point to the given target
277 */
278 public Set<Setting> getInverseReferences(EObject target);
279
280 /**
281 * For given <code>references</code> and an {@link EObject} <code>target</code>, find each source {@link EObject}
282 * such that any of these references of the the source object points to the given target object. The method will
283 * return a set of {@link Setting}s, one for each such source object - EReference - target triplet.
284 *
285 * <p>
286 * <strong>Precondition:</strong> Unset / null reference values are not indexed, so <code>target!=null</code>
287 *
288 * <p>
289 * <strong>Precondition:</strong> Will only find those EReferences that have already been registered using
290 * {@link #registerEStructuralFeatures(Set)}, unless running in <em>wildcard mode</em> (see
291 * {@link #isInWildcardMode()}).
292 *
293 * @param target
294 * the EObject pointed to by the references
295 * @param references a set of EReferences pointing to the target
296 * @return a set of {@link Setting}s, one for each source EObject and reference that point to the given target
297 */
298 public Set<Setting> getInverseReferences(EObject target, Collection<EReference> references);
299
300 /**
301 * Find all source {@link EObject}s for which the given <code>reference</code> points to the given <code>target</code> object.
302 *
303 * <p>
304 * <strong>Precondition:</strong> Unset / null reference values are not indexed, so <code>target!=null</code>
305 *
306 * <p>
307 * <strong>Precondition:</strong> Results will be returned only if either (a) the reference has already been
308 * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in <em>wildcard mode</em> (see
309 * {@link #isInWildcardMode()}).
310 *
311 * @param target
312 * the EObject pointed to by the references
313 * @param reference
314 * an EReference pointing to the target
315 * @return the collection of {@link EObject}s for which the given reference points to the given target object
316 */
317 public Set<EObject> getInverseReferences(EObject target, EReference reference);
318
319 /**
320 * Get the direct {@link EObject} instances of the given {@link EClass}. Instances of subclasses will be excluded.
321 *
322 * <p>
323 * <strong>Precondition:</strong> Results will be returned only if either (a) the EClass (or any superclass) has
324 * already been registered using {@link #registerEClasses(Set)}, or (b) running in <em>wildcard mode</em> (see
325 * {@link #isInWildcardMode()}).
326 *
327 * @param clazz
328 * an EClass
329 * @return the collection of {@link EObject} direct instances of the given EClass (not of subclasses)
330 *
331 * @see #getAllInstances(EClass)
332 */
333 public Set<EObject> getDirectInstances(EClass clazz);
334
335 /**
336 * Get the all {@link EObject} instances of the given {@link EClass}.
337 * This includes instances of subclasses.
338 *
339 * <p>
340 * <strong>Precondition:</strong> Results will be returned only if either (a) the EClass (or any superclass) has
341 * already been registered using {@link #registerEClasses(Set)}, or (b) running in <em>wildcard mode</em> (see
342 * {@link #isInWildcardMode()}).
343 *
344 * @param clazz
345 * an EClass
346 * @return the collection of {@link EObject} instances of the given EClass and any of its subclasses
347 *
348 * @see #getDirectInstances(EClass)
349 */
350 public Set<EObject> getAllInstances(EClass clazz);
351
352 /**
353 * Checks whether the given {@link EObject} is an instance of the given {@link EClass}.
354 * This includes instances of subclasses.
355 * <p> Special note: this method does not check whether the object is indexed in the scope,
356 * and will return true for out-of-scope objects as well (as long as they are instances of the class).
357 * <p> The given class does not have to be indexed.
358 * <p> The difference between this method and {@link EClassifier#isInstance(Object)} is that in dynamic EMF mode, EPackage equivalence is taken into account.
359 * @since 1.6
360 */
361 public boolean isInstanceOfUnscoped(EObject object, EClass clazz);
362
363 /**
364 * Checks whether the given {@link EObject} is an instance of the given {@link EClass}.
365 * This includes instances of subclasses.
366 * <p> Special note: this method does check whether the object is indexed in the scope,
367 * and will return false for out-of-scope objects as well (as long as they are instances of the class).
368 * <p> The given class does have to be indexed.
369 * @since 1.7
370 */
371 public boolean isInstanceOfScoped(EObject object, EClass clazz);
372
373 /**
374 * Get the total number of instances of the given {@link EClass} and all of its subclasses.
375 *
376 * @since 1.4
377 */
378 public int countAllInstances(EClass clazz);
379
380 /**
381 * Find all source {@link EObject}s for which the given <code>feature</code> points to / takes the given <code>value</code>.
382 *
383 * <p>
384 * <strong>Precondition:</strong> Unset / null-valued features are not indexed, so <code>value!=null</code>
385 *
386 * <p>
387 * <strong>Precondition:</strong> Results will be returned only if either (a) the feature has already been
388 * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in <em>wildcard mode</em> (see
389 * {@link #isInWildcardMode()}).
390 *
391 * @param value
392 * the value of the feature
393 * @param feature
394 * the feature instance
395 * @return the collection of {@link EObject} instances
396 */
397 public Set<EObject> findByFeatureValue(Object value, EStructuralFeature feature);
398
399 /**
400 * Returns those host {@link EObject}s that have a non-null value for the given feature
401 * (at least one, in case of multi-valued references).
402 *
403 * <p>
404 * Unset / null-valued features are not indexed, and will not be included in the results.
405 *
406 * <p>
407 * <strong>Precondition:</strong> Results will be returned only if either (a) the feature has already been
408 * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in <em>wildcard mode</em> (see
409 * {@link #isInWildcardMode()}).
410 *
411 * @param feature
412 * a structural feature
413 * @return the collection of {@link EObject}s that have some value for the given structural feature
414 */
415 public Set<EObject> getHoldersOfFeature(EStructuralFeature feature);
416 /**
417 * Returns all non-null values that the given feature takes at least once for any {@link EObject} in the scope
418 *
419 * <p>
420 * Unset / null-valued features are not indexed, and will not be included in the results.
421 *
422 * <p>
423 * <strong>Precondition:</strong> Results will be returned only if either (a) the feature has already been
424 * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in <em>wildcard mode</em> (see
425 * {@link #isInWildcardMode()}).
426 *
427 * @param feature
428 * a structural feature
429 * @return the collection of values that the given structural feature takes
430 * @since 2.1
431 */
432 public Set<Object> getValuesOfFeature(EStructuralFeature feature);
433
434 /**
435 * Call this method to dispose the NavigationHelper.
436 *
437 * <p>After its disposal, the NavigationHelper will no longer listen to EMF change notifications,
438 * and it will be possible to GC it even if the model is retained in memory.
439 *
440 * <dt><b>Precondition:</b><dd> no listeners can be registered at all.
441 * @throws IllegalStateException if there are any active listeners
442 *
443 */
444 public void dispose();
445
446 /**
447 * The given <code>listener</code> will be notified from now on whenever instances the given {@link EClass}es
448 * (and any of their subtypes) are added to or removed from the model.
449 *
450 * <br/>
451 * <b>Important</b>: Do not call this method from {@link InstanceListener} methods as it may cause a
452 * {@link ConcurrentModificationException}, if you want to add a listener
453 * at that point, wrap the call with {@link #executeAfterTraversal(Runnable)}.
454 *
455 * @param classes
456 * the collection of classes whose instances the listener should be notified of
457 * @param listener
458 * the listener instance
459 */
460 public void addInstanceListener(Collection<EClass> classes, InstanceListener listener);
461
462 /**
463 * Unregisters an instance listener for the given classes.
464 *
465 * <br/>
466 * <b>Important</b>: Do not call this method from {@link InstanceListener} methods as it may cause a
467 * {@link ConcurrentModificationException}, if you want to remove a listener at that point, wrap the call with
468 * {@link #executeAfterTraversal(Runnable)}.
469 *
470 * @param classes
471 * the collection of classes
472 * @param listener
473 * the listener instance
474 */
475 public void removeInstanceListener(Collection<EClass> classes, InstanceListener listener);
476
477 /**
478 * The given <code>listener</code> will be notified from now on whenever instances the given {@link EDataType}s are
479 * added to or removed from the model.
480 *
481 * <br/>
482 * <b>Important</b>: Do not call this method from {@link DataTypeListener} methods as it may cause a
483 * {@link ConcurrentModificationException}, if you want to add a listener at that point, wrap the call with
484 * {@link #executeAfterTraversal(Runnable)}.
485 *
486 * @param types
487 * the collection of types associated to the listener
488 * @param listener
489 * the listener instance
490 */
491 public void addDataTypeListener(Collection<EDataType> types, DataTypeListener listener);
492
493 /**
494 * Unregisters a data type listener for the given types.
495 *
496 * <br/>
497 * <b>Important</b>: Do not call this method from {@link DataTypeListener} methods as it may cause a
498 * {@link ConcurrentModificationException}, if you want to remove a listener at that point, wrap the call with
499 * {@link #executeAfterTraversal(Runnable)}.
500 *
501 * @param types
502 * the collection of data types
503 * @param listener
504 * the listener instance
505 */
506 public void removeDataTypeListener(Collection<EDataType> types, DataTypeListener listener);
507
508 /**
509 * The given <code>listener</code> will be notified from now on whenever instances the given
510 * {@link EStructuralFeature}s are added to or removed from the model.
511 *
512 * <br/>
513 * <b>Important</b>: Do not call this method from {@link FeatureListener} methods as it may cause a
514 * {@link ConcurrentModificationException}, if you want to add a listener at that point, wrap the call with
515 * {@link #executeAfterTraversal(Runnable)}.
516 *
517 * @param features
518 * the collection of features associated to the listener
519 * @param listener
520 * the listener instance
521 */
522 public void addFeatureListener(Collection<? extends EStructuralFeature> features, FeatureListener listener);
523
524 /**
525 * Unregisters a feature listener for the given features.
526 *
527 * <br/>
528 * <b>Important</b>: Do not call this method from {@link FeatureListener} methods as it may cause a
529 * {@link ConcurrentModificationException}, if you want to remove a listener at that point, wrap the call with
530 * {@link #executeAfterTraversal(Runnable)}.
531 *
532 * @param listener
533 * the listener instance
534 * @param features
535 * the collection of features
536 */
537 public void removeFeatureListener(Collection<? extends EStructuralFeature> features, FeatureListener listener);
538
539 /**
540 * Register a lightweight observer that is notified if the value of any feature of the given EObject changes.
541 *
542 * <br/>
543 * <b>Important</b>: Do not call this method from {@link LightweightEObjectObserver} methods as it may cause a
544 * {@link ConcurrentModificationException}, if you want to add an observer at that point, wrap the call with
545 * {@link #executeAfterTraversal(Runnable)}.
546 *
547 * @param observer
548 * the listener instance
549 * @param observedObject
550 * the observed EObject
551 * @return false if the observer was already attached to the object (call has no effect), true otherwise
552 */
553 public boolean addLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject);
554
555 /**
556 * Unregisters a lightweight observer for the given EObject.
557 *
558 * <br/>
559 * <b>Important</b>: Do not call this method from {@link LightweightEObjectObserver} methods as it may cause a
560 * {@link ConcurrentModificationException}, if you want to remove an observer at that point, wrap the call with
561 * {@link #executeAfterTraversal(Runnable)}.
562 *
563 * @param observer
564 * the listener instance
565 * @param observedObject
566 * the observed EObject
567 * @return false if the observer has not been previously attached to the object (call has no effect), true otherwise
568 */
569 public boolean removeLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject);
570
571 /**
572 * Manually turns on indexing for the given types (indexing of others are unaffected). Note that
573 * registering new types will result in a single iteration through the whole attached model.
574 * <b> Not usable in <em>wildcard mode</em>.</b>
575 *
576 * @param classes
577 * the set of classes to observe (null okay)
578 * @param dataTypes
579 * the set of data types to observe (null okay)
580 * @param features
581 * the set of features to observe (null okay)
582 * @throws IllegalStateException if in wildcard mode
583 * @since 1.4
584 */
585 public void registerObservedTypes(Set<EClass> classes, Set<EDataType> dataTypes, Set<? extends EStructuralFeature> features, IndexingLevel level);
586
587 /**
588 * Manually turns off indexing for the given types (indexing of others are unaffected). Note that if the
589 * unregistered types are re-registered later, the whole attached model needs to be visited again.
590 * <b> Not usable in <em>wildcard mode</em>.</b>
591 *
592 * <dt><b>Precondition:</b><dd> no listeners can be registered for the given types.
593 * @param classes
594 * the set of classes that will be ignored again from now on (null okay)
595 * @param dataTypes
596 * the set of data types that will be ignored again from now on (null okay)
597 * @param features
598 * the set of features that will be ignored again from now on (null okay)
599 * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types
600 */
601 public void unregisterObservedTypes(Set<EClass> classes, Set<EDataType> dataTypes, Set<? extends EStructuralFeature> features);
602
603 /**
604 * Manually turns on indexing for the given features (indexing of other features are unaffected). Note that
605 * registering new features will result in a single iteration through the whole attached model.
606 * <b> Not usable in <em>wildcard mode</em>.</b>
607 *
608 * @param features
609 * the set of features to observe
610 * @throws IllegalStateException if in wildcard mode
611 * @since 1.4
612 */
613 public void registerEStructuralFeatures(Set<? extends EStructuralFeature> features, IndexingLevel level);
614
615 /**
616 * Manually turns off indexing for the given features (indexing of other features are unaffected). Note that if the
617 * unregistered features are re-registered later, the whole attached model needs to be visited again.
618 * <b> Not usable in <em>wildcard mode</em>.</b>
619 *
620 * <dt><b>Precondition:</b><dd> no listeners can be registered for the given features.
621 *
622 * @param features
623 * the set of features that will be ignored again from now on
624 * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types
625 */
626 public void unregisterEStructuralFeatures(Set<? extends EStructuralFeature> features);
627
628 /**
629 * Manually turns on indexing for the given classes (indexing of other classes are unaffected). Instances of
630 * subclasses will also be indexed. Note that registering new classes will result in a single iteration through the whole
631 * attached model.
632 * <b> Not usable in <em>wildcard mode</em>.</b>
633 *
634 * @param classes
635 * the set of classes to observe
636 * @throws IllegalStateException if in wildcard mode
637 * @since 1.4
638 */
639 public void registerEClasses(Set<EClass> classes, IndexingLevel level);
640
641 /**
642 * Manually turns off indexing for the given classes (indexing of other classes are unaffected). Note that if the
643 * unregistered classes are re-registered later, the whole attached model needs to be visited again.
644 * <b> Not usable in <em>wildcard mode</em>.</b>
645 *
646 * <dt><b>Precondition:</b><dd> no listeners can be registered for the given classes.
647 * @param classes
648 * the set of classes that will be ignored again from now on
649 * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types
650 */
651 public void unregisterEClasses(Set<EClass> classes);
652
653 /**
654 * Manually turns on indexing for the given data types (indexing of other features are unaffected). Note that
655 * registering new data types will result in a single iteration through the whole attached model.
656 * <b> Not usable in <em>wildcard mode</em>.</b>
657 *
658 * @param dataTypes
659 * the set of data types to observe
660 * @throws IllegalStateException if in wildcard mode
661 * @since 1.4
662 */
663 public void registerEDataTypes(Set<EDataType> dataTypes, IndexingLevel level);
664
665 /**
666 * Manually turns off indexing for the given data types (indexing of other data types are unaffected). Note that if
667 * the unregistered data types are re-registered later, the whole attached model needs to be visited again.
668 * <b> Not usable in <em>wildcard mode</em>.</b>
669 *
670 * <dt><b>Precondition:</b><dd> no listeners can be registered for the given datatypes.
671 *
672 * @param dataTypes
673 * the set of data types that will be ignored again from now on
674 * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types
675 */
676 public void unregisterEDataTypes(Set<EDataType> dataTypes);
677
678 /**
679 * The given callback will be executed, and all model traversals and index registrations will be delayed until the
680 * execution is done. If there are any outstanding feature, class or datatype registrations, a single coalesced model
681 * traversal will initialize the caches and deliver the notifications.
682 *
683 * @param callable
684 */
685 public <V> V coalesceTraversals(Callable<V> callable) throws InvocationTargetException;
686
687 /**
688 * Execute the given runnable after traversal. It is guaranteed that the runnable is executed as soon as
689 * the indexing is finished. The callback is executed only once, then is removed from the callback queue.
690 * @param traversalCallback
691 * @throws InvocationTargetException
692 * @since 1.4
693 */
694 public void executeAfterTraversal(Runnable traversalCallback) throws InvocationTargetException;
695
696 /**
697 * Examines whether execution is currently in the callable
698 * block of an invocation of {#link {@link #coalesceTraversals(Callable)}}.
699 */
700 public boolean isCoalescing();
701
702 /**
703 * Adds a coarse-grained listener that will be invoked after the NavigationHelper index or the underlying model is changed. Can be used
704 * e.g. to check model contents. Not intended for general use.
705 *
706 * <p/> See {@link #removeBaseIndexChangeListener(EMFBaseIndexChangeListener)}
707 * @param listener
708 */
709 public void addBaseIndexChangeListener(EMFBaseIndexChangeListener listener);
710
711 /**
712 * Removes a registered listener.
713 *
714 * <p/> See {@link #addBaseIndexChangeListener(EMFBaseIndexChangeListener)}
715 *
716 * @param listener
717 */
718 public void removeBaseIndexChangeListener(EMFBaseIndexChangeListener listener);
719
720 /**
721 * Adds an additional EMF model root.
722 *
723 * @param emfRoot
724 * @throws ViatraQueryRuntimeException
725 */
726 public void addRoot(Notifier emfRoot);
727
728 /**
729 * Moves an EObject (along with its entire containment subtree) within the containment hierarchy of the EMF model.
730 * The object will be relocated from the original parent object to a different parent, or a different containment
731 * list of the same parent.
732 *
733 * <p> When indexing is enabled, such a relocation is costly if performed through normal getters/setters, as the index
734 * for the entire subtree is pruned at the old location and reconstructed at the new one.
735 * This method provides a workaround to keep the operation cheap.
736 *
737 * <p> This method is experimental. Re-entrancy not supported.
738 *
739 * @param element the eObject to be moved
740 * @param targetContainmentReferenceList containment list of the new parent object into which the element has to be moved
741 *
742 */
743 public <T extends EObject> void cheapMoveTo(T element, EList<T> targetContainmentReferenceList);
744
745 /**
746 * Moves an EObject (along with its entire containment subtree) within the containment hierarchy of the EMF model.
747 * The object will be relocated from the original parent object to a different parent, or a different containment
748 * list of the same parent.
749 *
750 * <p> When indexing is enabled, such a relocation is costly if performed through normal getters/setters, as the index
751 * for the entire subtree is pruned at the old location and reconstructed at the new one.
752 * This method provides a workaround to keep the operation cheap.
753 *
754 * <p> This method is experimental. Re-entrancy not supported.
755 *
756 * @param element the eObject to be moved
757 * @param parent the new parent object under which the element has to be moved
758 * @param containmentFeature the kind of containment reference that should be established between the new parent and the element
759 *
760 */
761 public void cheapMoveTo(EObject element, EObject parent, EReference containmentFeature);
762
763
764 /**
765 * Traverses all instances of a selected data type stored in the base index, and allows executing a custom function on
766 * it. There is no guaranteed order in which the processor will be called with the selected features.
767 *
768 * @param type
769 * @param processor
770 * @since 0.8
771 */
772 void processDataTypeInstances(EDataType type, IEDataTypeProcessor processor);
773
774 /**
775 * Traverses all direct instances of a selected class stored in the base index, and allows executing a custom function on
776 * it. There is no guaranteed order in which the processor will be called with the selected features.
777 *
778 * @param type
779 * @param processor
780 * @since 0.8
781 */
782 void processAllInstances(EClass type, IEClassProcessor processor);
783
784 /**
785 * Traverses all direct instances of a selected class stored in the base index, and allows executing a custom function on
786 * it. There is no guaranteed order in which the processor will be called with the selected features.
787 *
788 * @param type
789 * @param processor
790 * @since 0.8
791 */
792 void processDirectInstances(EClass type, IEClassProcessor processor);
793
794 /**
795 * Traverses all instances of a selected feature stored in the base index, and allows executing a custom function on
796 * it. There is no guaranteed order in which the processor will be called with the selected features.
797 *
798 * <p>
799 * <strong>Precondition:</strong> Will only find those {@link EStructuralFeature}s that have already been registered using
800 * {@link #registerEStructuralFeatures(Set)}, unless running in <em>wildcard mode</em> (see
801 * {@link #isInWildcardMode()}).
802 *
803 * @since 1.7
804 */
805 void processAllFeatureInstances(EStructuralFeature feature, IStructuralFeatureInstanceProcessor processor);
806 /**
807 * Returns all EClasses that currently have direct instances cached by the index. <ul>
808 * <li> Supertypes will not be returned, unless they have direct instances in the model as well.
809 * <li> If not in <em>wildcard mode</em>, only registered EClasses and their subtypes will be considered.
810 * <li> Note for advanced users: if a type is represented by multiple EClass objects, one of them is chosen as representative and returned.
811 * </ul>
812 */
813 public Set<EClass> getAllCurrentClasses();
814
815 /**
816 * Updates the value of indexed derived features that are not well-behaving.
817 */
818 void resampleDerivedFeatures();
819
820 /**
821 * Adds a listener for internal errors in the index. A listener can only be added once.
822 *
823 * @param listener
824 * @returns true if the listener was not already added
825 * @since 0.8
826 */
827 boolean addIndexingErrorListener(IEMFIndexingErrorListener listener);
828
829 /**
830 * Removes a listener for internal errors in the index.
831 *
832 * @param listener
833 * @returns true if the listener was successfully removed (e.g. it did exist)
834 * @since 0.8
835 */
836 boolean removeIndexingErrorListener(IEMFIndexingErrorListener listener);
837
838 /**
839 * Returns the internal, canonicalized implementation of an attribute value.
840 *
841 * <p> Behaviour: when in dynamic EMF mode, substitutes enum literals with a canonical version of the enum literal.
842 * Otherwise, returns the input.
843 *
844 * <p> The canonical enum literal will be guaranteed to be a valid EMF enum literal ({@link Enumerator}),
845 * and the best effort is made to ensure that it will be the same for all versions of the {@link EEnum},
846 * including {@link EEnumLiteral}s in different versions of ecore packages, as well as Java enums generated from them..
847 *
848 * <p> Usage is not required when simply querying the indexed model through the {@link NavigationHelper} API,
849 * as both method inputs and the results returned are automatically canonicalized in dynamic EMF mode.
850 * Using this method is required only if the client wants to do querying/filtering on the results returned, and wants to know what to look for.
851 */
852 Object toCanonicalValueRepresentation(Object value);
853
854 /**
855 * @since 1.4
856 */
857 IndexingLevel getIndexingLevel(EClass type);
858
859 /**
860 * @since 1.4
861 */
862 IndexingLevel getIndexingLevel(EDataType type);
863
864 /**
865 * @since 1.4
866 */
867 IndexingLevel getIndexingLevel(EStructuralFeature feature);
868
869 /**
870 * @since 1.4
871 */
872 public int countDataTypeInstances(EDataType dataType);
873
874 /**
875 * @since 1.4
876 */
877 public int countFeatureTargets(EObject seedSource, EStructuralFeature feature);
878
879 /**
880 * @since 1.4
881 */
882 public int countFeatures(EStructuralFeature feature);
883
884
885}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java
new file mode 100644
index 00000000..27d08506
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java
@@ -0,0 +1,322 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.Map.Entry;
16
17import org.apache.log4j.Logger;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19
20/**
21 * @author Abel Hegedus
22 *
23 */
24public abstract class QueryResultAssociativeStore<KeyType, ValueType> {
25 /**
26 * Error literal returned when associative store modification is attempted without a setter available
27 */
28 protected static final String NOT_ALLOW_MODIFICATIONS = "Query result associative store does not allow modifications";
29
30 /**
31 * Logger that can be used for reporting errors during runtime
32 */
33 private Logger logger;
34 /**
35 * The collection of listeners registered for this result associative store
36 */
37 private Collection<IQueryResultUpdateListener<KeyType, ValueType>> listeners;
38
39 /**
40 * The setter registered for changing the contents of the associative store
41 */
42 private IQueryResultSetter<KeyType, ValueType> setter;
43
44 /**
45 * @return the listeners
46 */
47 protected Collection<IQueryResultUpdateListener<KeyType, ValueType>> getListeners() {
48 return listeners;
49 }
50
51 /**
52 * @param listeners the listeners to set
53 */
54 protected void setListeners(Collection<IQueryResultUpdateListener<KeyType, ValueType>> listeners) {
55 this.listeners = listeners;
56 }
57
58 /**
59 * @return the setter
60 */
61 protected IQueryResultSetter<KeyType, ValueType> getSetter() {
62 return setter;
63 }
64
65 /**
66 * @param setter the setter to set
67 */
68 protected void setSetter(IQueryResultSetter<KeyType, ValueType> setter) {
69 this.setter = setter;
70 }
71
72 /**
73 * @param logger the logger to set
74 */
75 protected void setLogger(Logger logger) {
76 this.logger = logger;
77 }
78
79 /**
80 * Returns the entries in the cache as a collection.
81 * @return the entries
82 */
83 protected abstract Collection<Entry<KeyType, ValueType>> getCacheEntries();
84
85 /**
86 * Registers a listener for this query result associative store that is invoked every time when a key-value pair is inserted
87 * or removed from the associative store.
88 *
89 * <p>
90 * The listener can be unregistered via {@link #removeCallbackOnQueryResultUpdate(IQueryResultUpdateListener)}.
91 *
92 * @param listener
93 * the listener that will be notified of each key-value pair that is inserted or removed, starting from
94 * now.
95 * @param fireNow
96 * if true, notifyPut will be immediately invoked on all current key-values as a one-time effect.
97 */
98 public void addCallbackOnQueryResultUpdate(IQueryResultUpdateListener<KeyType, ValueType> listener, boolean fireNow) {
99 if (listeners == null) {
100 listeners = new HashSet<IQueryResultUpdateListener<KeyType, ValueType>>();
101 }
102 listeners.add(listener);
103 if(fireNow) {
104 for (Entry<KeyType, ValueType> entry : getCacheEntries()) {
105 sendNotificationToListener(Direction.INSERT, entry.getKey(), entry.getValue(), listener);
106 }
107 }
108 }
109
110 /**
111 * Unregisters a callback registered by {@link #addCallbackOnQueryResultUpdate(IQueryResultUpdateListener, boolean)}
112 * .
113 *
114 * @param listener
115 * the listener that will no longer be notified.
116 */
117 public void removeCallbackOnQueryResultUpdate(IQueryResultUpdateListener<KeyType, ValueType> listener) {
118 if (listeners != null) {
119 listeners.remove(listener);
120 }
121 }
122
123 /**
124 * This method notifies the listeners that the query result associative store has changed.
125 *
126 * @param direction
127 * the type of the change (insert or delete)
128 * @param key
129 * the key of the pair that changed
130 * @param value
131 * the value of the pair that changed
132 */
133 protected void notifyListeners(Direction direction, KeyType key, ValueType value) {
134 if(listeners != null) {
135 for (IQueryResultUpdateListener<KeyType, ValueType> listener : listeners) {
136 sendNotificationToListener(direction, key, value, listener);
137 }
138 }
139 }
140
141 private void sendNotificationToListener(Direction direction, KeyType key, ValueType value,
142 IQueryResultUpdateListener<KeyType, ValueType> listener) {
143 try {
144 if (direction == Direction.INSERT) {
145 listener.notifyPut(key, value);
146 } else {
147 listener.notifyRemove(key, value);
148 }
149 } catch (Exception e) { // NOPMD
150 logger.warn(
151 String.format(
152 "The query result associative store encountered an error during executing a callback on %s of key %s and value %s. Error message: %s. (Developer note: %s in %s called from QueryResultMultimap)",
153 direction == Direction.INSERT ? "insertion" : "removal", key, value, e.getMessage(), e
154 .getClass().getSimpleName(), listener), e);
155 throw new IllegalStateException("The query result associative store encountered an error during invoking setter",e);
156 }
157 }
158
159 /**
160 * Implementations of QueryResultAssociativeStore can put a new key-value pair into the associative store with this method. If the
161 * insertion of the key-value pair results in a change, the listeners are notified.
162 *
163 * <p>
164 * No validation or null-checking is performed during the method!
165 *
166 * @param key
167 * the key which identifies where the new value is put
168 * @param value
169 * the value that is put into the collection of the key
170 * @return true, if the insertion resulted in a change (the key-value pair was not yet in the associative store)
171 */
172 protected boolean internalPut(KeyType key, ValueType value){
173 boolean putResult = internalCachePut(key, value);
174 if (putResult) {
175 notifyListeners(Direction.INSERT, key, value);
176 }
177 return putResult;
178 }
179 /**
180 * Implementations of QueryResultAssociativeStore can remove a key-value pair from the associative store with this method. If the
181 * removal of the key-value pair results in a change, the listeners are notified.
182 *
183 * <p>
184 * No validation or null-checking is performed during the method!
185 *
186 * @param key
187 * the key which identifies where the value is removed from
188 * @param value
189 * the value that is removed from the collection of the key
190 * @return true, if the removal resulted in a change (the key-value pair was in the associative store)
191 */
192 protected boolean internalRemove(KeyType key, ValueType value){
193 boolean removeResult = internalCacheRemove(key, value);
194 if (removeResult) {
195 notifyListeners(Direction.DELETE, key, value);
196 }
197 return removeResult;
198 }
199
200
201 protected abstract boolean internalCachePut(KeyType key, ValueType value);
202 protected abstract boolean internalCacheRemove(KeyType key, ValueType value);
203 protected abstract int internalCacheSize();
204 protected abstract boolean internalCacheContainsEntry(KeyType key, ValueType value);
205
206 /**
207 * @param setter
208 * the setter to set
209 */
210 public void setQueryResultSetter(IQueryResultSetter<KeyType, ValueType> setter) {
211 this.setter = setter;
212 }
213
214 /**
215 * @return the logger
216 */
217 protected Logger getLogger() {
218 return logger;
219 }
220
221 protected void internalClear() {
222 if (setter == null) {
223 throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS);
224 }
225 Collection<Entry<KeyType, ValueType>> entries = new ArrayList<>(getCacheEntries());
226 Iterator<Entry<KeyType, ValueType>> iterator = entries.iterator();
227 while (iterator.hasNext()) {
228 Entry<KeyType, ValueType> entry = iterator.next();
229 modifyThroughQueryResultSetter(entry.getKey(), entry.getValue(), Direction.DELETE);
230 }
231 if (internalCacheSize() != 0) {
232 StringBuilder sb = new StringBuilder();
233 for (Entry<KeyType, ValueType> entry : getCacheEntries()) {
234 if (sb.length() > 0) {
235 sb.append(", ");
236 }
237 sb.append(entry.toString());
238 }
239 logger.warn(String
240 .format("The query result associative store is not empty after clear, remaining entries: %s. (Developer note: %s called from QueryResultMultimap)",
241 sb.toString(), setter));
242 }
243 }
244
245 /**
246 * This method is used for calling the query result setter to put or remove a value by modifying the model.
247 *
248 * <p>
249 * The given key-value pair is first validated (see {@link IQueryResultSetter#validate(Object, Object)}, then the
250 * put or remove method is called (see {@link IQueryResultSetter#put(Object, Object)} and
251 * {@link IQueryResultSetter#remove(Object, Object)}). If the setter reported that the model has been changed, the
252 * change is checked.
253 *
254 * <p>
255 * If the model modification did not change the result set in the desired way, a warning is logged.
256 *
257 * <p>
258 * If the setter throws any {@link Throwable}, it is either rethrown in case of {@link Error} and logged otherwise.
259 *
260 *
261 * @param key
262 * the key of the pair to be inserted or removed
263 * @param value
264 * the value of the pair to be inserted or removed
265 * @param direction
266 * specifies whether a put or a remove is performed
267 * @return true, if the associative store changed according to the direction
268 */
269 protected boolean modifyThroughQueryResultSetter(KeyType key, ValueType value, Direction direction) {
270 try {
271 if (setter.validate(key, value)) {
272 final int size = internalCacheSize();
273 final int expectedChange = (direction == Direction.INSERT) ? 1 : -1;
274 boolean changed = false;
275 if (direction == Direction.INSERT) {
276 changed = setter.put(key, value);
277 } else {
278 changed = setter.remove(key, value);
279 }
280 if (changed) {
281 return checkModificationThroughQueryResultSetter(key, value, direction, expectedChange, size);
282 } else {
283 logger.warn(String
284 .format("The query result associative store %s of key %s and value %s resulted in %s. (Developer note: %s called from QueryResultMultimap)",
285 direction == Direction.INSERT ? "insertion" : "removal", key, value,
286 Math.abs(internalCacheSize() - size) > 1 ? "more than one changed result"
287 : "no changed results", setter));
288 }
289 }
290 } catch (Exception e) { // NOPMD
291 logger.warn(
292 String.format(
293 "The query result associative store encountered an error during invoking setter on %s of key %s and value %s. Error message: %s. (Developer note: %s in %s called from QueryResultMultimap)",
294 direction == Direction.INSERT ? "insertion" : "removal", key, value, e.getMessage(), e
295 .getClass().getSimpleName(), setter), e);
296 throw new IllegalStateException("The query result associative store encountered an error during invoking setter",e);
297 }
298
299 return false;
300 }
301
302 /**
303 * Checks whether the model modification performed by the {@link IQueryResultSetter} resulted in the insertion or
304 * removal of exactly the required key-value pair.
305 *
306 * @param key
307 * the key for the pair that was inserted or removed
308 * @param value
309 * the value for the pair that was inserted or removed
310 * @param direction
311 * the direction of the change
312 * @param size
313 * the size of the cache before the change
314 * @return true, if the changes made by the query result setter were correct
315 */
316 protected boolean checkModificationThroughQueryResultSetter(KeyType key, ValueType value, Direction direction,
317 final int expectedChange, final int size) {
318 boolean isInsertion = direction == Direction.INSERT;
319 return (isInsertion == internalCacheContainsEntry(key, value)
320 && (internalCacheSize() - expectedChange) == size);
321 }
322}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java
new file mode 100644
index 00000000..a106ea71
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java
@@ -0,0 +1,210 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, 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.api;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.Map;
15import java.util.Set;
16
17import org.apache.log4j.Logger;
18import tools.refinery.viatra.runtime.matchers.util.Direction;
19
20/**
21 * @author Abel Hegedus
22 *
23 */
24public abstract class QueryResultMap<KeyType,ValueType> extends QueryResultAssociativeStore<KeyType, ValueType> implements Map<KeyType, ValueType> {
25
26 /**
27 * This map contains the current key-values. Implementing classes should not modify it directly
28 */
29 private Map<KeyType, ValueType> cache;
30
31 /**
32 * Constructor only visible to subclasses.
33 *
34 * @param logger
35 * a logger that can be used for error reporting
36 */
37 protected QueryResultMap(Logger logger) {
38 cache = new HashMap<KeyType, ValueType>();
39 setLogger(logger);
40 }
41
42 @Override
43 protected Collection<java.util.Map.Entry<KeyType, ValueType>> getCacheEntries() {
44 return cache.entrySet();
45 }
46
47 @Override
48 protected boolean internalCachePut(KeyType key, ValueType value) {
49 ValueType put = cache.put(key, value);
50 if(put == null) {
51 return value != null;
52 } else {
53 return !put.equals(value);
54 }
55 }
56
57 @Override
58 protected boolean internalCacheRemove(KeyType key, ValueType value) {
59 ValueType remove = cache.remove(key);
60 return remove != null;
61 }
62
63 @Override
64 protected int internalCacheSize() {
65 return cache.size();
66 }
67
68 @Override
69 protected boolean internalCacheContainsEntry(KeyType key, ValueType value) {
70 return cache.containsKey(key) && cache.get(key).equals(value);
71 }
72
73 /**
74 * @return the cache
75 */
76 protected Map<KeyType, ValueType> getCache() {
77 return cache;
78 }
79
80 /**
81 * @param cache
82 * the cache to set
83 */
84 protected void setCache(Map<KeyType, ValueType> cache) {
85 this.cache = cache;
86 }
87
88 // ======================= implemented Map methods ======================
89
90 @Override
91 public void clear() {
92 internalClear();
93 }
94
95 @Override
96 public boolean containsKey(Object key) {
97 return cache.containsKey(key);
98 }
99
100 @Override
101 public boolean containsValue(Object value) {
102 return cache.containsValue(value);
103 }
104
105 /**
106 * {@inheritDoc}
107 *
108 * <p>
109 * The returned set is immutable.
110 *
111 */
112 @Override
113 public Set<Entry<KeyType, ValueType>> entrySet() {
114 return Collections.unmodifiableSet((Set<Entry<KeyType, ValueType>>) getCacheEntries());
115 }
116
117 @Override
118 public ValueType get(Object key) {
119 return cache.get(key);
120 }
121
122 @Override
123 public boolean isEmpty() {
124 return cache.isEmpty();
125 }
126
127 /**
128 * {@inheritDoc}
129 *
130 * <p>
131 * The returned set is immutable.
132 *
133 */
134 @Override
135 public Set<KeyType> keySet() {
136 return Collections.unmodifiableSet(cache.keySet());
137 }
138
139 /**
140 * {@inheritDoc}
141 *
142 * <p>
143 * Throws {@link UnsupportedOperationException} if there is no {@link IQueryResultSetter}
144 */
145 @Override
146 public ValueType put(KeyType key, ValueType value) {
147 if (getSetter() == null) {
148 throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS);
149 }
150 ValueType oldValue = cache.get(key);
151 boolean modified = modifyThroughQueryResultSetter(key, value, Direction.INSERT);
152 return modified ? oldValue : null;
153 }
154
155 /**
156 * {@inheritDoc}
157 *
158 * <p>
159 * Throws {@link UnsupportedOperationException} if there is no {@link IQueryResultSetter}
160 */
161 @Override
162 public void putAll(Map<? extends KeyType, ? extends ValueType> map) {
163 if (getSetter() == null) {
164 throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS);
165 }
166 for (Entry<? extends KeyType, ? extends ValueType> entry : map.entrySet()) {
167 modifyThroughQueryResultSetter(entry.getKey(), entry.getValue(), Direction.INSERT);
168 }
169 return;
170 }
171
172 /**
173 * {@inheritDoc}
174 *
175 * <p>
176 * Throws {@link UnsupportedOperationException} if there is no {@link IQueryResultSetter}
177 */
178 @SuppressWarnings("unchecked")
179 @Override
180 public ValueType remove(Object key) {
181 if (getSetter() == null) {
182 throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS);
183 }
184 // if it contains the entry, the types MUST be correct
185 if (cache.containsKey(key)) {
186 ValueType value = cache.get(key);
187 modifyThroughQueryResultSetter((KeyType) key, value, Direction.DELETE);
188 return value;
189 }
190 return null;
191 }
192
193 @Override
194 public int size() {
195 return internalCacheSize();
196 }
197
198 /**
199 * {@inheritDoc}
200 *
201 * <p>
202 * The returned collection is immutable.
203 *
204 */
205 @Override
206 public Collection<ValueType> values() {
207 return Collections.unmodifiableCollection(cache.values());
208 }
209
210}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java
new file mode 100644
index 00000000..fc92fef3
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java
@@ -0,0 +1,26 @@
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
10package tools.refinery.viatra.runtime.base.api;
11
12import org.eclipse.emf.ecore.EObject;
13import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource;
14
15/**
16 * The class can be used to compute the transitive closure of a given emf model, where the nodes will be the objects in
17 * the model and the edges will be represented by the references between them. One must provide the set of references
18 * that the helper should treat as edges when creating an instance with the factory: only the notifications about these
19 * references will be handled.
20 *
21 * @author Tamas Szabo
22 *
23 */
24public interface TransitiveClosureHelper extends ITcDataSource<EObject> {
25
26}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java
new file mode 100644
index 00000000..81bd4f35
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java
@@ -0,0 +1,180 @@
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
10package tools.refinery.viatra.runtime.base.api;
11
12import java.util.Set;
13
14import org.apache.log4j.Logger;
15import org.eclipse.emf.common.notify.Notifier;
16import org.eclipse.emf.ecore.EClass;
17import org.eclipse.emf.ecore.EDataType;
18import org.eclipse.emf.ecore.EReference;
19import org.eclipse.emf.ecore.EStructuralFeature;
20import tools.refinery.viatra.runtime.base.core.NavigationHelperImpl;
21import tools.refinery.viatra.runtime.base.core.TransitiveClosureHelperImpl;
22
23/**
24 * Factory class for the utils in the library: <ul>
25 * <li>NavigationHelper (automatic and manual)
26 * <li>TransitiveClosureUtil
27 * </ul>
28 *
29 * @author Tamas Szabo
30 *
31 */
32public class ViatraBaseFactory {
33
34 private static ViatraBaseFactory instance;
35
36 /**
37 * Get the singleton instance of ViatraBaseFactory.
38 *
39 * @return the singleton instance
40 */
41 public static synchronized ViatraBaseFactory getInstance() {
42 if (instance == null) {
43 instance = new ViatraBaseFactory();
44 }
45
46 return instance;
47 }
48
49 protected ViatraBaseFactory() {
50 super();
51 }
52
53 /**
54 * The method creates a {@link NavigationHelper} index for the given EMF model root.
55 * A new instance will be created on every call.
56 * <p>
57 * A NavigationHelper in wildcard mode will process and index all EStructuralFeatures, EClasses and EDatatypes. If
58 * wildcard mode is off, the client will have to manually register the interesting aspects of the model.
59 * <p>
60 * The NavigationHelper will be created without dynamic EMF support by default.
61 * See {@link #createNavigationHelper(Notifier, boolean, boolean, Logger)} for more options.
62 *
63 * @see NavigationHelper
64 *
65 * @param emfRoot
66 * the root of the EMF tree to be indexed. Recommended: Resource or ResourceSet. Can be null - you can
67 * add a root later using {@link NavigationHelper#addRoot(Notifier)}
68 * @param wildcardMode
69 * true if all aspects of the EMF model should be indexed automatically, false if manual registration of
70 * interesting aspects is desirable
71 * @param logger
72 * the log output where errors will be logged if encountered during the operation of the
73 * NavigationHelper; if null, the default logger for {@link NavigationHelper} is used.
74 * @return the NavigationHelper instance
75 * @throws ViatraQueryRuntimeException
76 */
77 public NavigationHelper createNavigationHelper(Notifier emfRoot, boolean wildcardMode, Logger logger) {
78 BaseIndexOptions options = new BaseIndexOptions(false, wildcardMode ? IndexingLevel.FULL : IndexingLevel.NONE);
79 return createNavigationHelper(emfRoot, options, logger);
80 }
81
82 /**
83 * The method creates a {@link NavigationHelper} index for the given EMF model root.
84 * A new instance will be created on every call.
85 * <p>
86 * A NavigationHelper in wildcard mode will process and index all EStructuralFeatures, EClasses and EDatatypes. If
87 * wildcard mode is off, the client will have to manually register the interesting aspects of the model.
88 * <p>
89 * If the dynamic model flag is set to true, the index will use String ids to distinguish between the various
90 * {@link EStructuralFeature}, {@link EClass} and {@link EDataType} instances. This way the index is able to
91 * handle dynamic EMF instance models too.
92 *
93 * @see NavigationHelper
94 *
95 * @param emfRoot
96 * the root of the EMF tree to be indexed. Recommended: Resource or ResourceSet. Can be null - you can
97 * add a root later using {@link NavigationHelper#addRoot(Notifier)}
98 * @param wildcardMode
99 * true if all aspects of the EMF model should be indexed automatically, false if manual registration of
100 * interesting aspects is desirable
101 * @param dynamicModel
102 * true if the index should use String ids (nsURIs) for the various EMF types and features, and treat
103 * multiple EPackages sharing an nsURI as the same. false if dynamic model support is not required
104 * @param logger
105 * the log output where errors will be logged if encountered during the operation of the
106 * NavigationHelper; if null, the default logger for {@link NavigationHelper} is used.
107 * @return the NavigationHelper instance
108 * @throws ViatraQueryRuntimeException
109 */
110 public NavigationHelper createNavigationHelper(Notifier emfRoot, boolean wildcardMode, boolean dynamicModel, Logger logger) {
111 BaseIndexOptions options = new BaseIndexOptions(dynamicModel, wildcardMode ? IndexingLevel.FULL : IndexingLevel.NONE);
112 return createNavigationHelper(emfRoot, options, logger);
113 }
114
115 /**
116 * The method creates a {@link NavigationHelper} index for the given EMF model root.
117 * A new instance will be created on every call.
118 * <p>
119 * For details of base index options including wildcard and dynamic EMF mode, see {@link BaseIndexOptions}.
120 *
121 * @see NavigationHelper
122 *
123 * @param emfRoot
124 * the root of the EMF tree to be indexed. Recommended: Resource or ResourceSet. Can be null - you can
125 * add a root later using {@link NavigationHelper#addRoot(Notifier)}
126 * @param options the options used by the index
127 * @param logger
128 * the log output where errors will be logged if encountered during the operation of the
129 * NavigationHelper; if null, the default logger for {@link NavigationHelper} is used.
130 * @return the NavigationHelper instance
131 * @throws ViatraQueryRuntimeException
132 */
133 public NavigationHelper createNavigationHelper(Notifier emfRoot, BaseIndexOptions options, Logger logger) {
134 Logger l = logger;
135 if (l == null)
136 l = Logger.getLogger(NavigationHelper.class);
137 return new NavigationHelperImpl(emfRoot, options, l);
138 }
139
140
141
142 /**
143 * The method creates a TransitiveClosureHelper instance for the given EMF model root.
144 * A new instance will be created on every call.
145 *
146 * <p>
147 * One must specify the set of EReferences that will be considered as edges. The set can contain multiple elements;
148 * this way one can query forward and backward reachability information along heterogenous paths.
149 *
150 * @param emfRoot
151 * the root of the EMF tree to be processed. Recommended: Resource or ResourceSet.
152 * @param referencesToObserve
153 * the set of references to observe
154 * @return the TransitiveClosureHelper instance
155 * @throws ViatraQueryRuntimeException if the creation of the internal NavigationHelper failed
156 */
157 public TransitiveClosureHelper createTransitiveClosureHelper(Notifier emfRoot, Set<EReference> referencesToObserve) {
158 return new TransitiveClosureHelperImpl(getInstance().createNavigationHelper(emfRoot, false, null), true, referencesToObserve);
159 }
160
161 /**
162 * The method creates a TransitiveClosureHelper instance built on an existing NavigationHelper.
163 * A new instance will be created on every call.
164 *
165 * <p>
166 * One must specify the set of EReferences that will be considered as edges. The set can contain multiple elements;
167 * this way one can query forward and backward reachability information along heterogenous paths.
168 *
169 * @param baseIndex
170 * the already existing NavigationHelper index on the model
171 * @param referencesToObserve
172 * the set of references to observe
173 * @return the TransitiveClosureHelper instance
174 */
175 public TransitiveClosureHelper createTransitiveClosureHelper(NavigationHelper baseIndex, Set<EReference> referencesToObserve) {
176 return new TransitiveClosureHelperImpl(baseIndex, false, referencesToObserve);
177 }
178
179
180}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java
new file mode 100644
index 00000000..8929b2ab
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java
@@ -0,0 +1,38 @@
1/**
2 * Copyright (c) 2010-2016, Peter Lunk, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 */
9package tools.refinery.viatra.runtime.base.api.filters;
10
11import org.eclipse.emf.ecore.EStructuralFeature;
12
13/**
14 *
15 * Defines if an {@link EStructuralFeature} should not be indexed by VIATRA Base. This filtering
16 * method should only be used if the input metamodel has certain features, that the base indexer
17 * cannot handle. If the filtered feature is a containment feature, the whole sub-tree accessible
18 * through the said feature will be filtered.
19 *
20 * Note: This API feature is for advanced users only. Usage of this feature is not encouraged,
21 * unless the filtering task is impossible via using the more straightforward
22 * {@link IBaseIndexResourceFilter} or {@link IBaseIndexObjectFilter}.
23 *
24 * @author Peter Lunk
25 * @since 1.5
26 *
27 */
28public interface IBaseIndexFeatureFilter {
29
30 /**
31 * Decides whether the selected {@link EStructuralFeature} is filtered.
32 *
33 * @param feature
34 * @return true, if the feature should not be indexed
35 */
36 boolean isFiltered(EStructuralFeature feature);
37
38} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java
new file mode 100644
index 00000000..e1e46bbd
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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.api.filters;
10
11import org.eclipse.emf.common.notify.Notifier;
12
13/**
14 *
15 * Stores a collection of {@link Notifier} instances that need not to be indexed by VIATRA Base.
16 *
17 * @author Zoltan Ujhelyi
18 *
19 */
20public interface IBaseIndexObjectFilter {
21
22 /**
23 * Decides whether the selected notifier is filtered.
24 *
25 * @param notifier
26 * @return true, if the notifier should not be indexed
27 */
28 boolean isFiltered(Notifier notifier);
29
30} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java
new file mode 100644
index 00000000..73d3e961
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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.api.filters;
10
11import org.eclipse.emf.ecore.resource.Resource;
12
13/**
14 * Defines a filter for indexing resources
15 * @author Zoltan Ujhelyi
16 *
17 */
18public interface IBaseIndexResourceFilter {
19
20 /**
21 * Decides whether a selected resource needs to be indexed
22 * @param resource
23 * @return true, if the selected resource is filtered
24 */
25 boolean isResourceFiltered(Resource resource);
26
27} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java
new file mode 100644
index 00000000..9ae88a1a
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java
@@ -0,0 +1,46 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2014, Zoltan Ujhelyi, 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.api.filters;
10
11import org.eclipse.emf.common.notify.Notifier;
12
13import java.util.Collection;
14import java.util.HashSet;
15import java.util.Set;
16
17/**
18 * An index filter that is based on a collection of {@link Notifier} instances.
19 *
20 * @author Zoltan Ujhelyi
21 *
22 */
23public class SimpleBaseIndexFilter implements IBaseIndexObjectFilter {
24
25 Set<Notifier> filters;
26
27 /**
28 * Creates a filter using a collection of (Resource and) Notifier instances. Every containment subtree, selected by
29 * the given Notifiers are filtered out.
30 *
31 * @param filterConfiguration
32 */
33 public SimpleBaseIndexFilter(Collection<Notifier> filterConfiguration) {
34 filters = new HashSet<>(filterConfiguration);
35 }
36
37 public SimpleBaseIndexFilter(SimpleBaseIndexFilter other) {
38 this(other.filters);
39 }
40
41 @Override
42 public boolean isFiltered(Notifier notifier) {
43 return filters.contains(notifier);
44 }
45
46}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java
new file mode 100644
index 00000000..d3cc152e
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java
@@ -0,0 +1,79 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.api.profiler;
10
11import tools.refinery.viatra.runtime.base.api.NavigationHelper;
12import tools.refinery.viatra.runtime.base.core.NavigationHelperContentAdapter;
13import tools.refinery.viatra.runtime.base.core.NavigationHelperImpl;
14import tools.refinery.viatra.runtime.base.core.profiler.ProfilingNavigationHelperContentAdapter;
15import tools.refinery.viatra.runtime.matchers.util.Preconditions;
16
17/**
18 * An index profiler can be attached to an existing navigation helper instance to access the profiling data and control
19 * the profiler itself. If the NavigationHelper was not started in profiling mode, the profiler cannot be initialized.
20 *
21 * @since 2.3
22 */
23public class BaseIndexProfiler {
24
25 ProfilingNavigationHelperContentAdapter adapter;
26
27 /**
28 *
29 * @throws IllegalArgumentException if the profiler cannot be attached to the base index instance
30 */
31 public BaseIndexProfiler(NavigationHelper navigationHelper) {
32 if (navigationHelper instanceof NavigationHelperImpl) {
33 final NavigationHelperContentAdapter contentAdapter = ((NavigationHelperImpl) navigationHelper).getContentAdapter();
34 if (contentAdapter instanceof ProfilingNavigationHelperContentAdapter) {
35 adapter = (ProfilingNavigationHelperContentAdapter)contentAdapter;
36 }
37 }
38 Preconditions.checkArgument(adapter != null, "Cannot attach profiler to Base Index");
39 }
40
41 /**
42 * Returns the number of external request (e.g. model changes) the profiler recorded.
43 */
44 public long getNotificationCount() {
45 return adapter.getNotificationCount();
46 }
47
48 /**
49 * Return the total time base index profiler recorded for reacting to model operations.
50 */
51 public long getTotalMeasuredTimeInMS() {
52 return adapter.getTotalMeasuredTimeInMS();
53 }
54
55 /**
56 * Returns whether the profiler is turned on (e.g. measured values are increased).
57 */
58 public boolean isEnabled() {
59 return adapter.isEnabled();
60 }
61
62 /**
63 * Enables the base index profiling (e.g. measured values are increased)
64 */
65 public void setEnabled(boolean isEnabled) {
66 adapter.setEnabled(isEnabled);
67 }
68
69 /**
70 * Resets all measurements to 0, regardless whether the profiler is enabled or not.
71 * </p>
72 *
73 * <strong>Note</strong>: The behavior of the profiler is undefined when the measurements are reset while an EMF
74 * notification is being processed and the profiler is enabled.
75 */
76 public void resetMeasurement() {
77 adapter.resetMeasurement();
78 }
79}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java
new file mode 100644
index 00000000..74901263
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.api.profiler;
10
11/**
12 * @since 2.3
13 */
14public enum ProfilerMode {
15
16 /** The base index profiler is not available */
17 OFF,
18 /** The profiler is initialized but not started until necessary */
19 START_DISABLED,
20 /** The profiler is initialized and started by default */
21 START_ENABLED
22}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java
new file mode 100644
index 00000000..bde93367
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java
@@ -0,0 +1,356 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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
10package tools.refinery.viatra.runtime.base.comprehension;
11
12import java.util.ArrayList;
13import java.util.Iterator;
14import java.util.List;
15
16import org.eclipse.emf.common.notify.Notifier;
17import org.eclipse.emf.common.util.EList;
18import org.eclipse.emf.ecore.EAttribute;
19import org.eclipse.emf.ecore.EObject;
20import org.eclipse.emf.ecore.EReference;
21import org.eclipse.emf.ecore.EStructuralFeature;
22import org.eclipse.emf.ecore.EcorePackage;
23import org.eclipse.emf.ecore.InternalEObject;
24import org.eclipse.emf.ecore.resource.Resource;
25import org.eclipse.emf.ecore.resource.ResourceSet;
26import org.eclipse.emf.ecore.util.EcoreUtil;
27import org.eclipse.emf.ecore.util.ExtendedMetaData;
28import org.eclipse.emf.ecore.util.FeatureMap;
29import org.eclipse.emf.ecore.util.FeatureMap.Entry;
30import org.eclipse.emf.ecore.util.InternalEList;
31import tools.refinery.viatra.runtime.base.api.BaseIndexOptions;
32import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexFeatureFilter;
33import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter;
34import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter;
35
36/**
37 * @author Bergmann Gábor
38 *
39 * Does not directly visit derived links, unless marked as a WellBehavingFeature. Derived edges are
40 * automatically interpreted correctly in these cases: - EFeatureMaps - eOpposites of containments
41 *
42 * @noextend This class is not intended to be subclassed by clients.
43 */
44public class EMFModelComprehension {
45
46 /**
47 * @since 2.3
48 */
49 protected BaseIndexOptions options;
50
51 /**
52 * Creates a model comprehension with the specified options. The options are copied, therefore subsequent changes
53 * will not affect the comprehension.
54 */
55 public EMFModelComprehension(BaseIndexOptions options) {
56 this.options = options.copy();
57 }
58
59 /**
60 * Should not traverse this feature directly. It is still possible that it can be represented in IQBase if
61 * {@link #representable(EStructuralFeature)} is true.
62 */
63 public boolean untraversableDirectly(EStructuralFeature feature) {
64
65 if((feature instanceof EReference && ((EReference)feature).isContainer())) {
66 // container features are always represented through their opposite
67 return true;
68 }
69
70 //If the feature is filtered by the feature filter specified in the BaseIndexOptions, return true
71 final IBaseIndexFeatureFilter featureFilter = options.getFeatureFilterConfiguration();
72 if(featureFilter != null && featureFilter.isFiltered(feature)){
73 return true;
74 }
75
76 boolean suspect = onlySamplingFeature(feature);
77 if(suspect) {
78 // even if the feature can only be sampled, it may be used if the proper base index option is set
79 suspect = options.isTraverseOnlyWellBehavingDerivedFeatures();
80 }
81 return suspect;
82 }
83
84 /**
85 * Decides whether a feature can only be sampled as there is no guarantee that proper notifications will be
86 * delivered by their implementation.
87 *
88 * <p/> Such features are derived (and/or volatile) features that are not well-behaving.
89 */
90 public boolean onlySamplingFeature(EStructuralFeature feature) {
91 boolean suspect =
92 feature.isDerived() ||
93 feature.isVolatile();
94 if (suspect) {
95 // override support here
96 // (e.g. if manual notifications available, or no changes expected afterwards)
97 suspect = !WellbehavingDerivedFeatureRegistry.isWellbehavingFeature(feature);
98 // TODO verbose flag somewhere to ease debugging (for such warnings)
99 // TODO add warning about not visited subtree (containment, FeatureMap and annotation didn't define
100 // otherwise)
101 }
102 return suspect;
103 }
104
105 /**
106 * This feature can be represented in IQBase.
107 */
108 public boolean representable(EStructuralFeature feature) {
109 if (!untraversableDirectly(feature))
110 return true;
111
112 if (feature instanceof EReference) {
113 final EReference reference = (EReference) feature;
114 if (reference.isContainer() && representable(reference.getEOpposite()))
115 return true;
116 }
117
118 boolean isMixed = "mixed".equals(EcoreUtil.getAnnotation(feature.getEContainingClass(),
119 ExtendedMetaData.ANNOTATION_URI, "kind"));
120 if (isMixed)
121 return true; // TODO maybe check the "name"=":mixed" or ":group" feature for representability?
122
123 // Group features are alternative features that are used when the ecore is derived from an xsd schema containing
124 // choices; in that case instead of the asked feature we should index the corresponding group feature
125 final EStructuralFeature groupFeature = ExtendedMetaData.INSTANCE.getGroup(feature);
126 if (groupFeature != null) {
127 return representable(groupFeature);
128 }
129
130 return false;
131 }
132
133 /**
134 * Resource filters not consulted here (for performance), because model roots are assumed to be pre-filtered.
135 */
136 public void traverseModel(EMFVisitor visitor, Notifier source) {
137 if (source == null)
138 return;
139 if (source instanceof EObject) {
140 final EObject sourceObject = (EObject) source;
141 if (sourceObject.eIsProxy())
142 throw new IllegalArgumentException("Proxy EObject cannot act as model roots for VIATRA: " + source);
143 traverseObject(visitor, sourceObject);
144 } else if (source instanceof Resource) {
145 traverseResource(visitor, (Resource) source);
146 } else if (source instanceof ResourceSet) {
147 traverseResourceSet(visitor, (ResourceSet) source);
148 }
149 }
150
151 public void traverseResourceSet(EMFVisitor visitor, ResourceSet source) {
152 if (source == null)
153 return;
154 final List<Resource> resources = new ArrayList<Resource>(source.getResources());
155 for (Resource resource : resources) {
156 traverseResourceIfUnfiltered(visitor, resource);
157 }
158 }
159
160 public void traverseResourceIfUnfiltered(EMFVisitor visitor, Resource resource) {
161 final IBaseIndexResourceFilter resourceFilter = options.getResourceFilterConfiguration();
162 if (resourceFilter != null && resourceFilter.isResourceFiltered(resource))
163 return;
164 final IBaseIndexObjectFilter objectFilter = options.getObjectFilterConfiguration();
165 if (objectFilter != null && objectFilter.isFiltered(resource))
166 return;
167
168 traverseResource(visitor, resource);
169 }
170
171 public void traverseResource(EMFVisitor visitor, Resource source) {
172 if (source == null)
173 return;
174 if (visitor.pruneSubtrees(source))
175 return;
176 final EList<EObject> contents = source.getContents();
177 for (EObject eObject : contents) {
178 traverseObjectIfUnfiltered(visitor, eObject);
179 }
180 }
181
182
183 public void traverseObjectIfUnfiltered(EMFVisitor visitor, EObject targetObject) {
184 final IBaseIndexObjectFilter objectFilter = options.getObjectFilterConfiguration();
185 if (objectFilter != null && objectFilter.isFiltered(targetObject))
186 return;
187
188 traverseObject(visitor, targetObject);
189 }
190
191 public void traverseObject(EMFVisitor visitor, EObject source) {
192 if (source == null)
193 return;
194
195 if (visitor.preOrder()) visitor.visitElement(source);
196 for (EStructuralFeature feature : source.eClass().getEAllStructuralFeatures()) {
197 if (untraversableDirectly(feature))
198 continue;
199 final boolean visitorPrunes = visitor.pruneFeature(feature);
200 if (visitorPrunes && !unprunableFeature(visitor, source, feature))
201 continue;
202
203 traverseFeatureTargets(visitor, source, feature, visitorPrunes);
204 }
205 if (!visitor.preOrder()) visitor.visitElement(source);
206 }
207
208 protected void traverseFeatureTargets(EMFVisitor visitor, EObject source, EStructuralFeature feature,
209 final boolean visitorPrunes) {
210 boolean attemptResolve = (feature instanceof EAttribute) || visitor.attemptProxyResolutions(source, (EReference)feature);
211 if (feature.isMany()) {
212 EList<?> targets = (EList<?>) source.eGet(feature);
213 int position = 0;
214 Iterator<?> iterator = attemptResolve ? targets.iterator() : ((InternalEList<?>)targets).basicIterator();
215 while (iterator.hasNext()) {
216 Object target = iterator.next();
217 traverseFeatureInternal(visitor, source, feature, target, visitorPrunes, position++);
218 }
219 } else {
220 Object target = source.eGet(feature, attemptResolve);
221 if (target != null)
222 traverseFeatureInternal(visitor, source, feature, target, visitorPrunes, null);
223 }
224 }
225 /**
226 * @since 2.3
227 */
228 protected boolean unprunableFeature(EMFVisitor visitor, EObject source, EStructuralFeature feature) {
229 return (feature instanceof EAttribute && EcorePackage.eINSTANCE.getEFeatureMapEntry().equals(
230 ((EAttribute) feature).getEAttributeType()))
231 || (feature instanceof EReference && ((EReference) feature).isContainment() && (!visitor
232 .pruneSubtrees(source) || ((EReference) feature).getEOpposite() != null));
233 }
234
235 /**
236 * @param position optional: known position in multivalued collection (for more efficient proxy resolution)
237 */
238 public void traverseFeature(EMFVisitor visitor, EObject source, EStructuralFeature feature, Object target, Integer position) {
239 if (target == null)
240 return;
241 if (untraversableDirectly(feature))
242 return;
243 traverseFeatureInternalSimple(visitor, source, feature, target, position);
244 }
245
246 /**
247 * @param position optional: known position in multivalued collection (for more efficient proxy resolution)
248 * @since 2.3
249 */
250 protected void traverseFeatureInternalSimple(EMFVisitor visitor, EObject source, EStructuralFeature feature,
251 Object target, Integer position) {
252 final boolean visitorPrunes = visitor.pruneFeature(feature);
253 if (visitorPrunes && !unprunableFeature(visitor, source, feature))
254 return;
255
256 traverseFeatureInternal(visitor, source, feature, target, visitorPrunes, position);
257 }
258
259 /**
260 * @pre target != null
261 * @param position optional: known position in multivalued collection (for more efficient proxy resolution)
262 * @since 2.3
263 */
264 protected void traverseFeatureInternal(EMFVisitor visitor, EObject source, EStructuralFeature feature,
265 Object target, boolean visitorPrunes, Integer position) {
266 if (feature instanceof EAttribute) {
267 if (!visitorPrunes)
268 visitor.visitAttribute(source, (EAttribute) feature, target);
269 if (target instanceof FeatureMap.Entry) { // emulated derived edge based on FeatureMap
270 Entry entry = (FeatureMap.Entry) target;
271 final EStructuralFeature emulated = entry.getEStructuralFeature();
272 final Object emulatedTarget = entry.getValue();
273
274 emulateUntraversableFeature(visitor, source, emulated, emulatedTarget);
275 }
276 } else if (feature instanceof EReference) {
277 EReference reference = (EReference) feature;
278 EObject targetObject = (EObject) target;
279 if (reference.isContainment()) {
280 if (!visitor.avoidTransientContainmentLink(source, reference, targetObject)) {
281 if (!visitorPrunes)
282 visitor.visitInternalContainment(source, reference, targetObject);
283 if (!visitor.pruneSubtrees(source)) {
284 // Recursively follow containment...
285 // unless cross-resource containment (in which case we may skip)
286 Resource targetResource = (targetObject instanceof InternalEObject)?
287 ((InternalEObject)targetObject).eDirectResource() : null;
288 boolean crossResourceContainment = targetResource != null;
289 if (!crossResourceContainment || visitor.descendAlongCrossResourceContainments()) {
290 // in-resource containment shall be followed
291 // as well as cross-resource containment for an object scope
292 traverseObjectIfUnfiltered(visitor, targetObject);
293 } else {
294 // do not follow
295 // target will be traversed separately from its resource (resourceSet scope)
296 // or left out of scope (resource scope)
297 }
298 }
299
300 final EReference opposite = reference.getEOpposite();
301 if (opposite != null) { // emulated derived edge based on container opposite
302 emulateUntraversableFeature(visitor, targetObject, opposite, source);
303 }
304 }
305 } else {
306 // if (containedElements.contains(target))
307 if (!visitorPrunes)
308 visitor.visitNonContainmentReference(source, reference, targetObject);
309 }
310 if (targetObject.eIsProxy()) {
311 if (!reference.isResolveProxies()) {
312 throw new IllegalStateException(String.format(
313 "EReference '%s' of EClass %s is set as proxy-non-resolving (i.e. it should never point to a proxy, and never lead cross-resource), " +
314 "yet VIATRA Base encountered a proxy object %s referenced from %s.",
315 reference.getName(), reference.getEContainingClass().getInstanceTypeName(),
316 targetObject, source));
317 }
318 visitor.visitProxyReference(source, reference, targetObject, position);
319 }
320 }
321
322 }
323
324
325 /**
326 * Emulates a derived edge, if it is not visited otherwise
327 *
328 * @pre target != null
329 * @since 2.3
330 */
331 protected void emulateUntraversableFeature(EMFVisitor visitor, EObject source,
332 final EStructuralFeature emulated, final Object target) {
333 if (untraversableDirectly(emulated))
334 traverseFeatureInternalSimple(visitor, source, emulated, target, null);
335 }
336
337 /**
338 * Can be called to attempt to resolve a reference pointing to one or more proxies, using eGet().
339 */
340 @SuppressWarnings("unchecked")
341 public void tryResolveReference(EObject source, EReference reference) {
342 final Object result = source.eGet(reference, true);
343 if (reference.isMany()) {
344 // no idea which element to get, have to iterate through
345 ((Iterable<EObject>) result).forEach(EObject -> {/*proxy resolution as a side-effect of traversal*/});
346 }
347 }
348
349 /**
350 * Finds out whether the Resource is currently loading
351 */
352 public boolean isLoading(Resource resource) {
353 return !resource.isLoaded() || ((Resource.Internal)resource).isLoading();
354 }
355
356} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java
new file mode 100644
index 00000000..6029bb02
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java
@@ -0,0 +1,145 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2010 Gabor Bergmann 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
10package tools.refinery.viatra.runtime.base.comprehension;
11
12import org.eclipse.emf.ecore.EAttribute;
13import org.eclipse.emf.ecore.EObject;
14import org.eclipse.emf.ecore.EReference;
15import org.eclipse.emf.ecore.EStructuralFeature;
16import org.eclipse.emf.ecore.resource.Resource;
17
18/**
19 * Use EMFModelComprehension to visit an EMF model.
20 *
21 * @author Bergmann Gábor
22 *
23 */
24// FIXME:
25// - handle boundary of active emfRoot subtree
26// - more efficient traversal
27public class EMFVisitor {
28
29 boolean preOrder;
30
31 public EMFVisitor(boolean preOrder) {
32 super();
33 this.preOrder = preOrder;
34 }
35
36 /**
37 * @param resource
38 * @param element
39 */
40 public void visitTopElementInResource(Resource resource, EObject element) {
41 }
42
43 /**
44 * @param resource
45 */
46 public void visitResource(Resource resource) {
47 }
48
49 /**
50 * @param source
51 */
52 public void visitElement(EObject source) {
53 }
54
55 /**
56 * @param source
57 * @param feature
58 * @param target
59 */
60 public void visitNonContainmentReference(EObject source, EReference feature, EObject target) {
61 }
62
63 /**
64 * @param source
65 * @param feature
66 * @param target
67 */
68 public void visitInternalContainment(EObject source, EReference feature, EObject target) {
69 }
70
71 /**
72 * @param source
73 * @param feature
74 * @param target
75 */
76 public void visitAttribute(EObject source, EAttribute feature, Object target) {
77 }
78
79 /**
80 * Returns true if the given feature should not be traversed (interesting esp. if multi-valued)
81 */
82 public boolean pruneFeature(EStructuralFeature feature) {
83 return false;
84 }
85
86 /**
87 * Returns true if the contents of an object should be pruned (and not explored by the visitor)
88 */
89 public boolean pruneSubtrees(EObject source) {
90 return false;
91 }
92
93 /**
94 * Returns true if the contents of a resource should be pruned (and not explored by the visitor)
95 */
96 public boolean pruneSubtrees(Resource source) {
97 return false;
98 }
99
100 /**
101 * An opportunity for the visitor to indicate that the containment link is considered in a transient state, and the
102 * model comprehension should avoid following it.
103 *
104 * A containment is in a transient state from the point of view of the visitor if it connects a subtree that is
105 * being inserted <em>during</em> a full-model traversal, and a separate notification handler will deal with it
106 * later.
107 */
108 public boolean avoidTransientContainmentLink(EObject source, EReference reference, EObject targetObject) {
109 return false;
110 }
111
112 /**
113 * @return if objects should be visited before their outgoing edges
114 */
115 public boolean preOrder() {
116 return preOrder;
117 }
118
119 /**
120 * Called after visiting the reference, if the target is a proxy.
121 *
122 * @param position
123 * optional: known position in multivalued collection (for more efficient proxy resolution)
124 */
125 public void visitProxyReference(EObject source, EReference reference, EObject targetObject, Integer position) {
126 }
127
128 /**
129 * Whether the given reference of the given object should be resolved when it is a proxy
130 */
131 public boolean attemptProxyResolutions(EObject source, EReference feature) {
132 return true;
133 }
134
135 /**
136 * @return true if traversing visitors shall descend along cross-resource containments
137 * (this only makes sense for traversing visitors on an object scope)
138 *
139 * @since 1.7
140 */
141 public boolean descendAlongCrossResourceContainments() {
142 return false;
143 }
144
145} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java
new file mode 100644
index 00000000..d696ddd6
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java
@@ -0,0 +1,154 @@
1/*******************************************************************************
2 * Copyright (c) 2004-2011 Abel Hegedus 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.comprehension;
10
11import java.util.Collection;
12import java.util.Collections;
13import java.util.WeakHashMap;
14
15import org.apache.log4j.Logger;
16import org.eclipse.core.runtime.IConfigurationElement;
17import org.eclipse.core.runtime.IExtension;
18import org.eclipse.core.runtime.IExtensionPoint;
19import org.eclipse.core.runtime.IExtensionRegistry;
20import org.eclipse.core.runtime.Platform;
21import org.eclipse.emf.ecore.EClass;
22import org.eclipse.emf.ecore.EClassifier;
23import org.eclipse.emf.ecore.EPackage;
24import org.eclipse.emf.ecore.EStructuralFeature;
25import tools.refinery.viatra.runtime.base.ViatraBasePlugin;
26
27/**
28 * @author Abel Hegedus
29 *
30 */
31public class WellbehavingDerivedFeatureRegistry {
32
33
34 private static Collection<EStructuralFeature> contributedWellbehavingDerivedFeatures = Collections.newSetFromMap(new WeakHashMap<EStructuralFeature, Boolean>());
35 private static Collection<EClass> contributedWellbehavingDerivedClasses = Collections.newSetFromMap(new WeakHashMap<EClass, Boolean>());
36 private static Collection<EPackage> contributedWellbehavingDerivedPackages = Collections.newSetFromMap(new WeakHashMap<EPackage, Boolean>());
37
38 private WellbehavingDerivedFeatureRegistry() {
39 }
40
41 /**
42 * Called by ViatraBasePlugin.
43 */
44 public static void initRegistry() {
45 getContributedWellbehavingDerivedFeatures().clear();
46 getContributedWellbehavingDerivedClasses().clear();
47 getContributedWellbehavingDerivedPackages().clear();
48
49 IExtensionRegistry reg = Platform.getExtensionRegistry();
50 IExtensionPoint poi;
51
52 poi = reg.getExtensionPoint(ViatraBasePlugin.WELLBEHAVING_DERIVED_FEATURE_EXTENSION_POINT_ID);
53 if (poi != null) {
54 IExtension[] exts = poi.getExtensions();
55
56 for (IExtension ext : exts) {
57
58 IConfigurationElement[] els = ext.getConfigurationElements();
59 for (IConfigurationElement el : els) {
60 if (el.getName().equals("wellbehaving-derived-feature")) {
61 processWellbehavingExtension(el);
62 } else {
63 throw new UnsupportedOperationException("Unknown configuration element " + el.getName()
64 + " in plugin.xml of " + el.getDeclaringExtension().getUniqueIdentifier());
65 }
66 }
67 }
68 }
69 }
70
71 private static void processWellbehavingExtension(IConfigurationElement el) {
72 try {
73 String packageUri = el.getAttribute("package-nsUri");
74 String featureName = el.getAttribute("feature-name");
75 String classifierName = el.getAttribute("classifier-name");
76 String contributorName = el.getContributor().getName();
77 StringBuilder featureIdBuilder = new StringBuilder();
78 if (packageUri != null) {
79 EPackage pckg = EPackage.Registry.INSTANCE.getEPackage(packageUri);
80 featureIdBuilder.append(packageUri);
81 if (pckg != null) {
82 if (classifierName != null) {
83 EClassifier clsr = pckg.getEClassifier(classifierName);
84 featureIdBuilder.append("##").append(classifierName);
85 if (clsr instanceof EClass) {
86 if (featureName != null) {
87 EClass cls = (EClass) clsr;
88 EStructuralFeature feature = cls.getEStructuralFeature(featureName);
89 featureIdBuilder.append("##").append(featureName);
90 if (feature != null) {
91 registerWellbehavingDerivedFeature(feature);
92 } else {
93 throw new IllegalStateException(String.format("Feature %s of EClass %s in package %s not found! (plug-in %s)", featureName, classifierName, packageUri, contributorName));
94 }
95 } else {
96 registerWellbehavingDerivedClass((EClass) clsr);
97 }
98 } else {
99 throw new IllegalStateException(String.format("EClassifier %s does not exist in package %s! (plug-in %s)", classifierName, packageUri, contributorName));
100 }
101 } else {
102 if(featureName != null){
103 throw new IllegalStateException(String.format("Feature name must be empty if classifier name is not set! (package %s, plug-in %s)", packageUri, contributorName));
104 }
105 registerWellbehavingDerivedPackage(pckg);
106 }
107 }
108 }
109 } catch (Exception e) {
110 final Logger logger = Logger.getLogger(WellbehavingDerivedFeatureRegistry.class);
111 logger.error("Well-behaving feature registration failed", e);
112 }
113 }
114
115 /**
116 *
117 * @param feature
118 * @return true if the feature (or its defining EClass or ) is registered as well-behaving
119 */
120 public static boolean isWellbehavingFeature(EStructuralFeature feature) {
121 if(feature == null){
122 return false;
123 } else if (contributedWellbehavingDerivedFeatures.contains(feature)) {
124 return true;
125 } else if (contributedWellbehavingDerivedClasses.contains(feature.getEContainingClass())) {
126 return true;
127 } else return contributedWellbehavingDerivedPackages.contains(feature.getEContainingClass().getEPackage());
128 }
129
130 public static void registerWellbehavingDerivedFeature(EStructuralFeature feature) {
131 contributedWellbehavingDerivedFeatures.add(feature);
132 }
133
134 public static void registerWellbehavingDerivedClass(EClass cls) {
135 contributedWellbehavingDerivedClasses.add(cls);
136 }
137
138 public static void registerWellbehavingDerivedPackage(EPackage pkg) {
139 contributedWellbehavingDerivedPackages.add(pkg);
140 }
141
142 public static Collection<EStructuralFeature> getContributedWellbehavingDerivedFeatures() {
143 return contributedWellbehavingDerivedFeatures;
144 }
145
146 public static Collection<EClass> getContributedWellbehavingDerivedClasses() {
147 return contributedWellbehavingDerivedClasses;
148 }
149
150 public static Collection<EPackage> getContributedWellbehavingDerivedPackages() {
151 return contributedWellbehavingDerivedPackages;
152 }
153
154}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java
new file mode 100644
index 00000000..3d61b1bf
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.core;
10
11import org.apache.log4j.Logger;
12import tools.refinery.viatra.runtime.base.api.BaseIndexOptions;
13
14/**
15 * @since 1.6
16 */
17public class AbstractBaseIndexStore {
18
19 protected final NavigationHelperImpl navigationHelper;
20 protected final Logger logger;
21 protected final BaseIndexOptions options;
22
23 public AbstractBaseIndexStore(NavigationHelperImpl navigationHelper, Logger logger) {
24 this.navigationHelper = navigationHelper;
25 this.logger = logger;
26 this.options = navigationHelper.getBaseIndexOptions();
27 }
28}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java
new file mode 100644
index 00000000..2094bbbe
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java
@@ -0,0 +1,451 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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 java.util.Collections;
12import java.util.HashSet;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Set;
16
17import org.apache.log4j.Logger;
18import org.eclipse.emf.ecore.EClass;
19import org.eclipse.emf.ecore.EClassifier;
20import org.eclipse.emf.ecore.EObject;
21import tools.refinery.viatra.runtime.base.api.IStructuralFeatureInstanceProcessor;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.matchers.util.IMultiset;
24
25/**
26 * Stores the indexed contents of an EMF model
27 * (includes instance model information).
28 *
29 * @author Gabor Bergmann
30 * @noextend This class is not intended to be subclassed by clients.
31 */
32public class EMFBaseIndexInstanceStore extends AbstractBaseIndexStore {
33
34 public EMFBaseIndexInstanceStore(NavigationHelperImpl navigationHelper, Logger logger) {
35 super(navigationHelper, logger);
36 }
37
38 /**
39 * since last run of after-update callbacks
40 */
41 boolean isDirty = false;
42
43 /**
44 * feature (EAttribute or EReference or equivalent String key) -> FeatureData
45 * @since 1.7
46 */
47 private Map<Object, FeatureData> featureDataMap = CollectionsFactory.createMap();
48
49 /**
50 * value -> featureKey(s);
51 * constructed on-demand, null if unused (hopefully most of the time)
52 */
53 private Map<Object, IMultiset<Object>> valueToFeatureMap = null;
54
55
56 /**
57 * key (String id or EClass instance) -> instance(s)
58 */
59 private final Map<Object, Set<EObject>> instanceMap = CollectionsFactory.createMap();
60
61 /**
62 * key (String id or EDataType instance) -> multiset of value(s)
63 */
64 private final Map<Object, IMultiset<Object>> dataTypeMap = CollectionsFactory.createMap();
65
66 /**
67 * Bundles all instance store data specific to a given binary feature.
68 *
69 * <p> TODO: specialize for to-one features and unique to-many features
70 * <p> TODO: on-demand construction of valueToHolderMap
71 *
72 * @author Gabor Bergmann
73 * @since 1.7
74 */
75 class FeatureData {
76 /** value -> holder(s) */
77 private Map<Object, IMultiset<EObject>> valueToHolderMap = CollectionsFactory.createMap();
78 /**
79 * holder -> value(s);
80 * constructed on-demand, null if unused
81 */
82 private Map<EObject, IMultiset<Object>> holderToValueMap;
83
84 /**
85 * feature (EAttribute or EReference) or its string key (in dynamic EMF mode)
86 */
87 private Object featureKey;
88
89 /**
90 * @return feature (EAttribute or EReference) or its string key (in dynamic EMF mode)
91 */
92 public Object getFeatureKey() {
93 return featureKey;
94 }
95
96 @Override
97 public String toString() {
98 return this.getClass().getSimpleName() + ":" + featureKey;
99 }
100
101 /**
102 * @return true if this was the first time the value was added to this feature of this holder (false is only
103 * expected for non-unique features)
104 */
105 boolean insertFeatureTuple(boolean unique, final Object value, final EObject holder) {
106 // TODO we currently assume V2H map exists
107 boolean changed = addToValueToHolderMap(value, holder);
108 if (holderToValueMap != null) {
109 addToHolderToValueMap(value, holder);
110 }
111
112 if (unique && !changed) {
113 navigationHelper.logIncidentFeatureTupleInsertion(value, holder, featureKey);
114 }
115 return changed;
116 }
117
118 /**
119 * @return true if this was the last duplicate of the value added to this feature of this holder (false is only
120 * expected for non-unique features)
121 */
122 boolean removeFeatureTuple(boolean unique, final Object value, final EObject holder) {
123 Object featureKey = getFeatureKey();
124 try {
125 // TODO we currently assume V2H map exists
126 boolean changed = removeFromValueToHolderMap(value, holder);
127 if (holderToValueMap != null) {
128 removeFromHolderToValueMap(value, holder);
129 }
130
131 if (unique && !changed) {
132 navigationHelper.logIncidentFeatureTupleRemoval(value, holder, featureKey);
133 }
134 return changed;
135 } catch (IllegalStateException ex) {
136 navigationHelper.logIncidentFeatureTupleRemoval(value, holder, featureKey);
137 return false;
138 }
139 }
140
141
142 protected boolean addToHolderToValueMap(Object value, EObject holder) {
143 IMultiset<Object> values = holderToValueMap.computeIfAbsent(holder,
144 CollectionsFactory::emptyMultiset);
145 boolean changed = values.addOne(value);
146 return changed;
147 }
148
149 protected boolean addToValueToHolderMap(final Object value, final EObject holder) {
150 IMultiset<EObject> holders = valueToHolderMap.computeIfAbsent(value,
151 CollectionsFactory::emptyMultiset);
152 boolean changed = holders.addOne(holder);
153 return changed;
154 }
155
156 protected boolean removeFromHolderToValueMap(Object value, EObject holder) throws IllegalStateException {
157 IMultiset<Object> values = holderToValueMap.get(holder);
158 if (values == null)
159 throw new IllegalStateException();
160 boolean changed = values.removeOne(value);
161 if (changed && values.isEmpty())
162 holderToValueMap.remove(holder);
163 return changed;
164 }
165 protected boolean removeFromValueToHolderMap(final Object value, final EObject holder) throws IllegalStateException {
166 IMultiset<EObject> holders = valueToHolderMap.get(value);
167 if (holders == null)
168 throw new IllegalStateException();
169 boolean changed = holders.removeOne(holder);
170 if (changed && holders.isEmpty())
171 valueToHolderMap.remove(value);
172 return changed;
173 }
174
175 protected Map<EObject, IMultiset<Object>> getHolderToValueMap() {
176 if (holderToValueMap == null) {
177 holderToValueMap = CollectionsFactory.createMap();
178
179 // TODO we currently assume V2H map exists
180 for (Entry<Object, IMultiset<EObject>> entry : valueToHolderMap.entrySet()) {
181 Object value = entry.getKey();
182 IMultiset<EObject> holders = entry.getValue();
183 for (EObject holder : holders.distinctValues()) {
184 int count = holders.getCount(holder);
185
186 IMultiset<Object> valuesOfHolder = holderToValueMap.computeIfAbsent(holder,
187 CollectionsFactory::emptyMultiset);
188 valuesOfHolder.addPositive(value, count);
189 }
190 }
191 }
192 return holderToValueMap;
193 }
194 protected Map<Object, IMultiset<EObject>> getValueToHolderMap() {
195 // TODO we currently assume V2H map exists
196 return valueToHolderMap;
197 }
198
199 public void forEach(IStructuralFeatureInstanceProcessor processor) {
200 // TODO we currently assume V2H map exists
201 if (valueToHolderMap != null) {
202 for (Entry<Object, IMultiset<EObject>> entry : valueToHolderMap.entrySet()) {
203 Object value = entry.getKey();
204 for (EObject eObject : entry.getValue().distinctValues()) {
205 processor.process(eObject, value);
206 }
207 }
208 } else throw new UnsupportedOperationException("TODO implement");
209 }
210
211 public Set<EObject> getAllDistinctHolders() {
212 return getHolderToValueMap().keySet();
213 }
214
215 public Set<Object> getAllDistinctValues() {
216 return getValueToHolderMap().keySet();
217 }
218
219 public Set<EObject> getDistinctHoldersOfValue(Object value) {
220 IMultiset<EObject> holdersMultiset = getValueToHolderMap().get(value);
221 if (holdersMultiset == null)
222 return Collections.emptySet();
223 else return holdersMultiset.distinctValues();
224 }
225
226
227 public Set<Object> getDistinctValuesOfHolder(EObject holder) {
228 IMultiset<Object> valuesMultiset = getHolderToValueMap().get(holder);
229 if (valuesMultiset == null)
230 return Collections.emptySet();
231 else return valuesMultiset.distinctValues();
232 }
233
234 public boolean isInstance(EObject source, Object target) {
235 // TODO we currently assume V2H map exists
236 if (valueToHolderMap != null) {
237 IMultiset<EObject> holders = valueToHolderMap.get(target);
238 return holders != null && holders.containsNonZero(source);
239 } else throw new UnsupportedOperationException("TODO implement");
240 }
241
242
243 }
244
245
246 FeatureData getFeatureData(Object featureKey) {
247 FeatureData data = featureDataMap.get(featureKey);
248 if (data == null) {
249 data = createFeatureData(featureKey);
250 featureDataMap.put(featureKey, data);
251 }
252 return data;
253 }
254
255 /**
256 * TODO: specialize for to-one features and unique to-many features
257 */
258 protected FeatureData createFeatureData(Object featureKey) {
259 FeatureData data = new FeatureData();
260 data.featureKey = featureKey;
261 return data;
262 }
263
264
265
266
267 protected void insertFeatureTuple(final Object featureKey, boolean unique, final Object value, final EObject holder) {
268 boolean changed = getFeatureData(featureKey).insertFeatureTuple(unique, value, holder);
269 if (changed) { // if not duplicated
270
271 if (valueToFeatureMap != null) {
272 insertIntoValueToFeatureMap(featureKey, value);
273 }
274
275 isDirty = true;
276 navigationHelper.notifyFeatureListeners(holder, featureKey, value, true);
277 }
278 }
279
280 protected void removeFeatureTuple(final Object featureKey, boolean unique, final Object value, final EObject holder) {
281 boolean changed = getFeatureData(featureKey).removeFeatureTuple(unique, value, holder);
282 if (changed) { // if not duplicated
283
284 if (valueToFeatureMap != null) {
285 removeFromValueToFeatureMap(featureKey, value);
286 }
287
288 isDirty = true;
289 navigationHelper.notifyFeatureListeners(holder, featureKey, value, false);
290 }
291 }
292
293
294 public Set<Object> getFeatureKeysPointingTo(Object target) {
295 final IMultiset<Object> sources = getValueToFeatureMap().get(target);
296 return sources == null ? Collections.emptySet() : sources.distinctValues();
297 }
298
299 protected Map<Object, IMultiset<Object>> getValueToFeatureMap() {
300 if (valueToFeatureMap == null) { // must be inverted from feature data
301 valueToFeatureMap = CollectionsFactory.createMap();
302 for (FeatureData featureData : featureDataMap.values()) {
303 final Object featureKey = featureData.getFeatureKey();
304 featureData.forEach((source, target) -> insertIntoValueToFeatureMap(featureKey, target));
305 }
306 }
307 return valueToFeatureMap;
308 }
309
310 protected void insertIntoValueToFeatureMap(final Object featureKey, Object target) {
311 IMultiset<Object> featureKeys = valueToFeatureMap.computeIfAbsent(target, CollectionsFactory::emptyMultiset);
312 featureKeys.addOne(featureKey);
313 }
314 protected void removeFromValueToFeatureMap(final Object featureKey, final Object value) {
315 IMultiset<Object> featureKeys = valueToFeatureMap.get(value);
316 if (featureKeys == null)
317 throw new IllegalStateException();
318 featureKeys.removeOne(featureKey);
319 if (featureKeys.isEmpty())
320 valueToFeatureMap.remove(value);
321 }
322
323 // START ********* InstanceSet *********
324 public Set<EObject> getInstanceSet(final Object keyClass) {
325 return instanceMap.get(keyClass);
326 }
327
328 public void removeInstanceSet(final Object keyClass) {
329 instanceMap.remove(keyClass);
330 }
331
332 public void insertIntoInstanceSet(final Object keyClass, final EObject value) {
333 Set<EObject> set = instanceMap.computeIfAbsent(keyClass, CollectionsFactory::emptySet);
334
335 if (!set.add(value)) {
336 navigationHelper.logIncidentInstanceInsertion(keyClass, value);
337 } else {
338 isDirty = true;
339 navigationHelper.notifyInstanceListeners(keyClass, value, true);
340 }
341 }
342
343 public void removeFromInstanceSet(final Object keyClass, final EObject value) {
344 final Set<EObject> set = instanceMap.get(keyClass);
345 if (set != null) {
346 if(!set.remove(value)) {
347 navigationHelper.logIncidentInstanceRemoval(keyClass, value);
348 } else {
349 if (set.isEmpty()) {
350 instanceMap.remove(keyClass);
351 }
352 isDirty = true;
353 navigationHelper.notifyInstanceListeners(keyClass, value, false);
354 }
355 } else {
356 navigationHelper.logIncidentInstanceRemoval(keyClass, value);
357 }
358
359 }
360
361
362
363 // END ********* InstanceSet *********
364
365 // START ********* DataTypeMap *********
366 public Set<Object> getDistinctDataTypeInstances(final Object keyType) {
367 IMultiset<Object> values = dataTypeMap.get(keyType);
368 return values == null ? Collections.emptySet() : values.distinctValues();
369 }
370
371 public void removeDataTypeMap(final Object keyType) {
372 dataTypeMap.remove(keyType);
373 }
374
375 public void insertIntoDataTypeMap(final Object keyType, final Object value) {
376 IMultiset<Object> valMap = dataTypeMap.computeIfAbsent(keyType, CollectionsFactory::emptyMultiset);
377 final boolean firstOccurrence = valMap.addOne(value);
378
379 isDirty = true;
380 navigationHelper.notifyDataTypeListeners(keyType, value, true, firstOccurrence);
381 }
382
383 public void removeFromDataTypeMap(final Object keyType, final Object value) {
384 final IMultiset<Object> valMap = dataTypeMap.get(keyType);
385 if (valMap != null) {
386 final boolean lastOccurrence = valMap.removeOne(value);
387 if (lastOccurrence && valMap.isEmpty()) {
388 dataTypeMap.remove(keyType);
389 }
390
391 isDirty = true;
392 navigationHelper.notifyDataTypeListeners(keyType, value, false, lastOccurrence);
393 }
394 }
395
396 // END ********* DataTypeMap *********
397
398 protected Set<EObject> getHoldersOfFeature(Object featureKey) {
399 FeatureData featureData = getFeatureData(featureKey);
400 return featureData.getAllDistinctHolders();
401 }
402 protected Set<Object> getValuesOfFeature(Object featureKey) {
403 FeatureData featureData = getFeatureData(featureKey);
404 return featureData.getAllDistinctValues();
405 }
406
407 /**
408 * Returns all EClasses that currently have direct instances cached by the index.
409 * <p>
410 * Supertypes will not be returned, unless they have direct instances in the model as well. If not in
411 * <em>wildcard mode</em>, only registered EClasses and their subtypes will be returned.
412 * <p>
413 * Note for advanced users: if a type is represented by multiple EClass objects, one of them is chosen as
414 * representative and returned.
415 */
416 public Set<EClass> getAllCurrentClasses() {
417 final Set<EClass> result = CollectionsFactory.createSet();
418 final Set<Object> classifierKeys = instanceMap.keySet();
419 for (final Object classifierKey : classifierKeys) {
420 final EClassifier knownClassifier = navigationHelper.metaStore.getKnownClassifierForKey(classifierKey);
421 if (knownClassifier instanceof EClass) {
422 result.add((EClass) knownClassifier);
423 }
424 }
425 return result;
426 }
427
428 Set<Object> getOldValuesForHolderAndFeature(EObject source, Object featureKey) {
429 // while this is slower than using the holderToFeatureToValueMap, we do not want to construct that to avoid
430 // memory overhead
431 Map<Object, IMultiset<EObject>> oldValuesToHolders = getFeatureData(featureKey).valueToHolderMap;
432 Set<Object> oldValues = new HashSet<Object>();
433 for (Entry<Object, IMultiset<EObject>> entry : oldValuesToHolders.entrySet()) {
434 if (entry.getValue().containsNonZero(source)) {
435 oldValues.add(entry.getKey());
436 }
437 }
438 return oldValues;
439 }
440
441 protected void forgetFeature(Object featureKey) {
442 FeatureData removed = featureDataMap.remove(featureKey);
443 if (valueToFeatureMap != null) {
444 for (Object value : removed.getAllDistinctValues()) {
445 removeFromValueToFeatureMap(featureKey, value);
446 }
447 }
448 }
449
450
451}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java
new file mode 100644
index 00000000..52ca3a53
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java
@@ -0,0 +1,380 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, 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.eclipse.emf.common.util.Enumerator;
12import org.eclipse.emf.ecore.*;
13import tools.refinery.viatra.runtime.base.api.BaseIndexOptions;
14import tools.refinery.viatra.runtime.base.api.IndexingLevel;
15import tools.refinery.viatra.runtime.base.api.InstanceListener;
16import tools.refinery.viatra.runtime.base.exception.ViatraBaseException;
17import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
18import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType;
19import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
20import tools.refinery.viatra.runtime.matchers.util.IMultiLookup;
21import tools.refinery.viatra.runtime.matchers.util.Preconditions;
22
23import java.util.*;
24import java.util.Map.Entry;
25
26/**
27 * Stores the indexed metamodel information.
28 *
29 * @author Gabor Bergmann
30 * @noextend This class is not intended to be subclassed by clients.
31 */
32public class EMFBaseIndexMetaStore {
33
34 private static final EClass EOBJECT_CLASS = EcorePackage.eINSTANCE.getEObject();
35 private final boolean isDynamicModel;
36 private NavigationHelperImpl navigationHelper;
37
38 /**
39 *
40 */
41 public EMFBaseIndexMetaStore(final NavigationHelperImpl navigationHelper) {
42 this.navigationHelper = navigationHelper;
43 final BaseIndexOptions options = navigationHelper.getBaseIndexOptions();
44 this.isDynamicModel = options.isDynamicEMFMode();
45 }
46
47 /**
48 * Supports collision detection and EEnum canonicalization. Used for all EPackages that have types whose instances
49 * were encountered at least once.
50 */
51 private final Set<EPackage> knownPackages = new HashSet<EPackage>();
52
53 /**
54 * Field variable because it is needed for collision detection. Used for all EClasses whose instances were
55 * encountered at least once.
56 */
57 private final Set<EClassifier> knownClassifiers = new HashSet<EClassifier>();
58 /**
59 * Field variable because it is needed for collision detection. Used for all EStructuralFeatures whose instances
60 * were encountered at least once.
61 */
62 private final Set<EStructuralFeature> knownFeatures = new HashSet<EStructuralFeature>();
63
64 /**
65 * (EClass or String ID) -> all subtypes in knownClasses
66 */
67 private final Map<Object, Set<Object>> subTypeMap = new HashMap<Object, Set<Object>>();
68 /**
69 * (EClass or String ID) -> all supertypes in knownClasses
70 */
71 private final Map<Object, Set<Object>> superTypeMap = new HashMap<Object, Set<Object>>();
72
73 /**
74 * EPacakge NsURI -> EPackage instances; this is instance-level to detect collisions
75 */
76 private final IMultiLookup<String, EPackage> uniqueIDToPackage = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
77
78 /**
79 * static maps between metamodel elements and their unique IDs
80 */
81 private final Map<EClassifier, String> uniqueIDFromClassifier = new HashMap<EClassifier, String>();
82 private final Map<ETypedElement, String> uniqueIDFromTypedElement = new HashMap<ETypedElement, String>();
83 private final Map<Enumerator, String> uniqueIDFromEnumerator = new HashMap<Enumerator, String>();
84 private final IMultiLookup<String, EClassifier> uniqueIDToClassifier = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
85 private final IMultiLookup<String, ETypedElement> uniqueIDToTypedElement = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
86 private final IMultiLookup<String, Enumerator> uniqueIDToEnumerator = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class);
87 private final Map<String, Enumerator> uniqueIDToCanonicalEnumerator = new HashMap<String, Enumerator>();
88
89 /**
90 * Map from enum classes generated for {@link EEnum}s to the actual EEnum.
91 */
92 private Map<Class<?>, EEnum> generatedEENumClasses = new HashMap<Class<?>, EEnum>();
93
94 /**
95 * @return the eObjectClassKey
96 */
97 public Object getEObjectClassKey() {
98 if (eObjectClassKey == null) {
99 eObjectClassKey = toKey(EOBJECT_CLASS);
100 }
101 return eObjectClassKey;
102 }
103 private Object eObjectClassKey = null;
104
105 protected Object toKey(final EClassifier classifier) {
106 if (isDynamicModel) {
107 return toKeyDynamicInternal(classifier);
108 } else {
109 maintainMetamodel(classifier);
110 return classifier;
111 }
112 }
113
114 protected String toKeyDynamicInternal(final EClassifier classifier) {
115 String id = uniqueIDFromClassifier.get(classifier);
116 if (id == null) {
117 Preconditions.checkArgument(!classifier.eIsProxy(),
118 "Classifier %s is an unresolved proxy", classifier);
119 id = classifier.getEPackage().getNsURI() + "##" + classifier.getName();
120 uniqueIDFromClassifier.put(classifier, id);
121 uniqueIDToClassifier.addPair(id, classifier);
122 // metamodel maintenance will call back toKey(), but now the ID maps are already filled
123 maintainMetamodel(classifier);
124 }
125 return id;
126 }
127
128 protected String enumToKeyDynamicInternal(Enumerator enumerator) {
129 String id = uniqueIDFromEnumerator.get(enumerator);
130 if (id == null) {
131 if (enumerator instanceof EEnumLiteral) {
132 EEnumLiteral enumLiteral = (EEnumLiteral) enumerator;
133 final EEnum eEnum = enumLiteral.getEEnum();
134 maintainMetamodel(eEnum);
135
136 id = constructEnumID(eEnum.getEPackage().getNsURI(), eEnum.getName(), enumLiteral.getLiteral());
137
138 // there might be a generated enum for this enum literal!
139 // generated enum should pre-empt the ecore enum literal as canonical enumerator
140 Enumerator instanceEnum = enumLiteral.getInstance();
141 if (instanceEnum != null && !uniqueIDToCanonicalEnumerator.containsKey(id)) {
142 uniqueIDToCanonicalEnumerator.put(id, instanceEnum);
143 }
144 // if generated enum not found... delay selection of canonical enumerator
145 } else { // generated enum
146 final EEnum eEnum = generatedEENumClasses.get(enumerator.getClass());
147 if (eEnum != null)
148 id = constructEnumID(eEnum.getEPackage().getNsURI(), eEnum.getName(), enumerator.getLiteral());
149 else
150 id = constructEnumID("unkownPackage URI", enumerator.getClass().getSimpleName(),
151 enumerator.getLiteral());
152
153 // generated enum should pre-empt the ecore enum literal as canonical enumerator
154 if (!uniqueIDToCanonicalEnumerator.containsKey(id)) {
155 uniqueIDToCanonicalEnumerator.put(id, enumerator);
156 }
157 }
158 uniqueIDFromEnumerator.put(enumerator, id);
159 uniqueIDToEnumerator.addPair(id, enumerator);
160 }
161 return id;
162 }
163
164 protected String constructEnumID(String nsURI, String name, String literal) {
165 return String.format("%s##%s##%s", nsURI, name, literal);
166 }
167
168 protected Object toKey(final EStructuralFeature feature) {
169 if (isDynamicModel) {
170 String id = uniqueIDFromTypedElement.get(feature);
171 if (id == null) {
172 Preconditions.checkArgument(!feature.eIsProxy(),
173 "Element %s is an unresolved proxy", feature);
174 id = toKeyDynamicInternal((EClassifier) feature.eContainer()) + "##" + feature.getEType().getName()
175 + "##" + feature.getName();
176 uniqueIDFromTypedElement.put(feature, id);
177 uniqueIDToTypedElement.addPair(id, feature);
178 // metamodel maintenance will call back toKey(), but now the ID maps are already filled
179 maintainMetamodel(feature);
180 }
181 return id;
182 } else {
183 maintainMetamodel(feature);
184 return feature;
185 }
186 }
187
188 protected Enumerator enumToCanonicalDynamicInternal(final Enumerator value) {
189 final String key = enumToKeyDynamicInternal(value);
190 Enumerator canonicalEnumerator = uniqueIDToCanonicalEnumerator.computeIfAbsent(key,
191 // if no canonical version appointed yet, appoint first version
192 k -> uniqueIDToEnumerator.lookup(k).iterator().next());
193 return canonicalEnumerator;
194 }
195
196 /**
197 * If in dynamic EMF mode, substitutes enum literals with a canonical version of the enum literal.
198 */
199 protected Object toInternalValueRepresentation(final Object value) {
200 if (isDynamicModel) {
201 if (value instanceof Enumerator)
202 return enumToCanonicalDynamicInternal((Enumerator) value);
203 else
204 return value;
205 } else {
206 return value;
207 }
208 }
209
210 /**
211 * Checks the {@link EStructuralFeature}'s source and target {@link EPackage} for NsURI collision. An error message
212 * will be logged if a model element from an other {@link EPackage} instance with the same NsURI has been already
213 * processed. The error message will be logged only for the first time for a given {@link EPackage} instance.
214 *
215 * @param classifier
216 * the classifier instance
217 */
218 protected void maintainMetamodel(final EStructuralFeature feature) {
219 if (!knownFeatures.contains(feature)) {
220 knownFeatures.add(feature);
221 maintainMetamodel(feature.getEContainingClass());
222 maintainMetamodel(feature.getEType());
223 }
224 }
225
226 /**
227 * put subtype information into cache
228 */
229 protected void maintainMetamodel(final EClassifier classifier) {
230 if (!knownClassifiers.contains(classifier)) {
231 checkEPackage(classifier);
232 knownClassifiers.add(classifier);
233
234 if (classifier instanceof EClass) {
235 final EClass clazz = (EClass) classifier;
236 final Object clazzKey = toKey(clazz);
237 for (final EClass superType : clazz.getEAllSuperTypes()) {
238 maintainTypeHierarhyInternal(clazzKey, toKey(superType));
239 }
240 maintainTypeHierarhyInternal(clazzKey, getEObjectClassKey());
241 } else if (classifier instanceof EEnum) {
242 EEnum eEnum = (EEnum) classifier;
243
244 if (isDynamicModel) {
245 // if there is a generated enum class, save this model element for describing that class
246 if (eEnum.getInstanceClass() != null)
247 generatedEENumClasses.put(eEnum.getInstanceClass(), eEnum);
248
249 for (EEnumLiteral eEnumLiteral : eEnum.getELiterals()) {
250 // create string ID; register generated enum values
251 enumToKeyDynamicInternal(eEnumLiteral);
252 }
253 }
254 }
255 }
256 }
257
258 /**
259 * Checks the {@link EClassifier}'s {@link EPackage} for NsURI collision. An error message will be logged if a model
260 * element from an other {@link EPackage} instance with the same NsURI has been already processed. The error message
261 * will be logged only for the first time for a given {@link EPackage} instance.
262 *
263 * @param classifier
264 * the classifier instance
265 */
266 protected void checkEPackage(final EClassifier classifier) {
267 final EPackage ePackage = classifier.getEPackage();
268 if (knownPackages.add(ePackage)) { // this is a new EPackage
269 final String nsURI = ePackage.getNsURI();
270 final IMemoryView<EPackage> packagesOfURI = uniqueIDToPackage.lookupOrEmpty(nsURI);
271 if (!packagesOfURI.containsNonZero(ePackage)) { // this should be true
272 uniqueIDToPackage.addPair(nsURI, ePackage);
273 // collision detection between EPackages (disabled in dynamic model mode)
274 if (!isDynamicModel && packagesOfURI.size() == 2) { // only report the issue if the new EPackage
275 // instance is the second for the same URI
276 navigationHelper.processingError(
277 new ViatraBaseException("NsURI (" + nsURI
278 + ") collision detected between different instances of EPackages. If this is normal, try using dynamic EMF mode."),
279 "process new metamodel elements.");
280 }
281 }
282 }
283 }
284
285 /**
286 * Maintains subtype hierarchy
287 *
288 * @param subClassKey
289 * EClass or String id of subclass
290 * @param superClassKey
291 * EClass or String id of superclass
292 */
293 protected void maintainTypeHierarhyInternal(final Object subClassKey, final Object superClassKey) {
294 // update observed class and instance listener tables according to new subtype information
295 Map<Object, IndexingLevel> allObservedClasses = navigationHelper.getAllObservedClassesInternal();
296 if (allObservedClasses.containsKey(superClassKey)) {
297 // we know that there are no known subtypes of subClassKey at this point, so a single insert should suffice
298 allObservedClasses.put(subClassKey, allObservedClasses.get(superClassKey));
299 }
300 final Map<Object, Map<InstanceListener, Set<EClass>>> instanceListeners = navigationHelper.peekInstanceListeners();
301 if (instanceListeners != null) { // table already constructed
302 for (final Entry<InstanceListener, Set<EClass>> entry : instanceListeners.getOrDefault(superClassKey, Collections.emptyMap()).entrySet()) {
303 final InstanceListener listener = entry.getKey();
304 for (final EClass subscriptionType : entry.getValue()) {
305 navigationHelper.addInstanceListenerInternal(listener, subscriptionType, subClassKey);
306 }
307 }
308 }
309
310 // update subtype maps
311 Set<Object> subTypes = subTypeMap.computeIfAbsent(superClassKey, k -> new HashSet<>());
312 subTypes.add(subClassKey);
313 Set<Object> superTypes = superTypeMap.computeIfAbsent(subClassKey, k -> new HashSet<>());
314 superTypes.add(superClassKey);
315 }
316
317 /**
318 * @return the subTypeMap
319 */
320 protected Map<Object, Set<Object>> getSubTypeMap() {
321 return subTypeMap;
322 }
323
324 protected Map<Object, Set<Object>> getSuperTypeMap() {
325 return superTypeMap;
326 }
327
328 /**
329 * Returns the corresponding {@link EStructuralFeature} instance for the id.
330 *
331 * @param featureId
332 * the id of the feature
333 * @return the {@link EStructuralFeature} instance
334 */
335 public EStructuralFeature getKnownFeature(final String featureId) {
336 final IMemoryView<ETypedElement> features = uniqueIDToTypedElement.lookup(featureId);
337 if (features != null && !features.isEmpty()) {
338 final ETypedElement next = features.iterator().next();
339 if (next instanceof EStructuralFeature) {
340 return (EStructuralFeature) next;
341 }
342 }
343 return null;
344
345 }
346
347 public EStructuralFeature getKnownFeatureForKey(Object featureKey) {
348 EStructuralFeature feature;
349 if (isDynamicModel) {
350 feature = getKnownFeature((String) featureKey);
351 } else {
352 feature = (EStructuralFeature) featureKey;
353 }
354 return feature;
355 }
356
357 /**
358 * Returns the corresponding {@link EClassifier} instance for the id.
359 */
360 public EClassifier getKnownClassifier(final String key) {
361 final IMemoryView<EClassifier> classifiersOfThisID = uniqueIDToClassifier.lookup(key);
362 if (classifiersOfThisID != null && !classifiersOfThisID.isEmpty()) {
363 return classifiersOfThisID.iterator().next();
364 } else {
365 return null;
366 }
367 }
368
369 public EClassifier getKnownClassifierForKey(Object classifierKey) {
370 EClassifier cls;
371 if (isDynamicModel) {
372 cls = getKnownClassifier((String) classifierKey);
373 } else {
374 cls = (EClassifier) classifierKey;
375 }
376 return cls;
377 }
378
379
380}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java
new file mode 100644
index 00000000..de66de78
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java
@@ -0,0 +1,71 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.core;
10
11import java.util.HashMap;
12import java.util.Map;
13
14import org.apache.log4j.Logger;
15import org.eclipse.emf.ecore.EClassifier;
16import org.eclipse.emf.ecore.EStructuralFeature;
17
18/**
19 * @author Grill Balázs
20 * @noextend This class is not intended to be subclassed by clients.
21 */
22public class EMFBaseIndexStatisticsStore extends AbstractBaseIndexStore {
23
24 /**
25 * A common map is used to store instance/value statistics. The key can be an {@link EClassifier},
26 * {@link EStructuralFeature} or a String ID.
27 */
28 private final Map<Object, Integer> stats = new HashMap<Object, Integer>();
29
30 public EMFBaseIndexStatisticsStore(NavigationHelperImpl navigationHelper, Logger logger) {
31 super(navigationHelper, logger);
32 }
33 public void addFeature(Object element, Object feature){
34 addInstance(feature);
35 }
36
37 public void removeFeature(Object element, Object feature){
38 removeInstance(feature);
39 }
40
41 public void addInstance(Object key){
42 Integer v = stats.get(key);
43 stats.put(key, v == null ? 1 : v+1);
44 }
45
46 public void removeInstance(Object key){
47 Integer v = stats.get(key);
48 if(v == null || v <= 0) {
49 navigationHelper.logIncidentStatRemoval(key);
50 return;
51 }
52 if (v.intValue() == 1){
53 stats.remove(key);
54 }else{
55 stats.put(key, v-1);
56 }
57 }
58 public int countInstances(Object key){
59 Integer v = stats.get(key);
60 return v == null ? 0 : v.intValue();
61 }
62
63 public void removeType(Object key){
64 stats.remove(key);
65 }
66
67 public int countFeatures(Object feature) {
68 return countInstances(feature);
69 }
70
71}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java
new file mode 100644
index 00000000..35e590f2
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java
@@ -0,0 +1,137 @@
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
10package tools.refinery.viatra.runtime.base.core;
11
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Set;
15
16import org.eclipse.emf.ecore.EClass;
17import org.eclipse.emf.ecore.EObject;
18import org.eclipse.emf.ecore.EReference;
19import tools.refinery.viatra.runtime.base.api.NavigationHelper;
20import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource;
21import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver;
22import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
23import tools.refinery.viatra.runtime.matchers.util.IMemoryView;
24import tools.refinery.viatra.runtime.matchers.util.IMultiset;
25
26// TODO IBiDirectionalGraphDataSource
27public class EMFDataSource implements IGraphDataSource<EObject> {
28
29 private List<IGraphObserver<EObject>> observers;
30 private Set<EReference> references;
31 private Set<EClass> classes;
32 private NavigationHelper navigationHelper;
33 private IMultiset<EObject> allEObjects; // contains objects even if only appearing as sources or targets
34
35 /**
36 * @param navigationHelper
37 * @param references
38 * @param classes
39 * additional classes to treat as nodes. Source and target classes of references need not be added.
40 */
41 public EMFDataSource(NavigationHelper navigationHelper, Set<EReference> references, Set<EClass> classes) {
42 this.references = references;
43 this.classes = classes;
44 this.observers = new LinkedList<IGraphObserver<EObject>>();
45 this.navigationHelper = navigationHelper;
46 }
47
48 @Override
49 public Set<EObject> getAllNodes() {
50 return getAllEObjects().distinctValues();
51 }
52
53 @Override
54 public IMemoryView<EObject> getTargetNodes(EObject source) {
55 IMultiset<EObject> targetNodes = CollectionsFactory.createMultiset();
56
57 for (EReference ref : references) {
58 final Set<EObject> referenceValues = navigationHelper.getReferenceValues(source, ref);
59 for (EObject referenceValue : referenceValues) {
60 targetNodes.addOne(referenceValue);
61 }
62 }
63
64 return targetNodes;
65 }
66
67 @Override
68 public void attachObserver(IGraphObserver<EObject> go) {
69 observers.add(go);
70 }
71
72 @Override
73 public void attachAsFirstObserver(IGraphObserver<EObject> observer) {
74 observers.add(0, observer);
75 }
76
77 @Override
78 public void detachObserver(IGraphObserver<EObject> go) {
79 observers.remove(go);
80 }
81
82 public void notifyEdgeInserted(EObject source, EObject target) {
83 nodeAdditionInternal(source);
84 nodeAdditionInternal(target);
85 for (IGraphObserver<EObject> o : observers) {
86 o.edgeInserted(source, target);
87 }
88 }
89
90 public void notifyEdgeDeleted(EObject source, EObject target) {
91 for (IGraphObserver<EObject> o : observers) {
92 o.edgeDeleted(source, target);
93 }
94 nodeRemovalInternal(source);
95 nodeRemovalInternal(target);
96 }
97
98 public void notifyNodeInserted(EObject node) {
99 nodeAdditionInternal(node);
100 }
101
102 public void notifyNodeDeleted(EObject node) {
103 nodeRemovalInternal(node);
104 }
105
106 private void nodeAdditionInternal(EObject node) {
107 if (allEObjects.addOne(node))
108 for (IGraphObserver<EObject> o : observers) {
109 o.nodeInserted(node);
110 }
111 }
112
113 private void nodeRemovalInternal(EObject node) {
114 if (getAllEObjects().removeOne(node))
115 for (IGraphObserver<EObject> o : observers) {
116 o.nodeDeleted(node);
117 }
118 }
119
120 protected IMultiset<EObject> getAllEObjects() {
121 if (allEObjects == null) {
122 allEObjects = CollectionsFactory.createMultiset();
123 for (EClass clazz : classes) {
124 for (EObject obj : navigationHelper.getAllInstances(clazz)) {
125 allEObjects.addOne(obj);
126 }
127 }
128 for (EReference ref : references) {
129 navigationHelper.processAllFeatureInstances(ref, (source, target) -> {
130 allEObjects.addOne(source);
131 allEObjects.addOne((EObject) target);
132 });
133 }
134 }
135 return allEObjects;
136 }
137}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java
new file mode 100644
index 00000000..39271c5b
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java
@@ -0,0 +1,750 @@
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 * Note: this file contains methods copied from EContentAdapter.java of the EMF project
10 *******************************************************************************/
11package tools.refinery.viatra.runtime.base.core;
12
13import java.lang.reflect.InvocationTargetException;
14import java.util.Collection;
15import java.util.List;
16import java.util.Objects;
17import java.util.concurrent.Callable;
18
19import org.eclipse.emf.common.notify.Adapter;
20import org.eclipse.emf.common.notify.Notification;
21import org.eclipse.emf.common.notify.Notifier;
22import org.eclipse.emf.common.notify.impl.AdapterImpl;
23import org.eclipse.emf.common.util.EList;
24import org.eclipse.emf.ecore.EObject;
25import org.eclipse.emf.ecore.EReference;
26import org.eclipse.emf.ecore.EStructuralFeature;
27import org.eclipse.emf.ecore.InternalEObject;
28import org.eclipse.emf.ecore.resource.Resource;
29import org.eclipse.emf.ecore.resource.ResourceSet;
30import org.eclipse.emf.ecore.util.EContentAdapter;
31import tools.refinery.viatra.runtime.base.api.BaseIndexOptions;
32import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter;
33import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter;
34import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension;
35import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor;
36import tools.refinery.viatra.runtime.base.core.NavigationHelperVisitor.ChangeVisitor;
37
38/**
39 * Content Adapter that recursively attaches itself to the containment hierarchy of an EMF model.
40 * The purpose is to gather the contents of the model, and to subscribe to model change notifications.
41 *
42 * <p> Originally, this was implemented as a subclass of {@link EContentAdapter}.
43 * Because of Bug 490105, EContentAdapter is no longer a superclass; its code is copied over with modifications.
44 * See {@link EContentAdapter} header for original authorship and copyright information.
45 *
46 * @author Gabor Bergmann
47 * @see EContentAdapter
48 * @noextend This class is not intended to be subclassed by clients.
49 */
50public class NavigationHelperContentAdapter extends AdapterImpl {
51
52 private final NavigationHelperImpl navigationHelper;
53
54
55
56 // move optimization to avoid removing and re-adding entire subtrees
57 EObject ignoreInsertionAndDeletion;
58 // Set<EObject> ignoreRootInsertion = new HashSet<EObject>();
59 // Set<EObject> ignoreRootDeletion = new HashSet<EObject>();
60
61 private final EMFModelComprehension comprehension;
62
63 private IBaseIndexObjectFilter objectFilterConfiguration;
64 private IBaseIndexResourceFilter resourceFilterConfiguration;
65
66
67
68 private EMFVisitor removalVisitor;
69 private EMFVisitor insertionVisitor;
70
71 public NavigationHelperContentAdapter(final NavigationHelperImpl navigationHelper) {
72 this.navigationHelper = navigationHelper;
73 final BaseIndexOptions options = this.navigationHelper.getBaseIndexOptions();
74 objectFilterConfiguration = options.getObjectFilterConfiguration();
75 resourceFilterConfiguration = options.getResourceFilterConfiguration();
76 this.comprehension = navigationHelper.getComprehension();
77
78 removalVisitor = initChangeVisitor(false);
79 insertionVisitor = initChangeVisitor(true);
80 }
81
82 /**
83 * Point of customization, called by constructor.
84 */
85 protected ChangeVisitor initChangeVisitor(boolean isInsertion) {
86 return new NavigationHelperVisitor.ChangeVisitor(navigationHelper, isInsertion);
87 }
88
89 // key representative of the EObject class
90
91
92 @Override
93 public void notifyChanged(final Notification notification) {
94 try {
95 this.navigationHelper.coalesceTraversals(new Callable<Void>() {
96 @Override
97 public Void call() throws Exception {
98 simpleNotifyChanged(notification);
99
100 final Object oFeature = notification.getFeature();
101 final Object oNotifier = notification.getNotifier();
102 if (oNotifier instanceof EObject && oFeature instanceof EStructuralFeature) {
103 final EObject notifier = (EObject) oNotifier;
104 final EStructuralFeature feature = (EStructuralFeature) oFeature;
105
106 final boolean notifyLightweightObservers = handleNotification(notification, notifier, feature);
107
108 if (notifyLightweightObservers) {
109 navigationHelper.notifyLightweightObservers(notifier, feature, notification);
110 }
111 } else if (oNotifier instanceof Resource) {
112 if (notification.getFeatureID(Resource.class) == Resource.RESOURCE__IS_LOADED) {
113 final Resource resource = (Resource) oNotifier;
114 if (comprehension.isLoading(resource))
115 navigationHelper.resolutionDelayingResources.add(resource);
116 else
117 navigationHelper.resolutionDelayingResources.remove(resource);
118 }
119 }
120 return null;
121 }
122 });
123 } catch (final InvocationTargetException ex) {
124 navigationHelper.processingFatal(ex.getCause(), "handling the following update notification: " + notification);
125 } catch (final Exception ex) {
126 navigationHelper.processingFatal(ex, "handling the following update notification: " + notification);
127 }
128
129 navigationHelper.notifyBaseIndexChangeListeners();
130 }
131
132 @SuppressWarnings("deprecation")
133 protected boolean handleNotification(final Notification notification, final EObject notifier,
134 final EStructuralFeature feature) {
135 final Object oldValue = notification.getOldValue();
136 final Object newValue = notification.getNewValue();
137 final int positionInt = notification.getPosition();
138 final Integer position = positionInt == Notification.NO_INDEX ? null : positionInt;
139 final int eventType = notification.getEventType();
140 boolean notifyLightweightObservers = true;
141 switch (eventType) {
142 case Notification.ADD:
143 featureUpdate(true, notifier, feature, newValue, position);
144 break;
145 case Notification.ADD_MANY:
146 for (final Object newElement : (Collection<?>) newValue) {
147 featureUpdate(true, notifier, feature, newElement, position);
148 }
149 break;
150 case Notification.CREATE:
151 notifyLightweightObservers = false;
152 break;
153 case Notification.MOVE:
154 // lightweight observers should be notified on MOVE
155 break; // currently no support for ordering
156 case Notification.REMOVE:
157 featureUpdate(false, notifier, feature, oldValue, position);
158 break;
159 case Notification.REMOVE_MANY:
160 for (final Object oldElement : (Collection<?>) oldValue) {
161 featureUpdate(false, notifier, feature, oldElement, position);
162 }
163 break;
164 case Notification.REMOVING_ADAPTER:
165 notifyLightweightObservers = false;
166 break;
167 case Notification.RESOLVE: // must be EReference
168 if (navigationHelper.isFeatureResolveIgnored(feature))
169 break; // otherwise same as SET
170 if (!feature.isMany()) { // if single-valued, can be removed from delayed resolutions
171 navigationHelper.delayedProxyResolutions.removePairOrNop(notifier, (EReference) feature);
172 }
173 featureUpdate(false, notifier, feature, oldValue, position);
174 featureUpdate(true, notifier, feature, newValue, position);
175 break;
176 case Notification.UNSET:
177 case Notification.SET:
178 if(feature.isMany() && position == null){
179 // spurious UNSET notification of entire collection
180 notifyLightweightObservers = false;
181 } else {
182 featureUpdate(false, notifier, feature, oldValue, position);
183 featureUpdate(true, notifier, feature, newValue, position);
184 }
185 break;
186 default:
187 notifyLightweightObservers = false;
188 break;
189 }
190 return notifyLightweightObservers;
191 }
192
193 protected void featureUpdate(final boolean isInsertion, final EObject notifier, final EStructuralFeature feature,
194 final Object value, final Integer position) {
195 // this is a safe visitation, no reads will happen, thus no danger of notifications or matcher construction
196 comprehension.traverseFeature(getVisitorForChange(isInsertion), notifier, feature, value, position);
197 }
198
199 // OFFICIAL ENTRY POINT OF BASE INDEX RELATED PARTS
200 protected void addAdapter(final Notifier notifier) {
201 if (notifier == ignoreInsertionAndDeletion) {
202 return;
203 }
204 try {
205 // cross-resource containment workaround, see Bug 483089 and Bug 483086.
206 if (notifier.eAdapters().contains(this))
207 return;
208
209 if (objectFilterConfiguration != null && objectFilterConfiguration.isFiltered(notifier)) {
210 return;
211 }
212 this.navigationHelper.coalesceTraversals(new Callable<Void>() {
213 @Override
214 public Void call() throws Exception {
215 // the object is really traversed BEFORE the notification listener is added,
216 // so that if a proxy is resolved due to the traversal, we do not get notified about it
217 if (notifier instanceof EObject) {
218 comprehension.traverseObject(getVisitorForChange(true), (EObject) notifier);
219 } else if (notifier instanceof Resource) {
220 Resource resource = (Resource) notifier;
221 if (resourceFilterConfiguration != null
222 && resourceFilterConfiguration.isResourceFiltered(resource)) {
223 return null;
224 }
225 if (comprehension.isLoading(resource))
226 navigationHelper.resolutionDelayingResources.add(resource);
227 }
228 // subscribes to the adapter list, will receive setTarget callback that will spread addAdapter to
229 // children
230 simpleAddAdapter(notifier);
231 return null;
232 }
233 });
234 } catch (final InvocationTargetException ex) {
235 navigationHelper.processingFatal(ex.getCause(), "add the object: " + notifier);
236 } catch (final Exception ex) {
237 navigationHelper.processingFatal(ex, "add the object: " + notifier);
238 }
239 }
240
241 // OFFICIAL ENTRY POINT OF BASE INDEX RELATED PARTS
242 protected void removeAdapter(final Notifier notifier) {
243 if (notifier == ignoreInsertionAndDeletion) {
244 return;
245 }
246 try {
247 removeAdapterInternal(notifier);
248 } catch (final InvocationTargetException ex) {
249 navigationHelper.processingFatal(ex.getCause(), "remove the object: " + notifier);
250 } catch (final Exception ex) {
251 navigationHelper.processingFatal(ex, "remove the object: " + notifier);
252 }
253 }
254
255 // The additional boolean options are there to save the cost of extra checks, see Bug 483089 and Bug 483086.
256 protected void removeAdapter(final Notifier notifier, boolean additionalObjectContainerPossible,
257 boolean additionalResourceContainerPossible) {
258 if (notifier == ignoreInsertionAndDeletion) {
259 return;
260 }
261 try {
262
263 // cross-resource containment workaround, see Bug 483089 and Bug 483086.
264 if (notifier instanceof InternalEObject) {
265 InternalEObject internalEObject = (InternalEObject) notifier;
266 if (additionalResourceContainerPossible) {
267 Resource eDirectResource = internalEObject.eDirectResource();
268 if (eDirectResource != null && eDirectResource.eAdapters().contains(this)) {
269 return;
270 }
271 }
272 if (additionalObjectContainerPossible) {
273 InternalEObject eInternalContainer = internalEObject.eInternalContainer();
274 if (eInternalContainer != null && eInternalContainer.eAdapters().contains(this)) {
275 return;
276 }
277 }
278 }
279
280 removeAdapterInternal(notifier);
281 } catch (final InvocationTargetException ex) {
282 navigationHelper.processingFatal(ex.getCause(), "remove the object: " + notifier);
283 } catch (final Exception ex) {
284 navigationHelper.processingFatal(ex, "remove the object: " + notifier);
285 }
286 }
287
288 /**
289 * @throws InvocationTargetException
290 */
291 protected void removeAdapterInternal(final Notifier notifier) throws InvocationTargetException {
292 // some non-standard EMF implementations send these
293 if (!notifier.eAdapters().contains(this)) {
294 // the adapter was not even attached to the notifier
295 navigationHelper.logIncidentAdapterRemoval(notifier);
296
297 // skip the rest of the method, do not traverse contents
298 // as they have either never been added to the index or already removed
299 return;
300 }
301
302 if (objectFilterConfiguration != null && objectFilterConfiguration.isFiltered(notifier)) {
303 return;
304 }
305 this.navigationHelper.coalesceTraversals(new Callable<Void>() {
306 @Override
307 public Void call() throws Exception {
308 if (notifier instanceof EObject) {
309 final EObject eObject = (EObject) notifier;
310 comprehension.traverseObject(getVisitorForChange(false), eObject);
311 navigationHelper.delayedProxyResolutions.lookupAndRemoveAll(eObject);
312 } else if (notifier instanceof Resource) {
313 if (resourceFilterConfiguration != null
314 && resourceFilterConfiguration.isResourceFiltered((Resource) notifier)) {
315 return null;
316 }
317 navigationHelper.resolutionDelayingResources.remove(notifier);
318 }
319 // unsubscribes from the adapter list, will receive unsetTarget callback that will spread
320 // removeAdapter to children
321 simpleRemoveAdapter(notifier);
322 return null;
323 }
324 });
325 }
326
327 protected EMFVisitor getVisitorForChange(final boolean isInsertion) {
328 return isInsertion ? insertionVisitor : removalVisitor;
329 }
330
331
332 // WORKAROUND (TMP) for eContents vs. derived features bug
333 protected void setTarget(final EObject target) {
334 basicSetTarget(target);
335 spreadToChildren(target, true);
336 }
337
338 protected void unsetTarget(final EObject target) {
339 basicUnsetTarget(target);
340 spreadToChildren(target, false);
341 }
342
343 // Spread adapter removal/addition to children of EObject
344 protected void spreadToChildren(final EObject target, final boolean add) {
345 final EList<EReference> features = target.eClass().getEAllReferences();
346 for (final EReference feature : features) {
347 if (!feature.isContainment()) {
348 continue;
349 }
350 if (!comprehension.representable(feature)) {
351 continue;
352 }
353 if (feature.isMany()) {
354 final Collection<?> values = (Collection<?>) target.eGet(feature);
355 for (final Object value : values) {
356 final Notifier notifier = (Notifier) value;
357 if (add) {
358 addAdapter(notifier);
359 } else {
360 removeAdapter(notifier, false, true);
361 }
362 }
363 } else {
364 final Object value = target.eGet(feature);
365 if (value != null) {
366 final Notifier notifier = (Notifier) value;
367 if (add) {
368 addAdapter(notifier);
369 } else {
370 removeAdapter(notifier, false, true);
371 }
372 }
373 }
374 }
375 }
376
377
378 //
379 // ***********************************************************
380 // RENAMED METHODS COPIED OVER FROM EContentAdapter DOWN BELOW
381 // ***********************************************************
382 //
383
384 /**
385 * Handles a notification by calling {@link #selfAdapt selfAdapter}.
386 */
387 public void simpleNotifyChanged(Notification notification)
388 {
389 selfAdapt(notification);
390
391 super.notifyChanged(notification);
392 }
393
394 protected void simpleAddAdapter(Notifier notifier)
395 {
396 EList<Adapter> eAdapters = notifier.eAdapters();
397 if (!eAdapters.contains(this))
398 {
399 eAdapters.add(this);
400 }
401 }
402
403 protected void simpleRemoveAdapter(Notifier notifier)
404 {
405 notifier.eAdapters().remove(this);
406 }
407
408
409 //
410 // *********************************************************
411 // CODE COPIED OVER VERBATIM FROM EContentAdapter DOWN BELOW
412 // *********************************************************
413 //
414
415
416 /**
417 * Handles a notification by calling {@link #handleContainment handleContainment}
418 * for any containment-based notification.
419 */
420 protected void selfAdapt(Notification notification)
421 {
422 Object notifier = notification.getNotifier();
423 if (notifier instanceof ResourceSet)
424 {
425 if (notification.getFeatureID(ResourceSet.class) == ResourceSet.RESOURCE_SET__RESOURCES)
426 {
427 handleContainment(notification);
428 }
429 }
430 else if (notifier instanceof Resource)
431 {
432 if (notification.getFeatureID(Resource.class) == Resource.RESOURCE__CONTENTS)
433 {
434 handleContainment(notification);
435 }
436 }
437 else if (notifier instanceof EObject)
438 {
439 Object feature = notification.getFeature();
440 if (feature instanceof EReference)
441 {
442 EReference eReference = (EReference)feature;
443 if (eReference.isContainment())
444 {
445 handleContainment(notification);
446 }
447 }
448 }
449 }
450
451 /**
452 * Handles a containment change by adding and removing the adapter as appropriate.
453 */
454 protected void handleContainment(Notification notification)
455 {
456 switch (notification.getEventType())
457 {
458 case Notification.RESOLVE:
459 {
460 // We need to be careful that the proxy may be resolved while we are attaching this adapter.
461 // We need to avoid attaching the adapter during the resolve
462 // and also attaching it again as we walk the eContents() later.
463 // Checking here avoids having to check during addAdapter.
464 //
465 Notifier oldValue = (Notifier)notification.getOldValue();
466 if (oldValue.eAdapters().contains(this))
467 {
468 removeAdapter(oldValue);
469 Notifier newValue = (Notifier)notification.getNewValue();
470 addAdapter(newValue);
471 }
472 break;
473 }
474 case Notification.UNSET:
475 {
476 Object oldValue = notification.getOldValue();
477 if (!Objects.equals(oldValue, Boolean.TRUE) && !Objects.equals(oldValue, Boolean.FALSE))
478 {
479 if (oldValue != null)
480 {
481 removeAdapter((Notifier)oldValue, false, true);
482 }
483 Notifier newValue = (Notifier)notification.getNewValue();
484 if (newValue != null)
485 {
486 addAdapter(newValue);
487 }
488 }
489 break;
490 }
491 case Notification.SET:
492 {
493 Notifier oldValue = (Notifier)notification.getOldValue();
494 if (oldValue != null)
495 {
496 removeAdapter(oldValue, false, true);
497 }
498 Notifier newValue = (Notifier)notification.getNewValue();
499 if (newValue != null)
500 {
501 addAdapter(newValue);
502 }
503 break;
504 }
505 case Notification.ADD:
506 {
507 Notifier newValue = (Notifier)notification.getNewValue();
508 if (newValue != null)
509 {
510 addAdapter(newValue);
511 }
512 break;
513 }
514 case Notification.ADD_MANY:
515 {
516 @SuppressWarnings("unchecked") Collection<Notifier> newValues = (Collection<Notifier>)notification.getNewValue();
517 for (Notifier newValue : newValues)
518 {
519 addAdapter(newValue);
520 }
521 break;
522 }
523 case Notification.REMOVE:
524 {
525 Notifier oldValue = (Notifier)notification.getOldValue();
526 if (oldValue != null)
527 {
528 boolean checkContainer = notification.getNotifier() instanceof Resource;
529 boolean checkResource = notification.getFeature() != null;
530 removeAdapter(oldValue, checkContainer, checkResource);
531 }
532 break;
533 }
534 case Notification.REMOVE_MANY:
535 {
536 boolean checkContainer = notification.getNotifier() instanceof Resource;
537 boolean checkResource = notification.getFeature() != null;
538 @SuppressWarnings("unchecked") Collection<Notifier> oldValues = (Collection<Notifier>)notification.getOldValue();
539 for ( Notifier oldContentValue : oldValues)
540 {
541 removeAdapter(oldContentValue, checkContainer, checkResource);
542 }
543 break;
544 }
545 }
546 }
547
548 /**
549 * Handles installation of the adapter
550 * by adding the adapter to each of the directly contained objects.
551 */
552 @Override
553 public void setTarget(Notifier target)
554 {
555 if (target instanceof EObject)
556 {
557 setTarget((EObject)target);
558 }
559 else if (target instanceof Resource)
560 {
561 setTarget((Resource)target);
562 }
563 else if (target instanceof ResourceSet)
564 {
565 setTarget((ResourceSet)target);
566 }
567 else
568 {
569 basicSetTarget(target);
570 }
571 }
572
573 /**
574 * Actually sets the target by calling super.
575 */
576 protected void basicSetTarget(Notifier target)
577 {
578 super.setTarget(target);
579 }
580
581 /**
582 * Handles installation of the adapter on a Resource
583 * by adding the adapter to each of the directly contained objects.
584 */
585 protected void setTarget(Resource target)
586 {
587 basicSetTarget(target);
588 List<EObject> contents = target.getContents();
589 for (int i = 0, size = contents.size(); i < size; ++i)
590 {
591 Notifier notifier = contents.get(i);
592 addAdapter(notifier);
593 }
594 }
595
596 /**
597 * Handles installation of the adapter on a ResourceSet
598 * by adding the adapter to each of the directly contained objects.
599 */
600 protected void setTarget(ResourceSet target)
601 {
602 basicSetTarget(target);
603 List<Resource> resources = target.getResources();
604 for (int i = 0; i < resources.size(); ++i)
605 {
606 Notifier notifier = resources.get(i);
607 addAdapter(notifier);
608 }
609 }
610
611 /**
612 * Handles undoing the installation of the adapter
613 * by removing the adapter from each of the directly contained objects.
614 */
615 @Override
616 public void unsetTarget(Notifier target)
617 {
618 Object target1 = target;
619 if (target1 instanceof EObject)
620 {
621 unsetTarget((EObject)target1);
622 }
623 else if (target1 instanceof Resource)
624 {
625 unsetTarget((Resource)target1);
626 }
627 else if (target1 instanceof ResourceSet)
628 {
629 unsetTarget((ResourceSet)target1);
630 }
631 else
632 {
633 basicUnsetTarget((Notifier)target1);
634 }
635 }
636
637 /**
638 * Actually unsets the target by calling super.
639 */
640 protected void basicUnsetTarget(Notifier target)
641 {
642 super.unsetTarget(target);
643 }
644
645 /**
646 * Handles undoing the installation of the adapter from a Resource
647 * by removing the adapter from each of the directly contained objects.
648 */
649 protected void unsetTarget(Resource target)
650 {
651 basicUnsetTarget(target);
652 List<EObject> contents = target.getContents();
653 for (int i = 0, size = contents.size(); i < size; ++i)
654 {
655 Notifier notifier = contents.get(i);
656 removeAdapter(notifier, true, false);
657 }
658 }
659
660 /**
661 * Handles undoing the installation of the adapter from a ResourceSet
662 * by removing the adapter from each of the directly contained objects.
663 */
664 protected void unsetTarget(ResourceSet target)
665 {
666 basicUnsetTarget(target);
667 List<Resource> resources = target.getResources();
668 for (int i = 0; i < resources.size(); ++i)
669 {
670 Notifier notifier = resources.get(i);
671 removeAdapter(notifier, false, false);
672 }
673 }
674
675 protected boolean resolve()
676 {
677 return true;
678 }
679
680 //
681 // *********************************************************
682 // OBSOLETE CODE COPIED OVER FROM EContentAdapter DOWN BELOW
683 // *********************************************************
684 //
685 // *** Preserved on purpose as comments,
686 // *** in order to more easily follow future changes to EContentAdapter.
687 //
688
689
690// protected void removeAdapter(Notifier notifier, boolean checkContainer, boolean checkResource)
691// {
692// if (checkContainer || checkResource)
693// {
694// InternalEObject internalEObject = (InternalEObject) notifier;
695// if (checkResource)
696// {
697// Resource eDirectResource = internalEObject.eDirectResource();
698// if (eDirectResource != null && eDirectResource.eAdapters().contains(this))
699// {
700// return;
701// }
702// }
703// if (checkContainer)
704// {
705// InternalEObject eInternalContainer = internalEObject.eInternalContainer();
706// if (eInternalContainer != null && eInternalContainer.eAdapters().contains(this))
707// {
708// return;
709// }
710// }
711// }
712//
713// removeAdapter(notifier);
714// }
715
716// /**
717// * Handles undoing the installation of the adapter from an EObject
718// * by removing the adapter from each of the directly contained objects.
719// */
720// protected void unsetTarget(EObject target)
721// {
722// basicUnsetTarget(target);
723// for (Iterator<? extends Notifier> i = resolve() ?
724// target.eContents().iterator() :
725// ((InternalEList<EObject>)target.eContents()).basicIterator();
726// i.hasNext(); )
727// {
728// Notifier notifier = i.next();
729// removeAdapter(notifier, false, true);
730// }
731// }
732
733// /**
734// * Handles installation of the adapter on an EObject
735// * by adding the adapter to each of the directly contained objects.
736// */
737// protected void setTarget(EObject target)
738// {
739// basicSetTarget(target);
740// for (Iterator<? extends Notifier> i = resolve() ?
741// target.eContents().iterator() :
742// ((InternalEList<? extends Notifier>)target.eContents()).basicIterator();
743// i.hasNext(); )
744// {
745// Notifier notifier = i.next();
746// addAdapter(notifier);
747// }
748// }
749
750}
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}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java
new file mode 100644
index 00000000..56556ea8
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java
@@ -0,0 +1,73 @@
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
10package tools.refinery.viatra.runtime.base.core;
11
12import org.eclipse.emf.ecore.EObject;
13import org.eclipse.emf.ecore.EStructuralFeature;
14import org.eclipse.emf.ecore.EStructuralFeature.Setting;
15
16/**
17 * EStructuralFeature.Setting implementation for the NavigationHelper.
18 *
19 * @author Tamás Szabó
20 *
21 */
22public class NavigationHelperSetting implements Setting {
23
24 private EStructuralFeature feature;
25 private EObject holder;
26 private Object value;
27
28 public NavigationHelperSetting() {
29 super();
30 }
31
32 public NavigationHelperSetting(EStructuralFeature feature, EObject holder, Object value) {
33 super();
34 this.feature = feature;
35 this.holder = holder;
36 this.value = value;
37 }
38
39 @Override
40 public EObject getEObject() {
41 return holder;
42 }
43
44 @Override
45 public EStructuralFeature getEStructuralFeature() {
46 return feature;
47 }
48
49 @Override
50 public Object get(boolean resolve) {
51 return value;
52 }
53
54 @Override
55 public void set(Object newValue) {
56 this.value = newValue;
57 }
58
59 @Override
60 public boolean isSet() {
61 return (value != null);
62 }
63
64 @Override
65 public void unset() {
66 this.value = null;
67 }
68
69 @Override
70 public String toString() {
71 return "feature = " + feature + " holder = " + holder + " value = " + value;
72 }
73}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java
new file mode 100644
index 00000000..2bab4914
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java
@@ -0,0 +1,14 @@
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
10package tools.refinery.viatra.runtime.base.core;
11
12public enum NavigationHelperType {
13 REGISTER, ALL
14}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java
new file mode 100644
index 00000000..b5de8d20
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java
@@ -0,0 +1,441 @@
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 java.util.Collections;
12import java.util.HashMap;
13import java.util.Map;
14import java.util.Set;
15
16import org.eclipse.emf.ecore.EAttribute;
17import org.eclipse.emf.ecore.EClass;
18import org.eclipse.emf.ecore.EClassifier;
19import org.eclipse.emf.ecore.EObject;
20import org.eclipse.emf.ecore.EReference;
21import org.eclipse.emf.ecore.EStructuralFeature;
22import org.eclipse.emf.ecore.resource.Resource;
23import org.eclipse.emf.ecore.util.EcoreUtil;
24import tools.refinery.viatra.runtime.base.api.IndexingLevel;
25import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension;
26import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor;
27
28public abstract class NavigationHelperVisitor extends EMFVisitor {
29
30 /**
31 * A visitor for processing a single change event. Does not traverse the model. Uses all the observed types.
32 */
33 public static class ChangeVisitor extends NavigationHelperVisitor {
34 // local copies to save actual state, in case visitor has to be saved for later due unresolvable proxies
35 private final IndexingLevel wildcardMode;
36 private final Map<Object, IndexingLevel> allObservedClasses;
37 private final Map<Object, IndexingLevel> observedDataTypes;
38 private final Map<Object, IndexingLevel> observedFeatures;
39 private final Map<Object, Boolean> sampledClasses;
40
41 public ChangeVisitor(NavigationHelperImpl navigationHelper, boolean isInsertion) {
42 super(navigationHelper, isInsertion, false);
43 wildcardMode = navigationHelper.getWildcardLevel();
44 allObservedClasses = navigationHelper.getAllObservedClassesInternal(); // new HashSet<EClass>();
45 observedDataTypes = navigationHelper.getObservedDataTypesInternal(); // new HashSet<EDataType>();
46 observedFeatures = navigationHelper.getObservedFeaturesInternal(); // new HashSet<EStructuralFeature>();
47 sampledClasses = new HashMap<Object, Boolean>();
48 }
49
50 @Override
51 protected boolean observesClass(Object eClass) {
52 return wildcardMode.hasInstances() || (IndexingLevel.FULL == allObservedClasses.get(eClass)) || registerSampledClass(eClass);
53 }
54
55 protected boolean registerSampledClass(Object eClass) {
56 Boolean classAlreadyChecked = sampledClasses.get(eClass);
57 if (classAlreadyChecked != null) {
58 return classAlreadyChecked;
59 }
60 boolean isSampledClass = isSampledClass(eClass);
61 sampledClasses.put(eClass, isSampledClass);
62 // do not modify observation configuration during traversal
63 return false;
64 }
65
66 @Override
67 protected boolean observesDataType(Object type) {
68 return wildcardMode.hasInstances() || (IndexingLevel.FULL == observedDataTypes.get(type));
69 }
70
71 @Override
72 protected boolean observesFeature(Object feature) {
73 return wildcardMode.hasInstances() || (IndexingLevel.FULL == observedFeatures.get(feature));
74 }
75
76 @Override
77 protected boolean countsFeature(Object feature) {
78 return wildcardMode.hasStatistics() || observedFeatures.containsKey(feature) && observedFeatures.get(feature).hasStatistics();
79 }
80
81 @Override
82 protected boolean countsDataType(Object type) {
83 return wildcardMode.hasStatistics() || observedDataTypes.containsKey(type) && observedDataTypes.get(type).hasStatistics();
84 }
85
86 @Override
87 protected boolean countsClass(Object eClass) {
88 return wildcardMode.hasStatistics() || allObservedClasses.containsKey(eClass) && allObservedClasses.get(eClass).hasStatistics();
89 }
90 }
91
92 /**
93 * A visitor for a single-pass traversal of the whole model, processing only the given types and inserting them.
94 */
95 public static class TraversingVisitor extends NavigationHelperVisitor {
96 private final IndexingLevel wildcardMode;
97 Map<Object, IndexingLevel> features;
98 Map<Object, IndexingLevel> newClasses;
99 Map<Object, IndexingLevel> oldClasses; // if decends from an old class, no need to add!
100 Map<Object, IndexingLevel> classObservationMap; // true for a class even if only a supertype is included in classes;
101 Map<Object, IndexingLevel> dataTypes;
102
103 public TraversingVisitor(NavigationHelperImpl navigationHelper, Map<Object, IndexingLevel> features, Map<Object, IndexingLevel> newClasses,
104 Map<Object, IndexingLevel> oldClasses, Map<Object, IndexingLevel> dataTypes) {
105 super(navigationHelper, true, true);
106 wildcardMode = navigationHelper.getWildcardLevel();
107 this.features = features;
108 this.newClasses = newClasses;
109 this.oldClasses = oldClasses;
110 this.classObservationMap = new HashMap<Object, IndexingLevel>();
111 this.dataTypes = dataTypes;
112 }
113
114 protected IndexingLevel getExistingIndexingLevel(Object eClass){
115 IndexingLevel result = IndexingLevel.NONE;
116 result = result.merge(oldClasses.get(eClass));
117 result = result.merge(oldClasses.get(metaStore.getEObjectClassKey()));
118 if (IndexingLevel.FULL == result) return result;
119 Set<Object> superTypes = metaStore.getSuperTypeMap().get(eClass);
120 if (superTypes != null){
121 for(Object superType: superTypes){
122 result = result.merge(oldClasses.get(superType));
123 if (IndexingLevel.FULL == result) return result;
124 }
125 }
126 return result;
127 }
128
129 protected IndexingLevel getRequestedIndexingLevel(Object eClass){
130 IndexingLevel result = IndexingLevel.NONE;
131 result = result.merge(newClasses.get(eClass));
132 result = result.merge(newClasses.get(metaStore.getEObjectClassKey()));
133 if (IndexingLevel.FULL == result) return result;
134 Set<Object> superTypes = metaStore.getSuperTypeMap().get(eClass);
135 if (superTypes != null){
136 for(Object superType: superTypes){
137 result = result.merge(newClasses.get(superType));
138 if (IndexingLevel.FULL == result) return result;
139 }
140 }
141 return result;
142 }
143
144 protected IndexingLevel getTraversalIndexing(Object eClass){
145 IndexingLevel level = classObservationMap.get(eClass);
146 if (level == null){
147 IndexingLevel existing = getExistingIndexingLevel(eClass);
148 IndexingLevel requested = getRequestedIndexingLevel(eClass);
149
150 // Calculate the type of indexing which needs to be executed to reach requested indexing state
151 // Considering indexes which are already available
152 if (existing == requested || existing == IndexingLevel.FULL) return IndexingLevel.NONE;
153 if (requested == IndexingLevel.FULL) return IndexingLevel.FULL;
154 if (requested.hasStatistics() == existing.hasStatistics()) return IndexingLevel.NONE;
155 if (requested.hasStatistics()) return IndexingLevel.STATISTICS;
156 return IndexingLevel.NONE;
157 }
158 return level;
159 }
160
161 @Override
162 protected boolean observesClass(Object eClass) {
163 if (wildcardMode.hasInstances()) {
164 return true;
165 }
166 return IndexingLevel.FULL == getTraversalIndexing(eClass);
167 }
168
169 @Override
170 protected boolean countsClass(Object eClass) {
171 return wildcardMode.hasStatistics() || getTraversalIndexing(eClass).hasStatistics();
172 }
173
174 @Override
175 protected boolean observesDataType(Object type) {
176 return wildcardMode.hasInstances() || (IndexingLevel.FULL == dataTypes.get(type));
177 }
178
179 @Override
180 protected boolean observesFeature(Object feature) {
181 return wildcardMode.hasInstances() || (IndexingLevel.FULL == features.get(feature));
182 }
183
184 @Override
185 protected boolean countsDataType(Object type) {
186 return wildcardMode.hasStatistics() || dataTypes.containsKey(type) && dataTypes.get(type).hasStatistics();
187 }
188
189 @Override
190 protected boolean countsFeature(Object feature) {
191 return wildcardMode.hasStatistics() || features.containsKey(feature) && features.get(feature).hasStatistics();
192 }
193
194 @Override
195 public boolean avoidTransientContainmentLink(EObject source, EReference reference, EObject targetObject) {
196 return !targetObject.eAdapters().contains(navigationHelper.contentAdapter);
197 }
198 }
199
200 protected NavigationHelperImpl navigationHelper;
201 boolean isInsertion;
202 boolean descendHierarchy;
203 boolean traverseOnlyWellBehavingDerivedFeatures;
204 EMFBaseIndexInstanceStore instanceStore;
205 EMFBaseIndexStatisticsStore statsStore;
206 EMFBaseIndexMetaStore metaStore;
207
208 NavigationHelperVisitor(NavigationHelperImpl navigationHelper, boolean isInsertion, boolean descendHierarchy) {
209 super(isInsertion /* preOrder iff insertion */);
210 this.navigationHelper = navigationHelper;
211 instanceStore = navigationHelper.instanceStore;
212 metaStore = navigationHelper.metaStore;
213 statsStore = navigationHelper.statsStore;
214 this.isInsertion = isInsertion;
215 this.descendHierarchy = descendHierarchy;
216 this.traverseOnlyWellBehavingDerivedFeatures = navigationHelper.getBaseIndexOptions()
217 .isTraverseOnlyWellBehavingDerivedFeatures();
218 }
219
220 @Override
221 public boolean pruneSubtrees(EObject source) {
222 return !descendHierarchy;
223 }
224
225 @Override
226 public boolean pruneSubtrees(Resource source) {
227 return !descendHierarchy;
228 }
229
230 @Override
231 public boolean pruneFeature(EStructuralFeature feature) {
232 Object featureKey = toKey(feature);
233 if (observesFeature(featureKey) || countsFeature(featureKey)) {
234 return false;
235 }
236 if (feature instanceof EAttribute){
237 Object dataTypeKey = toKey(((EAttribute) feature).getEAttributeType());
238 if (observesDataType(dataTypeKey) || countsDataType(dataTypeKey)) {
239 return false;
240 }
241 }
242 return !(isInsertion && navigationHelper.isExpansionAllowed() && feature instanceof EReference
243 && !((EReference) feature).isContainment());
244 }
245
246 /**
247 * @param feature
248 * key of feature (EStructuralFeature or String id)
249 */
250 protected abstract boolean observesFeature(Object feature);
251
252 /**
253 * @param feature
254 * key of data type (EDatatype or String id)
255 */
256 protected abstract boolean observesDataType(Object type);
257
258 /**
259 * @param feature
260 * key of class (EClass or String id)
261 */
262 protected abstract boolean observesClass(Object eClass);
263
264 protected abstract boolean countsFeature(Object feature);
265
266 protected abstract boolean countsDataType(Object type);
267
268 protected abstract boolean countsClass(Object eClass);
269
270 @Override
271 public void visitElement(EObject source) {
272 EClass eClass = source.eClass();
273 if (eClass.eIsProxy()) {
274 eClass = (EClass) EcoreUtil.resolve(eClass, source);
275 }
276
277 final Object classKey = toKey(eClass);
278 if (observesClass(classKey)) {
279 if (isInsertion) {
280 instanceStore.insertIntoInstanceSet(classKey, source);
281 } else {
282 instanceStore.removeFromInstanceSet(classKey, source);
283 }
284 }
285 if (countsClass(classKey)){
286 if (isInsertion){
287 statsStore.addInstance(classKey);
288 } else {
289 statsStore.removeInstance(classKey);
290 }
291 }
292 }
293
294 @Override
295 public void visitAttribute(EObject source, EAttribute feature, Object target) {
296 Object featureKey = toKey(feature);
297 final Object eAttributeType = toKey(feature.getEAttributeType());
298 Object internalValueRepresentation = null;
299 if (observesFeature(featureKey)) {
300 // if (internalValueRepresentation == null) // always true
301 internalValueRepresentation = metaStore.toInternalValueRepresentation(target);
302 boolean unique = feature.isUnique();
303 if (isInsertion) {
304 instanceStore.insertFeatureTuple(featureKey, unique, internalValueRepresentation, source);
305 } else {
306 instanceStore.removeFeatureTuple(featureKey, unique, internalValueRepresentation, source);
307 }
308 }
309 if (countsFeature(featureKey)){
310 if (isInsertion) {
311 statsStore.addFeature(source, featureKey);
312 }else{
313 statsStore.removeFeature(source, featureKey);
314 }
315 }
316 if (observesDataType(eAttributeType)) {
317 if (internalValueRepresentation == null)
318 internalValueRepresentation = metaStore.toInternalValueRepresentation(target);
319 if (isInsertion) {
320 instanceStore.insertIntoDataTypeMap(eAttributeType, internalValueRepresentation);
321 } else {
322 instanceStore.removeFromDataTypeMap(eAttributeType, internalValueRepresentation);
323 }
324 }
325 if (countsDataType(eAttributeType)){
326 if (isInsertion){
327 statsStore.addInstance(eAttributeType);
328 } else {
329 statsStore.removeInstance(eAttributeType);
330 }
331 }
332 }
333
334 @Override
335 public void visitInternalContainment(EObject source, EReference feature, EObject target) {
336 visitReference(source, feature, target);
337 }
338
339 @Override
340 public void visitNonContainmentReference(EObject source, EReference feature, EObject target) {
341 visitReference(source, feature, target);
342 if (isInsertion) {
343 navigationHelper.considerForExpansion(target);
344 }
345 }
346
347 protected void visitReference(EObject source, EReference feature, EObject target) {
348 Object featureKey = toKey(feature);
349 if (observesFeature(featureKey)) {
350 boolean unique = feature.isUnique();
351 if (isInsertion) {
352 instanceStore.insertFeatureTuple(featureKey, unique, target, source);
353 } else {
354 instanceStore.removeFeatureTuple(featureKey, unique, target, source);
355 }
356 }
357 if (countsFeature(featureKey)){
358 if (isInsertion){
359 statsStore.addFeature(source, featureKey);
360 } else {
361 statsStore.removeFeature(source, featureKey);
362 }
363 }
364 }
365
366 @Override
367 // do not attempt to resolve proxies referenced from resources that are still being loaded
368 public boolean attemptProxyResolutions(EObject source, EReference feature) {
369 // emptyness is checked first to avoid costly resource lookup in most cases
370 if (navigationHelper.resolutionDelayingResources.isEmpty())
371 return true;
372 else
373 return ! navigationHelper.resolutionDelayingResources.contains(source.eResource());
374 }
375
376 @Override
377 public void visitProxyReference(EObject source, EReference reference, EObject targetObject, Integer position) {
378 if (isInsertion) { // only attempt to resolve proxies if they are inserted
379 // final Object result = source.eGet(reference, true);
380 // if (reference.isMany()) {
381 // // no idea which element to get, have to iterate through
382 // for (EObject touch : (Iterable<EObject>) result);
383 // }
384 if (navigationHelper.isFeatureResolveIgnored(reference))
385 return; // skip resolution; would be ignored anyways
386 if (position != null && reference.isMany() && attemptProxyResolutions(source, reference)) {
387 // there is added value in doing the resolution now, when we know the position
388 // this may save an iteration through the EList if successful
389 @SuppressWarnings("unchecked")
390 EObject touch = ((java.util.List<EObject>) source.eGet(reference, true)).get(position);
391 // if resolution successful, no further action needed
392 if (!touch.eIsProxy())
393 return;
394 }
395 // otherwise, attempt resolution later, at the end of the coalesced traversal block
396 navigationHelper.delayedProxyResolutions.addPairOrNop(source, reference);
397 }
398 }
399
400 protected Object toKey(EStructuralFeature feature) {
401 return metaStore.toKey(feature);
402 }
403
404 protected Object toKey(EClassifier eClassifier) {
405 return metaStore.toKey(eClassifier);
406 }
407
408 /**
409 * Decides whether the type must be observed in order to allow re-sampling of any of its features. If not
410 * well-behaving features are traversed and there is such a feature for this class, the class will be registered
411 * into the navigation helper, which may cause a re-traversal.
412 *
413 */
414 protected boolean isSampledClass(Object eClass) {
415 if (!traverseOnlyWellBehavingDerivedFeatures) {
416 // TODO we could save this reverse lookup if the calling method would have the EClass, not just the key
417 EClass knownClass = (EClass) metaStore.getKnownClassifierForKey(eClass);
418 // check features that are traversed, and whether there is any that must be sampled
419 for (EStructuralFeature feature : knownClass.getEAllStructuralFeatures()) {
420 EMFModelComprehension comprehension = navigationHelper.getComprehension();
421 if (comprehension.untraversableDirectly(feature))
422 continue;
423 final boolean visitorPrunes = pruneFeature(feature);
424 if (visitorPrunes)
425 continue;
426 // we found a feature to be visited
427 if (comprehension.onlySamplingFeature(feature)) {
428 // we found a feature that must be sampled
429 navigationHelper.registerEClasses(Collections.singleton(feature.getEContainingClass()), IndexingLevel.FULL);
430 return true;
431 }
432 }
433 }
434 return false;
435 }
436
437 @Override
438 public boolean descendAlongCrossResourceContainments() {
439 return this.navigationHelper.traversalDescendsAlongCrossResourceContainment();
440 }
441}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java
new file mode 100644
index 00000000..552696cb
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java
@@ -0,0 +1,153 @@
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
10package tools.refinery.viatra.runtime.base.core;
11
12import java.util.ArrayList;
13import java.util.HashSet;
14import java.util.List;
15import java.util.Set;
16
17import org.eclipse.emf.ecore.EClass;
18import org.eclipse.emf.ecore.EObject;
19import org.eclipse.emf.ecore.EReference;
20import org.eclipse.emf.ecore.EStructuralFeature;
21import org.eclipse.emf.ecore.util.EContentAdapter;
22import tools.refinery.viatra.runtime.base.api.FeatureListener;
23import tools.refinery.viatra.runtime.base.api.IndexingLevel;
24import tools.refinery.viatra.runtime.base.api.InstanceListener;
25import tools.refinery.viatra.runtime.base.api.NavigationHelper;
26import tools.refinery.viatra.runtime.base.api.TransitiveClosureHelper;
27import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg;
28import tools.refinery.viatra.runtime.base.itc.alg.misc.IGraphPathFinder;
29import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver;
30
31/**
32 * Implementation class for the {@link TransitiveClosureHelper}.
33 * It uses a {@link NavigationHelper} instance to wrap an EMF model
34 * and make it suitable for the {@link IncSCCAlg} algorithm.
35 *
36 * @author Tamas Szabo
37 *
38 */
39public class TransitiveClosureHelperImpl extends EContentAdapter implements TransitiveClosureHelper,
40 ITcObserver<EObject>, FeatureListener, InstanceListener {
41
42 private IncSCCAlg<EObject> sccAlg;
43 private Set<EStructuralFeature> features;
44 private Set<EClass> classes;
45 private EMFDataSource dataSource;
46 private List<ITcObserver<EObject>> tcObservers;
47 private NavigationHelper navigationHelper;
48 private boolean disposeBaseIndexWhenDisposed;
49
50 public TransitiveClosureHelperImpl(final NavigationHelper navigationHelper, boolean disposeBaseIndexWhenDisposed, Set<EReference> references) {
51 this.tcObservers = new ArrayList<ITcObserver<EObject>>();
52 this.navigationHelper = navigationHelper;
53 this.disposeBaseIndexWhenDisposed = disposeBaseIndexWhenDisposed;
54
55 //NavigationHelper only accepts Set<EStructuralFeature> upon registration
56 this.features = new HashSet<EStructuralFeature>(references);
57 this.classes = collectEClasses();
58 /*this.classes = Collections.emptySet();*/
59 if (!navigationHelper.isInWildcardMode())
60 navigationHelper.registerObservedTypes(classes, null, features, IndexingLevel.FULL);
61
62 this.navigationHelper.addFeatureListener(features, this);
63 this.navigationHelper.addInstanceListener(classes, this);
64
65 this.dataSource = new EMFDataSource(navigationHelper, references, classes);
66
67 this.sccAlg = new IncSCCAlg<EObject>(dataSource);
68 this.sccAlg.attachObserver(this);
69 }
70
71 private Set<EClass> collectEClasses() {
72 Set<EClass> classes = new HashSet<EClass>();
73 for (EStructuralFeature ref : features) {
74 classes.add(ref.getEContainingClass());
75 classes.add(((EReference) ref).getEReferenceType());
76 }
77 return classes;
78 }
79
80 @Override
81 public void attachObserver(ITcObserver<EObject> to) {
82 this.tcObservers.add(to);
83 }
84
85 @Override
86 public void detachObserver(ITcObserver<EObject> to) {
87 this.tcObservers.remove(to);
88 }
89
90 @Override
91 public Set<EObject> getAllReachableTargets(EObject source) {
92 return this.sccAlg.getAllReachableTargets(source);
93 }
94
95 @Override
96 public Set<EObject> getAllReachableSources(EObject target) {
97 return this.sccAlg.getAllReachableSources(target);
98 }
99
100 @Override
101 public boolean isReachable(EObject source, EObject target) {
102 return this.sccAlg.isReachable(source, target);
103 }
104
105 @Override
106 public void tupleInserted(EObject source, EObject target) {
107 for (ITcObserver<EObject> to : tcObservers) {
108 to.tupleInserted(source, target);
109 }
110 }
111
112 @Override
113 public void tupleDeleted(EObject source, EObject target) {
114 for (ITcObserver<EObject> to : tcObservers) {
115 to.tupleDeleted(source, target);
116 }
117 }
118
119 @Override
120 public void dispose() {
121 this.sccAlg.dispose();
122 this.navigationHelper.removeInstanceListener(classes, this);
123 this.navigationHelper.removeFeatureListener(features, this);
124
125 if (disposeBaseIndexWhenDisposed)
126 this.navigationHelper.dispose();
127 }
128
129 @Override
130 public void featureInserted(EObject host, EStructuralFeature feature, Object value) {
131 this.dataSource.notifyEdgeInserted(host, (EObject) value);
132 }
133
134 @Override
135 public void featureDeleted(EObject host, EStructuralFeature feature, Object value) {
136 this.dataSource.notifyEdgeDeleted(host, (EObject) value);
137 }
138
139 @Override
140 public void instanceInserted(EClass clazz, EObject instance) {
141 this.dataSource.notifyNodeInserted(instance);
142 }
143
144 @Override
145 public void instanceDeleted(EClass clazz, EObject instance) {
146 this.dataSource.notifyNodeDeleted(instance);
147 }
148
149 @Override
150 public IGraphPathFinder<EObject> getPathFinder() {
151 return this.sccAlg.getPathFinder();
152 }
153}
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java
new file mode 100644
index 00000000..3ab15430
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java
@@ -0,0 +1,155 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2019, Laszlo Gati, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.base.core.profiler;
10
11import org.eclipse.emf.common.notify.Notification;
12import org.eclipse.emf.common.notify.Notifier;
13import tools.refinery.viatra.runtime.base.core.NavigationHelperContentAdapter;
14import tools.refinery.viatra.runtime.base.core.NavigationHelperImpl;
15
16/**
17 *
18 * @noinstantiate This class is not intended to be instantiated by clients.
19 * @noreference This class is not intended to be referenced by clients.
20 */
21public final class ProfilingNavigationHelperContentAdapter extends NavigationHelperContentAdapter {
22
23 private static class StopWatch {
24
25 private long currentStartTimeNs = 0l;
26 private long totalElapsedTimeNs = 0l;
27 private boolean running = false;
28
29 /**
30 * Puts the timer in running state and saves the current time.
31 */
32 private void start() {
33 currentStartTimeNs = System.nanoTime();
34 running = true;
35
36 }
37
38 /**
39 * Puts the the timer in stopped state and saves the total time spent in started
40 * state between the last reset and now
41 */
42 private void stop() {
43 totalElapsedTimeNs = getTotalElapsedTimeNs();
44 running = false;
45 }
46
47 /**
48 * @return time between the last start and now
49 */
50 private long getCurrentElapsedTimeNs() {
51 return System.nanoTime() - currentStartTimeNs;
52 }
53
54 /**
55 * @return the total time spent in started state between the last reset and now
56 */
57 private long getTotalElapsedTimeNs() {
58 return running ? getCurrentElapsedTimeNs() + totalElapsedTimeNs : totalElapsedTimeNs;
59 }
60
61 /**
62 * Saves the current time and resets all the time spent between the last reset and now.
63 */
64 private void resetTime() {
65 currentStartTimeNs = System.currentTimeMillis();
66 totalElapsedTimeNs = 0;
67 }
68 }
69
70 long notificationCount = 0l;
71 StopWatch watch = new StopWatch();
72 boolean isEnabled = false;
73
74 boolean measurement = false;
75
76 public ProfilingNavigationHelperContentAdapter(NavigationHelperImpl navigationHelper, boolean enabled) {
77 super(navigationHelper);
78 this.isEnabled = enabled;
79 }
80
81 @Override
82 public void notifyChanged(Notification notification) {
83 // Handle possibility of reentrancy
84 if (isEnabled && !measurement) {
85 try {
86 measurement = true;
87 notificationCount++;
88 watch.start();
89 super.notifyChanged(notification);
90 } finally {
91 watch.stop();
92 measurement = false;
93 }
94 } else {
95 super.notifyChanged(notification);
96 }
97 }
98
99 @Override
100 public void setTarget(Notifier target) {
101 // Handle possibility of reentrancy
102 if (isEnabled && !measurement) {
103 try {
104 measurement = true;
105 notificationCount++;
106 watch.start();
107 super.setTarget(target);
108 } finally {
109 watch.stop();
110 measurement = false;
111 }
112 } else {
113 super.setTarget(target);
114 }
115 }
116
117 @Override
118 public void unsetTarget(Notifier target) {
119 // Handle possibility of reentrancy
120 if (isEnabled && !measurement) {
121 try {
122 measurement = true;
123 notificationCount++;
124 watch.start();
125 super.unsetTarget(target);
126 } finally {
127 watch.stop();
128 measurement = false;
129 }
130 } else {
131 super.unsetTarget(target);
132 }
133 }
134
135 public long getNotificationCount() {
136 return notificationCount;
137 }
138
139 public long getTotalMeasuredTimeInMS() {
140 return watch.getTotalElapsedTimeNs() / 1_000_000l;
141 }
142
143 public boolean isEnabled() {
144 return isEnabled;
145 }
146
147 public void setEnabled(boolean isEnabled) {
148 this.isEnabled = isEnabled;
149 }
150
151 public void resetMeasurement() {
152 notificationCount = 0;
153 watch.resetTime();
154 }
155} \ No newline at end of file
diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java
new file mode 100644
index 00000000..fe656c34
--- /dev/null
+++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java
@@ -0,0 +1,25 @@
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
10package tools.refinery.viatra.runtime.base.exception;
11
12import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
13
14public class ViatraBaseException extends ViatraQueryRuntimeException {
15
16 private static final long serialVersionUID = -5145445047912938251L;
17
18 public static final String EMPTY_REF_LIST = "At least one EReference must be provided!";
19 public static final String INVALID_EMFROOT = "Emf navigation helper can only be attached on the contents of an EMF EObject, Resource, or ResourceSet.";
20
21 public ViatraBaseException(String s) {
22 super(s);
23 }
24
25}