1

I am dynamically removing edges from a JUNG graph from a thread, but this results in NullPointerExceptions.

The stack trace I am seeing:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer.paintEdge(BasicEdgeRenderer.java:51)
    at edu.uci.ics.jung.visualization.renderers.BasicRenderer.renderEdge(BasicRenderer.java:78)
    at edu.uci.ics.jung.visualization.renderers.BasicRenderer.render(BasicRenderer.java:38)
    at edu.uci.ics.jung.visualization.BasicVisualizationServer.renderGraph(BasicVisualizationServer.java:346)
    at edu.uci.ics.jung.visualization.BasicVisualizationServer.paintComponent(BasicVisualizationServer.java:301)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1074)
    at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618)
    at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556)
    at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323)
    at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203)
    at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:865)
    at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848)
    at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823)
    at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772)
    at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Here is a code snippet that reproduces the errors:

import javax.swing.JFrame;

import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseGraph;
import edu.uci.ics.jung.graph.util.Graphs;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;

public class JungRepro {

    public static void main(String[] args) throws InterruptedException {

        // Create the graph with vertices and edges
        Graph<Integer, Integer> graph = Graphs.synchronizedGraph(new SparseGraph<Integer, Integer> ());
        final int nbrVertices = 10;
        for (int i = 0; i < nbrVertices; i++) 
            graph.addVertex(i);
        int e = 0;
        for (int i = 0; i < nbrVertices; i++) 
            for (int j = 0; j < i; j++) 
                graph.addEdge(e++, i, j);

        AbstractLayout<Integer, Integer> layout = new ISOMLayout<Integer, Integer> (graph);
        VisualizationViewer<Integer, Integer> vv = new VisualizationViewer<Integer, Integer> (layout);

        JFrame frame = new JFrame ("JungRepro");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new GraphZoomScrollPane (vv));
        frame.pack();
        frame.setVisible(true);

        // Remove edges one by one
        while (e >= 0) {
            graph.removeEdge(e--);
            Thread.sleep(25);
        }
    }

}

Thanks in advance for your help!

[EDIT: partly re-wrote the question to reflect the fact that the errors are not caused by calls to repaint() but by the calls to removeEdge().]

  • Further investigation reveals that this may be a bug in JUNG due to the fact that SparseGraph.removeEdge() is not thread-safe: https://github.com/jrtom/jung/issues/235 – Allamistakeo Sep 06 '19 at 18:06
  • FWIW (responding to the above comment) most standard Java containers are not thread-safe; the JUNG graph classes are not exceptional in this regard. – Joshua O'Madadhain Sep 11 '19 at 00:32
  • I totally understand. I'm just looking for a way to remove edges from a JUNG graph without getting `NullPointerExceptions`. All workarounds are welcome. I could imagine something like pausing the rendering while the edge is being removed (I don't know how to do that). But the most user-friendly thing to do would be for the JUNG code to gracefully catch the exception instead of throwing it. – Allamistakeo Sep 12 '19 at 17:18

3 Answers3

0

You might try calling repaint() from inside a SwingUtilities.invokeLater run method.

0

There are a couple of samples (AddNodeDemo and AnimatedAddNodeDemo) that modify the graph and continue to update the visualization; you might want to look at them for examples.

Updated based on response below

If you iterate through a non-synchronized data structure on one thread, and update it on a different thread, you will have this problem. This doesn't have anything to do with visualization, or JUNG, this is just a fact about concurrent processing in Java.

I'm not an expert on multithreaded programming, but as far as I know, if you want to not have this problem, you have two basic options:

(1) Control the interaction between your threads so that you're not updating and iterating over the graph at the same time.

(2) Use Graphs.synchronized*Graph() to wrap your graph object. Since I haven't seen your code, I'm not 100% convinced that this will work, but that's probably the easiest thing to try. I'm not sure how that will affect your visualization, though.

If you do repost a more general question, supply a minimal code snippet that reproduces your problem.

Joshua O'Madadhain
  • 2,704
  • 1
  • 14
  • 18
  • Thanks for your suggestions. I looked into these two examples, and here are my conclusions. First, AnimatedAddNodeDemo is not a good example to follow, because it starts throwing ConcurrentModificationExceptions when I increase the frequency of node additions. Second, AddNodeDemo doesn't even call repaint(); this gave me the idea to comment out my calls to repaint(), it turns out that I am still getting the same NullPointerExceptions, only less frequently. Further investigation has revealed what I believe may be a bug in JUNG. See separate answer. – Allamistakeo Sep 06 '19 at 17:51
  • See the modified answer above for more on this. – Joshua O'Madadhain Sep 08 '19 at 02:22
  • Thanks for your detailed insights. See my revised question. I only have a single thread; the other thread is the event dispatch thread, in which JUNG tries to iterate through edges while I am removing edges in my own thread. Using a synchronized graph doesn't seem to solve the issue. – Allamistakeo Sep 08 '19 at 06:46
0

Thanks for posting a simple example of what you want to do.

Does the below code achieve the results you want?

public class JungRepro {
    public static void main(String[] args) throws Exception {

        // Create the graph with vertices and edges
        Graph<Integer, Integer> graph = Graphs.synchronizedGraph(new SparseGraph<Integer, Integer>());
        final int nbrVertices = 10;
        for (int i = 0; i < nbrVertices; i++)
            graph.addVertex(i);
        int e = 0;
        for (int i = 0; i < nbrVertices; i++)
            for (int j = 0; j < i; j++)
                graph.addEdge(e++, i, j);

        AbstractLayout<Integer, Integer> layout = new KKLayout<Integer, Integer>(graph);
        VisualizationViewer<Integer, Integer> vv = new VisualizationViewer<Integer, Integer> (layout);

        JFrame frame = new JFrame ("JungRepro");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new GraphZoomScrollPane(vv));
        frame.pack();
        frame.setVisible(true);
        // Remove edges one by one
        while (graph.getEdgeCount() > 0) {
            SwingUtilities.invokeAndWait(() ->
                graph.removeEdge(graph.getEdgeCount() - 1)
            );
            vv.repaint();
            Thread.sleep(25);
        }
    }
}
  • Thanks a lot! Wrapping `removeEdge()` inside `SwingUtilities.invokeLater()` does resolve the `NullPointerExceptions`, by pushing the edge removal to the same (event dispatch) thread from which the exceptions used to be thrown when iterating through the graph's edges. However, it still doesn't achieve what I want, because `repaint()` and `sleep()` end up being called asynchronously from `removeEdge()`, and therefore the frame does not show one-by-one edge removal. To provide a complete fix, you need `SwingUtilities.invokeAndWait()`. Can you edit your answer accordingly? – Allamistakeo Sep 23 '19 at 21:12