1 package org.simantics.structural.synchronization.base;
3 import java.io.PrintWriter;
4 import java.util.Collections;
6 import java.util.function.Consumer;
8 import org.simantics.scl.runtime.tuple.Tuple2;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
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;
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
24 * @author Hannu Niemistö
26 abstract public class MappingBase<T extends ComponentBase<T>> {
28 private final transient Logger LOGGER = LoggerFactory.getLogger(getClass());
30 abstract public T getConfiguration();
33 * Set of all components indexed by their UID.
35 transient protected THashMap<String, T> configurationByUid;
38 * Set of all components indexed by their solver name.
40 transient protected Map<String, T> configurationBySolverName;
43 * Set of all mapped components indexed by their solver component id.
45 transient protected TIntObjectMap<T> configurationByComponentId;
48 * Set of components whose removal is delayed because they might
49 * have been moved somewhere else.
51 transient THashSet<T> pendingRemoval = new THashSet<T>();
54 * This is a structure that is used to return the state of the removed modules
55 * when the removal is undone.
57 public transient StateUndoContextBase undoContext;
59 public transient ComponentFactory<T> componentFactory;
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.
67 public long currentRevision;
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
75 private boolean trustUids;
77 public MappingBase() {
78 this(null, -1L, false);
81 public MappingBase(T configuration, boolean trustUids) {
82 this(configuration, -1L, trustUids);
85 public MappingBase(T configuration, long currentRevision, boolean trustUids) {
86 undoContext = createUndoContext();
87 componentFactory = createComponentFactory();
89 createConfigurationById(configuration);
90 this.currentRevision = currentRevision;
91 this.trustUids = trustUids;
94 abstract public StateUndoContextBase createUndoContext();
95 abstract public ComponentFactory<T> createComponentFactory();
97 protected void createConfigurationById(T configuration) {
98 THashMap<String, T> configurationByUid = new THashMap<String, T>();
99 browseConfiguration(configurationByUid, configuration);
100 this.configurationByUid = configurationByUid;
103 private void browseConfiguration(
104 THashMap<String, T> configurationByUid,
106 configurationByUid.put(configuration.uid, configuration);
107 for(T child : configuration.getChildren()) {
108 browseConfiguration(configurationByUid, child);
109 child.parent = configuration;
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);
122 result = Collections.emptyMap();
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);
137 result = new TIntObjectHashMap<>(1);
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);
152 private void browseConfigurationCacheMaps(
153 THashMap<String, T> configurationBySolverName,
154 TIntObjectMap<T> configurationByComponentId,
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);
163 for(T child : configuration.getChildren()) {
164 browseConfigurationCacheMaps(configurationBySolverName, configurationByComponentId, child);
168 public T detachOrCreateComponent(String uid) {
169 T result = configurationByUid.get(uid);
171 result = componentFactory.create(uid);
172 configurationByUid.put(uid, result);
173 configurationBySolverName = null; // forces recalculation
176 if(result.getParent() == null)
177 pendingRemoval.remove(result);
179 result.getParent().detachByUid(uid);
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
190 public void addPendingRemoval(T component) {
191 pendingRemoval.add(component);
194 public void printUidMap() {
195 printUidMap(new PrintWriter(System.out));
198 public void printUidMap(final PrintWriter out) {
199 out.println("Component tree");
201 getConfiguration().printConfiguration(out, 1);
202 if(configurationByUid != null) {
204 configurationByUid.forEachEntry(new TObjectObjectProcedure<String, T>() {
206 public boolean execute(String a, T b) {
207 out.println(" " + a + " (" + b.solverComponentName + ", " + b.componentId + ", " + b.uid + ")");
215 * Removes the component recursively.
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>() {
225 public boolean execute(T child) {
226 remove(solver, child);
230 if(component.componentId > 0 && !component.attached)
231 solver.remove(component.componentId);
235 * Saves undo state recursively
237 public void saveUndoState(final Solver solver, T component) {
238 if(component.getChildMap() != null)
239 component.getChildMap().forEachValue(new TObjectProcedure<T>() {
241 public boolean execute(T child) {
242 saveUndoState(solver, child);
246 else if(component.componentId > 0 && !component.attached)
247 undoContext.saveState(solver, component.componentId, component.uid);
251 * Removes components that are marked pending with {@link #addPendingRemoval} method.
253 public void removePending(final Solver solver) {
254 pendingRemoval.forEach(new TObjectProcedure<T>() {
256 public boolean execute(T component) {
257 saveUndoState(solver, component);
261 pendingRemoval.forEach(new TObjectProcedure<T>() {
263 public boolean execute(T component) {
264 remove(solver, component);
268 pendingRemoval.clear();
272 * Changes the {@link #trustUids} flag.
274 public void setTrustUids(boolean trustUids) {
275 if(trustUids != this.trustUids) {
276 this.trustUids = trustUids;
278 T configuration = getConfiguration();
279 if(configuration != null)
280 createConfigurationById(configuration);
284 configurationByUid = null;
287 public boolean getTrustUids() {
291 public void dispose() {
292 if (configurationByUid != null)
293 configurationByUid.clear();
294 if (configurationBySolverName != null) {
295 configurationBySolverName.clear();
296 configurationBySolverName = null;
298 pendingRemoval.clear();
301 public boolean hasPendingRemovals() {
302 return !pendingRemoval.isEmpty();
305 public void forEachPendingRemoval(Consumer<T> consumer) {
306 pendingRemoval.forEach(c -> {