Merge commit '2a46c55'
[simantics/platform.git] / bundles / org.simantics.structural.synchronization.client / src / org / simantics / structural / synchronization / Synchronizer.java
1 package org.simantics.structural.synchronization;\r
2 \r
3 import gnu.trove.map.hash.TObjectIntHashMap;\r
4 import gnu.trove.set.hash.THashSet;\r
5 \r
6 import java.util.ArrayList;\r
7 import java.util.Collection;\r
8 import java.util.Collections;\r
9 import java.util.List;\r
10 \r
11 import org.eclipse.core.runtime.IProgressMonitor;\r
12 import org.simantics.db.ReadGraph;\r
13 import org.simantics.db.Resource;\r
14 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;\r
15 import org.simantics.db.exception.DatabaseException;\r
16 import org.simantics.db.layer0.exception.MissingVariableValueException;\r
17 import org.simantics.db.layer0.request.ResourceToPossibleVariable;\r
18 import org.simantics.db.layer0.variable.RVI;\r
19 import org.simantics.db.layer0.variable.Variable;\r
20 import org.simantics.db.service.ManagementSupport;\r
21 import org.simantics.layer0.Layer0;\r
22 import org.simantics.scl.runtime.SCLContext;\r
23 import org.simantics.structural.stubs.StructuralResource2;\r
24 import org.simantics.structural.synchronization.protocol.ChildInfo;\r
25 import org.simantics.structural.synchronization.protocol.Connection;\r
26 import org.simantics.structural.synchronization.protocol.SerializedVariable;\r
27 import org.simantics.structural.synchronization.protocol.SynchronizationEventHandler;\r
28 import org.simantics.structural.synchronization.protocol.SynchronizationException;\r
29 import org.simantics.structural2.variables.VariableConnectionPointDescriptor;\r
30 \r
31 public class Synchronizer {\r
32     \r
33     public static boolean TRACE = false;\r
34     \r
35     ReadGraph graph;\r
36     Layer0 L0;\r
37     StructuralResource2 STR;\r
38     THashSet<String> visitedTypes = new THashSet<String>();\r
39     IProgressMonitor monitor;\r
40     \r
41     double workDone = 0.0;\r
42     int workDoneInteger;\r
43     int maxWork;\r
44     \r
45     public Synchronizer(ReadGraph graph) {\r
46         this.graph = graph;\r
47         L0 = Layer0.getInstance(graph);\r
48         STR = StructuralResource2.getInstance(graph);\r
49     }\r
50     \r
51     public void setMonitor(IProgressMonitor monitor, int maxWork) {\r
52         this.monitor = monitor;\r
53         this.maxWork = maxWork;\r
54         this.workDoneInteger = 0;\r
55     }\r
56     \r
57     private void didWork(double amount) {\r
58         workDone += amount;\r
59         //System.out.println(workDone);\r
60         if(monitor != null) {\r
61             int t = (int)(workDone * maxWork);\r
62             if(t > workDoneInteger) {\r
63                 monitor.worked(t-workDoneInteger);\r
64                 workDoneInteger = t;\r
65             }\r
66         }\r
67     }\r
68 \r
69     ChildInfo mapChild(Variable child) throws DatabaseException {\r
70         String name = child.getName(graph);\r
71         RVI rvi = child.getRVI(graph);\r
72         return new ChildInfo(name, rvi.toString());\r
73     }\r
74 \r
75     Collection<ChildInfo> mapChildren(SynchronizationEventHandler handler, Collection<Variable> children) throws DatabaseException {\r
76         ArrayList<ChildInfo> result = new ArrayList<ChildInfo>(children.size());\r
77         for(Variable child : children) {\r
78             if(child.getPossibleType(graph, STR.Component) != null)\r
79                 try {\r
80                     result.add(mapChild(child));\r
81                 } catch(Exception e) {\r
82                     handler.reportProblem("Failed to get ChildInfo for " + child + ".", e);\r
83                 }\r
84         }\r
85         return result;\r
86     }\r
87 \r
88     Collection<Connection> mapConnections(SynchronizationEventHandler handler, Variable child) throws DatabaseException {\r
89         ArrayList<Connection> result = new ArrayList<Connection>();\r
90         for(Variable conn : child.getProperties(graph, StructuralResource2.URIs.SynchronizedConnectionRelation)) {\r
91             String name = conn.getName(graph);\r
92             org.simantics.structural2.variables.Connection vc = conn.getValue(graph);\r
93             Collection<VariableConnectionPointDescriptor> connectionPoints = vc.getConnectionPointDescriptors(graph, null);\r
94             List<String> cps = new ArrayList<String>(connectionPoints.size());\r
95             for(VariableConnectionPointDescriptor desc : connectionPoints) {\r
96                 if(desc.isFlattenedFrom(graph, conn)) continue;\r
97                 if(!desc.hasClassification(graph, StructuralResource2.URIs.ProvidingConnectionRelation)) continue;\r
98                 String cpRef = desc.getRelativeRVI(graph, child);\r
99                 cps.add(cpRef);\r
100             }\r
101             \r
102             result.add(new Connection(name, cps));\r
103             \r
104         }\r
105         \r
106         return result;\r
107         \r
108     }\r
109     \r
110     private SerializedVariable serialize(SynchronizationEventHandler handler, Variable var, String name) throws DatabaseException {\r
111         try {\r
112                 SerializedVariable result = new SerializedVariable(name, var.getVariantValue(graph));\r
113                 for(Variable prop : var.getProperties(graph, StructuralResource2.URIs.SynchronizedRelation)) {\r
114                         String pName = prop.getName(graph);\r
115                         SerializedVariable v = serialize(handler, prop, pName);\r
116                         if(v != null) result.addProperty(pName, v);\r
117                 }\r
118                 return result;\r
119         } catch (MissingVariableValueException e) {\r
120             handler.reportProblem("Failed to read " + name + ". " + e.getMessage());\r
121             \r
122             Throwable cur = e;\r
123             while((cur = cur.getCause()) != null) {\r
124                 if(!(cur instanceof MissingVariableValueException)) {\r
125                     handler.reportProblem(cur.getMessage());\r
126                     break;\r
127                 }\r
128             }\r
129         } catch (Exception e) {\r
130                 handler.reportProblem("Failed to serialize " + name + ": " + e.getMessage(), e);\r
131         }\r
132         \r
133         return null;\r
134         \r
135     }\r
136 \r
137     Collection<SerializedVariable> mapProperties(SynchronizationEventHandler handler, Variable child) throws DatabaseException {\r
138         ArrayList<SerializedVariable> result = new ArrayList<SerializedVariable>();\r
139         for(Variable prop : child.getProperties(graph, StructuralResource2.URIs.SynchronizedRelation)) {\r
140                 SerializedVariable serialized = serialize(handler, prop, prop.getName(graph));\r
141                 if(serialized != null) result.add(serialized);\r
142         }\r
143         return result;\r
144     }\r
145 \r
146     /**\r
147      * Assumes that the variable points to a composite.\r
148      */\r
149     public void fullSynchronization(Variable variable, SynchronizationEventHandler handler) throws DatabaseException {\r
150         long duration = 0;\r
151         if(TRACE) {\r
152             System.out.println("fullSynchronization " + variable.getURI(graph));\r
153             duration -= System.nanoTime();\r
154         }\r
155         SCLContext context = SCLContext.getCurrent();\r
156         Object oldGraph = context.put("graph", graph);\r
157         try {\r
158             handler.beginSynchronization();\r
159             synchronizationRec(variable, handler, null, 1.0);\r
160             handler.endSynchronization();\r
161         } finally {\r
162             context.put("graph", oldGraph);\r
163         }\r
164         if(TRACE) {\r
165             duration += System.nanoTime();\r
166             System.out.println("full sync in " + 1e-9*duration + "s.");\r
167         }\r
168     }\r
169     \r
170     /**\r
171      * Recursive implementation of partial and full synchronization. If {@code changeFlags}\r
172      * is null, this is full synchronization, otherwise partial synchronization.\r
173      */\r
174     private void synchronizationRec(Variable variable, SynchronizationEventHandler handler,\r
175             TObjectIntHashMap<Variable> changeFlags,\r
176             double proportionOfWork) throws DatabaseException {\r
177         String name = variable.getName(graph);\r
178         Resource type = variable.getPossibleType(graph, STR.Component);\r
179         if(type == null) {\r
180             // This same filtering is done separately in mapChildren when beginComponent has been called for the parent.\r
181             return;\r
182         }\r
183         Collection<Variable> children = variable.getChildren(graph);\r
184         if(graph.isInheritedFrom(type, STR.Composite) || graph.isInheritedFrom(type, STR.AbstractDefinedComponentType)) {\r
185             String typeId = graph.getPossibleURI(type);\r
186             if(typeId == null)\r
187                 throw new SynchronizationException("User component " + type + " does not have an URI.");\r
188             if(visitedTypes.add(typeId))\r
189                 visitType(typeId, type, handler);\r
190             boolean endComponentNeeded = false;\r
191             try {\r
192                 Collection<SerializedVariable> propertyMap = mapProperties(handler, variable);\r
193                 Collection<ChildInfo> childMap = mapChildren(handler, children);\r
194                 endComponentNeeded = true;\r
195                 handler.beginComponent(name, typeId, \r
196                         propertyMap,\r
197                         Collections.<Connection>emptyList(), \r
198                         childMap);\r
199             } catch(Exception e) {\r
200                 handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e);\r
201                 if (endComponentNeeded)\r
202                     handler.endComponent();\r
203                 return;\r
204             }\r
205         } else {\r
206             String typeId = graph.getPossibleURI(type);\r
207             if(typeId == null)\r
208                 throw new SynchronizationException("User component " + type + " does not have an URI.");\r
209             if(visitedTypes.add(typeId))\r
210                 visitType(typeId, type, handler);\r
211             boolean endComponentNeeded = false;\r
212             try {\r
213                 Collection<SerializedVariable> propertyMap = mapProperties(handler, variable);\r
214                 Collection<Connection> connectionMap = mapConnections(handler, variable);\r
215                 Collection<ChildInfo> childMap = mapChildren(handler, children);\r
216                 endComponentNeeded = true;\r
217                 handler.beginComponent(name, \r
218                         typeId,\r
219                         propertyMap,\r
220                         connectionMap,\r
221                         childMap);\r
222             } catch(Exception e) {\r
223                 handler.reportProblem("Failed to synchronize " + name + ": " + e.getMessage(), e);\r
224                 if (endComponentNeeded)\r
225                     handler.endComponent();\r
226                 return;\r
227             }\r
228         }\r
229         \r
230         if(changeFlags == null) {\r
231             // Full synchronization, synchronize all childre\r
232             if(children.size() > 0) {\r
233                 double proportionOfWorkForChildren = proportionOfWork / children.size();\r
234                 for(Variable child : children)\r
235                     synchronizationRec(child, handler, null, proportionOfWorkForChildren);\r
236             }\r
237             else {\r
238                 didWork(proportionOfWork);\r
239             }\r
240         }\r
241         else {\r
242             // Partial synchronization, synchronize only children with positive changeFlag\r
243             int relevantChildCount = 0;\r
244             for(final Variable child : children) {\r
245                 int changeStatus = changeFlags.get(child);\r
246                 if(changeStatus != 0)\r
247                     ++relevantChildCount;\r
248             }\r
249             if(relevantChildCount > 0) {\r
250                 double proportionOfWorkForChildren = proportionOfWork / relevantChildCount;\r
251                 for(final Variable child : children) {\r
252                     int changeStatus = changeFlags.get(child);\r
253                     if(changeStatus == 0)\r
254                         continue;\r
255                     synchronizationRec(child, handler,\r
256                             // Apply full synchronization for subtree if changeStatus > 1\r
257                             changeStatus==1 ? changeFlags : null,\r
258                             proportionOfWorkForChildren\r
259                             );\r
260                 }\r
261             }\r
262             else {\r
263                 didWork(proportionOfWork);\r
264             }\r
265         }\r
266         handler.endComponent();\r
267     }\r
268     \r
269     public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, TObjectIntHashMap<Variable> changeFlags) throws DatabaseException {\r
270         long duration = 0;\r
271         if(TRACE) {\r
272             System.out.println("partialSynchronization " + variable.getURI(graph));\r
273             duration -= System.nanoTime();\r
274         }\r
275         int changeStatus = changeFlags.get(variable);\r
276         if(changeStatus == 0) return;\r
277         SCLContext context = SCLContext.getCurrent();\r
278         Object oldGraph = context.put("graph", graph);\r
279         try {\r
280             handler.beginSynchronization();\r
281             synchronizationRec(variable, handler, changeStatus == 1 ? changeFlags : null, 1.0);\r
282             handler.endSynchronization();\r
283         } finally {\r
284             context.put("graph", oldGraph);\r
285         } \r
286         if(TRACE) {\r
287             duration += System.nanoTime();\r
288             System.out.println("partial sync in " + 1e-9*duration + "s.");\r
289         }\r
290     }\r
291     \r
292     public void partialSynchronization(Variable variable, SynchronizationEventHandler handler, long fromRevision) throws DatabaseException {\r
293         TObjectIntHashMap<Variable> modifiedComponents = StructuralChangeFlattener.getModifiedComponents(graph, variable, fromRevision);\r
294         /*System.out.println("----------------");\r
295         modifiedComponents.forEachEntry(\r
296                 new TObjectIntProcedure<Variable>() {\r
297                     @Override\r
298                     public boolean execute(Variable a, int b) {\r
299                         try {\r
300                             System.out.println("Changed: " + a.getURI(graph) + " " + b);\r
301                         } catch (DatabaseException e) {\r
302                             e.printStackTrace();\r
303                         }\r
304                         return true;\r
305                     }\r
306                 });*/\r
307         partialSynchronization(variable, handler, modifiedComponents);\r
308     }\r
309     \r
310     void visitType(String typeId, Resource typeResource, SynchronizationEventHandler handler) throws DatabaseException {\r
311         Variable typeVariable = graph.syncRequest(new ResourceToPossibleVariable(typeResource), TransientCacheAsyncListener.<Variable>instance());\r
312         handler.beginType(typeId, mapProperties(handler, typeVariable));\r
313         handler.endType();\r
314     }\r
315 \r
316     public long getHeadRevisionId() throws DatabaseException {\r
317         return graph.getService(ManagementSupport.class).getHeadRevisionId()+1;\r
318     }\r
319 \r
320     \r
321 }\r