diff options
Diffstat (limited to 'subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java')
-rw-r--r-- | subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java | 356 |
1 files changed, 356 insertions, 0 deletions
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 | ||