package org.simantics.structural.synchronization.utils; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TObjectObjectProcedure; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; import java.io.PrintWriter; import java.util.function.Consumer; /** * The entry point to the mapping structure between Simantics database and a * designated solver. It is used to synchronize changes from Simantics to the * solver. * * @author Hannu Niemistö */ abstract public class MappingBase> { abstract public T getConfiguration(); /** * Set of all components indexed by their UID. */ transient protected THashMap configurationByUid; /** * Set of components whose removal is delayed because they might * have been moved somewhere else. */ transient THashSet pendingRemoval = new THashSet(); /** * This is a structure that is used to return the state of the removed modules * when the removal is undone. */ public transient StateUndoContextBase undoContext; public transient ComponentFactory componentFactory; /** * The synchronization has processed all change sets before this change set * (excluding this change set). * It means that next synchronization operation should start examining changes * made in this revision and revisions after this. */ public long currentRevision; /** * Tells whether the uids in the components can be used * in the synchronization. This is normally true, but * may be set temporarily false when export/import:in * the model. */ private boolean trustUids; public MappingBase() { this(null, -1L, false); } public MappingBase(T configuration, boolean trustUids) { this(configuration, -1L, trustUids); } public MappingBase(T configuration, long currentRevision, boolean trustUids) { undoContext = createUndoContext(); componentFactory = createComponentFactory(); if(trustUids) createConfigurationById(configuration); this.currentRevision = currentRevision; this.trustUids = trustUids; } abstract public StateUndoContextBase createUndoContext(); abstract public ComponentFactory createComponentFactory(); protected void createConfigurationById(T configuration) { THashMap configurationByUid = new THashMap(); browseConfiguration(configurationByUid, configuration); this.configurationByUid = configurationByUid; } private void browseConfiguration( THashMap configurationByUid, T configuration) { configurationByUid.put(configuration.uid, configuration); for(T child : configuration.getChildren()) { browseConfiguration(configurationByUid, child); child.parent = configuration; } } public T detachOrCreateComponent(String uid) { T result = configurationByUid.get(uid); if(result == null) { result = componentFactory.create(uid); configurationByUid.put(uid, result); } else { if(result.getParent() == null) pendingRemoval.remove(result); else result.getParent().detachByUid(uid); } return result; } /** * Marks that the component should be removed. The actual removal * is delayed until the call of {@link #removePending} so that if the * component is not actually removed but moved or renamed, we don't lose * its state. */ public void addPendingRemoval(T component) { pendingRemoval.add(component); } public void printUidMap() { printUidMap(new PrintWriter(System.out)); } public void printUidMap(final PrintWriter out) { out.println("Component tree"); out.print(" "); getConfiguration().printConfiguration(out, 1); if(configurationByUid != null) { out.println("UIDs"); configurationByUid.forEachEntry(new TObjectObjectProcedure() { @Override public boolean execute(String a, T b) { out.println(" " + a + " (" + b.solverComponentName + ", " + b.componentId + ", " + b.uid + ")"); return true; } }); } } /** * Removes the component recursively. */ public void remove(final Solver solver, T component) { if(configurationByUid != null) configurationByUid.remove(component.uid); if(component.getChildMap() != null) component.getChildMap().forEachValue(new TObjectProcedure() { @Override public boolean execute(T child) { remove(solver, child); return true; } }); if(component.componentId > 0 && !component.attached) solver.remove(component.componentId); } /** * Saves undo state recursively */ public void saveUndoState(final Solver solver, T component) { if(component.getChildMap() != null) component.getChildMap().forEachValue(new TObjectProcedure() { @Override public boolean execute(T child) { saveUndoState(solver, child); return true; } }); else if(component.componentId > 0 && !component.attached) undoContext.saveState(solver, component.componentId, component.uid); } /** * Removes components that are marked pending with {@link #addPendingRemoval} method. */ public void removePending(final Solver solver) { pendingRemoval.forEach(new TObjectProcedure() { @Override public boolean execute(T component) { saveUndoState(solver, component); return true; } }); pendingRemoval.forEach(new TObjectProcedure() { @Override public boolean execute(T component) { remove(solver, component); return true; } }); pendingRemoval.clear(); } /** * Changes the {@link #trustUids} flag. */ public void setTrustUids(boolean trustUids) { if(trustUids != this.trustUids) { this.trustUids = trustUids; if(trustUids) { T configuration = getConfiguration(); if(configuration != null) createConfigurationById(configuration); } } if(!trustUids) configurationByUid = null; } public boolean getTrustUids() { return trustUids; } public void dispose() { if (configurationByUid != null) configurationByUid.clear(); pendingRemoval.clear(); } public boolean hasPendingRemovals() { return !pendingRemoval.isEmpty(); } public void forEachPendingRemoval(Consumer consumer) { pendingRemoval.forEach(c -> { consumer.accept(c); return true; }); } }