]> gerrit.simantics Code Review - simantics/platform.git/blob
9f557eccf9943a9b8a6aaa02736ef799a04a6918
[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         for(T child : configuration.getChildren()) {
108             browseConfiguration(configurationByUid, child);
109             child.parent = configuration;
110         }
111     }
112
113     @SuppressWarnings("unchecked")
114     public Map<String, T> getConfigurationBySolverName() {
115         Map<String, T> result = configurationBySolverName;
116         if (result == null) {
117             T configuration = getConfiguration();
118             if (configuration != null) {
119                 Tuple2 t = createConfigurationCacheMaps(configuration);
120                 return (Map<String, T>) t.get(0);
121             } else {
122                 result = Collections.emptyMap();
123             }
124         }
125         return result;
126     }
127
128     @SuppressWarnings("unchecked")
129     public TIntObjectMap<T> getConfigurationByComponentId() {
130         TIntObjectMap<T> result = configurationByComponentId;
131         if (result == null) {
132             T configuration = getConfiguration();
133             if (configuration != null) {
134                 Tuple2 t = createConfigurationCacheMaps(configuration);
135                 return (TIntObjectMap<T>) t.get(1);
136             } else {
137                 result = new TIntObjectHashMap<>(1);
138             }
139         }
140         return result;
141     }
142
143     protected Tuple2 createConfigurationCacheMaps(T configuration) {
144         THashMap<String, T> configurationBySolverName = new THashMap<>();
145         TIntObjectMap<T> configurationByComponentId = new TIntObjectHashMap<>();
146         browseConfigurationCacheMaps(configurationBySolverName, configurationByComponentId, configuration);
147         this.configurationBySolverName = configurationBySolverName;
148         this.configurationByComponentId = configurationByComponentId;
149         return new Tuple2(configurationBySolverName, configurationByComponentId);
150     }
151
152     private void browseConfigurationCacheMaps(
153             THashMap<String, T> configurationBySolverName,
154             TIntObjectMap<T> configurationByComponentId,
155             T configuration)
156     {
157         if (configuration.solverComponentName != null) {
158             configurationBySolverName.put(configuration.solverComponentName, configuration);
159             configurationByComponentId.put(configuration.componentId, configuration);
160         } else if (configuration.componentId != 0) {
161             LOGGER.warn("configuration.solverComponentName is null! configuration uid is {} and component id {}", configuration.getUid(), configuration.componentId);
162         }
163         for(T child : configuration.getChildren()) {
164             browseConfigurationCacheMaps(configurationBySolverName, configurationByComponentId, child);
165         }
166     }
167
168     public T detachOrCreateComponent(String uid) {
169         T result = configurationByUid.get(uid);
170         if(result == null) {
171             result = componentFactory.create(uid);
172             configurationByUid.put(uid, result);
173             configurationBySolverName = null; // forces recalculation
174         }
175         else {
176             if(result.getParent() == null)
177                 pendingRemoval.remove(result);
178             else
179                 result.getParent().detachByUid(uid);
180         }
181         return result;
182     }
183
184     /**
185      * Marks that the component should be removed. The actual removal
186      * is delayed until the call of {@link #removePending} so that if the
187      * component is not actually removed but moved or renamed, we don't lose
188      * its state.
189      */
190     public void addPendingRemoval(T component) {
191         pendingRemoval.add(component);
192     }
193
194     public void printUidMap() {
195         printUidMap(new PrintWriter(System.out));
196     }
197
198     public void printUidMap(final PrintWriter out) {
199         out.println("Component tree");
200         out.print("    ");
201         getConfiguration().printConfiguration(out, 1);
202         if(configurationByUid != null) {
203             out.println("UIDs");
204             configurationByUid.forEachEntry(new TObjectObjectProcedure<String, T>() {
205                 @Override
206                 public boolean execute(String a, T b) {
207                     out.println("    " + a + " (" + b.solverComponentName + ", " + b.componentId + ", " + b.uid + ")");
208                     return true;
209                 }
210             });
211         }
212     }
213
214     /**
215      * Removes the component recursively.
216      */
217     public void remove(final Solver solver, T component) {
218         if(configurationByUid != null)
219             configurationByUid.remove(component.uid);
220         if (configurationBySolverName != null && component.solverComponentName != null)
221             configurationBySolverName.remove(component.solverComponentName);
222         if(component.getChildMap() != null)
223             component.getChildMap().forEachValue(new TObjectProcedure<T>() {
224                 @Override
225                 public boolean execute(T child) {
226                     remove(solver, child);
227                     return true;
228                 }
229             });
230         if(component.componentId > 0 && !component.attached)
231             solver.remove(component.componentId);
232     }
233     
234     /**
235      * Saves undo state recursively
236      */
237     public void saveUndoState(final Solver solver, T component) {
238         if(component.getChildMap() != null)
239             component.getChildMap().forEachValue(new TObjectProcedure<T>() {
240                 @Override
241                 public boolean execute(T child) {
242                     saveUndoState(solver, child);
243                     return true;
244                 }
245             });
246         else if(component.componentId > 0 && !component.attached)
247             undoContext.saveState(solver, component.componentId, component.uid);
248     }
249
250     /**
251      * Removes components that are marked pending with {@link #addPendingRemoval} method.
252      */
253     public void removePending(final Solver solver) {
254         pendingRemoval.forEach(new TObjectProcedure<T>() {
255             @Override
256             public boolean execute(T component) {
257                 saveUndoState(solver, component);
258                 return true;
259             }
260         });
261         pendingRemoval.forEach(new TObjectProcedure<T>() {
262             @Override
263             public boolean execute(T component) {
264                 remove(solver, component);
265                 return true;
266             }
267         });
268         pendingRemoval.clear();
269     }
270     
271     /**
272      * Changes the {@link #trustUids} flag.
273      */
274     public void setTrustUids(boolean trustUids) {
275         if(trustUids != this.trustUids) {
276             this.trustUids = trustUids;
277             if(trustUids) {
278                 T configuration = getConfiguration();
279                 if(configuration != null)
280                     createConfigurationById(configuration);
281             }
282         }
283         if(!trustUids)
284             configurationByUid = null;
285     }
286     
287     public boolean getTrustUids() {
288         return trustUids;
289     }
290
291     public void dispose() {
292         if (configurationByUid != null)
293             configurationByUid.clear();
294         if (configurationBySolverName != null) {
295             configurationBySolverName.clear();
296             configurationBySolverName = null;
297         }
298         pendingRemoval.clear();
299     }
300     
301     public boolean hasPendingRemovals() {
302         return !pendingRemoval.isEmpty();
303     }
304
305     public void forEachPendingRemoval(Consumer<T> consumer) {
306         pendingRemoval.forEach(c -> {
307             consumer.accept(c);
308             return true;
309         });
310     }
311
312 }