package org.simantics.structural.synchronization; import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.set.hash.THashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener; import org.simantics.db.exception.CancelTransactionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.exception.MissingVariableValueException; import org.simantics.db.layer0.request.ResourceToPossibleVariable; import org.simantics.db.layer0.variable.RVI; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.service.ManagementSupport; import org.simantics.layer0.Layer0; import org.simantics.scl.runtime.SCLContext; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.structural.synchronization.protocol.ChildInfo; import org.simantics.structural.synchronization.protocol.Connection; import org.simantics.structural.synchronization.protocol.SerializedVariable; import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler; import org.simantics.structural.synchronization.protocol.SynchronizationException; import org.simantics.structural2.variables.VariableConnectionPointDescriptor; public class Synchronizer { public static boolean TRACE = false; ReadGraph graph; Layer0 L0; StructuralResource2 STR; THashSet visitedTypes = new THashSet(); IProgressMonitor monitor; double workDone = 0.0; int workDoneInteger; int maxWork; public Synchronizer(ReadGraph graph) { this.graph = graph; L0 = Layer0.getInstance(graph); STR = StructuralResource2.getInstance(graph); } public void setMonitor(IProgressMonitor monitor, int maxWork) { this.monitor = monitor; this.maxWork = maxWork; this.workDoneInteger = 0; } private void didWork(double amount) { workDone += amount; //System.out.println(workDone); if(monitor != null) { int t = (int)(workDone * maxWork); if(t > workDoneInteger) { monitor.worked(t-workDoneInteger); workDoneInteger = t; } } } ChildInfo mapChild(Variable child) throws DatabaseException { String name = child.getName(graph); RVI rvi = child.getRVI(graph); return new ChildInfo(name, rvi.toString()); } Collection mapChildren(SynchronizationEventHandler handler, Collection children) throws DatabaseException { ArrayList result = new ArrayList(children.size()); for(Variable child : children) { if(child.getPossibleType(graph, STR.Component) != null) try { result.add(mapChild(child)); } catch(Exception e) { handler.reportProblem("Failed to get ChildInfo for " + child + ".", e); } } return result; } Collection mapConnections(SynchronizationEventHandler handler, Variable child) throws DatabaseException { ArrayList result = new ArrayList(); for(Variable conn : child.getProperties(graph, StructuralResource2.URIs.SynchronizedConnectionRelation)) { String name = conn.getName(graph); org.simantics.structural2.variables.Connection vc = conn.getValue(graph); Collection connectionPoints = vc.getConnectionPointDescriptors(graph, null); List cps = new ArrayList(connectionPoints.size()); for(VariableConnectionPointDescriptor desc : connectionPoints) { if(desc.isFlattenedFrom(graph, conn)) continue; if(!desc.hasClassification(graph, StructuralResource2.URIs.ProvidingConnectionRelation)) continue; String cpRef = desc.getRelativeRVI(graph, child); cps.add(cpRef); } result.add(new Connection(name, cps)); } return result; } private SerializedVariable serialize(SynchronizationEventHandler handler, Variable var, String name) throws DatabaseException { try { SerializedVariable result = new SerializedVariable(name, var.getVariantValue(graph)); for(Variable prop : var.getProperties(graph, StructuralResource2.URIs.SynchronizedRelation)) { String pName = prop.getName(graph); SerializedVariable v = serialize(handler, prop, pName); if(v != null) result.addProperty(pName, v); } return result; } catch (MissingVariableValueException e) { handler.reportProblem("Failed to read " + name + ". " + e.getMessage()); Throwable cur = e; while((cur = cur.getCause()) != null) { if(!(cur instanceof MissingVariableValueException)) { handler.reportProblem(cur.getMessage()); break; } } } catch (Exception e) { handler.reportProblem("Failed to serialize " + name + ": " + e.getMessage(), e); } return null; } Collection mapProperties(SynchronizationEventHandler handler, Variable child) throws DatabaseException { ArrayList result = new ArrayList(); for(Variable prop : child.getProperties(graph, StructuralResource2.URIs.SynchronizedRelation)) { SerializedVariable serialized = serialize(handler, prop, prop.getName(graph)); if(serialized != null) result.add(serialized); } return result; } /** * Assumes that the variable points to a composite. */ public void fullSynchronization(Variable variable, SynchronizationEventHandler handler) throws DatabaseException { long duration = 0; if(TRACE) { System.out.println("fullSynchronization " + variable.getURI(graph)); duration -= System.nanoTime(); } SCLContext context = SCLContext.getCurrent(); Object oldGraph = context.put("graph", graph); try { handler.beginSynchronization(); synchronizationRec(variable, handler, null, 1.0); handler.endSynchronization(); } finally { context.put("graph", oldGraph); } if(TRACE) { duration += System.nanoTime(); System.out.println("full sync in " + 1e-9*duration + "s."); } } /** * Recursive implementation of partial and full synchronization. If {@code changeFlags} * is null, this is full synchronization, otherwise partial synchronization. */ private void synchronizationRec(Variable variable, SynchronizationEventHandler handler, TObjectIntHashMap changeFlags, double proportionOfWork) throws DatabaseException { String name = variable.getName(graph); Resource type = variable.getPossibleType(graph, STR.Component); if(type == null) { // This same filtering is done separately in mapChildren when beginComponent has been called for the parent. return; } Collection children = variable.getChildren(graph); if(graph.isInheritedFrom(type, STR.Composite) || graph.isInheritedFrom(type, STR.AbstractDefinedComponentType)) { String typeId = graph.getPossibleURI(type); if(typeId == null) throw new SynchronizationException("User component " + type + " does not have an URI."); if(visitedTypes.add(typeId)) visitType(typeId, type, handler); boolean endComponentNeeded = false; try { Collection propertyMap = mapProperties(handler, variable); Collection childMap = mapChildren(handler, children); endComponentNeeded = true; handler.beginComponent(name, typeId, propertyMap, Collections.emptyList(), childMap); } catch(Exception e) { handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e); if (endComponentNeeded) handler.endComponent(); return; } } else { String typeId = graph.getPossibleURI(type); if(typeId == null) throw new SynchronizationException("User component " + type + " does not have an URI."); if(visitedTypes.add(typeId)) visitType(typeId, type, handler); boolean endComponentNeeded = false; try { Collection propertyMap = mapProperties(handler, variable); Collection connectionMap = mapConnections(handler, variable); Collection childMap = mapChildren(handler, children); endComponentNeeded = true; handler.beginComponent(name, typeId, propertyMap, connectionMap, childMap); } catch(Exception e) { handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e); if (endComponentNeeded) handler.endComponent(); return; } } if(changeFlags == null) { // Full synchronization, synchronize all children if(children.size() > 0) { double proportionOfWorkForChildren = proportionOfWork / children.size(); for(Variable child : children) synchronizationRec(child, handler, null, proportionOfWorkForChildren); } else { didWork(proportionOfWork); // Full synchronization is cancelable if(monitor != null && monitor.isCanceled()) throw new CancelTransactionException(); } } else { // Partial synchronization, synchronize only children with positive changeFlag int relevantChildCount = 0; for(final Variable child : children) { int changeStatus = changeFlags.get(child); if(changeStatus != 0) ++relevantChildCount; } if(relevantChildCount > 0) { double proportionOfWorkForChildren = proportionOfWork / relevantChildCount; for(final Variable child : children) { int changeStatus = changeFlags.get(child); if(changeStatus == 0) continue; synchronizationRec(child, handler, // Apply full synchronization for subtree if changeStatus > 1 changeStatus==1 ? changeFlags : null, proportionOfWorkForChildren ); } } else { didWork(proportionOfWork); } } handler.endComponent(); } public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, TObjectIntHashMap changeFlags) throws DatabaseException { long duration = 0; if(TRACE) { System.out.println("partialSynchronization " + variable.getURI(graph)); duration -= System.nanoTime(); } int changeStatus = changeFlags.get(variable); if(changeStatus == 0) return; SCLContext context = SCLContext.getCurrent(); Object oldGraph = context.put("graph", graph); try { handler.beginSynchronization(); synchronizationRec(variable, handler, changeStatus == 1 ? changeFlags : null, 1.0); handler.endSynchronization(); } finally { context.put("graph", oldGraph); } if(TRACE) { duration += System.nanoTime(); System.out.println("partial sync in " + 1e-9*duration + "s."); } } public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, long fromRevision) throws DatabaseException { TObjectIntHashMap modifiedComponents = StructuralChangeFlattener.getModifiedComponents(graph, variable, fromRevision); /*System.out.println("----------------"); modifiedComponents.forEachEntry( new TObjectIntProcedure() { @Override public boolean execute(Variable a, int b) { try { System.out.println("Changed: " + a.getURI(graph) + " " + b); } catch (DatabaseException e) { e.printStackTrace(); } return true; } });*/ partialSynchronization(variable, handler, modifiedComponents); } void visitType(String typeId, Resource typeResource, SynchronizationEventHandler handler) throws DatabaseException { Variable typeVariable = graph.syncRequest(new ResourceToPossibleVariable(typeResource), TransientCacheAsyncListener.instance()); handler.beginType(typeId, mapProperties(handler, typeVariable)); handler.endType(); } public long getHeadRevisionId() throws DatabaseException { return graph.getService(ManagementSupport.class).getHeadRevisionId()+1; } }