Java Swing gives occasional NullPointerExceptions with no indication of where in my code it occurs
Asked Answered
L

0

1

I'm making a chat client for a assignment and I have managed to get everything working pretty well. However, I have one error and cannot find the cause for it at all. When printing a message to one of my chat windows I get a NullPointerException. The thing is, the code still does exactly what it is supposed to and its very inconsistent. I could print 5 messages and get it 5 times, or print 20 and get none.

So far I have been able to narrow down the error to one function, but have not being able to narrow it down to a single line within the function. The error message I get isn't much help either. I've tried commenting out each line of code and still can't find out where it is going wrong. I've even tried to catch the error using a try and catch and it didn't work.

Below is is the minimum code required to cause the error.

import java.awt.*;
import java.util.*;

// Import window library and listeners
import javax.swing.*;
import java.awt.event.*;

// Import time libraries
import java.util.Date;
import java.text.SimpleDateFormat;

public class ChatClientTest {
    // Initialize user information
    private String nickname = "testNickname";

    // Track open windows
    private ArrayList<JFrame> windows = new ArrayList<>();

    public ChatClientTest() {
        openWindow("@ChatBot");
        openWindow("@someUser");

        // Create thread to listen to server
        Thread server = new Thread(new Runnable() {
            @Override
            public void run() {
                // Run listenToServer in separate thread
                listenToServer();
            }
        });
        // Start server listening thread
        server.start();
    }

    private void listenToServer() {
        int i = 0;
        while (true) {
            for (JFrame window : windows) {
                try {
                    Thread.sleep(2000);
                    printToWindow(window.getTitle(), nickname, "Test message" + i);
                } catch (InterruptedException e) {
                    System.out.println("Interrupted");
                }
            }
            i++;
        }
    }

    // Opens a new chat window for a given channel or user
    private void openWindow(String name) {

        // Create new JFrame with a text area and scroll bar
        JFrame frame = new JFrame(name);
        JTextArea textArea = new JTextArea(20,60);
        JScrollPane scrollBar = new JScrollPane(textArea);
        JTextArea inputArea = new JTextArea("");

        // Set text area properties
        textArea.setMargin(new Insets(5,10,20,10));
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.setEditable(false);

        // Set scroll bar properties
        scrollBar.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        // Set input field properties
        inputArea.setMargin(new Insets(5,10,5,10));
        inputArea.setLineWrap(true);
        inputArea.setWrapStyleWord(true);

        // Add event listener on input area to track enter being pressed
        inputArea.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_ENTER){
                    // Stop new line
                    e.consume();

                    // Check message isn't empty
                    if (!inputArea.getText().equals("")) {
                        // Print to the window and clear the text area
                        printToWindow(nickname, name, inputArea.getText());
                        inputArea.setText("");
                    }
                }
            }
            // These are required but don't do anything
            @Override public void keyReleased(KeyEvent e) {}
            @Override public void keyTyped(KeyEvent e) {}
        });

        // put things into the frame
        frame.add(scrollBar, BorderLayout.CENTER);
        frame.add(inputArea, BorderLayout.SOUTH);
        frame.pack();

        // Set properties of the frame
        frame.setSize(400,500);
        frame.setVisible(true);

        // Print a different starting message depending on the recipient
        if (name.startsWith("#")) { // Joining a channel
            textArea.append("Welcome, " + nickname + ", to the " + name + " channel! Be nice, start chatting and get to know some people. Leave the channel to close this window.");
        } else { // Messaging a user
            textArea.append("This is the start of your messages with " + name + ". Be nice and have fun chatting.");
        }
        windows.add(frame);
    }

    // Prints messages to/from a given channel in the appropriate chat window
    private void printToWindow(String sender, String recipient, String message) {

        // Iterate through windows
        for (JFrame window : windows) {

            // Get current timestamp
            String timestamp = new SimpleDateFormat("h:mm a").format(new Date()).toLowerCase();

            // Check who the recipient/sender is so the appropriate window is chosen
            if (recipient.equals(nickname) && window.getTitle().equalsIgnoreCase(sender)) { // User is the recipient

                // Get the JFrames textArea and scrollPane
                JScrollPane scrollPane = (JScrollPane) window.getContentPane().getComponent(0);
                JTextArea textArea = (JTextArea) scrollPane.getViewport().getView();

                // Make the textArea editable
                textArea.setEditable(true);

                // Append the message to the window and scroll window down
                textArea.append("\n\n" + sender.replace("@", "") + " | " + timestamp + "\n" + message);
                textArea.validate();
                scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
                scrollPane.validate();

                // Make it non-editable again
                textArea.setEditable(false);

            } else if (window.getTitle().equalsIgnoreCase(recipient)) { // Any other recipient

                // Get the JFrames textArea and scrollPane
                JScrollPane scrollPane = (JScrollPane) window.getContentPane().getComponent(0);
                JTextArea textArea = (JTextArea) scrollPane.getViewport().getView();

                // Make the textArea editable
                textArea.setEditable(true);

                // Append the message to the window
                textArea.append("\n\n" + sender.replace("@", "") + " | " + timestamp + "\n" + message);
                textArea.validate();
                scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
                scrollPane.validate();

                // Make it non-editable again
                textArea.setEditable(false);
            }
        }
    }

    public static void main(String[] args) {
        new ChatClientTest();
    }
}

This is the error intellij is giving me:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.text.WrappedPlainView$WrappedLine.paint(WrappedPlainView.java:584)
at javax.swing.text.BoxView.paintChild(BoxView.java:161)
at javax.swing.text.BoxView.paint(BoxView.java:433)
at javax.swing.text.WrappedPlainView.paint(WrappedPlainView.java:369)
at javax.swing.plaf.basic.BasicTextUI$RootView.paint(BasicTextUI.java:1434)
at javax.swing.plaf.basic.BasicTextUI.paintSafely(BasicTextUI.java:737)
at javax.swing.plaf.basic.BasicTextUI.paint(BasicTextUI.java:881)
at javax.swing.plaf.basic.BasicTextUI.update(BasicTextUI.java:860)
at javax.swing.JComponent.paintComponent(JComponent.java:780)
at javax.swing.JComponent.paint(JComponent.java:1056)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JViewport.paint(JViewport.java:728)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5210)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
at javax.swing.JComponent._paintImmediately(JComponent.java:5158)
at javax.swing.JComponent.paintImmediately(JComponent.java:4969)
at javax.swing.RepaintManager$4.run(RepaintManager.java:831)
at javax.swing.RepaintManager$4.run(RepaintManager.java:814)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738)
at javax.swing.RepaintManager.access$1200(RepaintManager.java:64)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

The program is still functions exactly as expected, it just randomly gives this error.

Can someone help me find the cause of the NullPointerException? Let me know if there is anything unclear about how the program works and thank you for your help. This is my first post here so I hope I made everything clear.

Louralourdes answered 9/5, 2019 at 2:3 Comment(13)
A guess -- you've got code that may not comply with Swing threading rules. Without a valid minimal reproducible example though about all I'm able to do is guess. perhaps someone can do better.Dowdy
All Swing components should be updated on the Event Dispatch Thread (EDT) or you can have random results. I'm making a chat client - so I'm guessing the chat client runs in a separate Thread, which could cause the problem. You need to use SwingUtilities.invokeLater(...) when you want to update the GUI. Read the section from the Swing tutorial on Concurrency for more information.Markman
@Markman I put the code that edits the GUI into an invokeLater() but I still received the same error. Even when the error occurs, the GUI updates normally and everything else works as expected. I just get occasional NullPointerExceptions.Louralourdes
Just shoving code into .invokeLater() may not be enough. Again, do consider posting a minimal reproducible example with your question.Dowdy
@Hovercraft Full Of Eels the program has to connect to my universities network so I doubt I can provide working code.Louralourdes
@Louralourdes So? Make a dummy which does the "network" stuff and produces some kind of result similar to what you're expecting - this commonly known as stubbingShrapnel
@HovercraftFullOfEels As you suggested I have made a minimal version of the code that causes the error. I really appreciate any help you can give.Louralourdes
@Louralourdes you're starting a thread which calls printToWindow() which manipulates swing components all over the place. That's forbidden by the swing threading rules as already explained. All the accesses to swing components must be done in the event dispatch thread.Allative
In fact, implementing the function as private void printToWindow(....) { SwingUtilites.invokeLater(() -> printToWindowOnEdt(...)); } (with printToWindowOnEdt containing all the code that is currently in the printToWindow method) should solve the issue. Not very clean and elegant, but at least it should behave deterministically after that...Tarnish
@Tarnish Indeed, it seems to do the trick: I could get the exception by simply switching the windows a few times, but then, it doesn't occur anymore.Arequipa
@JBNixet Thanks for your explaination. Along with HovercraftFullOfEels and camickers, I think I understand the problem much better now.Louralourdes
@Tarnish I don't really understand why this works, but it does and I am incredibly grateful. I can hand in a bug free assignment now! If you wouldn't mind writing this answer out I will mark the question as solved.Louralourdes
@Louralourdes see Lesson: Concurrency in SwingQuilting

© 2022 - 2024 — McMap. All rights reserved.