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 THashMap<String, T> children = configuration.getChildMap();
108 if (children != null) {
109 children.forEachValue(child -> {
110 browseConfiguration(configurationByUid, child);
111 child.parent = configuration;
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);
126 result = Collections.emptyMap();
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);
141 result = new TIntObjectHashMap<>(1);
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);
156 private void browseConfigurationCacheMaps(
157 THashMap<String, T> configurationBySolverName,
158 TIntObjectMap<T> configurationByComponentId,
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);
167 for(T child : configuration.getChildren()) {
168 browseConfigurationCacheMaps(configurationBySolverName, configurationByComponentId, child);
172 public T detachOrCreateComponent(String uid) {
173 T result = configurationByUid.get(uid);
175 result = componentFactory.create(uid);
176 configurationByUid.put(uid, result);
177 configurationBySolverName = null; // forces recalculation
180 if(result.getParent() == null)
181 pendingRemoval.remove(result);
183 result.getParent().detachByUid(uid);
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
194 public void addPendingRemoval(T component) {
195 pendingRemoval.add(component);
198 public void printUidMap() {
199 printUidMap(new PrintWriter(System.out));
202 public void printUidMap(final PrintWriter out) {
203 out.println("Component tree");
205 getConfiguration().printConfiguration(out, 1);
206 if(configurationByUid != null) {
208 configurationByUid.forEachEntry(new TObjectObjectProcedure<String, T>() {
210 public boolean execute(String a, T b) {
211 out.println(" " + a + " (" + b.solverComponentName + ", " + b.componentId + ", " + b.uid + ")");
219 * Removes the component recursively.
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>() {
229 public boolean execute(T child) {
230 remove(solver, child);
234 if(component.componentId > 0 && !component.attached)
235 solver.remove(component.componentId);
239 * Saves undo state recursively
241 public void saveUndoState(final Solver solver, T component) {
242 if(component.getChildMap() != null)
243 component.getChildMap().forEachValue(new TObjectProcedure<T>() {
245 public boolean execute(T child) {
246 saveUndoState(solver, child);
250 else if(component.componentId > 0 && !component.attached)
251 undoContext.saveState(solver, component.componentId, component.uid);
255 * Removes components that are marked pending with {@link #addPendingRemoval} method.
257 public void removePending(final Solver solver) {
258 pendingRemoval.forEach(new TObjectProcedure<T>() {
260 public boolean execute(T component) {
261 saveUndoState(solver, component);
265 pendingRemoval.forEach(new TObjectProcedure<T>() {
267 public boolean execute(T component) {
268 remove(solver, component);
272 pendingRemoval.clear();
276 * Changes the {@link #trustUids} flag.
278 public void setTrustUids(boolean trustUids) {
279 if(trustUids != this.trustUids) {
280 this.trustUids = trustUids;
282 T configuration = getConfiguration();
283 if(configuration != null)
284 createConfigurationById(configuration);
288 configurationByUid = null;
291 public boolean getTrustUids() {
295 public void dispose() {
296 if (configurationByUid != null)
297 configurationByUid.clear();
298 if (configurationBySolverName != null) {
299 configurationBySolverName.clear();
300 configurationBySolverName = null;
302 pendingRemoval.clear();
305 public boolean hasPendingRemovals() {
306 return !pendingRemoval.isEmpty();
309 public void forEachPendingRemoval(Consumer<T> consumer) {
310 pendingRemoval.forEach(c -> {