aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java')
-rw-r--r--subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java214
1 files changed, 214 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java
new file mode 100644
index 00000000..1f2c27e8
--- /dev/null
+++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java
@@ -0,0 +1,214 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.internal.engine;
10
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.EnumMap;
15import java.util.HashSet;
16import java.util.Map;
17import java.util.Map.Entry;
18
19import org.apache.log4j.Logger;
20import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine;
21import tools.refinery.viatra.runtime.api.IMatchUpdateListener;
22import tools.refinery.viatra.runtime.api.IPatternMatch;
23import tools.refinery.viatra.runtime.api.ViatraQueryEngineLifecycleListener;
24import tools.refinery.viatra.runtime.api.ViatraQueryMatcher;
25import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener;
26import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener.ChangeLevel;
27import tools.refinery.viatra.runtime.api.scope.ViatraBaseIndexChangeListener;
28import tools.refinery.viatra.runtime.exception.ViatraQueryException;
29import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory;
30
31public final class ModelUpdateProvider extends ListenerContainer<ViatraQueryModelUpdateListener> {
32
33 private final AdvancedViatraQueryEngine queryEngine;
34 private ChangeLevel currentChange = ChangeLevel.NO_CHANGE;
35 private ChangeLevel maxLevel = ChangeLevel.NO_CHANGE;
36 private final Map<ChangeLevel, Collection<ViatraQueryModelUpdateListener>> listenerMap;
37 private final Logger logger;
38
39 public ModelUpdateProvider(AdvancedViatraQueryEngine queryEngine, Logger logger) {
40 super();
41 this.queryEngine = queryEngine;
42 this.logger = logger;
43 listenerMap = new EnumMap<>(ChangeLevel.class);
44 }
45
46 @Override
47 protected void listenerAdded(ViatraQueryModelUpdateListener listener) {
48 // check ChangeLevel
49 // create callback for given level if required
50 if(listenerMap.isEmpty()) {
51 try {
52 this.queryEngine.getBaseIndex().addBaseIndexChangeListener(indexListener);
53 // add listener to new matchers (use lifecycle listener)
54 this.queryEngine.addLifecycleListener(selfListener);
55 } catch (ViatraQueryException e) {
56 throw new IllegalStateException("Model update listener used on engine without base index", e);
57 }
58 }
59
60 ChangeLevel changeLevel = listener.getLevel();
61 listenerMap.computeIfAbsent(changeLevel, k -> CollectionsFactory.createSet()).add(listener);
62 // increase or keep max level of listeners
63 ChangeLevel oldMaxLevel = maxLevel;
64 maxLevel = maxLevel.changeOccured(changeLevel);
65 if(!maxLevel.equals(oldMaxLevel) && ChangeLevel.MATCHSET.compareTo(oldMaxLevel) > 0 && ChangeLevel.MATCHSET.compareTo(maxLevel) <= 0) {
66 // add matchUpdateListener to all matchers
67 for (ViatraQueryMatcher<?> matcher : this.queryEngine.getCurrentMatchers()) {
68 this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false);
69 }
70 }
71 }
72
73 @Override
74 protected void listenerRemoved(ViatraQueryModelUpdateListener listener) {
75 ChangeLevel changeLevel = listener.getLevel();
76 Collection<ViatraQueryModelUpdateListener> old = listenerMap.getOrDefault(changeLevel, Collections.emptySet());
77 boolean removed = old.remove(listener);
78 if(removed) {
79 if (old.isEmpty()) listenerMap.remove(changeLevel);
80 } else {
81 handleUnsuccesfulRemove(listener);
82 }
83
84 updateMaxLevel();
85
86 if(listenerMap.isEmpty()) {
87 this.queryEngine.removeLifecycleListener(selfListener);
88 removeBaseIndexChangeListener();
89 }
90 }
91
92 private void removeBaseIndexChangeListener() {
93 try {
94 this.queryEngine.getBaseIndex().removeBaseIndexChangeListener(indexListener);
95 } catch (ViatraQueryException e) {
96 throw new IllegalStateException("Model update listener used on engine without base index", e);
97 }
98 }
99
100 private void updateMaxLevel() {
101 if(!listenerMap.containsKey(maxLevel)) {
102 ChangeLevel newMaxLevel = ChangeLevel.NO_CHANGE;
103 for (ChangeLevel level : new HashSet<>(listenerMap.keySet())) {
104 newMaxLevel = newMaxLevel.changeOccured(level);
105 }
106 maxLevel = newMaxLevel;
107 }
108 if(maxLevel.compareTo(ChangeLevel.MATCHSET) < 0) {
109 // remove listener from matchers
110 for (ViatraQueryMatcher<?> matcher : this.queryEngine.getCurrentMatchers()) {
111 this.queryEngine.removeMatchUpdateListener(matcher, matchSetListener);
112 }
113 }
114 }
115
116 private void handleUnsuccesfulRemove(ViatraQueryModelUpdateListener listener) {
117 for (Entry<ChangeLevel, Collection<ViatraQueryModelUpdateListener>> entry : listenerMap.entrySet()) {
118 Collection<ViatraQueryModelUpdateListener> existingListeners = entry.getValue();
119 // if the listener is contained in some other bucket, remove it from there
120 if(existingListeners.remove(listener)) {
121 logger.error("Listener "+listener+" change level changed since initialization!");
122 if (existingListeners.isEmpty()) listenerMap.remove(entry.getKey());
123 return; // listener is contained only once
124 }
125 }
126 logger.error("Listener "+listener+" already removed from map (e.g. engine was already disposed)!");
127 }
128
129 private void notifyListeners() {
130
131 // any change that occurs after this point should be regarded as a new event
132 // FIXME what should happen when a listener creates new notifications?
133 // -> other listeners will get events in different order
134 ChangeLevel tempLevel = currentChange;
135 currentChange = ChangeLevel.NO_CHANGE;
136
137 if(!listenerMap.isEmpty()) {
138 for (ChangeLevel level : new HashSet<>(listenerMap.keySet())) {
139 if(tempLevel.compareTo(level) >= 0) {
140 for (ViatraQueryModelUpdateListener listener : new ArrayList<>(listenerMap.get(level))) {
141 try {
142 listener.notifyChanged(tempLevel);
143 } catch (Exception ex) {
144 logger.error(
145 "VIATRA Query encountered an error in delivering model update notification to listener "
146 + listener + ".", ex);
147 }
148 }
149 }
150 }
151 } else {
152 throw new IllegalStateException("Notify listeners must not be called without listeners! Maybe an update callback was not removed correctly.");
153 }
154
155 }
156
157 // model update "providers":
158 // - model: IQBase callback even if not dirty
159 // - index: IQBase dirty callback
160 private final ViatraBaseIndexChangeListener indexListener = new ViatraBaseIndexChangeListener() {
161
162 @Override
163 public boolean onlyOnIndexChange() {
164 return false;
165 }
166
167 @Override
168 public void notifyChanged(boolean indexChanged) {
169 if(indexChanged) {
170 currentChange = currentChange.changeOccured(ChangeLevel.INDEX);
171 } else {
172 currentChange = currentChange.changeOccured(ChangeLevel.MODEL);
173 }
174 notifyListeners();
175 }
176
177 };
178 // - matchset: add the same listener to each matcher and use a dirty flag. needs IQBase callback as well
179 private final IMatchUpdateListener<IPatternMatch> matchSetListener = new IMatchUpdateListener<IPatternMatch>() {
180
181 @Override
182 public void notifyDisappearance(IPatternMatch match) {
183 currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET);
184 }
185
186 @Override
187 public void notifyAppearance(IPatternMatch match) {
188 currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET);
189 }
190 };
191
192 private final ViatraQueryEngineLifecycleListener selfListener = new ViatraQueryEngineLifecycleListener() {
193
194 @Override
195 public void matcherInstantiated(ViatraQueryMatcher<? extends IPatternMatch> matcher) {
196 if (maxLevel.compareTo(ChangeLevel.MATCHSET) >= 0) {
197 ModelUpdateProvider.this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false);
198 }
199 }
200
201 @Override
202 public void engineWiped() {}
203
204 @Override
205 public void engineDisposed() {
206 removeBaseIndexChangeListener();
207 listenerMap.clear();
208 maxLevel = ChangeLevel.NO_CHANGE;
209 }
210
211 @Override
212 public void engineBecameTainted(String description, Throwable t) {}
213 };
214}