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