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