6430d029beca6185d922dddd3e9a1bc41a4d3bb8
[simantics/platform.git] / bundles / org.simantics.debug.graphical / src / org / simantics / debug / graphical / DebuggerCanvas.java
1 package org.simantics.debug.graphical;
2
3 import java.awt.Color;
4 import java.awt.GradientPaint;
5 import java.awt.Graphics;
6 import java.awt.Graphics2D;
7 import java.awt.RenderingHints;
8 import java.awt.datatransfer.DataFlavor;
9 import java.awt.datatransfer.Transferable;
10 import java.awt.dnd.DnDConstants;
11 import java.awt.dnd.DropTarget;
12 import java.awt.dnd.DropTargetAdapter;
13 import java.awt.dnd.DropTargetDropEvent;
14 import java.awt.event.KeyAdapter;
15 import java.awt.event.KeyEvent;
16 import java.awt.event.MouseAdapter;
17 import java.awt.event.MouseEvent;
18 import java.awt.event.MouseMotionAdapter;
19 import java.awt.event.MouseWheelEvent;
20 import java.awt.event.MouseWheelListener;
21 import java.awt.geom.AffineTransform;
22 import java.awt.geom.Point2D;
23 import java.awt.geom.Rectangle2D;
24 import java.io.Reader;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Random;
28
29 import javax.swing.JPanel;
30 import javax.swing.SwingUtilities;
31
32 import org.eclipse.core.runtime.IAdaptable;
33 import org.eclipse.jface.viewers.IStructuredSelection;
34 import org.eclipse.jface.viewers.StructuredSelection;
35 import org.simantics.Simantics;
36 import org.simantics.db.ChangeSet;
37 import org.simantics.db.ChangeSetIdentifier;
38 import org.simantics.db.ReadGraph;
39 import org.simantics.db.Resource;
40 import org.simantics.db.Session;
41 import org.simantics.db.Statement;
42 import org.simantics.db.common.request.ReadRequest;
43 import org.simantics.db.common.utils.NameUtils;
44 import org.simantics.db.exception.DatabaseException;
45 import org.simantics.db.request.Read;
46 import org.simantics.db.service.ManagementSupport;
47 import org.simantics.db.service.SerialisationSupport;
48 import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;
49 import org.simantics.debug.graphical.layout.LayoutGraph;
50 import org.simantics.debug.graphical.model.Edge;
51 import org.simantics.debug.graphical.model.LabelContent;
52 import org.simantics.debug.graphical.model.Node;
53 import org.simantics.debug.graphical.model.NodeData;
54 import org.simantics.layer0.Layer0;
55 import org.simantics.scl.runtime.SCLContext;
56 import org.simantics.scl.runtime.function.Function;
57 import org.simantics.ui.dnd.LocalObjectTransfer;
58 import org.simantics.ui.dnd.LocalObjectTransferable;
59 import org.simantics.ui.selection.AnyResource;
60 import org.simantics.ui.selection.WorkbenchSelectionElement;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 import com.fasterxml.jackson.databind.JsonNode;
65 import com.fasterxml.jackson.databind.ObjectMapper;
66 import com.fasterxml.jackson.databind.node.NumericNode;
67
68 import gnu.trove.list.array.TDoubleArrayList;
69 import gnu.trove.map.hash.THashMap;
70 import gnu.trove.map.hash.TObjectIntHashMap;
71
72 public class DebuggerCanvas extends JPanel {
73     private static final Logger LOGGER = LoggerFactory.getLogger(DebuggerCanvas.class);
74     
75     private static final long serialVersionUID = -718678297301786379L;
76     
77     ArrayList<Node> nodes = new ArrayList<Node>();
78     THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>(); 
79     ArrayList<Edge> edges = new ArrayList<Edge>();
80     ArrayList<Node> extensionNodes = new ArrayList<Node>();
81     ArrayList<Edge> extensionEdges = new ArrayList<Edge>();
82     ArrayList<Edge> addedEdges = new ArrayList<Edge>();
83     ArrayList<Edge> removedEdges = new ArrayList<Edge>();
84     double canvasPosX = 0.0;
85     double canvasPosY = 0.0;
86     double canvasZoom = 1.0;
87     boolean extensionMode = false;
88     Random random = new Random();
89     public Function statementFilter;
90     
91     LabelingPreferences labelingPreferences = 
92             new LabelingPreferences();
93     
94     public void setStatementFilter(Function statementFilter) {
95         this.statementFilter = statementFilter;
96     }
97     
98     public void removeStatementFilter() {
99         this.statementFilter = null;
100     }
101     
102     @Override
103     public void paint(Graphics _g) {
104         Graphics2D g = (Graphics2D)_g;
105         
106         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
107                 RenderingHints.VALUE_ANTIALIAS_ON);
108         g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, 
109                 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
110         g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
111                 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
112         
113         g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));
114         g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
115         g.setColor(Color.BLACK);
116         g.setTransform(new AffineTransform(
117                 canvasZoom, 0.0, 
118                 0.0, canvasZoom, 
119                 -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
120         for(Node node : nodes)
121             node.render(g);
122         for(Edge edge : edges)
123             edge.render(g);
124         if(extensionMode) {
125             for(Node node : extensionNodes)
126                 node.render(g);
127             for(Edge edge : extensionEdges)
128                 edge.render(g);
129         }
130         g.setColor(Color.GREEN);
131         for(Edge edge : addedEdges)
132             edge.render(g);
133         g.setColor(Color.RED);
134         for(Edge edge : removedEdges)
135             edge.render(g);
136     }
137     
138     public Node pick(double x, double y) {
139         for(Node node : nodes)
140             if(node.pick(x, y))
141                 return node;
142         return null;
143     }
144     
145     public Node pickExtension(double x, double y) {
146         for(Node node : extensionNodes)
147             if(node.pick(x, y))
148                 return node;
149         return null;
150     }
151     
152     {
153         addMouseListener(new MouseAdapter() {
154             @Override
155             public void mousePressed(MouseEvent e) {                
156                 double x = canvasPosX+e.getX()/canvasZoom;
157                 double y = canvasPosY+e.getY()/canvasZoom;
158                 if(e.getButton() == MouseEvent.BUTTON1)
159                     handleMouseLeftPressed(x, y);
160                 else if(e.getButton() == MouseEvent.BUTTON2)
161                     handleMouseMiddlePressed(x, y);
162             }
163             @Override
164             public void mouseMoved(MouseEvent e) {
165                 handleMouseMoved(canvasPosX+e.getX()/canvasZoom, 
166                         canvasPosY+e.getY()/canvasZoom);
167             }
168             @Override
169             public void mouseReleased(MouseEvent e) {
170                 handleMouseReleased(canvasPosX+e.getX()/canvasZoom, 
171                         canvasPosY+e.getY()/canvasZoom);
172             }
173             @Override
174             public void mouseWheelMoved(MouseWheelEvent e) {
175                 double x = canvasPosX+e.getX()/canvasZoom;
176                 double y = canvasPosY+e.getY()/canvasZoom;
177                 handleMouseWheelMoved(x, y, e.getWheelRotation());
178             }
179         });
180         addMouseMotionListener(new MouseMotionAdapter() {
181             @Override
182             public void mouseDragged(MouseEvent e) {
183                 handleMouseMoved(canvasPosX+e.getX()/canvasZoom, 
184                         canvasPosY+e.getY()/canvasZoom);
185             }            
186         });
187         addMouseWheelListener(new MouseWheelListener() {            
188             @Override
189             public void mouseWheelMoved(MouseWheelEvent e) {
190                 double x = canvasPosX+e.getX()/canvasZoom;
191                 double y = canvasPosY+e.getY()/canvasZoom;
192                 handleMouseWheelMoved(x, y, e.getWheelRotation());
193             }
194         });
195         addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
196             @Override
197             public void keyPressed(KeyEvent e) {
198                 DebuggerCanvas.this.keyPressed(e);
199             }
200             @Override
201             public void keyReleased(KeyEvent e) {
202                 DebuggerCanvas.this.keyReleased(e);
203             }
204         }));
205         DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
206             @Override
207             public void drop(DropTargetDropEvent dtde) {
208                 try {
209                     Transferable transferable = dtde.getTransferable();
210                     if( transferable.isDataFlavorSupported( 
211                             LocalObjectTransferable.FLAVOR ) ) {
212                         dtde.acceptDrop( DnDConstants.ACTION_MOVE );
213                         
214                         transferable.getTransferData(LocalObjectTransferable.FLAVOR);
215                         Object obj = LocalObjectTransfer.getTransfer().getObject();
216                         handleDrop(dtde, obj);
217                         
218                         dtde.getDropTargetContext().dropComplete( true );
219                     }
220                     else {
221                         DataFlavor textFlavor = DataFlavor.selectBestTextFlavor(transferable.getTransferDataFlavors());
222                         if(textFlavor != null) {
223                             try(Reader reader = textFlavor.getReaderForText(transferable)) {
224                                 ObjectMapper mapper = new ObjectMapper();
225                                 JsonNode node = mapper.readTree(reader);
226                                 Object resourceId = node.get("resourceId");
227                                 if(resourceId instanceof NumericNode) {
228                                     dtde.acceptDrop( DnDConstants.ACTION_MOVE );
229                                     
230                                     transferable.getTransferData(LocalObjectTransferable.FLAVOR);
231                                     Object obj = new StructuredSelection(Simantics.getSession().syncRequest(new Read<Resource>() {
232                                         public Resource perform(ReadGraph graph) throws DatabaseException {
233                                             SerialisationSupport ss = graph.getService(SerialisationSupport.class);
234                                             return ss.getResource(((NumericNode)resourceId).longValue());
235                                         }
236                                     }));
237                                     handleDrop(dtde, obj);
238                                     
239                                     dtde.getDropTargetContext().dropComplete( true );
240                                     return;
241                                 }
242                             }
243                         }
244                         dtde.rejectDrop();
245                     }
246                 } catch( Exception exception ) {
247                     LOGGER.warn("Drop failed.", exception);
248                     dtde.rejectDrop();
249                 } 
250             }     
251         });
252     }    
253     
254     private void handleDrop(DropTargetDropEvent dtde, Object obj) {
255         double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
256         double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
257         handleDrop(x, y, obj);
258     }
259     
260     public void keyPressed(KeyEvent e) {
261         switch(e.getKeyCode()) {
262         case KeyEvent.VK_1:
263             zoomToFit();
264             break;
265         case KeyEvent.VK_L:
266             layoutGraph();
267             break;
268         case KeyEvent.VK_CONTROL:
269             if(!extensionMode) {
270                 initializeExtension();
271                 extensionMode = true;
272                 repaint();
273             }
274             break;
275         case KeyEvent.VK_C:
276             findPreviousChangeset();
277             break;
278         case KeyEvent.VK_DELETE:
279             if (!extensionMode && dragging != null) {
280                 nodes.remove(dragging);
281                 scheduleUpdate();
282                 repaint();
283             }
284             break;
285         }
286     }
287     
288     public void keyReleased(KeyEvent e) {
289         if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
290             extensionMode = false;
291             scheduleUpdate();
292             repaint();
293         }
294     }
295
296     private static Resource extractResource(Object obj) {
297         System.out.println("- " + obj.getClass().getName());
298         if(obj instanceof WorkbenchSelectionElement) {
299             Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
300             if(resource != null)
301                 return resource;
302         }
303         if(obj instanceof IAdaptable) {
304             Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
305             if(resource != null)
306                 return resource;
307         }
308         if(obj instanceof Resource)
309             return (Resource)obj;
310         return null;
311     }
312     
313     private void handleDrop(double x, double y, Object obj) {
314         //System.out.println(obj.getClass().getName());
315         if(obj instanceof IStructuredSelection) {
316             for(Object element : ((IStructuredSelection)obj).toArray()) {
317                 Resource resource = extractResource(element);
318                 if(resource != null && !nodeMap.containsKey(resource)) {
319                     addResource(x, y, resource);                    
320                     repaint();
321                 }
322             }
323         }
324     }       
325     
326     private Node addResource(double x, double y, Resource resource) {
327         Node a = new Node(new NodeData(resource));
328         a.setPos(x, y);
329         scheduleUpdate();
330         nodes.add(a);
331         return a;
332     }
333     
334     public void addResource(Resource resource) {
335         double x, y;
336         if(nodes.isEmpty()) {
337             x = 0.0;
338             y = 0.0;
339         }
340         else {
341             double xMin=Double.POSITIVE_INFINITY, yMin=Double.POSITIVE_INFINITY;
342             double xMax=Double.NEGATIVE_INFINITY, yMax=Double.NEGATIVE_INFINITY;
343             for(Node node : nodes) {
344                 xMin = Math.min(node.getMinX(), xMin);
345                 yMin = Math.min(node.getMinY(), yMin);
346                 xMax = Math.max(node.getMaxX(), xMax);
347                 yMax = Math.max(node.getMaxY(), yMax);
348             }
349             x = xMin + (xMax - xMin) * random.nextDouble();
350             y = yMin + (yMax - yMin) * random.nextDouble();
351         }
352         
353         addResource(x, y, resource);
354         repaint();
355     }
356
357     private void scheduleUpdate() {
358         Simantics.getSession().asyncRequest(new ReadRequest() {            
359             @Override
360             public void run(ReadGraph graph) throws DatabaseException {                
361                 updateNodes(graph);
362                 updateEdges(graph);
363                 SwingUtilities.invokeLater(new Runnable() {
364                     @Override
365                     public void run() {
366                         repaint();
367                     }
368                     
369                 });
370             }            
371         });        
372     }
373     
374     public void layoutGraph() {
375         ArrayList<Edge> allEdges = new ArrayList<Edge>(
376                 edges.size() + addedEdges.size() + removedEdges.size()
377                 );
378         allEdges.addAll(edges);
379         allEdges.addAll(addedEdges);
380         allEdges.addAll(removedEdges);
381         LayoutGraph.layout(
382                 nodes.toArray(new Node[nodes.size()]), 
383                 allEdges.toArray(new Edge[edges.size()]));
384         repaint();                
385     }
386     
387     private void updateNodes(ReadGraph graph) throws DatabaseException {
388         for(Node node : nodes) {
389             node.getData().updateData(graph, labelingPreferences);
390             node.setContent(new LabelContent(node.getData().getLabels()));
391         }
392         nodeMap.clear();
393         for(Node node : nodes) {
394             NodeData data = node.getData();
395             nodeMap.put(data.getResource(), node);
396         }
397     }
398     
399     private void updateEdges(ReadGraph graph) throws DatabaseException {
400         ArrayList<Edge> edges = new ArrayList<Edge>();
401         for(Node node : nodes) {
402             NodeData data = node.getData();
403             Resource subject = data.getResource();
404             ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
405             for(Statement stat : data.getStatements()) {
406                 Resource object = stat.getObject();
407                 Node node2 = nodeMap.get(object);
408                 if(node2 != null) {
409                     if(object.getResourceId() > subject.getResourceId() ||
410                             graph.getPossibleInverse(stat.getPredicate()) == null) {
411                         edges.add(createEdge(graph, stat, node, node2));
412                     }
413                 }
414                 else
415                     filteredStatements.add(stat);
416             }
417             data.setStatements(filteredStatements);
418         }
419         this.edges = edges;
420         this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
421         this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
422     }
423
424     private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
425         ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
426         for (Edge e : edges) {
427             if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
428                 continue;
429             result.add(e);
430         }
431         return result;
432     }
433
434     private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
435         Resource predicate = stat.getPredicate();
436         Resource inverse = graph.getPossibleInverse(predicate);        
437         if(inverse != null) {
438             Layer0 L0 = Layer0.getInstance(graph);
439             if(graph.hasStatement(predicate, L0.PartOf, inverse)
440                     || predicate.equals(L0.PartOf)
441                     || predicate.equals(L0.SuperrelationOf)
442                     || predicate.equals(L0.SupertypeOf)) {
443                 predicate = inverse;
444                 Node temp = n1;
445                 n1 = n2;
446                 n2 = temp;
447             }
448         }
449         Edge edge = new Edge(n1, n2);
450         edge.setContent(new LabelContent(new String[] {
451                 NameUtils.getSafeName(graph, predicate)}));
452         return edge;
453     }
454     
455     Node dragging = null;
456     double dragDX, dragDY;
457     private void handleMouseLeftPressed(double x, double y) {
458         Node node;
459         if(extensionMode) {
460             node = pickExtension(x, y);
461             if(node != null) {
462                 nodes.add(node);
463                 extensionNodes.remove(node);
464             }
465         }
466         else
467             node = pick(x, y);
468         if(node != null) {
469             dragDX = x - node.getX();
470             dragDY = y - node.getY();
471             dragging = node;
472         }
473     }
474     
475     Point2D panningStartMouse;
476     private void handleMouseMiddlePressed(double x, double y) {
477         panningStartMouse = new Point2D.Double(x, y);
478     }
479     
480     private void handleMouseMoved(double x, double y) {
481         if(dragging != null) {
482             dragging.setPos(x-dragDX, y-dragDY);
483             repaint();
484         }
485         if(panningStartMouse != null) {
486             canvasPosX -= x - panningStartMouse.getX();
487             canvasPosY -= y - panningStartMouse.getY();
488             repaint();
489         }
490     }
491     
492     private void handleMouseWheelMoved(double x, double y, double amount) {
493         double s = Math.exp(-0.2*amount);
494         canvasZoom *= s;
495         canvasPosX = x - (x-canvasPosX)/s;
496         canvasPosY = y - (y-canvasPosY)/s;
497         repaint();
498     }
499
500     private void handleMouseReleased(double x, double y) {
501         dragging = null;
502         panningStartMouse = null;
503     }
504     
505     public void zoomToFit() {
506         if(!nodes.isEmpty()) {
507             double minX = Double.POSITIVE_INFINITY;
508             double minY = Double.POSITIVE_INFINITY;
509             double maxX = Double.NEGATIVE_INFINITY;
510             double maxY = Double.NEGATIVE_INFINITY;
511             System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
512             for(Node node : nodes) {
513                 minX = Math.min(minX, node.getMinX());
514                 minY = Math.min(minY, node.getMinY());
515                 maxX = Math.max(maxX, node.getMaxX());
516                 maxY = Math.max(maxY, node.getMaxY());
517             }            
518             canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
519             canvasZoom *= 0.9;
520             canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
521             canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
522             repaint();
523         }
524     }
525
526     THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
527     public void initializeExtension() {
528         extensionNodes.clear();
529         extensionEdges.clear();
530         try {
531             Simantics.getSession().syncRequest(new ReadRequest() {
532                 @Override
533                 public void run(ReadGraph graph) throws DatabaseException {
534                     SCLContext sclContext = SCLContext.getCurrent();
535                     Object oldGraph = sclContext.put("graph", graph);
536                     try {
537                         THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
538                         THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
539                         for(Node node : nodes) {
540                             for(Statement stat : node.getData().getStatements()) {
541                                 Resource object = stat.getObject();
542                                 Node node2 = extensionNodeMap.get(object);
543                                 if(node2 == null) {
544                                     if(statementFilter != null && Boolean.FALSE.equals(statementFilter.apply(stat)))
545                                         continue;
546                                     node2 = oldExtensionNodeMap.get(object);
547                                     if(node2 == null) {
548                                         node2 = new Node(new NodeData(object));
549                                         double angle = random.nextDouble() * Math.PI * 2.0;
550                                         double dx = Math.cos(angle);
551                                         double dy = Math.sin(angle);
552                                         double len = 150.0;
553                                         node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
554                                     }
555                                     node2.getData().updateData(graph, labelingPreferences);                                
556                                     node2.setContent(new LabelContent(node2.getData().getLabels()));
557                                     extensionNodeMap.put(object, node2);
558                                     extensionNodes.add(node2);
559                                 }
560                                 extensionEdges.add(createEdge(graph, stat, node, node2));
561                             }
562                         }
563                     } catch (Throwable t) {
564                         if (t instanceof DatabaseException)
565                             throw (DatabaseException) t;
566                         throw new DatabaseException(t);
567                     } finally {
568                         sclContext.put("graph", oldGraph);
569                     }
570                     DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
571                     layoutExtension();
572                 }      
573             });
574         } catch (DatabaseException e) {
575             e.printStackTrace();
576         }
577     }
578
579     private void layoutExtension() {        
580         TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
581         for(int i=0;i<extensionNodes.size();++i)
582             extensionNodeIds.put(extensionNodes.get(i), i);
583         
584         double[][] neighbors = new double[extensionNodes.size()][];
585         {
586             TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];        
587             for(int i=0;i<neighborLists.length;++i)
588                 neighborLists[i] = new TDoubleArrayList();
589             for(Edge edge : extensionEdges) {
590                 int id;
591                 Node node;
592                 if(extensionNodeIds.containsKey(edge.getA())) {
593                     id = extensionNodeIds.get(edge.getA());
594                     node = edge.getB();
595                 }
596                 else {
597                     id = extensionNodeIds.get(edge.getB());
598                     node = edge.getA();
599                 }
600                 TDoubleArrayList list = neighborLists[id];
601                 list.add(node.getX());
602                 list.add(node.getY());
603             }            
604             for(int i=0;i<neighborLists.length;++i) {
605                 neighbors[i] = neighborLists[i].toArray();
606             }
607         }
608         
609         double[] fixedRepulsiveX = new double[nodes.size()];
610         double[] fixedRepulsiveY = new double[nodes.size()];
611         for(int i=0;i<nodes.size();++i) {
612             Node node = nodes.get(i);
613             fixedRepulsiveX[i] = node.getX();
614             fixedRepulsiveY[i] = node.getY();
615         }
616         ExtensionLayoutAlgorithm algo = 
617                 new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
618         double[] posX = algo.getPosX();
619         double[] posY = algo.getPosY();
620         for(int i=0;i<extensionNodes.size();++i) {
621             posX[i] = extensionNodes.get(i).getX();
622             posY[i] = extensionNodes.get(i).getY();
623         }        
624         algo.optimize();
625         for(int i=0;i<extensionNodes.size();++i) {
626             extensionNodes.get(i).setPos(posX[i], posY[i]);
627         }        
628     }      
629     
630     private Node getNode(Resource resource) {
631         Node node = nodeMap.get(resource);
632         if(node == null) {
633             node = addResource(random.nextDouble()*200.0-100.0, 
634                     random.nextDouble()*200.0-100.0, 
635                     resource);
636             nodeMap.put(resource, node);
637         }
638         return node;
639     }
640     
641     public void findPreviousChangeset() {
642         try {
643             Session session = Simantics.getSession();
644             final ManagementSupport ms = session.getService(ManagementSupport.class);
645             final long lastId = ms.getHeadRevisionId();
646             final long firstId = getOpId(ms, lastId);
647             //System.out.println(firstId + "-" + lastId);
648             addedEdges.clear();
649             removedEdges.clear();            
650             session.asyncRequest(new ReadRequest() {
651                 @Override
652                 public void run(ReadGraph graph) throws DatabaseException {
653                     final Collection<ChangeSet> css = 
654                             ms.fetchChangeSets(graph, firstId, lastId);
655                     Layer0 L0 = Layer0.getInstance(graph);
656                     for(ChangeSet cs : css) {
657                         for(ChangeSet.StatementChange stat : cs.changedStatements()) {
658                             Resource predicate = stat.getPredicate();
659                             if(predicate.equals(L0.InstanceOf) ||
660                                     predicate.equals(L0.HasName) ||
661                                     predicate.equals(L0.NameOf))
662                                 continue;
663                             Edge edge = createEdge(graph, stat, 
664                                     getNode(stat.getSubject()),
665                                     getNode(stat.getObject()));
666                             if(stat.isClaim())
667                                 addedEdges.add(edge);
668                             else
669                                 removedEdges.add(edge);
670                         }
671                     }    
672                     scheduleUpdate();
673                 }                
674             });                  
675         } catch(DatabaseException e) {
676             e.printStackTrace();
677         }
678     } 
679     
680     private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
681         Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
682         ChangeSetIdentifier curId = ids.iterator().next();
683         byte[] opIdData = curId.getMetadata().get("opid");
684         System.out.println(new String(opIdData));
685         long opId = Long.parseLong(new String(opIdData));
686         if(opId == 0)
687             opId = revisionId;
688         return opId;
689     }
690
691 }