]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.structural.synchronization/src/org/simantics/structural/synchronization/utils/MappingBase.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.structural.synchronization / src / org / simantics / structural / synchronization / utils / MappingBase.java
1 package org.simantics.structural.synchronization.utils;
2
3 import java.io.PrintWriter;
4 import java.util.Collections;
5 import java.util.Map;
6 import java.util.function.Consumer;
7
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10
11 import gnu.trove.map.hash.THashMap;
12 import gnu.trove.procedure.TObjectObjectProcedure;
13 import gnu.trove.procedure.TObjectProcedure;
14 import gnu.trove.set.hash.THashSet;
15
16 /**
17  * The entry point to the mapping structure between Simantics database and a
18  * designated solver. It is used to synchronize changes from Simantics to the
19  * solver.
20  * 
21  * @author Hannu Niemistö
22  */
23 abstract public class MappingBase<T extends ComponentBase<T>> {
24
25     private final transient Logger LOGGER = LoggerFactory.getLogger(getClass());
26     
27         abstract public T getConfiguration();
28     
29     /**
30      * Set of all components indexed by their UID.
31      */
32     transient protected THashMap<String, T> configurationByUid;
33
34     /**
35      * Set of all components indexed by their solver name.
36      */
37     transient protected Map<String, T> configurationBySolverName;
38
39     /** 
40      * Set of components whose removal is delayed because they might
41      * have been moved somewhere else.
42      */
43     transient THashSet<T> pendingRemoval = new THashSet<T>();
44     
45     /**
46      * This is a structure that is used to return the state of the removed modules
47      * when the removal is undone.
48      */
49     public transient StateUndoContextBase undoContext;
50     
51     public transient ComponentFactory<T> componentFactory;
52     
53     /**
54      * The synchronization has processed all change sets before this change set 
55      * (excluding this change set).
56      * It means that next synchronization operation should start examining changes
57      * made in this revision and revisions after this.
58      */
59     public long currentRevision;
60     
61     /**
62      * Tells whether the uids in the components can be used
63      * in the synchronization. This is normally true, but
64      * may be set temporarily false when export/import:in 
65      * the model.
66      */
67     private boolean trustUids;
68
69     public MappingBase() {
70         this(null, -1L, false);
71     }
72
73     public MappingBase(T configuration, boolean trustUids) {
74         this(configuration, -1L, trustUids);
75     }
76
77     public MappingBase(T configuration, long currentRevision, boolean trustUids) {
78         undoContext = createUndoContext();
79         componentFactory = createComponentFactory();
80         if(trustUids)
81             createConfigurationById(configuration);
82         this.currentRevision = currentRevision;
83         this.trustUids = trustUids;
84     }
85     
86     abstract public StateUndoContextBase createUndoContext();
87     abstract public ComponentFactory<T> createComponentFactory();
88
89     protected void createConfigurationById(T configuration) {
90         THashMap<String, T> configurationByUid = new THashMap<String, T>();
91         browseConfiguration(configurationByUid, configuration);
92         this.configurationByUid = configurationByUid;
93     }
94
95     private void browseConfiguration(
96             THashMap<String, T> configurationByUid,
97             T configuration) {
98         configurationByUid.put(configuration.uid, configuration);
99         for(T child : configuration.getChildren()) {
100             browseConfiguration(configurationByUid, child);
101             child.parent = configuration;
102         }
103     }
104
105     public Map<String, T> getConfigurationBySolverName() {
106         Map<String, T> result = configurationBySolverName;
107         if (result == null) {
108             T configuration = getConfiguration();
109             if (configuration != null)
110                 result = configurationBySolverName = createConfigurationBySolverName(configuration);
111             else
112                 result = Collections.emptyMap();
113         }
114         return result;
115     }
116
117     protected Map<String, T> createConfigurationBySolverName(T configuration) {
118         THashMap<String, T> configurationBySolverName = new THashMap<>();
119         browseConfigurationBySolverName(configurationBySolverName, configuration);
120         return configurationBySolverName;
121     }
122
123     private void browseConfigurationBySolverName(
124             THashMap<String, T> configurationBySolverName,
125             T configuration) {
126         if (configuration.solverComponentName != null) {
127             configurationBySolverName.put(configuration.solverComponentName, configuration);
128         } else if (configuration.componentId != 0) {
129             LOGGER.warn("configuration.solverComponentName is null! configuration uid is {} and component id {}", configuration.getUid(), configuration.componentId);
130         }
131         for(T child : configuration.getChildren()) {
132             browseConfigurationBySolverName(configurationBySolverName, child);
133             child.parent = configuration;
134         }
135     }
136
137     public T detachOrCreateComponent(String uid) {
138         T result = configurationByUid.get(uid);
139         if(result == null) {
140             result = componentFactory.create(uid);
141             configurationByUid.put(uid, result);
142             configurationBySolverName = null; // forces recalculation
143         }
144         else {
145             if(result.getParent() == null)
146                 pendingRemoval.remove(result);
147             else
148                 result.getParent().detachByUid(uid);
149         }
150         return result;
151     }
152
153     /**
154      * Marks that the component should be removed. The actual removal
155      * is delayed until the call of {@link #removePending} so that if the
156      * component is not actually removed but moved or renamed, we don't lose
157      * its state.
158      */
159     public void addPendingRemoval(T component) {
160         pendingRemoval.add(component);
161     }
162
163     public void printUidMap() {
164         printUidMap(new PrintWriter(System.out));
165     }
166
167     public void printUidMap(final PrintWriter out) {
168         out.println("Component tree");
169         out.print("    ");
170         getConfiguration().printConfiguration(out, 1);
171         if(configurationByUid != null) {
172             out.println("UIDs");
173             configurationByUid.forEachEntry(new TObjectObjectProcedure<String, T>() {
174                 @Override
175                 public boolean execute(String a, T b) {
176                     out.println("    " + a + " (" + b.solverComponentName + ", " + b.componentId + ", " + b.uid + ")");
177                     return true;
178                 }
179             });
180         }
181     }
182
183     /**
184      * Removes the component recursively.
185      */
186     public void remove(final Solver solver, T component) {
187         if(configurationByUid != null)
188             configurationByUid.remove(component.uid);
189         if (configurationBySolverName != null && component.solverComponentName != null)
190             configurationBySolverName.remove(component.solverComponentName);
191         if(component.getChildMap() != null)
192             component.getChildMap().forEachValue(new TObjectProcedure<T>() {
193                 @Override
194                 public boolean execute(T child) {
195                     remove(solver, child);
196                     return true;
197                 }
198             });
199         if(component.componentId > 0 && !component.attached)
200             solver.remove(component.componentId);
201     }
202     
203     /**
204      * Saves undo state recursively
205      */
206     public void saveUndoState(final Solver solver, T component) {
207         if(component.getChildMap() != null)
208             component.getChildMap().forEachValue(new TObjectProcedure<T>() {
209                 @Override
210                 public boolean execute(T child) {
211                     saveUndoState(solver, child);
212                     return true;
213                 }
214             });
215         else if(component.componentId > 0 && !component.attached)
216             undoContext.saveState(solver, component.componentId, component.uid);
217     }
218
219     /**
220      * Removes components that are marked pending with {@link #addPendingRemoval} method.
221      */
222     public void removePending(final Solver solver) {
223         pendingRemoval.forEach(new TObjectProcedure<T>() {
224             @Override
225             public boolean execute(T component) {
226                 saveUndoState(solver, component);
227                 return true;
228             }
229         });
230         pendingRemoval.forEach(new TObjectProcedure<T>() {
231             @Override
232             public boolean execute(T component) {
233                 remove(solver, component);
234                 return true;
235             }
236         });
237         pendingRemoval.clear();
238     }
239     
240     /**
241      * Changes the {@link #trustUids} flag.
242      */
243     public void setTrustUids(boolean trustUids) {
244         if(trustUids != this.trustUids) {
245             this.trustUids = trustUids;
246             if(trustUids) {
247                 T configuration = getConfiguration();
248                 if(configuration != null)
249                     createConfigurationById(configuration);
250             }
251         }
252         if(!trustUids)
253             configurationByUid = null;
254     }
255     
256     public boolean getTrustUids() {
257         return trustUids;
258     }
259
260     public void dispose() {
261         if (configurationByUid != null)
262             configurationByUid.clear();
263         if (configurationBySolverName != null) {
264             configurationBySolverName.clear();
265             configurationBySolverName = null;
266         }
267         pendingRemoval.clear();
268     }
269     
270     public boolean hasPendingRemovals() {
271         return !pendingRemoval.isEmpty();
272     }
273
274     public void forEachPendingRemoval(Consumer<T> consumer) {
275         pendingRemoval.forEach(c -> {
276             consumer.accept(c);
277             return true;
278         });
279     }
280
281 }