Support Json content as drop data in Graphical Debugger
[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                     else {
219                         DataFlavor textFlavor = DataFlavor.selectBestTextFlavor(transferable.getTransferDataFlavors());
220                         if(textFlavor != null) {
221                             // Try to read the textual content of the drop event as a JSON object containing a field named resourceId
222                             try(Reader reader = textFlavor.getReaderForText(transferable)) {
223                                 ObjectMapper mapper = new ObjectMapper();
224                                 JsonNode node = mapper.readTree(reader);
225                                 Object resourceId = node.get("resourceId");
226                                 if(resourceId instanceof NumericNode) {
227                                     dtde.acceptDrop( DnDConstants.ACTION_MOVE );
228                                     
229                                     transferable.getTransferData(LocalObjectTransferable.FLAVOR);
230                                     Object obj = new StructuredSelection(Simantics.getSession().syncRequest(new Read<Resource>() {
231                                         public Resource perform(ReadGraph graph) throws DatabaseException {
232                                             SerialisationSupport ss = graph.getService(SerialisationSupport.class);
233                                             return ss.getResource(((NumericNode)resourceId).longValue());
234                                         }
235                                     }));
236                                     handleDrop(dtde, obj);
237                                     return;
238                                 }
239                             }
240                         }
241                         dtde.rejectDrop();
242                     }
243                 } catch( Exception exception ) {
244                     LOGGER.warn("Drop failed.", exception);
245                     dtde.rejectDrop();
246                 } 
247             }     
248         });
249     }    
250     
251     private void handleDrop(DropTargetDropEvent dtde, Object obj) {
252         double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
253         double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
254         handleDrop(x, y, obj);
255         
256         dtde.getDropTargetContext().dropComplete( true );
257     }
258     
259     public void keyPressed(KeyEvent e) {
260         switch(e.getKeyCode()) {
261         case KeyEvent.VK_1:
262             zoomToFit();
263             break;
264         case KeyEvent.VK_L:
265             layoutGraph();
266             break;
267         case KeyEvent.VK_CONTROL:
268             if(!extensionMode) {
269                 initializeExtension();
270                 extensionMode = true;
271                 repaint();
272             }
273             break;
274         case KeyEvent.VK_C:
275             findPreviousChangeset();
276             break;
277         case KeyEvent.VK_DELETE:
278             if (!extensionMode && dragging != null) {
279                 nodes.remove(dragging);
280                 scheduleUpdate();
281                 repaint();
282             }
283             break;
284         }
285     }
286     
287     public void keyReleased(KeyEvent e) {
288         if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
289             extensionMode = false;
290             scheduleUpdate();
291             repaint();
292         }
293     }
294
295     private static Resource extractResource(Object obj) {
296         System.out.println("- " + obj.getClass().getName());
297         if(obj instanceof WorkbenchSelectionElement) {
298             Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
299             if(resource != null)
300                 return resource;
301         }
302         if(obj instanceof IAdaptable) {
303             Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
304             if(resource != null)
305                 return resource;
306         }
307         if(obj instanceof Resource)
308             return (Resource)obj;
309         return null;
310     }
311     
312     private void handleDrop(double x, double y, Object obj) {
313         //System.out.println(obj.getClass().getName());
314         if(obj instanceof IStructuredSelection) {
315             for(Object element : ((IStructuredSelection)obj).toArray()) {
316                 Resource resource = extractResource(element);
317                 if(resource != null && !nodeMap.containsKey(resource)) {
318                     addResource(x, y, resource);                    
319                     repaint();
320                 }
321             }
322         }
323     }       
324     
325     private Node addResource(double x, double y, Resource resource) {
326         Node a = new Node(new NodeData(resource));
327         a.setPos(x, y);
328         scheduleUpdate();
329         nodes.add(a);
330         return a;
331     }
332     
333     public void addResource(Resource resource) {
334         double x, y;
335         if(nodes.isEmpty()) {
336             x = 0.0;
337             y = 0.0;
338         }
339         else {
340             double xMin=Double.POSITIVE_INFINITY, yMin=Double.POSITIVE_INFINITY;
341             double xMax=Double.NEGATIVE_INFINITY, yMax=Double.NEGATIVE_INFINITY;
342             for(Node node : nodes) {
343                 xMin = Math.min(node.getMinX(), xMin);
344                 yMin = Math.min(node.getMinY(), yMin);
345                 xMax = Math.max(node.getMaxX(), xMax);
346                 yMax = Math.max(node.getMaxY(), yMax);
347             }
348             x = xMin + (xMax - xMin) * random.nextDouble();
349             y = yMin + (yMax - yMin) * random.nextDouble();
350         }
351         
352         addResource(x, y, resource);
353         repaint();
354     }
355
356     private void scheduleUpdate() {
357         Simantics.getSession().asyncRequest(new ReadRequest() {            
358             @Override
359             public void run(ReadGraph graph) throws DatabaseException {                
360                 updateNodes(graph);
361                 updateEdges(graph);
362                 SwingUtilities.invokeLater(new Runnable() {
363                     @Override
364                     public void run() {
365                         repaint();
366                     }
367                     
368                 });
369             }            
370         });        
371     }
372     
373     public void layoutGraph() {
374         ArrayList<Edge> allEdges = new ArrayList<Edge>(
375                 edges.size() + addedEdges.size() + removedEdges.size()
376                 );
377         allEdges.addAll(edges);
378         allEdges.addAll(addedEdges);
379         allEdges.addAll(removedEdges);
380         LayoutGraph.layout(
381                 nodes.toArray(new Node[nodes.size()]), 
382                 allEdges.toArray(new Edge[edges.size()]));
383         repaint();                
384     }
385     
386     private void updateNodes(ReadGraph graph) throws DatabaseException {
387         for(Node node : nodes) {
388             node.getData().updateData(graph, labelingPreferences);
389             node.setContent(new LabelContent(node.getData().getLabels()));
390         }
391         nodeMap.clear();
392         for(Node node : nodes) {
393             NodeData data = node.getData();
394             nodeMap.put(data.getResource(), node);
395         }
396     }
397     
398     private void updateEdges(ReadGraph graph) throws DatabaseException {
399         ArrayList<Edge> edges = new ArrayList<Edge>();
400         for(Node node : nodes) {
401             NodeData data = node.getData();
402             Resource subject = data.getResource();
403             ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
404             for(Statement stat : data.getStatements()) {
405                 Resource object = stat.getObject();
406                 Node node2 = nodeMap.get(object);
407                 if(node2 != null) {
408                     if(object.getResourceId() > subject.getResourceId() ||
409                             graph.getPossibleInverse(stat.getPredicate()) == null) {
410                         edges.add(createEdge(graph, stat, node, node2));
411                     }
412                 }
413                 else
414                     filteredStatements.add(stat);
415             }
416             data.setStatements(filteredStatements);
417         }
418         this.edges = edges;
419         this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
420         this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
421     }
422
423     private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
424         ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
425         for (Edge e : edges) {
426             if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
427                 continue;
428             result.add(e);
429         }
430         return result;
431     }
432
433     private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
434         Resource predicate = stat.getPredicate();
435         Resource inverse = graph.getPossibleInverse(predicate);        
436         if(inverse != null) {
437             Layer0 L0 = Layer0.getInstance(graph);
438             if(graph.hasStatement(predicate, L0.PartOf, inverse)
439                     || predicate.equals(L0.PartOf)
440                     || predicate.equals(L0.SuperrelationOf)
441                     || predicate.equals(L0.SupertypeOf)) {
442                 predicate = inverse;
443                 Node temp = n1;
444                 n1 = n2;
445                 n2 = temp;
446             }
447         }
448         Edge edge = new Edge(n1, n2);
449         edge.setContent(new LabelContent(new String[] {
450                 NameUtils.getSafeName(graph, predicate)}));
451         return edge;
452     }
453     
454     Node dragging = null;
455     double dragDX, dragDY;
456     private void handleMouseLeftPressed(double x, double y) {
457         Node node;
458         if(extensionMode) {
459             node = pickExtension(x, y);
460             if(node != null) {
461                 nodes.add(node);
462                 extensionNodes.remove(node);
463             }
464         }
465         else
466             node = pick(x, y);
467         if(node != null) {
468             dragDX = x - node.getX();
469             dragDY = y - node.getY();
470             dragging = node;
471         }
472     }
473     
474     Point2D panningStartMouse;
475     private void handleMouseMiddlePressed(double x, double y) {
476         panningStartMouse = new Point2D.Double(x, y);
477     }
478     
479     private void handleMouseMoved(double x, double y) {
480         if(dragging != null) {
481             dragging.setPos(x-dragDX, y-dragDY);
482             repaint();
483         }
484         if(panningStartMouse != null) {
485             canvasPosX -= x - panningStartMouse.getX();
486             canvasPosY -= y - panningStartMouse.getY();
487             repaint();
488         }
489     }
490     
491     private void handleMouseWheelMoved(double x, double y, double amount) {
492         double s = Math.exp(-0.2*amount);
493         canvasZoom *= s;
494         canvasPosX = x - (x-canvasPosX)/s;
495         canvasPosY = y - (y-canvasPosY)/s;
496         repaint();
497     }
498
499     private void handleMouseReleased(double x, double y) {
500         dragging = null;
501         panningStartMouse = null;
502     }
503     
504     public void zoomToFit() {
505         if(!nodes.isEmpty()) {
506             double minX = Double.POSITIVE_INFINITY;
507             double minY = Double.POSITIVE_INFINITY;
508             double maxX = Double.NEGATIVE_INFINITY;
509             double maxY = Double.NEGATIVE_INFINITY;
510             System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
511             for(Node node : nodes) {
512                 minX = Math.min(minX, node.getMinX());
513                 minY = Math.min(minY, node.getMinY());
514                 maxX = Math.max(maxX, node.getMaxX());
515                 maxY = Math.max(maxY, node.getMaxY());
516             }            
517             canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
518             canvasZoom *= 0.9;
519             canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
520             canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
521             repaint();
522         }
523     }
524
525     THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
526     public void initializeExtension() {
527         extensionNodes.clear();
528         extensionEdges.clear();
529         try {
530             Simantics.getSession().syncRequest(new ReadRequest() {
531                 @Override
532                 public void run(ReadGraph graph) throws DatabaseException {
533                     SCLContext sclContext = SCLContext.getCurrent();
534                     Object oldGraph = sclContext.put("graph", graph);
535                     try {
536                         THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
537                         THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
538                         for(Node node : nodes) {
539                             for(Statement stat : node.getData().getStatements()) {
540                                 Resource object = stat.getObject();
541                                 Node node2 = extensionNodeMap.get(object);
542                                 if(node2 == null) {
543                                     if(statementFilter != null && Boolean.FALSE.equals(statementFilter.apply(stat)))
544                                         continue;
545                                     node2 = oldExtensionNodeMap.get(object);
546                                     if(node2 == null) {
547                                         node2 = new Node(new NodeData(object));
548                                         double angle = random.nextDouble() * Math.PI * 2.0;
549                                         double dx = Math.cos(angle);
550                                         double dy = Math.sin(angle);
551                                         double len = 150.0;
552                                         node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
553                                     }
554                                     node2.getData().updateData(graph, labelingPreferences);                                
555                                     node2.setContent(new LabelContent(node2.getData().getLabels()));
556                                     extensionNodeMap.put(object, node2);
557                                     extensionNodes.add(node2);
558                                 }
559                                 extensionEdges.add(createEdge(graph, stat, node, node2));
560                             }
561                         }
562                     } catch (Throwable t) {
563                         if (t instanceof DatabaseException)
564                             throw (DatabaseException) t;
565                         throw new DatabaseException(t);
566                     } finally {
567                         sclContext.put("graph", oldGraph);
568                     }
569                     DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
570                     layoutExtension();
571                 }      
572             });
573         } catch (DatabaseException e) {
574             e.printStackTrace();
575         }
576     }
577
578     private void layoutExtension() {        
579         TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
580         for(int i=0;i<extensionNodes.size();++i)
581             extensionNodeIds.put(extensionNodes.get(i), i);
582         
583         double[][] neighbors = new double[extensionNodes.size()][];
584         {
585             TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];        
586             for(int i=0;i<neighborLists.length;++i)
587                 neighborLists[i] = new TDoubleArrayList();
588             for(Edge edge : extensionEdges) {
589                 int id;
590                 Node node;
591                 if(extensionNodeIds.containsKey(edge.getA())) {
592                     id = extensionNodeIds.get(edge.getA());
593                     node = edge.getB();
594                 }
595                 else {
596                     id = extensionNodeIds.get(edge.getB());
597                     node = edge.getA();
598                 }
599                 TDoubleArrayList list = neighborLists[id];
600                 list.add(node.getX());
601                 list.add(node.getY());
602             }            
603             for(int i=0;i<neighborLists.length;++i) {
604                 neighbors[i] = neighborLists[i].toArray();
605             }
606         }
607         
608         double[] fixedRepulsiveX = new double[nodes.size()];
609         double[] fixedRepulsiveY = new double[nodes.size()];
610         for(int i=0;i<nodes.size();++i) {
611             Node node = nodes.get(i);
612             fixedRepulsiveX[i] = node.getX();
613             fixedRepulsiveY[i] = node.getY();
614         }
615         ExtensionLayoutAlgorithm algo = 
616                 new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
617         double[] posX = algo.getPosX();
618         double[] posY = algo.getPosY();
619         for(int i=0;i<extensionNodes.size();++i) {
620             posX[i] = extensionNodes.get(i).getX();
621             posY[i] = extensionNodes.get(i).getY();
622         }        
623         algo.optimize();
624         for(int i=0;i<extensionNodes.size();++i) {
625             extensionNodes.get(i).setPos(posX[i], posY[i]);
626         }        
627     }      
628     
629     private Node getNode(Resource resource) {
630         Node node = nodeMap.get(resource);
631         if(node == null) {
632             node = addResource(random.nextDouble()*200.0-100.0, 
633                     random.nextDouble()*200.0-100.0, 
634                     resource);
635             nodeMap.put(resource, node);
636         }
637         return node;
638     }
639     
640     public void findPreviousChangeset() {
641         try {
642             Session session = Simantics.getSession();
643             final ManagementSupport ms = session.getService(ManagementSupport.class);
644             final long lastId = ms.getHeadRevisionId();
645             final long firstId = getOpId(ms, lastId);
646             //System.out.println(firstId + "-" + lastId);
647             addedEdges.clear();
648             removedEdges.clear();            
649             session.asyncRequest(new ReadRequest() {
650                 @Override
651                 public void run(ReadGraph graph) throws DatabaseException {
652                     final Collection<ChangeSet> css = 
653                             ms.fetchChangeSets(graph, firstId, lastId);
654                     Layer0 L0 = Layer0.getInstance(graph);
655                     for(ChangeSet cs : css) {
656                         for(ChangeSet.StatementChange stat : cs.changedStatements()) {
657                             Resource predicate = stat.getPredicate();
658                             if(predicate.equals(L0.InstanceOf) ||
659                                     predicate.equals(L0.HasName) ||
660                                     predicate.equals(L0.NameOf))
661                                 continue;
662                             Edge edge = createEdge(graph, stat, 
663                                     getNode(stat.getSubject()),
664                                     getNode(stat.getObject()));
665                             if(stat.isClaim())
666                                 addedEdges.add(edge);
667                             else
668                                 removedEdges.add(edge);
669                         }
670                     }    
671                     scheduleUpdate();
672                 }                
673             });                  
674         } catch(DatabaseException e) {
675             e.printStackTrace();
676         }
677     } 
678     
679     private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
680         Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
681         ChangeSetIdentifier curId = ids.iterator().next();
682         byte[] opIdData = curId.getMetadata().get("opid");
683         System.out.println(new String(opIdData));
684         long opId = Long.parseLong(new String(opIdData));
685         if(opId == 0)
686             opId = revisionId;
687         return opId;
688     }
689
690 }