2

As a follow-up to this question that I posted earlier, I am wondering about the cause of the issue I had.

The problem was that I was getting this error when updating a JLabel with a lot of HTML text.

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at javax.swing.text.html.StyleSheet$ListPainter.paint(Unknown Source)
    at javax.swing.text.html.ListView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.text.html.ListView.paint(Unknown Source)
    at javax.swing.text.BoxView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.text.BoxView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.text.BoxView.paintChild(Unknown Source)
    at javax.swing.text.BoxView.paint(Unknown Source)
    at javax.swing.text.html.BlockView.paint(Unknown Source)
    at javax.swing.plaf.basic.BasicHTML$Renderer.paint(Unknown Source)
    at javax.swing.plaf.basic.BasicLabelUI.paint(Unknown Source)
    at javax.swing.plaf.ComponentUI.update(Unknown Source)
    at javax.swing.JComponent.paintComponent(Unknown Source)
    at javax.swing.JComponent.paint(Unknown Source)
    at javax.swing.JComponent.paintToOffscreen(Unknown Source)
    at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source)
    at javax.swing.RepaintManager$PaintManager.paint(Unknown Source)
    at javax.swing.RepaintManager.paint(Unknown Source)
    at javax.swing.JComponent._paintImmediately(Unknown Source)
    at javax.swing.JComponent.paintImmediately(Unknown Source)
    at javax.swing.RepaintManager$4.run(Unknown Source)
    at javax.swing.RepaintManager$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.prePaintDirtyRegions(Unknown Source)
    at javax.swing.RepaintManager.access$1200(Unknown Source)
    at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

The HTML that's being set is generated by a StringBuilder and the converted .toString. Something like this, but more extensive:

public static String entityOverviewHTML() {
    StringBuilder sb = new StringBuilder();
    List<Entity> entities = Entity.getEntities();

    sb.append("<html><div style='padding: 12px;'>");

    sb.append("<h1>People: alive</h1>");

    // A lot of appending: also loop through getEntities (dynamic, can change)
    // and get contents from each entity in #getEntityInfo below
    if (entities.size() > 0) {
        sb.append("<ul>");
        for (Entity e: entities) {
            getEntityInfo(e);
        }
        sb.append("</ul>");
    }

    sb.append("</div></html>");
    return sb.toString();
}

private static StringBuilder getEntityInfo(Entity e) {
    StringBuilder sbInfo = new StringBuilder();
    // A lot of appending: append info of Entity e such as e.getFirstName()
    sbInfo.append("<li>");
    sbInfo.append(e.getFirstName())
    sbInfo.append("</li>");

    return sbInfo;
}

After some events, the HTML will change after which I call a custom refresh method:

public static void bringToFront() {
    getInstance().setVisible(true);
    getInstance().setExtendedState(JFrame.ICONIFIED);
    getInstance().setExtendedState(JFrame.NORMAL);
}

public static void refresh() {
    // text is a JLabel
    text.setText(entityOverviewHTML());
    bringToFront();
}

And it's then that the errors at the top of this post happen, however not always! I haven't figured out why this happens, but I did find that when resetting the text to an empty string and then calling entityOverviewHTML solves the issue.

public static void refresh() {
    text.setText(""); // Here we go
    text.setText(entityOverviewHTML());
    bringToFront();
}

text is defined as a Class variable:

private static JLabel text = new JLabel();

What I like to know is: why? How can this single line of seemingly obsolete code solve the problem? Exactly what is the problem?

Community
  • 1
  • 1
Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239
  • do you do `text = new JLabel()` anywhere in your code? – SomeJavaGuy Jan 04 '16 at 14:36
  • @KevinEsche See my edit near the bottom. Yes, it is a class variable. – Bram Vanroy Jan 04 '16 at 14:41
  • Might [this](http://www.experts-exchange.com/questions/26654280/JLabel-fails-with-NullPointerException.html) be a little bit of help? There is no real solution in there, but it seems that corrupt html can cause the poor html parser in java to create a complete mess which in the end causes a `NPE`. – SomeJavaGuy Jan 04 '16 at 14:49
  • @KevinEsche The thing is that I don't see how the HTML could be malformed, and if it were, why it still renders correctly when first resetting the contents to an empty string. – Bram Vanroy Jan 04 '16 at 14:52
  • 1
    i can´t tell you exactly why, but i found another link that grants two possible sultions for your exact problem. [here](http://objectmix.com/java/23930-manipulation-jlabel-causes-nullpointer-exception.html). In addtion it seems that they didn´t find it either but did only reinstall theyr jre – SomeJavaGuy Jan 04 '16 at 14:59
  • For random problems, make sure the code is executed on the EDT. See [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html) for more information. – camickr Jan 04 '16 at 16:04
  • question isn't clear, and for why reason(s) is there toggling with JFrame.ICONIFIED / NORMAL – mKorbel Jan 04 '16 at 17:57
  • @mKorbel the toggle is to put focus on the window. I got it from an answer here on SO. I think the question is clear: why don't I get an error message when I reset the JLabel, in other words: why do I need to reset a JLabel before changing its contents? – Bram Vanroy Jan 04 '16 at 18:14
  • @Bram Vanroy exception talking about ... there is something wrong with building an Html syntax at runtime, then ..., hmmm did you tried to put formatted Html (version =< 3.2) to JEditorPane, here is a few questons about HtmlEditorKit – mKorbel Jan 04 '16 at 18:32

2 Answers2

2

The problem is that your refresh() method is not called on the Swing EDT.

Swing is not threadsafe (https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) which means that the call text.setText(entityOverviewHTML()); corrupts the internal data structures that your JLabel instance uses to display the text.

Your refresh-method needs to be rewritten like this:

public static void refresh() {
    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                text.setText(entityOverviewHTML());
                bringToFront();
            }
        });
    } catch (InvocationTargetException | InterruptedException e) {
        e.printStackTrace();
    }
}
Thomas Kläger
  • 17,754
  • 3
  • 23
  • 34
  • I guess I'd also need this in the constructor of my JFrame. How do I decide which lies of code go in the run method, and which don't? Do all the component initialisations and methods go into that method? And what about event listeners. – Bram Vanroy Jan 06 '16 at 10:07
  • Yes, basically all code that creates or updates swing components should run on the EDT and be invoked through `SwingUtilities.invokeLater()` / `SwingUtilities.invokeAndWait()`. Event listeners are no problem though, since these are called from swing and therefore always run on the EDT. – Thomas Kläger Jan 06 '16 at 11:13
  • Thank you Thomas. I tried it out, and thought it'd be best to move the try-catch to the constructor, but this causes me an infinite loop. If you have the time, you can take a look [here](http://stackoverflow.com/questions/34632187/infinite-disappear-reappear-loop-in-jframe-java). – Bram Vanroy Jan 06 '16 at 11:37
0

Usually when JLabel is instantiated, it has no width , if it is instantiated with empty text width is zero. When JLabel text is updated it won't show up as its size is not set properly.

Use preferred size: text.setPreferredSize(new Dimension(x, y)); then text.setText(html)

pqnet
  • 6,070
  • 1
  • 30
  • 51
SomeDude
  • 13,876
  • 5
  • 21
  • 44
  • Care to elaborate on that? – Bram Vanroy Jan 04 '16 at 14:46
  • Usually when JLabel is instantiated, it has no width , if it is instantiated with empty text width is zero. When JLabel text is updated it won't show up as its size is not set properly. – SomeDude Jan 04 '16 at 14:54
  • @svasa By calling `setText` you're causing the label to recalculate it's `preferredSize` implicitly, it will also cause it's parent container to be revalidated and repainted – MadProgrammer Jan 04 '16 at 20:48