]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.structural.synchronization.client/src/org/simantics/structural/synchronization/client/Synchronizer.java
Separate DB and non-DB code to different structural sync bundles
[simantics/platform.git] / bundles / org.simantics.structural.synchronization.client / src / org / simantics / structural / synchronization / client / Synchronizer.java
diff --git a/bundles/org.simantics.structural.synchronization.client/src/org/simantics/structural/synchronization/client/Synchronizer.java b/bundles/org.simantics.structural.synchronization.client/src/org/simantics/structural/synchronization/client/Synchronizer.java
new file mode 100644 (file)
index 0000000..b2b4ea2
--- /dev/null
@@ -0,0 +1,325 @@
+package org.simantics.structural.synchronization.client;
+
+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<String> visitedTypes = new THashSet<String>();
+    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<ChildInfo> mapChildren(SynchronizationEventHandler handler, Collection<Variable> children) throws DatabaseException {
+        ArrayList<ChildInfo> result = new ArrayList<ChildInfo>(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<Connection> mapConnections(SynchronizationEventHandler handler, Variable child) throws DatabaseException {
+        ArrayList<Connection> result = new ArrayList<Connection>();
+        for(Variable conn : child.getProperties(graph, StructuralResource2.URIs.SynchronizedConnectionRelation)) {
+            String name = conn.getName(graph);
+            org.simantics.structural2.variables.Connection vc = conn.getValue(graph);
+            Collection<VariableConnectionPointDescriptor> connectionPoints = vc.getConnectionPointDescriptors(graph, null);
+            List<String> cps = new ArrayList<String>(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<SerializedVariable> mapProperties(SynchronizationEventHandler handler, Variable child) throws DatabaseException {
+        ArrayList<SerializedVariable> result = new ArrayList<SerializedVariable>();
+        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<Variable> 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<Variable> 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<SerializedVariable> propertyMap = mapProperties(handler, variable);
+                Collection<ChildInfo> childMap = mapChildren(handler, children);
+                endComponentNeeded = true;
+                handler.beginComponent(name, typeId, 
+                        propertyMap,
+                        Collections.<Connection>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<SerializedVariable> propertyMap = mapProperties(handler, variable);
+                Collection<Connection> connectionMap = mapConnections(handler, variable);
+                Collection<ChildInfo> 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<Variable> 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<Variable> modifiedComponents = StructuralChangeFlattener.getModifiedComponents(graph, variable, fromRevision);
+        /*System.out.println("----------------");
+        modifiedComponents.forEachEntry(
+                new TObjectIntProcedure<Variable>() {
+                    @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.<Variable>instance());
+        handler.beginType(typeId, mapProperties(handler, typeVariable));
+        handler.endType();
+    }
+
+    public long getHeadRevisionId() throws DatabaseException {
+        return graph.getService(ManagementSupport.class).getHeadRevisionId()+1;
+    }
+
+    
+}