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