]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.debug.graphical/src/org/simantics/debug/graphical/DebuggerCanvas.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.debug.graphical / src / org / simantics / debug / graphical / DebuggerCanvas.java
index 86000643ba647fb7c17426f95821ffdf27de93a1..00a539990a6c466ae42ac0788a254889cab68508 100644 (file)
-package org.simantics.debug.graphical;\r
-\r
-import gnu.trove.list.array.TDoubleArrayList;\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-\r
-import java.awt.Color;\r
-import java.awt.GradientPaint;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.RenderingHints;\r
-import java.awt.datatransfer.Transferable;\r
-import java.awt.datatransfer.UnsupportedFlavorException;\r
-import java.awt.dnd.DnDConstants;\r
-import java.awt.dnd.DropTarget;\r
-import java.awt.dnd.DropTargetAdapter;\r
-import java.awt.dnd.DropTargetDropEvent;\r
-import java.awt.event.KeyAdapter;\r
-import java.awt.event.KeyEvent;\r
-import java.awt.event.MouseAdapter;\r
-import java.awt.event.MouseEvent;\r
-import java.awt.event.MouseMotionAdapter;\r
-import java.awt.event.MouseWheelEvent;\r
-import java.awt.event.MouseWheelListener;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.io.IOException;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Random;\r
-\r
-import javax.swing.JPanel;\r
-import javax.swing.SwingUtilities;\r
-\r
-import org.eclipse.core.runtime.IAdaptable;\r
-import org.eclipse.jface.viewers.IStructuredSelection;\r
-import org.simantics.Simantics;\r
-import org.simantics.db.ChangeSet;\r
-import org.simantics.db.ChangeSetIdentifier;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.service.ManagementSupport;\r
-import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;\r
-import org.simantics.debug.graphical.layout.LayoutGraph;\r
-import org.simantics.debug.graphical.model.Edge;\r
-import org.simantics.debug.graphical.model.LabelContent;\r
-import org.simantics.debug.graphical.model.Node;\r
-import org.simantics.debug.graphical.model.NodeData;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.ui.dnd.LocalObjectTransfer;\r
-import org.simantics.ui.dnd.LocalObjectTransferable;\r
-import org.simantics.ui.selection.AnyResource;\r
-import org.simantics.ui.selection.WorkbenchSelectionElement;\r
-\r
-public class DebuggerCanvas extends JPanel {\r
-\r
-    private static final long serialVersionUID = -718678297301786379L;\r
-    \r
-    ArrayList<Node> nodes = new ArrayList<Node>();\r
-    THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>(); \r
-    ArrayList<Edge> edges = new ArrayList<Edge>();\r
-    ArrayList<Node> extensionNodes = new ArrayList<Node>();\r
-    ArrayList<Edge> extensionEdges = new ArrayList<Edge>();\r
-    ArrayList<Edge> addedEdges = new ArrayList<Edge>();\r
-    ArrayList<Edge> removedEdges = new ArrayList<Edge>();\r
-    double canvasPosX = 0.0;\r
-    double canvasPosY = 0.0;\r
-    double canvasZoom = 1.0;\r
-    boolean extensionMode = false;\r
-    Random random = new Random();\r
-\r
-    LabelingPreferences labelingPreferences = \r
-            new LabelingPreferences();\r
-    \r
-    @Override\r
-    public void paint(Graphics _g) {\r
-        Graphics2D g = (Graphics2D)_g;\r
-        \r
-        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, \r
-                RenderingHints.VALUE_ANTIALIAS_ON);\r
-        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, \r
-                RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
-        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, \r
-                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
-        \r
-        g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));\r
-        g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));\r
-        g.setColor(Color.BLACK);\r
-        g.setTransform(new AffineTransform(\r
-                canvasZoom, 0.0, \r
-                0.0, canvasZoom, \r
-                -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));\r
-        for(Node node : nodes)\r
-            node.render(g);\r
-        for(Edge edge : edges)\r
-            edge.render(g);\r
-        if(extensionMode) {\r
-            for(Node node : extensionNodes)\r
-                node.render(g);\r
-            for(Edge edge : extensionEdges)\r
-                edge.render(g);\r
-        }\r
-        g.setColor(Color.GREEN);\r
-        for(Edge edge : addedEdges)\r
-            edge.render(g);\r
-        g.setColor(Color.RED);\r
-        for(Edge edge : removedEdges)\r
-            edge.render(g);\r
-    }\r
-    \r
-    public Node pick(double x, double y) {\r
-        for(Node node : nodes)\r
-            if(node.pick(x, y))\r
-                return node;\r
-        return null;\r
-    }\r
-    \r
-    public Node pickExtension(double x, double y) {\r
-        for(Node node : extensionNodes)\r
-            if(node.pick(x, y))\r
-                return node;\r
-        return null;\r
-    }\r
-    \r
-    {\r
-        addMouseListener(new MouseAdapter() {\r
-            @Override\r
-            public void mousePressed(MouseEvent e) {                \r
-                double x = canvasPosX+e.getX()/canvasZoom;\r
-                double y = canvasPosY+e.getY()/canvasZoom;\r
-                if(e.getButton() == MouseEvent.BUTTON1)\r
-                    handleMouseLeftPressed(x, y);\r
-                else if(e.getButton() == MouseEvent.BUTTON2)\r
-                    handleMouseMiddlePressed(x, y);\r
-            }\r
-            @Override\r
-            public void mouseMoved(MouseEvent e) {\r
-                handleMouseMoved(canvasPosX+e.getX()/canvasZoom, \r
-                        canvasPosY+e.getY()/canvasZoom);\r
-            }\r
-            @Override\r
-            public void mouseReleased(MouseEvent e) {\r
-                handleMouseReleased(canvasPosX+e.getX()/canvasZoom, \r
-                        canvasPosY+e.getY()/canvasZoom);\r
-            }\r
-            @Override\r
-            public void mouseWheelMoved(MouseWheelEvent e) {\r
-                double x = canvasPosX+e.getX()/canvasZoom;\r
-                double y = canvasPosY+e.getY()/canvasZoom;\r
-                handleMouseWheelMoved(x, y, e.getWheelRotation());\r
-            }\r
-        });\r
-        addMouseMotionListener(new MouseMotionAdapter() {\r
-            @Override\r
-            public void mouseDragged(MouseEvent e) {\r
-                handleMouseMoved(canvasPosX+e.getX()/canvasZoom, \r
-                        canvasPosY+e.getY()/canvasZoom);\r
-            }            \r
-        });\r
-        addMouseWheelListener(new MouseWheelListener() {            \r
-            @Override\r
-            public void mouseWheelMoved(MouseWheelEvent e) {\r
-                double x = canvasPosX+e.getX()/canvasZoom;\r
-                double y = canvasPosY+e.getY()/canvasZoom;\r
-                handleMouseWheelMoved(x, y, e.getWheelRotation());\r
-            }\r
-        });\r
-        addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {\r
-            @Override\r
-            public void keyPressed(KeyEvent e) {\r
-                DebuggerCanvas.this.keyPressed(e);\r
-            }\r
-            @Override\r
-            public void keyReleased(KeyEvent e) {\r
-                DebuggerCanvas.this.keyReleased(e);\r
-            }\r
-        }));\r
-        DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {\r
-            @Override\r
-            public void drop(DropTargetDropEvent dtde) {\r
-                try {\r
-                    Transferable transferable = dtde.getTransferable();\r
-                    \r
-                    if( transferable.isDataFlavorSupported( \r
-                            LocalObjectTransferable.FLAVOR ) ) {\r
-                        dtde.acceptDrop( DnDConstants.ACTION_MOVE );\r
-                        \r
-                        transferable.getTransferData(LocalObjectTransferable.FLAVOR );\r
-                        Object obj = LocalObjectTransfer.getTransfer().getObject();\r
-                        double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;\r
-                        double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;\r
-                        handleDrop(x, y, obj);\r
-                        \r
-                        dtde.getDropTargetContext().dropComplete( true );\r
-                    }\r
-                    else {\r
-                        dtde.rejectDrop();\r
-                    }\r
-                } catch( IOException exception ) {\r
-                    exception.printStackTrace();\r
-                    dtde.rejectDrop();\r
-                } catch( UnsupportedFlavorException ufException ) {\r
-                    ufException.printStackTrace();\r
-                    dtde.rejectDrop();\r
-                }\r
-            }     \r
-        });\r
-    }    \r
-    \r
-    public void keyPressed(KeyEvent e) {\r
-        switch(e.getKeyCode()) {\r
-        case KeyEvent.VK_1:\r
-            zoomToFit();\r
-            break;\r
-        case KeyEvent.VK_L:\r
-            layoutGraph();\r
-            break;\r
-        case KeyEvent.VK_CONTROL:\r
-            if(!extensionMode) {\r
-                initializeExtension();\r
-                extensionMode = true;\r
-                repaint();\r
-            }\r
-            break;\r
-        case KeyEvent.VK_C:\r
-            findPreviousChangeset();\r
-            break;\r
-        case KeyEvent.VK_DELETE:\r
-            if (!extensionMode && dragging != null) {\r
-                nodes.remove(dragging);\r
-                scheduleUpdate();\r
-                repaint();\r
-            }\r
-            break;\r
-        }\r
-    }\r
-    \r
-    public void keyReleased(KeyEvent e) {\r
-        if(e.getKeyCode() == KeyEvent.VK_CONTROL) {\r
-            extensionMode = false;\r
-            scheduleUpdate();\r
-            repaint();\r
-        }\r
-    }\r
-\r
-    private static Resource extractResource(Object obj) {\r
-        System.out.println("- " + obj.getClass().getName());\r
-        if(obj instanceof WorkbenchSelectionElement) {\r
-            Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));\r
-            if(resource != null)\r
-                return resource;\r
-        }\r
-        if(obj instanceof IAdaptable) {\r
-            Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);\r
-            if(resource != null)\r
-                return resource;\r
-        }\r
-        return null;\r
-    }\r
-    \r
-    private void handleDrop(double x, double y, Object obj) {\r
-        //System.out.println(obj.getClass().getName());\r
-        if(obj instanceof IStructuredSelection) {\r
-            for(Object element : ((IStructuredSelection)obj).toArray()) {\r
-                Resource resource = extractResource(element);\r
-                if(resource != null && !nodeMap.containsKey(resource)) {\r
-                    addResource(x, y, resource);                    \r
-                    repaint();\r
-                }\r
-            }\r
-        }\r
-    }       \r
-    \r
-    private Node addResource(double x, double y, Resource resource) {\r
-        Node a = new Node(new NodeData(resource));\r
-        a.setPos(x, y);\r
-        scheduleUpdate();\r
-        nodes.add(a);\r
-        return a;\r
-    }\r
-\r
-    private void scheduleUpdate() {\r
-        Simantics.getSession().asyncRequest(new ReadRequest() {            \r
-            @Override\r
-            public void run(ReadGraph graph) throws DatabaseException {                \r
-                updateNodes(graph);\r
-                updateEdges(graph);\r
-                SwingUtilities.invokeLater(new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        repaint();\r
-                    }\r
-                    \r
-                });\r
-            }            \r
-        });        \r
-    }\r
-    \r
-    public void layoutGraph() {\r
-        ArrayList<Edge> allEdges = new ArrayList<Edge>(\r
-                edges.size() + addedEdges.size() + removedEdges.size()\r
-                );\r
-        allEdges.addAll(edges);\r
-        allEdges.addAll(addedEdges);\r
-        allEdges.addAll(removedEdges);\r
-        LayoutGraph.layout(\r
-                nodes.toArray(new Node[nodes.size()]), \r
-                allEdges.toArray(new Edge[edges.size()]));\r
-        repaint();                \r
-    }\r
-    \r
-    private void updateNodes(ReadGraph graph) throws DatabaseException {\r
-        for(Node node : nodes) {\r
-            node.getData().updateData(graph, labelingPreferences);\r
-            node.setContent(new LabelContent(node.getData().getLabels()));\r
-        }\r
-        nodeMap.clear();\r
-        for(Node node : nodes) {\r
-            NodeData data = node.getData();\r
-            nodeMap.put(data.getResource(), node);\r
-        }\r
-    }\r
-    \r
-    private void updateEdges(ReadGraph graph) throws DatabaseException {\r
-        ArrayList<Edge> edges = new ArrayList<Edge>();\r
-        for(Node node : nodes) {\r
-            NodeData data = node.getData();\r
-            Resource subject = data.getResource();\r
-            ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());\r
-            for(Statement stat : data.getStatements()) {\r
-                Resource object = stat.getObject();\r
-                Node node2 = nodeMap.get(object);\r
-                if(node2 != null) {\r
-                    if(object.getResourceId() > subject.getResourceId() ||\r
-                            graph.getPossibleInverse(stat.getPredicate()) == null) {\r
-                        edges.add(createEdge(graph, stat, node, node2));\r
-                    }\r
-                }\r
-                else\r
-                    filteredStatements.add(stat);\r
-            }\r
-            data.setStatements(filteredStatements);\r
-        }\r
-        this.edges = edges;\r
-        this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );\r
-        this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );\r
-    }\r
-\r
-    private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {\r
-        ArrayList<Edge> result = new ArrayList<Edge>(edges.size());\r
-        for (Edge e : edges) {\r
-            if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))\r
-                continue;\r
-            result.add(e);\r
-        }\r
-        return result;\r
-    }\r
-\r
-    private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {\r
-        Resource predicate = stat.getPredicate();\r
-        Resource inverse = graph.getPossibleInverse(predicate);        \r
-        if(inverse != null) {\r
-            Layer0 L0 = Layer0.getInstance(graph);\r
-            if(graph.hasStatement(predicate, L0.PartOf, inverse)\r
-                    || predicate.equals(L0.PartOf)\r
-                    || predicate.equals(L0.SuperrelationOf)\r
-                    || predicate.equals(L0.SupertypeOf)) {\r
-                predicate = inverse;\r
-                Node temp = n1;\r
-                n1 = n2;\r
-                n2 = temp;\r
-            }\r
-        }\r
-        Edge edge = new Edge(n1, n2);\r
-        edge.setContent(new LabelContent(new String[] {\r
-                NameUtils.getSafeName(graph, predicate)}));\r
-        return edge;\r
-    }\r
-    \r
-    Node dragging = null;\r
-    double dragDX, dragDY;\r
-    private void handleMouseLeftPressed(double x, double y) {\r
-        Node node;\r
-        if(extensionMode) {\r
-            node = pickExtension(x, y);\r
-            if(node != null) {\r
-                nodes.add(node);\r
-                extensionNodes.remove(node);\r
-            }\r
-        }\r
-        else\r
-            node = pick(x, y);\r
-        if(node != null) {\r
-            dragDX = x - node.getX();\r
-            dragDY = y - node.getY();\r
-            dragging = node;\r
-        }\r
-    }\r
-    \r
-    Point2D panningStartMouse;\r
-    private void handleMouseMiddlePressed(double x, double y) {\r
-        panningStartMouse = new Point2D.Double(x, y);\r
-    }\r
-    \r
-    private void handleMouseMoved(double x, double y) {\r
-        if(dragging != null) {\r
-            dragging.setPos(x-dragDX, y-dragDY);\r
-            repaint();\r
-        }\r
-        if(panningStartMouse != null) {\r
-            canvasPosX -= x - panningStartMouse.getX();\r
-            canvasPosY -= y - panningStartMouse.getY();\r
-            repaint();\r
-        }\r
-    }\r
-    \r
-    private void handleMouseWheelMoved(double x, double y, double amount) {\r
-        double s = Math.exp(-0.2*amount);\r
-        canvasZoom *= s;\r
-        canvasPosX = x - (x-canvasPosX)/s;\r
-        canvasPosY = y - (y-canvasPosY)/s;\r
-        repaint();\r
-    }\r
-\r
-    private void handleMouseReleased(double x, double y) {\r
-        dragging = null;\r
-        panningStartMouse = null;\r
-    }\r
-    \r
-    public void zoomToFit() {\r
-        if(!nodes.isEmpty()) {\r
-            double minX = Double.POSITIVE_INFINITY;\r
-            double minY = Double.POSITIVE_INFINITY;\r
-            double maxX = Double.NEGATIVE_INFINITY;\r
-            double maxY = Double.NEGATIVE_INFINITY;\r
-            System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");\r
-            for(Node node : nodes) {\r
-                minX = Math.min(minX, node.getMinX());\r
-                minY = Math.min(minY, node.getMinY());\r
-                maxX = Math.max(maxX, node.getMaxX());\r
-                maxY = Math.max(maxY, node.getMaxY());\r
-            }            \r
-            canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));\r
-            canvasZoom *= 0.9;\r
-            canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);\r
-            canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);\r
-            repaint();\r
-        }\r
-    }\r
-\r
-    THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();\r
-    public void initializeExtension() {\r
-        extensionNodes.clear();\r
-        extensionEdges.clear();\r
-        try {\r
-            Simantics.getSession().syncRequest(new ReadRequest() {\r
-                @Override\r
-                public void run(ReadGraph graph) throws DatabaseException {\r
-                    THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;\r
-                    THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();\r
-                    for(Node node : nodes) {\r
-                        for(Statement stat : node.getData().getStatements()) {\r
-                            Resource object = stat.getObject();\r
-                            Node node2 = extensionNodeMap.get(object);\r
-                            if(node2 == null) {\r
-                                node2 = oldExtensionNodeMap.get(object);\r
-                                if(node2 == null) {\r
-                                    node2 = new Node(new NodeData(object));\r
-                                    double angle = random.nextDouble() * Math.PI * 2.0;\r
-                                    double dx = Math.cos(angle);\r
-                                    double dy = Math.sin(angle);\r
-                                    double len = 150.0;\r
-                                    node2.setPos(node.getX() + dx*len, node.getY() + dy*len);\r
-                                }\r
-                                node2.getData().updateData(graph, labelingPreferences);                                \r
-                                node2.setContent(new LabelContent(node2.getData().getLabels()));\r
-                                extensionNodeMap.put(object, node2);\r
-                                extensionNodes.add(node2);\r
-                            }\r
-                            extensionEdges.add(createEdge(graph, stat, node, node2));\r
-                        }\r
-                    }\r
-                    DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;\r
-                    layoutExtension();\r
-                }      \r
-            });\r
-        } catch (DatabaseException e) {\r
-            e.printStackTrace();\r
-        }\r
-    }\r
-\r
-    private void layoutExtension() {        \r
-        TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();\r
-        for(int i=0;i<extensionNodes.size();++i)\r
-            extensionNodeIds.put(extensionNodes.get(i), i);\r
-        \r
-        double[][] neighbors = new double[extensionNodes.size()][];\r
-        {\r
-            TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];        \r
-            for(int i=0;i<neighborLists.length;++i)\r
-                neighborLists[i] = new TDoubleArrayList();\r
-            for(Edge edge : extensionEdges) {\r
-                int id;\r
-                Node node;\r
-                if(extensionNodeIds.containsKey(edge.getA())) {\r
-                    id = extensionNodeIds.get(edge.getA());\r
-                    node = edge.getB();\r
-                }\r
-                else {\r
-                    id = extensionNodeIds.get(edge.getB());\r
-                    node = edge.getA();\r
-                }\r
-                TDoubleArrayList list = neighborLists[id];\r
-                list.add(node.getX());\r
-                list.add(node.getY());\r
-            }            \r
-            for(int i=0;i<neighborLists.length;++i) {\r
-                neighbors[i] = neighborLists[i].toArray();\r
-            }\r
-        }\r
-        \r
-        double[] fixedRepulsiveX = new double[nodes.size()];\r
-        double[] fixedRepulsiveY = new double[nodes.size()];\r
-        for(int i=0;i<nodes.size();++i) {\r
-            Node node = nodes.get(i);\r
-            fixedRepulsiveX[i] = node.getX();\r
-            fixedRepulsiveY[i] = node.getY();\r
-        }\r
-        ExtensionLayoutAlgorithm algo = \r
-                new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);\r
-        double[] posX = algo.getPosX();\r
-        double[] posY = algo.getPosY();\r
-        for(int i=0;i<extensionNodes.size();++i) {\r
-            posX[i] = extensionNodes.get(i).getX();\r
-            posY[i] = extensionNodes.get(i).getY();\r
-        }        \r
-        algo.optimize();\r
-        for(int i=0;i<extensionNodes.size();++i) {\r
-            extensionNodes.get(i).setPos(posX[i], posY[i]);\r
-        }        \r
-    }      \r
-    \r
-    private Node getNode(Resource resource) {\r
-        Node node = nodeMap.get(resource);\r
-        if(node == null) {\r
-            node = addResource(random.nextDouble()*200.0-100.0, \r
-                    random.nextDouble()*200.0-100.0, \r
-                    resource);\r
-            nodeMap.put(resource, node);\r
-        }\r
-        return node;\r
-    }\r
-    \r
-    public void findPreviousChangeset() {\r
-        try {\r
-            Session session = Simantics.getSession();\r
-            final ManagementSupport ms = session.getService(ManagementSupport.class);\r
-            final long lastId = ms.getHeadRevisionId();\r
-            final long firstId = getOpId(ms, lastId);\r
-            //System.out.println(firstId + "-" + lastId);\r
-            addedEdges.clear();\r
-            removedEdges.clear();            \r
-            session.asyncRequest(new ReadRequest() {\r
-                @Override\r
-                public void run(ReadGraph graph) throws DatabaseException {\r
-                    final Collection<ChangeSet> css = \r
-                            ms.fetchChangeSets(graph, firstId, lastId);\r
-                    Layer0 L0 = Layer0.getInstance(graph);\r
-                    for(ChangeSet cs : css) {\r
-                        for(ChangeSet.StatementChange stat : cs.changedStatements()) {\r
-                            Resource predicate = stat.getPredicate();\r
-                            if(predicate.equals(L0.InstanceOf) ||\r
-                                    predicate.equals(L0.HasName) ||\r
-                                    predicate.equals(L0.NameOf))\r
-                                continue;\r
-                            Edge edge = createEdge(graph, stat, \r
-                                    getNode(stat.getSubject()),\r
-                                    getNode(stat.getObject()));\r
-                            if(stat.isClaim())\r
-                                addedEdges.add(edge);\r
-                            else\r
-                                removedEdges.add(edge);\r
-                        }\r
-                    }    \r
-                    scheduleUpdate();\r
-                }                \r
-            });                  \r
-        } catch(DatabaseException e) {\r
-            e.printStackTrace();\r
-        }\r
-    } \r
-    \r
-    private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {\r
-        Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);\r
-        ChangeSetIdentifier curId = ids.iterator().next();\r
-        byte[] opIdData = curId.getMetadata().get("opid");\r
-        System.out.println(new String(opIdData));\r
-        long opId = Long.parseLong(new String(opIdData));\r
-        if(opId == 0)\r
-            opId = revisionId;\r
-        return opId;\r
-    }\r
-\r
-}\r
+package org.simantics.debug.graphical;
+
+import gnu.trove.list.array.TDoubleArrayList;
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectIntHashMap;
+
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Random;
+
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.simantics.Simantics;
+import org.simantics.db.ChangeSet;
+import org.simantics.db.ChangeSetIdentifier;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.Statement;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.service.ManagementSupport;
+import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;
+import org.simantics.debug.graphical.layout.LayoutGraph;
+import org.simantics.debug.graphical.model.Edge;
+import org.simantics.debug.graphical.model.LabelContent;
+import org.simantics.debug.graphical.model.Node;
+import org.simantics.debug.graphical.model.NodeData;
+import org.simantics.layer0.Layer0;
+import org.simantics.ui.dnd.LocalObjectTransfer;
+import org.simantics.ui.dnd.LocalObjectTransferable;
+import org.simantics.ui.selection.AnyResource;
+import org.simantics.ui.selection.WorkbenchSelectionElement;
+
+public class DebuggerCanvas extends JPanel {
+
+    private static final long serialVersionUID = -718678297301786379L;
+    
+    ArrayList<Node> nodes = new ArrayList<Node>();
+    THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>(); 
+    ArrayList<Edge> edges = new ArrayList<Edge>();
+    ArrayList<Node> extensionNodes = new ArrayList<Node>();
+    ArrayList<Edge> extensionEdges = new ArrayList<Edge>();
+    ArrayList<Edge> addedEdges = new ArrayList<Edge>();
+    ArrayList<Edge> removedEdges = new ArrayList<Edge>();
+    double canvasPosX = 0.0;
+    double canvasPosY = 0.0;
+    double canvasZoom = 1.0;
+    boolean extensionMode = false;
+    Random random = new Random();
+
+    LabelingPreferences labelingPreferences = 
+            new LabelingPreferences();
+    
+    @Override
+    public void paint(Graphics _g) {
+        Graphics2D g = (Graphics2D)_g;
+        
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
+                RenderingHints.VALUE_ANTIALIAS_ON);
+        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, 
+                RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 
+                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        
+        g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));
+        g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
+        g.setColor(Color.BLACK);
+        g.setTransform(new AffineTransform(
+                canvasZoom, 0.0, 
+                0.0, canvasZoom, 
+                -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
+        for(Node node : nodes)
+            node.render(g);
+        for(Edge edge : edges)
+            edge.render(g);
+        if(extensionMode) {
+            for(Node node : extensionNodes)
+                node.render(g);
+            for(Edge edge : extensionEdges)
+                edge.render(g);
+        }
+        g.setColor(Color.GREEN);
+        for(Edge edge : addedEdges)
+            edge.render(g);
+        g.setColor(Color.RED);
+        for(Edge edge : removedEdges)
+            edge.render(g);
+    }
+    
+    public Node pick(double x, double y) {
+        for(Node node : nodes)
+            if(node.pick(x, y))
+                return node;
+        return null;
+    }
+    
+    public Node pickExtension(double x, double y) {
+        for(Node node : extensionNodes)
+            if(node.pick(x, y))
+                return node;
+        return null;
+    }
+    
+    {
+        addMouseListener(new MouseAdapter() {
+            @Override
+            public void mousePressed(MouseEvent e) {                
+                double x = canvasPosX+e.getX()/canvasZoom;
+                double y = canvasPosY+e.getY()/canvasZoom;
+                if(e.getButton() == MouseEvent.BUTTON1)
+                    handleMouseLeftPressed(x, y);
+                else if(e.getButton() == MouseEvent.BUTTON2)
+                    handleMouseMiddlePressed(x, y);
+            }
+            @Override
+            public void mouseMoved(MouseEvent e) {
+                handleMouseMoved(canvasPosX+e.getX()/canvasZoom, 
+                        canvasPosY+e.getY()/canvasZoom);
+            }
+            @Override
+            public void mouseReleased(MouseEvent e) {
+                handleMouseReleased(canvasPosX+e.getX()/canvasZoom, 
+                        canvasPosY+e.getY()/canvasZoom);
+            }
+            @Override
+            public void mouseWheelMoved(MouseWheelEvent e) {
+                double x = canvasPosX+e.getX()/canvasZoom;
+                double y = canvasPosY+e.getY()/canvasZoom;
+                handleMouseWheelMoved(x, y, e.getWheelRotation());
+            }
+        });
+        addMouseMotionListener(new MouseMotionAdapter() {
+            @Override
+            public void mouseDragged(MouseEvent e) {
+                handleMouseMoved(canvasPosX+e.getX()/canvasZoom, 
+                        canvasPosY+e.getY()/canvasZoom);
+            }            
+        });
+        addMouseWheelListener(new MouseWheelListener() {            
+            @Override
+            public void mouseWheelMoved(MouseWheelEvent e) {
+                double x = canvasPosX+e.getX()/canvasZoom;
+                double y = canvasPosY+e.getY()/canvasZoom;
+                handleMouseWheelMoved(x, y, e.getWheelRotation());
+            }
+        });
+        addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
+            @Override
+            public void keyPressed(KeyEvent e) {
+                DebuggerCanvas.this.keyPressed(e);
+            }
+            @Override
+            public void keyReleased(KeyEvent e) {
+                DebuggerCanvas.this.keyReleased(e);
+            }
+        }));
+        DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
+            @Override
+            public void drop(DropTargetDropEvent dtde) {
+                try {
+                    Transferable transferable = dtde.getTransferable();
+                    
+                    if( transferable.isDataFlavorSupported( 
+                            LocalObjectTransferable.FLAVOR ) ) {
+                        dtde.acceptDrop( DnDConstants.ACTION_MOVE );
+                        
+                        transferable.getTransferData(LocalObjectTransferable.FLAVOR );
+                        Object obj = LocalObjectTransfer.getTransfer().getObject();
+                        double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
+                        double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
+                        handleDrop(x, y, obj);
+                        
+                        dtde.getDropTargetContext().dropComplete( true );
+                    }
+                    else {
+                        dtde.rejectDrop();
+                    }
+                } catch( IOException exception ) {
+                    exception.printStackTrace();
+                    dtde.rejectDrop();
+                } catch( UnsupportedFlavorException ufException ) {
+                    ufException.printStackTrace();
+                    dtde.rejectDrop();
+                }
+            }     
+        });
+    }    
+    
+    public void keyPressed(KeyEvent e) {
+        switch(e.getKeyCode()) {
+        case KeyEvent.VK_1:
+            zoomToFit();
+            break;
+        case KeyEvent.VK_L:
+            layoutGraph();
+            break;
+        case KeyEvent.VK_CONTROL:
+            if(!extensionMode) {
+                initializeExtension();
+                extensionMode = true;
+                repaint();
+            }
+            break;
+        case KeyEvent.VK_C:
+            findPreviousChangeset();
+            break;
+        case KeyEvent.VK_DELETE:
+            if (!extensionMode && dragging != null) {
+                nodes.remove(dragging);
+                scheduleUpdate();
+                repaint();
+            }
+            break;
+        }
+    }
+    
+    public void keyReleased(KeyEvent e) {
+        if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
+            extensionMode = false;
+            scheduleUpdate();
+            repaint();
+        }
+    }
+
+    private static Resource extractResource(Object obj) {
+        System.out.println("- " + obj.getClass().getName());
+        if(obj instanceof WorkbenchSelectionElement) {
+            Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
+            if(resource != null)
+                return resource;
+        }
+        if(obj instanceof IAdaptable) {
+            Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
+            if(resource != null)
+                return resource;
+        }
+        return null;
+    }
+    
+    private void handleDrop(double x, double y, Object obj) {
+        //System.out.println(obj.getClass().getName());
+        if(obj instanceof IStructuredSelection) {
+            for(Object element : ((IStructuredSelection)obj).toArray()) {
+                Resource resource = extractResource(element);
+                if(resource != null && !nodeMap.containsKey(resource)) {
+                    addResource(x, y, resource);                    
+                    repaint();
+                }
+            }
+        }
+    }       
+    
+    private Node addResource(double x, double y, Resource resource) {
+        Node a = new Node(new NodeData(resource));
+        a.setPos(x, y);
+        scheduleUpdate();
+        nodes.add(a);
+        return a;
+    }
+
+    private void scheduleUpdate() {
+        Simantics.getSession().asyncRequest(new ReadRequest() {            
+            @Override
+            public void run(ReadGraph graph) throws DatabaseException {                
+                updateNodes(graph);
+                updateEdges(graph);
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        repaint();
+                    }
+                    
+                });
+            }            
+        });        
+    }
+    
+    public void layoutGraph() {
+        ArrayList<Edge> allEdges = new ArrayList<Edge>(
+                edges.size() + addedEdges.size() + removedEdges.size()
+                );
+        allEdges.addAll(edges);
+        allEdges.addAll(addedEdges);
+        allEdges.addAll(removedEdges);
+        LayoutGraph.layout(
+                nodes.toArray(new Node[nodes.size()]), 
+                allEdges.toArray(new Edge[edges.size()]));
+        repaint();                
+    }
+    
+    private void updateNodes(ReadGraph graph) throws DatabaseException {
+        for(Node node : nodes) {
+            node.getData().updateData(graph, labelingPreferences);
+            node.setContent(new LabelContent(node.getData().getLabels()));
+        }
+        nodeMap.clear();
+        for(Node node : nodes) {
+            NodeData data = node.getData();
+            nodeMap.put(data.getResource(), node);
+        }
+    }
+    
+    private void updateEdges(ReadGraph graph) throws DatabaseException {
+        ArrayList<Edge> edges = new ArrayList<Edge>();
+        for(Node node : nodes) {
+            NodeData data = node.getData();
+            Resource subject = data.getResource();
+            ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
+            for(Statement stat : data.getStatements()) {
+                Resource object = stat.getObject();
+                Node node2 = nodeMap.get(object);
+                if(node2 != null) {
+                    if(object.getResourceId() > subject.getResourceId() ||
+                            graph.getPossibleInverse(stat.getPredicate()) == null) {
+                        edges.add(createEdge(graph, stat, node, node2));
+                    }
+                }
+                else
+                    filteredStatements.add(stat);
+            }
+            data.setStatements(filteredStatements);
+        }
+        this.edges = edges;
+        this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
+        this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
+    }
+
+    private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
+        ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
+        for (Edge e : edges) {
+            if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
+                continue;
+            result.add(e);
+        }
+        return result;
+    }
+
+    private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
+        Resource predicate = stat.getPredicate();
+        Resource inverse = graph.getPossibleInverse(predicate);        
+        if(inverse != null) {
+            Layer0 L0 = Layer0.getInstance(graph);
+            if(graph.hasStatement(predicate, L0.PartOf, inverse)
+                    || predicate.equals(L0.PartOf)
+                    || predicate.equals(L0.SuperrelationOf)
+                    || predicate.equals(L0.SupertypeOf)) {
+                predicate = inverse;
+                Node temp = n1;
+                n1 = n2;
+                n2 = temp;
+            }
+        }
+        Edge edge = new Edge(n1, n2);
+        edge.setContent(new LabelContent(new String[] {
+                NameUtils.getSafeName(graph, predicate)}));
+        return edge;
+    }
+    
+    Node dragging = null;
+    double dragDX, dragDY;
+    private void handleMouseLeftPressed(double x, double y) {
+        Node node;
+        if(extensionMode) {
+            node = pickExtension(x, y);
+            if(node != null) {
+                nodes.add(node);
+                extensionNodes.remove(node);
+            }
+        }
+        else
+            node = pick(x, y);
+        if(node != null) {
+            dragDX = x - node.getX();
+            dragDY = y - node.getY();
+            dragging = node;
+        }
+    }
+    
+    Point2D panningStartMouse;
+    private void handleMouseMiddlePressed(double x, double y) {
+        panningStartMouse = new Point2D.Double(x, y);
+    }
+    
+    private void handleMouseMoved(double x, double y) {
+        if(dragging != null) {
+            dragging.setPos(x-dragDX, y-dragDY);
+            repaint();
+        }
+        if(panningStartMouse != null) {
+            canvasPosX -= x - panningStartMouse.getX();
+            canvasPosY -= y - panningStartMouse.getY();
+            repaint();
+        }
+    }
+    
+    private void handleMouseWheelMoved(double x, double y, double amount) {
+        double s = Math.exp(-0.2*amount);
+        canvasZoom *= s;
+        canvasPosX = x - (x-canvasPosX)/s;
+        canvasPosY = y - (y-canvasPosY)/s;
+        repaint();
+    }
+
+    private void handleMouseReleased(double x, double y) {
+        dragging = null;
+        panningStartMouse = null;
+    }
+    
+    public void zoomToFit() {
+        if(!nodes.isEmpty()) {
+            double minX = Double.POSITIVE_INFINITY;
+            double minY = Double.POSITIVE_INFINITY;
+            double maxX = Double.NEGATIVE_INFINITY;
+            double maxY = Double.NEGATIVE_INFINITY;
+            System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
+            for(Node node : nodes) {
+                minX = Math.min(minX, node.getMinX());
+                minY = Math.min(minY, node.getMinY());
+                maxX = Math.max(maxX, node.getMaxX());
+                maxY = Math.max(maxY, node.getMaxY());
+            }            
+            canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
+            canvasZoom *= 0.9;
+            canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
+            canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
+            repaint();
+        }
+    }
+
+    THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
+    public void initializeExtension() {
+        extensionNodes.clear();
+        extensionEdges.clear();
+        try {
+            Simantics.getSession().syncRequest(new ReadRequest() {
+                @Override
+                public void run(ReadGraph graph) throws DatabaseException {
+                    THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
+                    THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
+                    for(Node node : nodes) {
+                        for(Statement stat : node.getData().getStatements()) {
+                            Resource object = stat.getObject();
+                            Node node2 = extensionNodeMap.get(object);
+                            if(node2 == null) {
+                                node2 = oldExtensionNodeMap.get(object);
+                                if(node2 == null) {
+                                    node2 = new Node(new NodeData(object));
+                                    double angle = random.nextDouble() * Math.PI * 2.0;
+                                    double dx = Math.cos(angle);
+                                    double dy = Math.sin(angle);
+                                    double len = 150.0;
+                                    node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
+                                }
+                                node2.getData().updateData(graph, labelingPreferences);                                
+                                node2.setContent(new LabelContent(node2.getData().getLabels()));
+                                extensionNodeMap.put(object, node2);
+                                extensionNodes.add(node2);
+                            }
+                            extensionEdges.add(createEdge(graph, stat, node, node2));
+                        }
+                    }
+                    DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
+                    layoutExtension();
+                }      
+            });
+        } catch (DatabaseException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void layoutExtension() {        
+        TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
+        for(int i=0;i<extensionNodes.size();++i)
+            extensionNodeIds.put(extensionNodes.get(i), i);
+        
+        double[][] neighbors = new double[extensionNodes.size()][];
+        {
+            TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];        
+            for(int i=0;i<neighborLists.length;++i)
+                neighborLists[i] = new TDoubleArrayList();
+            for(Edge edge : extensionEdges) {
+                int id;
+                Node node;
+                if(extensionNodeIds.containsKey(edge.getA())) {
+                    id = extensionNodeIds.get(edge.getA());
+                    node = edge.getB();
+                }
+                else {
+                    id = extensionNodeIds.get(edge.getB());
+                    node = edge.getA();
+                }
+                TDoubleArrayList list = neighborLists[id];
+                list.add(node.getX());
+                list.add(node.getY());
+            }            
+            for(int i=0;i<neighborLists.length;++i) {
+                neighbors[i] = neighborLists[i].toArray();
+            }
+        }
+        
+        double[] fixedRepulsiveX = new double[nodes.size()];
+        double[] fixedRepulsiveY = new double[nodes.size()];
+        for(int i=0;i<nodes.size();++i) {
+            Node node = nodes.get(i);
+            fixedRepulsiveX[i] = node.getX();
+            fixedRepulsiveY[i] = node.getY();
+        }
+        ExtensionLayoutAlgorithm algo = 
+                new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
+        double[] posX = algo.getPosX();
+        double[] posY = algo.getPosY();
+        for(int i=0;i<extensionNodes.size();++i) {
+            posX[i] = extensionNodes.get(i).getX();
+            posY[i] = extensionNodes.get(i).getY();
+        }        
+        algo.optimize();
+        for(int i=0;i<extensionNodes.size();++i) {
+            extensionNodes.get(i).setPos(posX[i], posY[i]);
+        }        
+    }      
+    
+    private Node getNode(Resource resource) {
+        Node node = nodeMap.get(resource);
+        if(node == null) {
+            node = addResource(random.nextDouble()*200.0-100.0, 
+                    random.nextDouble()*200.0-100.0, 
+                    resource);
+            nodeMap.put(resource, node);
+        }
+        return node;
+    }
+    
+    public void findPreviousChangeset() {
+        try {
+            Session session = Simantics.getSession();
+            final ManagementSupport ms = session.getService(ManagementSupport.class);
+            final long lastId = ms.getHeadRevisionId();
+            final long firstId = getOpId(ms, lastId);
+            //System.out.println(firstId + "-" + lastId);
+            addedEdges.clear();
+            removedEdges.clear();            
+            session.asyncRequest(new ReadRequest() {
+                @Override
+                public void run(ReadGraph graph) throws DatabaseException {
+                    final Collection<ChangeSet> css = 
+                            ms.fetchChangeSets(graph, firstId, lastId);
+                    Layer0 L0 = Layer0.getInstance(graph);
+                    for(ChangeSet cs : css) {
+                        for(ChangeSet.StatementChange stat : cs.changedStatements()) {
+                            Resource predicate = stat.getPredicate();
+                            if(predicate.equals(L0.InstanceOf) ||
+                                    predicate.equals(L0.HasName) ||
+                                    predicate.equals(L0.NameOf))
+                                continue;
+                            Edge edge = createEdge(graph, stat, 
+                                    getNode(stat.getSubject()),
+                                    getNode(stat.getObject()));
+                            if(stat.isClaim())
+                                addedEdges.add(edge);
+                            else
+                                removedEdges.add(edge);
+                        }
+                    }    
+                    scheduleUpdate();
+                }                
+            });                  
+        } catch(DatabaseException e) {
+            e.printStackTrace();
+        }
+    } 
+    
+    private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
+        Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
+        ChangeSetIdentifier curId = ids.iterator().next();
+        byte[] opIdData = curId.getMetadata().get("opid");
+        System.out.println(new String(opIdData));
+        long opId = Long.parseLong(new String(opIdData));
+        if(opId == 0)
+            opId = revisionId;
+        return opId;
+    }
+
+}