aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <marussy@mit.bme.hu>2023-10-15 01:53:58 +0200
committerLibravatar GitHub <noreply@github.com>2023-10-15 01:53:58 +0200
commit5b1302b138867781c90fc083b14cce7b4eecf409 (patch)
tree67ebd118d317a4274febf574e10c568d149e8cfc
parentfix: Docker image tags (diff)
parentrefactor(semantics): simple name creation (diff)
downloadrefinery-5b1302b138867781c90fc083b14cce7b4eecf409.tar.gz
refinery-5b1302b138867781c90fc083b14cce7b4eecf409.tar.zst
refinery-5b1302b138867781c90fc083b14cce7b4eecf409.zip
Merge pull request #44 from kris7t/interpreter-performance-fix
Interpreter performance fix
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/java/tools/refinery/interpreter/rete/recipes/helper/RecipeRecognizer.java487
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore2
-rw-r--r--subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel11
-rw-r--r--subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java58
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java7
-rw-r--r--subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java3
6 files changed, 344 insertions, 224 deletions
diff --git a/subprojects/interpreter-rete-recipes/src/main/java/tools/refinery/interpreter/rete/recipes/helper/RecipeRecognizer.java b/subprojects/interpreter-rete-recipes/src/main/java/tools/refinery/interpreter/rete/recipes/helper/RecipeRecognizer.java
index ed551b6e..ad203316 100644
--- a/subprojects/interpreter-rete-recipes/src/main/java/tools/refinery/interpreter/rete/recipes/helper/RecipeRecognizer.java
+++ b/subprojects/interpreter-rete-recipes/src/main/java/tools/refinery/interpreter/rete/recipes/helper/RecipeRecognizer.java
@@ -1,5 +1,6 @@
1/******************************************************************************* 1/*******************************************************************************
2 * Copyright (c) 2010-2016, Gabor Bergmann, Istvan Rath and Daniel Varro 2 * Copyright (c) 2010-2016, Gabor Bergmann, Istvan Rath and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
3 * This program and the accompanying materials are made available under the 4 * 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 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html. 6 * http://www.eclipse.org/legal/epl-v20.html.
@@ -9,16 +10,15 @@
9package tools.refinery.interpreter.rete.recipes.helper; 10package tools.refinery.interpreter.rete.recipes.helper;
10 11
11import org.eclipse.emf.common.util.EList; 12import org.eclipse.emf.common.util.EList;
12import org.eclipse.emf.ecore.EAttribute; 13import org.eclipse.emf.ecore.*;
13import org.eclipse.emf.ecore.EClass;
14import org.eclipse.emf.ecore.EObject;
15import org.eclipse.emf.ecore.EStructuralFeature;
16import org.eclipse.emf.ecore.util.EcoreUtil; 14import org.eclipse.emf.ecore.util.EcoreUtil;
15import org.jetbrains.annotations.Nullable;
17import tools.refinery.interpreter.rete.recipes.RecipesPackage; 16import tools.refinery.interpreter.rete.recipes.RecipesPackage;
18import tools.refinery.interpreter.rete.recipes.ReteNodeRecipe; 17import tools.refinery.interpreter.rete.recipes.ReteNodeRecipe;
19import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; 18import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext;
20 19
21import java.util.*; 20import java.util.*;
21import java.util.concurrent.atomic.AtomicLong;
22 22
23/** 23/**
24 * Stores a set of known <em>canonical</em> recipes, each representing a disjoint equivalence class of recipes, modulo 24 * Stores a set of known <em>canonical</em> recipes, each representing a disjoint equivalence class of recipes, modulo
@@ -26,179 +26,312 @@ import java.util.*;
26 * 26 *
27 * @author Gabor Bergmann 27 * @author Gabor Bergmann
28 * @since 1.3 28 * @since 1.3
29 *
30 */ 29 */
31public class RecipeRecognizer { 30public class RecipeRecognizer {
32 private static long nextRecipeEquivalenceClassID = 0; 31 private static final AtomicLong nextRecipeEquivalenceClassID = new AtomicLong(0);
33 32
34 /** 33 /**
35 * if EcoreUtil.equals(recipe1, recipe2), only one of them will be included here 34 * if EcoreUtil.equals(recipe1, recipe2), only one of them will be included here
36 */ 35 */
37 Map<EClass, Set<ReteNodeRecipe>> canonicalRecipesByClass = new HashMap<>(); 36 Map<Long, Set<ReteNodeRecipe>> canonicalRecipesByHashCode = new HashMap<>();
38 Map<Long, ReteNodeRecipe> canonicalRecipeByEquivalenceClassID = new HashMap<>(); 37 Map<Long, ReteNodeRecipe> canonicalRecipeByEquivalenceClassID = new HashMap<>();
39 38 Set<ReteNodeRecipe> canonicalRecipesWithoutHashCode = new LinkedHashSet<>();
40 private IQueryRuntimeContext runtimeContext; 39
41 40 private final IQueryRuntimeContext runtimeContext;
42 /** 41
43 * @param can be null; if provided, further equivalences can be detected based on {@link IQueryRuntimeContext#wrapElement(Object)} 42 /**
44 * @since 1.6 43 * @param runtimeContext can be null; if provided, further equivalences can be detected based on
45 */ 44 * {@link IQueryRuntimeContext#wrapElement(Object)}
46 public RecipeRecognizer(IQueryRuntimeContext runtimeContext) { 45 * @since 1.6
47 this.runtimeContext = runtimeContext; 46 */
48 } 47 public RecipeRecognizer(IQueryRuntimeContext runtimeContext) {
49 public RecipeRecognizer() { 48 this.runtimeContext = runtimeContext;
50 this(null); 49 }
51 } 50
52 51 public RecipeRecognizer() {
53 /** 52 this(null);
54 * Recognizes when an equivalent canonical recipe is already known. 53 }
55 * 54
56 * @return an equivalent canonical recipe, or the null if no known equivalent found 55 /**
57 */ 56 * Recognizes when an equivalent canonical recipe is already known.
58 public ReteNodeRecipe peekCanonicalRecipe(final ReteNodeRecipe recipe) { 57 *
59 // equivalence class already known 58 * @return an equivalent canonical recipe, or the null if no known equivalent found
60 for (Long classID : recipe.getEquivalenceClassIDs()) { 59 */
61 ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(classID); 60 public ReteNodeRecipe peekCanonicalRecipe(final ReteNodeRecipe recipe) {
62 if (knownRecipe != null) 61 var recipeByEquivalenceClass = getRecipeByEquivalenceClass(recipe);
63 return knownRecipe; 62 if (recipeByEquivalenceClass != null) {
64 } 63 return recipeByEquivalenceClass;
65 64 }
66 // equivalence class not known, but maybe equivalent recipe still 65
67 // available 66 var hashCode = computeHashCode(recipe);
68 Collection<ReteNodeRecipe> sameClassRecipes = getSameClassCanonicalRecipes(recipe); 67 if (hashCode != null) {
69 for (ReteNodeRecipe knownRecipe : sameClassRecipes) { 68 var recipeByHashCode = getRecipeByHashCode(recipe, hashCode);
70 if (isEquivalentRecipe(recipe, knownRecipe)) { 69 if (recipeByHashCode != null) {
71 // FOUND EQUIVALENT RECIPE 70 return recipeByHashCode;
72 recipe.getEquivalenceClassIDs().add(knownRecipe.getEquivalenceClassIDs().get(0)); 71 }
73 return knownRecipe; 72 }
74 } 73
75 } 74 // If we already designated {@code recipe} as canonical during a recursive call in {@code computeHashCode},
76 75 // it will be found here, and we will move it to {@code canonicalRecipesByHashCode}. This could be improved by
77 return null; 76 // checking whether {@code recipe} is already canonical explicitly if there are many recursive patterns.
78 } 77 return getRecipeAndAssignHashCode(recipe, hashCode);
79 78 }
80 /** 79
81 * This recipe will be remembered as a canonical recipe. Method maintains both internal data structures and the 80 @Nullable
82 * equivalence class attribute of the recipe. PRECONDITION: {@link #peekCanonicalRecipe(ReteNodeRecipe)} must return 81 private ReteNodeRecipe getRecipeByEquivalenceClass(ReteNodeRecipe recipe) {
83 * null or the recipe itself 82 for (Long classID : recipe.getEquivalenceClassIDs()) {
84 */ 83 ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(classID);
85 public void makeCanonical(final ReteNodeRecipe recipe) { 84 if (knownRecipe != null) {
86 // this is a canonical recipe, chosen representative of its new 85 return knownRecipe;
87 // equivalence class 86 }
88 if (recipe.getEquivalenceClassIDs().isEmpty()) { 87 }
89 recipe.getEquivalenceClassIDs().add(nextRecipeEquivalenceClassID++); 88 return null;
90 } 89 }
91 for (Long classID : recipe.getEquivalenceClassIDs()) { 90
92 canonicalRecipeByEquivalenceClassID.put(classID, recipe); 91 @Nullable
93 } 92 private ReteNodeRecipe getRecipeByHashCode(ReteNodeRecipe recipe, Long hashCode) {
94 getSameClassCanonicalRecipes(recipe).add(recipe); 93 var equivalentRecipesByHashCode = canonicalRecipesByHashCode.get(hashCode);
95 } 94 if (equivalentRecipesByHashCode != null) {
96 95 for (ReteNodeRecipe knownRecipe : equivalentRecipesByHashCode) {
97 /** 96 if (isEquivalentRecipe(recipe, knownRecipe)) {
98 * Ensures that there is an equivalent canonical recipe; if none is known yet, this recipe will be remembered as 97 // FOUND EQUIVALENT RECIPE
99 * canonical. 98 recipe.getEquivalenceClassIDs().add(knownRecipe.getEquivalenceClassIDs().get(0));
100 * 99 return knownRecipe;
101 * @return an equivalent canonical recipe; the argument recipe itself (which is made canonical) if no known 100 }
102 * equivalent found 101 }
103 */ 102 }
104 public ReteNodeRecipe canonicalizeRecipe(final ReteNodeRecipe recipe) { 103 return null;
105 ReteNodeRecipe knownRecipe = peekCanonicalRecipe(recipe); 104 }
106 if (knownRecipe == null) { 105
107 knownRecipe = recipe; 106 @Nullable
108 makeCanonical(recipe); 107 private ReteNodeRecipe getRecipeAndAssignHashCode(ReteNodeRecipe recipe, Long hashCode) {
109 } 108 var iterator = canonicalRecipesWithoutHashCode.iterator();
110 return knownRecipe; 109 while (iterator.hasNext()) {
111 } 110 var knownRecipe = iterator.next();
112 111 if (isEquivalentRecipe(recipe, knownRecipe)) {
113 /** 112 // FOUND EQUIVALENT RECIPE
114 * @return true iff recipe is a canonical recipe 113 recipe.getEquivalenceClassIDs().add(knownRecipe.getEquivalenceClassIDs().get(0));
115 */ 114 var cachedHashCode = knownRecipe.getCachedHashCode();
116 public boolean isKnownCanonicalRecipe(final ReteNodeRecipe recipe) { 115 if (cachedHashCode != null && !cachedHashCode.equals(hashCode)) {
117 if (recipe.getEquivalenceClassIDs().isEmpty()) { 116 throw new AssertionError("Cached recipe %s already had hash code %s"
118 return false; 117 .formatted(knownRecipe, cachedHashCode));
119 } 118 }
120 ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(recipe.getEquivalenceClassIDs().get(0)); 119 if (hashCode != null) {
121 return recipe == knownRecipe; 120 knownRecipe.setCachedHashCode(hashCode);
122 } 121 addHashCodeRepresentative(hashCode, knownRecipe);
123 122 iterator.remove();
124 private Set<ReteNodeRecipe> getSameClassCanonicalRecipes(final ReteNodeRecipe recipe) { 123 }
125 Set<ReteNodeRecipe> sameClassRecipes = canonicalRecipesByClass.get(recipe.eClass()); 124 return knownRecipe;
126 if (sameClassRecipes == null) { 125 }
127 sameClassRecipes = new HashSet<>(); 126 }
128 canonicalRecipesByClass.put(recipe.eClass(), sameClassRecipes); 127 return null;
129 } 128 }
130 return sameClassRecipes; 129
131 } 130 private final Deque<EObject> hashCodeStack = new ArrayDeque<>();
132 131
133 private boolean isEquivalentRecipe(ReteNodeRecipe recipe, ReteNodeRecipe knownRecipe) { 132 private Long computeHashCode(Object object) {
134 return new EqualityHelper(runtimeContext).equals(recipe, knownRecipe); 133 if (object instanceof List<?> list) {
135 } 134 return computeListHashCode(list);
136 135 }
137 // TODO reuse in more cases later, e.g. switching join node parents, etc. 136 if (object instanceof ReteNodeRecipe recipe) {
138 private static class EqualityHelper extends EcoreUtil.EqualityHelper { 137 return ensureRecipeHashCode(recipe);
139 138 }
140 139 if (object instanceof EObject eObject) {
141 140 return computeEObjectHashCode(eObject);
142 141 }
143 private static final long serialVersionUID = -8841971394686015188L; 142 return (long) Objects.hashCode(object);
144 143 }
145 private static final EAttribute RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS = 144
146 RecipesPackage.eINSTANCE.getReteNodeRecipe_EquivalenceClassIDs(); 145 private Long computeHashCodeOrEquivalenceClassId(Object object) {
147 private static final EAttribute CONSTANT_RECIPE_CONSTANT_VALUES = 146 if (object instanceof ReteNodeRecipe recipe) {
148 RecipesPackage.eINSTANCE.getConstantRecipe_ConstantValues(); 147 var equivalenceClassIDs = recipe.getEquivalenceClassIDs();
149 private static final EAttribute DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY = 148 if (!equivalenceClassIDs.isEmpty()) {
150 RecipesPackage.eINSTANCE.getDiscriminatorBucketRecipe_BucketKey(); 149 return equivalenceClassIDs.get(0);
151 150 }
152 private IQueryRuntimeContext runtimeContext; 151 if (hashCodeStack.contains(recipe)) {
153 152 return null;
154 public EqualityHelper(IQueryRuntimeContext runtimeContext) { 153 }
155 this.runtimeContext = runtimeContext; 154 var canonicalRecipe = canonicalizeRecipe(recipe);
156 } 155 return canonicalRecipe.getEquivalenceClassIDs().get(0);
157 156 } else {
158 @Override 157 return computeHashCode(object);
159 protected boolean haveEqualFeature(EObject eObject1, EObject eObject2, EStructuralFeature feature) { 158 }
160 // ignore differences in this attribute, as it may only be assigned 159 }
161 // after the equivalence check 160
162 if (RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS.equals(feature)) 161 private Long computeListHashCode(List<?> list) {
163 return true; 162 long result = 1;
164 163 for (var item : list) {
165 if (runtimeContext != null) { 164 var update = computeHashCodeOrEquivalenceClassId(item);
166 // constant values 165 if (update == null) {
167 if (/*CONSTANT_RECIPE_CONSTANT_VALUES.equals(feature) ||*/ DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY.equals(feature)) { 166 return null;
168 // use runtime context to map to canonical wrapped form 167 }
169 // this is costly for constant recipes (TODO improve this), but essential for discriminator buckets 168 result = result * 37 + update;
170 169 }
171 Object val1 = eObject1.eGet(feature); 170 return result;
172 Object val2 = eObject2.eGet(feature); 171 }
173 172
174 if (val1 != null && val2 != null) { 173 private Long ensureRecipeHashCode(ReteNodeRecipe recipe) {
175 return runtimeContext.wrapElement(val1).equals(runtimeContext.wrapElement(val2)); 174 var hashCode = recipe.getCachedHashCode();
176 } else { 175 if (hashCode != null) {
177 return val1 == null && val2 == null; 176 return hashCode;
178 } 177 }
179 178 hashCode = computeEObjectHashCode(recipe);
180 } 179 if (hashCode == null) {
181 } 180 return null;
182 181 }
183 // fallback to general comparison 182 recipe.setCachedHashCode(hashCode);
184 return super.haveEqualFeature(eObject1, eObject2, feature); 183 return hashCode;
185 } 184 }
186 185
187 @Override 186 private Long computeEObjectHashCode(EObject eObject) {
188 public boolean equals(EObject eObject1, EObject eObject2) { 187 if (hashCodeStack.contains(eObject)) {
189 // short-circuit if already known to be equivalent 188 return null;
190 if (eObject1 instanceof ReteNodeRecipe) { 189 }
191 if (eObject2 instanceof ReteNodeRecipe) { 190 hashCodeStack.addLast(eObject);
192 EList<Long> eqClassIDs1 = ((ReteNodeRecipe) eObject1).getEquivalenceClassIDs(); 191 try {
193 EList<Long> eqClassIDs2 = ((ReteNodeRecipe) eObject2).getEquivalenceClassIDs(); 192 long result = eObject.eClass().hashCode();
194 193 for (var feature : eObject.eClass().getEAllStructuralFeatures()) {
195 if (!Collections.disjoint(eqClassIDs1, eqClassIDs2)) 194 if (eObject instanceof ReteNodeRecipe && (
196 return true; 195 RecipesPackage.Literals.RETE_NODE_RECIPE__EQUIVALENCE_CLASS_IDS.equals(feature) ||
197 } 196 RecipesPackage.Literals.RETE_NODE_RECIPE__CACHED_HASH_CODE.equals(feature) ||
198 } 197 RecipesPackage.Literals.RETE_NODE_RECIPE__CONSTRUCTED.equals(feature))) {
199 198 continue;
200 // fallback to general comparison 199 }
201 return super.equals(eObject1, eObject2); 200 var value = eObject.eGet(feature);
202 } 201 var update = computeHashCodeOrEquivalenceClassId(value);
203 } 202 if (update == null) {
203 return null;
204 }
205 result = result * 37 + update;
206 }
207 return result;
208 } finally {
209 hashCodeStack.removeLast();
210 }
211 }
212
213 private void addHashCodeRepresentative(Long hashCode, ReteNodeRecipe recipe) {
214 canonicalRecipesByHashCode.computeIfAbsent(hashCode, ignored -> new LinkedHashSet<>()).add(recipe);
215 }
216
217 /**
218 * This recipe will be remembered as a canonical recipe. Method maintains both internal data structures and the
219 * equivalence class attribute of the recipe. PRECONDITION: {@link #peekCanonicalRecipe(ReteNodeRecipe)} must
220 * return null or the recipe itself
221 */
222 public void makeCanonical(final ReteNodeRecipe recipe) {
223 // this is a canonical recipe, chosen representative of its new
224 // equivalence class
225 if (recipe.getEquivalenceClassIDs().isEmpty()) {
226 recipe.getEquivalenceClassIDs().add(nextRecipeEquivalenceClassID.getAndIncrement());
227 }
228 for (Long classID : recipe.getEquivalenceClassIDs()) {
229 canonicalRecipeByEquivalenceClassID.put(classID, recipe);
230 }
231 var hashCode = computeHashCode(recipe);
232 if (hashCode == null) {
233 canonicalRecipesWithoutHashCode.add(recipe);
234 } else {
235 addHashCodeRepresentative(hashCode, recipe);
236 }
237 }
238
239 /**
240 * Ensures that there is an equivalent canonical recipe; if none is known yet, this recipe will be remembered as
241 * canonical.
242 *
243 * @return an equivalent canonical recipe; the argument recipe itself (which is made canonical) if no known
244 * equivalent found
245 */
246 public ReteNodeRecipe canonicalizeRecipe(final ReteNodeRecipe recipe) {
247 ReteNodeRecipe knownRecipe = peekCanonicalRecipe(recipe);
248 if (knownRecipe == null) {
249 knownRecipe = recipe;
250 makeCanonical(recipe);
251 }
252 return knownRecipe;
253 }
254
255 /**
256 * @return true iff recipe is a canonical recipe
257 */
258 public boolean isKnownCanonicalRecipe(final ReteNodeRecipe recipe) {
259 if (recipe.getEquivalenceClassIDs().isEmpty()) {
260 return false;
261 }
262 ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(recipe.getEquivalenceClassIDs().get(0));
263 return recipe == knownRecipe;
264 }
265
266 private boolean isEquivalentRecipe(ReteNodeRecipe recipe, ReteNodeRecipe knownRecipe) {
267 return new EqualityHelper(runtimeContext).equals(recipe, knownRecipe);
268 }
269
270 // TODO reuse in more cases later, e.g. switching join node parents, etc.
271 private static class EqualityHelper extends EcoreUtil.EqualityHelper {
272 private static final long serialVersionUID = -8841971394686015188L;
273
274 private static final EAttribute RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS =
275 RecipesPackage.eINSTANCE.getReteNodeRecipe_EquivalenceClassIDs();
276 private static final EAttribute RETE_NODE_RECIPE_CACHED_HASH_CODE =
277 RecipesPackage.eINSTANCE.getReteNodeRecipe_CachedHashCode();
278 private static final EAttribute RETE_NODE_RECIPE_CONSTRUCTED =
279 RecipesPackage.eINSTANCE.getReteNodeRecipe_Constructed();
280 private static final EAttribute DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY =
281 RecipesPackage.eINSTANCE.getDiscriminatorBucketRecipe_BucketKey();
282
283 private final IQueryRuntimeContext runtimeContext;
284
285 public EqualityHelper(IQueryRuntimeContext runtimeContext) {
286 this.runtimeContext = runtimeContext;
287 }
288
289 @Override
290 protected boolean haveEqualFeature(EObject eObject1, EObject eObject2, EStructuralFeature feature) {
291 // ignore differences in this attribute, as it may only be assigned
292 // after the equivalence check
293 if (RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS.equals(feature) ||
294 RETE_NODE_RECIPE_CACHED_HASH_CODE.equals(feature) ||
295 RETE_NODE_RECIPE_CONSTRUCTED.equals(feature))
296 return true;
297
298 if (runtimeContext != null) {
299 // constant values
300 if (DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY.equals(feature)) {
301 // use runtime context to map to canonical wrapped form
302 // this is costly for constant recipes (TODO improve this), but essential for discriminator buckets
303
304 Object val1 = eObject1.eGet(feature);
305 Object val2 = eObject2.eGet(feature);
306
307 if (val1 != null && val2 != null) {
308 return runtimeContext.wrapElement(val1).equals(runtimeContext.wrapElement(val2));
309 } else {
310 return val1 == null && val2 == null;
311 }
312
313 }
314 }
315
316 // fallback to general comparison
317 return super.haveEqualFeature(eObject1, eObject2, feature);
318 }
319
320 @Override
321 public boolean equals(EObject eObject1, EObject eObject2) {
322 // short-circuit if already known to be equivalent
323 if (eObject1 instanceof ReteNodeRecipe) {
324 if (eObject2 instanceof ReteNodeRecipe) {
325 EList<Long> eqClassIDs1 = ((ReteNodeRecipe) eObject1).getEquivalenceClassIDs();
326 EList<Long> eqClassIDs2 = ((ReteNodeRecipe) eObject2).getEquivalenceClassIDs();
327
328 if (!Collections.disjoint(eqClassIDs1, eqClassIDs2))
329 return true;
330 }
331 }
332
333 // fallback to general comparison
334 return super.equals(eObject1, eObject2);
335 }
336 }
204} 337}
diff --git a/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore b/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore
index a4617833..6b8f10ea 100644
--- a/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore
+++ b/subprojects/interpreter-rete-recipes/src/main/resources/model/recipes.ecore
@@ -31,6 +31,8 @@
31 <details key="documentation" value="If two recipes were found equivalent, a matching equivalence ID can be assigned to them by {@link RecipeRecognizer}. &#xD;&#xA;If two recipes share (at least one) equivalence ID, they are known to be equivalent.&#xD;&#xA;&#xD;&#xA;&lt;p>&#xD;&#xA;A difference in this attribute only does not preclude two recipe elements to be considered equal. &#xD;&#xA;If they are shown to be equivalent using deeper analysis, equivalence ids can be set so that the equivalence is recognized more easily the next time.&#xD;&#xA;&#xD;&#xA;@since 1.3"/> 31 <details key="documentation" value="If two recipes were found equivalent, a matching equivalence ID can be assigned to them by {@link RecipeRecognizer}. &#xD;&#xA;If two recipes share (at least one) equivalence ID, they are known to be equivalent.&#xD;&#xA;&#xD;&#xA;&lt;p>&#xD;&#xA;A difference in this attribute only does not preclude two recipe elements to be considered equal. &#xD;&#xA;If they are shown to be equivalent using deeper analysis, equivalence ids can be set so that the equivalence is recognized more easily the next time.&#xD;&#xA;&#xD;&#xA;@since 1.3"/>
32 </eAnnotations> 32 </eAnnotations>
33 </eStructuralFeatures> 33 </eStructuralFeatures>
34 <eStructuralFeatures xsi:type="ecore:EAttribute" name="cachedHashCode" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//ELongObject"/>
35 <eStructuralFeatures xsi:type="ecore:EAttribute" name="constructed" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean"/>
34 </eClassifiers> 36 </eClassifiers>
35 <eClassifiers xsi:type="ecore:EClass" name="SingleParentNodeRecipe" abstract="true" 37 <eClassifiers xsi:type="ecore:EClass" name="SingleParentNodeRecipe" abstract="true"
36 eSuperTypes="#//ReteNodeRecipe"> 38 eSuperTypes="#//ReteNodeRecipe">
diff --git a/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel b/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel
index 89ff6e3a..f7dc7a4e 100644
--- a/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel
+++ b/subprojects/interpreter-rete-recipes/src/main/resources/model/rete-recipes.genmodel
@@ -2,10 +2,11 @@
2<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" 2<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
3 xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" copyrightText="Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro&#xA;Copyright (c) 2023 The Refinery Authors &lt;https://refinery.tools>&#xA;This program and the accompanying materials are made available under the&#xA;terms of the Eclipse Public License v. 2.0 which is available at&#xA;http://www.eclipse.org/legal/epl-v20.html.&#xA;&#xA;SPDX-License-Identifier: EPL-2.0" 3 xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" copyrightText="Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro&#xA;Copyright (c) 2023 The Refinery Authors &lt;https://refinery.tools>&#xA;This program and the accompanying materials are made available under the&#xA;terms of the Eclipse Public License v. 2.0 which is available at&#xA;http://www.eclipse.org/legal/epl-v20.html.&#xA;&#xA;SPDX-License-Identifier: EPL-2.0"
4 modelDirectory="/tools.refinery.refinery-interpreter-rete-recipes/src/main/emf-gen" 4 modelDirectory="/tools.refinery.refinery-interpreter-rete-recipes/src/main/emf-gen"
5 modelPluginID="tools.refinery.refinery-interpreter-rete-recipes" runtimeJar="true" forceOverwrite="true" 5 modelPluginID="tools.refinery.refinery-interpreter-rete-recipes" runtimeJar="true"
6 modelName="Rete-recipes" updateClasspath="false" nonNLSMarkers="true" rootExtendsClass="org.eclipse.emf.ecore.impl.MinimalEObjectImpl$Container" 6 forceOverwrite="true" modelName="Rete-recipes" updateClasspath="false" nonNLSMarkers="true"
7 testsDirectory="" importerID="org.eclipse.emf.importer.ecore" containmentProxies="true" 7 rootExtendsClass="org.eclipse.emf.ecore.impl.MinimalEObjectImpl$Container" testsDirectory=""
8 complianceLevel="7.0" language="en" operationReflection="true"> 8 importerID="org.eclipse.emf.importer.ecore" containmentProxies="true" complianceLevel="7.0"
9 language="en" operationReflection="true">
9 <genAnnotations source="http://www.eclipse.org/emf/2002/GenModel/exporter/org.eclipse.xsd.ecore.exporter"> 10 <genAnnotations source="http://www.eclipse.org/emf/2002/GenModel/exporter/org.eclipse.xsd.ecore.exporter">
10 <genAnnotations source="selectedPackages"> 11 <genAnnotations source="selectedPackages">
11 <details key="http://www.eclipse.org/emf/2002/Ecore" value="Ecore.xsd"/> 12 <details key="http://www.eclipse.org/emf/2002/Ecore" value="Ecore.xsd"/>
@@ -32,6 +33,8 @@
32 <genClasses image="false" ecoreClass="recipes.ecore#//ReteNodeRecipe"> 33 <genClasses image="false" ecoreClass="recipes.ecore#//ReteNodeRecipe">
33 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/traceInfo"/> 34 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/traceInfo"/>
34 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/equivalenceClassIDs"/> 35 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/equivalenceClassIDs"/>
36 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/cachedHashCode"/>
37 <genFeatures createChild="false" ecoreFeature="ecore:EAttribute recipes.ecore#//ReteNodeRecipe/constructed"/>
35 <genOperations ecoreOperation="recipes.ecore#//ReteNodeRecipe/getArity" body="throw new &lt;%java.lang.UnsupportedOperationException%>();"/> 38 <genOperations ecoreOperation="recipes.ecore#//ReteNodeRecipe/getArity" body="throw new &lt;%java.lang.UnsupportedOperationException%>();"/>
36 </genClasses> 39 </genClasses>
37 <genClasses image="false" ecoreClass="recipes.ecore#//SingleParentNodeRecipe"> 40 <genClasses image="false" ecoreClass="recipes.ecore#//SingleParentNodeRecipe">
diff --git a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java
index 0a68f449..e7901822 100644
--- a/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java
+++ b/subprojects/interpreter-rete/src/main/java/tools/refinery/interpreter/rete/network/NodeProvisioner.java
@@ -1,5 +1,6 @@
1/******************************************************************************* 1/*******************************************************************************
2 * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro 2 * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro
3 * Copyright (c) 2023 The Refinery Authors <https://refinery.tools/>
3 * This program and the accompanying materials are made available under the 4 * 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 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html. 6 * http://www.eclipse.org/legal/epl-v20.html.
@@ -74,49 +75,28 @@ public class NodeProvisioner {
74 public synchronized Address<? extends Node> getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) { 75 public synchronized Address<? extends Node> getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) {
75 ReteNodeRecipe recipe = recipeTrace.getRecipe(); 76 ReteNodeRecipe recipe = recipeTrace.getRecipe();
76 Address<? extends Node> result = getNodesByRecipe().get(recipe); 77 Address<? extends Node> result = getNodesByRecipe().get(recipe);
77 if (result != null) { 78 if (result == null) {
78 // NODE ALREADY CONSTRUCTED FOR RECIPE, only needs to add trace
79 if (getRecipeTraces().add(recipeTrace))
80 result.getNodeCache().assignTraceInfo(recipeTrace);
81 } else {
82 // No node for this recipe object - but equivalent recipes still 79 // No node for this recipe object - but equivalent recipes still
83 // reusable 80 // reusable
84 ReteNodeRecipe canonicalRecipe = recognizer.canonicalizeRecipe(recipe); 81 ReteNodeRecipe canonicalRecipe = recognizer.canonicalizeRecipe(recipe);
85 if (canonicalRecipe != recipe) { 82 result = getNodesByRecipe().get(canonicalRecipe);
86 // FOUND EQUIVALENT RECIPE 83 if (result == null) {
87 result = getNodesByRecipe().get(canonicalRecipe); 84 if (canonicalRecipe.isConstructed()) {
88 if (result != null) { 85 throw new IllegalStateException(
89 // NODE ALREADY CONSTRUCTED FOR EQUIVALENT RECIPE 86 "Already constructed node is missing for canonical recipe " + canonicalRecipe);
90 recipeTrace.shadowWithEquivalentRecipe(canonicalRecipe); 87 }
91 getNodesByRecipe().put(recipe, result); 88 canonicalRecipe.setConstructed(true);
92 if (getRecipeTraces().add(recipeTrace)) 89 final Node freshNode = instantiateNodeForRecipe(recipeTrace, canonicalRecipe);
93 result.getNodeCache().assignTraceInfo(recipeTrace); 90 result = reteContainer.makeAddress(freshNode);
94 // Bug 491922: ensure that recipe shadowing propagates to 91 }
95 // parent traces 92 if (canonicalRecipe != recipe) {
96 // note that if equivalentRecipes() becomes more 93 recipeTrace.shadowWithEquivalentRecipe(canonicalRecipe);
97 // sophisticated 94 getNodesByRecipe().put(recipe, result);
98 // and considers recipes with different parents, this might 95 }
99 // have to be changed
100 ensureParents(recipeTrace);
101 } else {
102 // CONSTRUCTION IN PROGRESS FOR EQUIVALENT RECIPE
103 if (recipe instanceof IndexerRecipe) {
104 // this is allowed for indexers;
105 // go on with the construction, as the same indexer node
106 // will be obtained anyways
107 } else {
108 throw new IllegalStateException(
109 "This should not happen: " + "non-indexer nodes are are supposed to be constructed "
110 + "as soon as they are designated as canonical recipes");
111 }
112 }
113 }
114 if (result == null) {
115 // MUST INSTANTIATE NEW NODE FOR RECIPE
116 final Node freshNode = instantiateNodeForRecipe(recipeTrace, recipe);
117 result = reteContainer.makeAddress(freshNode);
118 }
119 } 96 }
97 if (getRecipeTraces().add(recipeTrace)) {
98 result.getNodeCache().assignTraceInfo(recipeTrace);
99 }
120 return result; 100 return result;
121 } 101 }
122 102
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java
index cc262129..3694f5f4 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/metadata/MetadataCreator.java
@@ -171,16 +171,15 @@ public class MetadataCreator {
171 171
172 private QualifiedName getSimpleName(EObject eObject, QualifiedName qualifiedName, IScope scope) { 172 private QualifiedName getSimpleName(EObject eObject, QualifiedName qualifiedName, IScope scope) {
173 var descriptions = scope.getElements(eObject); 173 var descriptions = scope.getElements(eObject);
174 var names = new HashSet<QualifiedName>(); 174 var names = new ArrayList<QualifiedName>();
175 for (var description : descriptions) { 175 for (var description : descriptions) {
176 // {@code getQualifiedName()} will refer to the full name for objects that are loaded from the global 176 // {@code getQualifiedName()} will refer to the full name for objects that are loaded from the global
177 // scope, but {@code getName()} returns the qualified name that we set in 177 // scope, but {@code getName()} returns the qualified name that we set in
178 // {@code ProblemResourceDescriptionStrategy}. 178 // {@code ProblemResourceDescriptionStrategy}.
179 names.add(description.getName()); 179 names.add(description.getName());
180 } 180 }
181 var iterator = names.stream().sorted(Comparator.comparingInt(QualifiedName::getSegmentCount)).iterator(); 181 names.sort(Comparator.comparingInt(QualifiedName::getSegmentCount));
182 while (iterator.hasNext()) { 182 for (var simpleName : names) {
183 var simpleName = iterator.next();
184 if (names.contains(simpleName) && isUnique(scope, simpleName)) { 183 if (names.contains(simpleName) && isUnique(scope, simpleName)) {
185 return simpleName; 184 return simpleName;
186 } 185 }
diff --git a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java
index c0d802da..4e839b43 100644
--- a/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java
+++ b/subprojects/store-query-interpreter/src/main/java/tools/refinery/store/query/interpreter/internal/QueryInterpreterBuilderImpl.java
@@ -5,6 +5,8 @@
5 */ 5 */
6package tools.refinery.store.query.interpreter.internal; 6package tools.refinery.store.query.interpreter.internal;
7 7
8import org.eclipse.emf.ecore.EPackage;
9import tools.refinery.interpreter.rete.recipes.RecipesPackage;
8import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 10import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
9import tools.refinery.store.model.ModelStore; 11import tools.refinery.store.model.ModelStore;
10import tools.refinery.store.query.dnf.AnyQuery; 12import tools.refinery.store.query.dnf.AnyQuery;
@@ -40,6 +42,7 @@ public class QueryInterpreterBuilderImpl extends AbstractModelAdapterBuilder<Que
40 private final Set<AnyQuery> queries = new LinkedHashSet<>(); 42 private final Set<AnyQuery> queries = new LinkedHashSet<>();
41 43
42 public QueryInterpreterBuilderImpl() { 44 public QueryInterpreterBuilderImpl() {
45 EPackage.Registry.INSTANCE.put(RecipesPackage.eNS_URI, RecipesPackage.eINSTANCE);
43 engineOptionsBuilder = new InterpreterEngineOptions.Builder() 46 engineOptionsBuilder = new InterpreterEngineOptions.Builder()
44 .withDefaultBackend(ReteBackendFactory.INSTANCE) 47 .withDefaultBackend(ReteBackendFactory.INSTANCE)
45 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) 48 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE)