aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-base/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime-base/src/main/java')
-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
41 files changed, 7900 insertions, 0 deletions
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}