JUNG: Удаление ребер приводит к NullPointerException, выдаваемому из BasicEdgeRenderer.paintEdge().

Я динамически удаляю ребра из графа JUNG из потока, но это приводит к NullPointerExceptions.

Трассировка стека, которую я вижу:

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)

Вот фрагмент кода, который воспроизводит ошибки:

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);
        }
    }

}

Заранее спасибо за помощь!

[EDIT: частично переписал вопрос, чтобы отразить тот факт, что ошибки вызваны не вызовами repaint(), а вызовами removeEdge().]


person Allamistakeo    schedule 04.08.2019    source источник
comment
Дальнейшее расследование показывает, что это может быть ошибка в JUNG из-за того, что SparseGraph.removeEdge() не является потокобезопасным: github.com/jrtom/jung/issues/235   -  person Allamistakeo    schedule 06.09.2019
comment
FWIW (отвечая на комментарий выше) большинство стандартных контейнеров Java не являются потокобезопасными; классы графов JUNG не являются исключением в этом отношении.   -  person Joshua O'Madadhain    schedule 11.09.2019
comment
Я полностью понимаю. Я просто ищу способ удалить ребра из графа JUNG, не получая NullPointerExceptions. Все обходные пути приветствуются. Я мог бы представить что-то вроде приостановки рендеринга во время удаления края (я не знаю, как это сделать). Но наиболее удобной для пользователя вещью было бы, чтобы код JUNG изящно перехватывал исключение, а не генерировал его.   -  person Allamistakeo    schedule 12.09.2019


Ответы (3)


Спасибо за публикацию простого примера того, что вы хотите сделать.

Приведенный ниже код достигает желаемых результатов?

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);
        }
    }
}
person Tom    schedule 22.09.2019
comment
Большое спасибо! Обертывание removeEdge() внутри SwingUtilities.invokeLater() действительно разрешает NullPointerExceptions, отправляя удаление края в тот же поток (отправление событий), из которого раньше выбрасывались исключения при итерации по краям графа. Тем не менее, это все еще не достигает того, что я хочу, потому что repaint() и sleep() в конечном итоге вызываются асинхронно из removeEdge(), и поэтому кадр не показывает удаление ребер один за другим. Чтобы предоставить полное исправление, вам нужно SwingUtilities.invokeAndWait(). Можете ли вы отредактировать свой ответ соответствующим образом? - person Allamistakeo; 24.09.2019

Вы можете попробовать вызвать repaint() из метода запуска SwingUtilities.invokeLater.

person Tom    schedule 04.08.2019
comment
Спасибо за ваше предложение. К сожалению, это не решает проблему. - person Allamistakeo; 05.08.2019

Есть пара образцов (AddNodeDemo и AnimatedAddNodeDemo), которые изменяют график и продолжают обновлять визуализацию; вы могли бы посмотреть на них для примеров.

Обновлено на основе ответа ниже

Если вы повторяете несинхронизированную структуру данных в одном потоке и обновляете ее в другом потоке, у вас возникнет эта проблема. Это не имеет ничего общего с визуализацией или JUNG, это просто факт параллельной обработки в Java.

Я не специалист по многопоточному программированию, но, насколько я знаю, если вы хотите, чтобы у вас не было этой проблемы, у вас есть два основных варианта:

(1) Контролируйте взаимодействие между вашими потоками, чтобы вы не обновляли и не повторяли график одновременно.

(2) Используйте Graphs.synchronized*Graph() для переноса объекта графика. Поскольку я не видел вашего кода, я не уверен на 100%, что это сработает, но это, вероятно, самый простой способ попробовать. Однако я не уверен, как это повлияет на вашу визуализацию.

Если вы публикуете более общий вопрос, предоставьте минимальный фрагмент кода, который воспроизводит вашу проблему.

person Joshua O'Madadhain    schedule 24.08.2019
comment
Спасибо за ваши предложения. Я изучил эти два примера, и вот мои выводы. Во-первых, AnimatedAddNodeDemo не является хорошим примером для подражания, потому что он начинает генерировать исключения ConcurrentModificationException, когда я увеличиваю частоту добавления узлов. Во-вторых, AddNodeDemo даже не вызывает repaint(); это натолкнуло меня на мысль закомментировать мои вызовы repaint(), оказывается, я все еще получаю те же исключения NullPointerException, только реже. Дальнейшее расследование выявило то, что, как мне кажется, может быть ошибкой в ​​JUNG. См. отдельный ответ. - person Allamistakeo; 06.09.2019
comment
См. измененный ответ выше для получения дополнительной информации об этом. - person Joshua O'Madadhain; 08.09.2019
comment
Спасибо за ваши подробные идеи. Смотрите мой пересмотренный вопрос. У меня есть только один поток; другой поток — это поток диспетчеризации событий, в котором JUNG пытается перебирать ребра, в то время как я удаляю ребра в своем собственном потоке. Использование синхронизированного графика, похоже, не решает проблему. - person Allamistakeo; 08.09.2019