Abstract: When an exception occurs in Swing code, it is typically printed to the console, assuming we have one. On some systems the Swing thread is then stopped. In this newsletter we present a way that we can intercept the exception and display a warning dialog.
Welcome to the 81st edition of The Java(tm) Specialists' Newsletter. I can hardly believe that it is now already over six weeks since I wrote the last newsletter. Since then, the Grupo de Usu¨¢rios Java - GUJ, from Brazil has started translating some of our newsletters into Portuguese. You can see the result on our archive webpage. Please send us an email if you would like to translate the newsletter into your language.
Design Patterns are the key to really understanding object orientation. What is the difference between the Gang-of-Four Design Patterns and J2EE Patterns? The Gang-of-Four patterns represent the basic building blocks of object orientation. Most of the patterns rely on polymorphism. The J2EE patterns, on the other hand, are design solutions for the J2EE architecture. They are at a far higher level than the Gang-of-Four patterns. Knowing the Gang-of-Four patterns will help you understand how the J2EE patterns work.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
One of the reasons that I continuously promote Design Patterns is that they really work. A pattern that I enjoy teaching is the Command pattern, because the exercise with this pattern is particularly hard unless your mind has already been altered. During our discussion time around that pattern, I usually pose this questions: "What happens when our command causes an exception?"
The answer is not that obvious. The invoker usually does not know what to do with an uncaught exception. Unless you have an alternative exception listening mechanism in place, the exception will simply be lost.
Let us take the following GUI as an example:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; public class Gui extends JFrame { private static final int[] DAYS_PER_MONTH = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; public Gui() { super("GUI Example"); final JTextArea text = new JTextArea(14, 30); getContentPane().add(new JScrollPane(text)); getContentPane().add(new JButton(new AbstractAction("Calculate") { public void actionPerformed(ActionEvent e) { text.setText(""); for (int i=0; i<=DAYS_PER_MONTH.length; i++) { text.append("Month " + (i+1) + ": " + DAYS_PER_MONTH[i] + "\n"); } } }), BorderLayout.NORTH); } public static void main(String[] args) { Gui gui = new Gui(); gui.pack(); gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); gui.show(); } }
Please compile and run this program using javaw.exe instead of java.exe. i.e. type the following:
javaw Gui
This will start the Gui frame and wait for you to click the button. Now it gets really interesting! When you press the button, you see the months and days per month appearing on the textpane. It all seems good, except that the button looks like it stays depressed.
Please run the program again using java.exe, and when you press the button you will see the following exception on the console:
java.lang.ArrayIndexOutOfBoundsException: 12 at Gui$1.actionPerformed(Gui.java:16) at javax.swing.AbstractButton.fireActionPerformed at javax.swing.AbstractButton$ForwardActionEvents.actionPerformed at javax.swing.DefaultButtonModel.fireActionPerformed at javax.swing.DefaultButtonModel.setPressed at javax.swing.plaf.basic.BasicButtonListener$ReleasedAction.actionPerformed at javax.swing.SwingUtilities.notifyAction at javax.swing.JComponent.processKeyBinding at javax.swing.JComponent.processKeyBindings at javax.swing.JComponent.processKeyEvent at java.awt.Component.processEvent at java.awt.Container.processEvent at java.awt.Component.dispatchEventImpl at java.awt.Container.dispatchEventImpl at java.awt.Component.dispatchEvent at java.awt.KeyboardFocusManager.redispatchEvent at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions at java.awt.DefaultKeyboardFocusManager.dispatchEvent at java.awt.Component.dispatchEventImpl at java.awt.Container.dispatchEventImpl at java.awt.Window.dispatchEventImpl at java.awt.Component.dispatchEvent at java.awt.EventQueue.dispatchEvent at java.awt.EventDispatchThread.pumpOneEventForHierarchy at java.awt.EventDispatchThread.pumpEventsForHierarchy at java.awt.EventDispatchThread.pumpEvents at java.awt.EventDispatchThread.pumpEvents at java.awt.EventDispatchThread.run
Besides printing the thread stack trace to the console, the Java event dispatch thread also does another thing: it dies! I only discovered that recently. The dispatch thread dying is probably the reason that the button stays depressed. If you look in the EventDispatchThread code, you will see that a new thread is only created once more Gui events occur. Unless you move the mouse, or press a key, no events will happen and so the button stays depressed.
We can verify that the event dispatch thread dies by printing out the System.identityHashCode() of the thread that executes the actionPerformed method (this should be the EventDispatchThread). In this example, the exception occurs after the button is pressed for the fourth time.
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; public class ThreadsDie extends JFrame { public ThreadsDie() { super("GUI Example"); final JTextArea text = new JTextArea(14, 30); getContentPane().add(new JScrollPane(text)); getContentPane().add(new JButton(new AbstractAction("Calculate") { private int countdown = 3; public void actionPerformed(ActionEvent e) { text.append("Event Queue Thread: " + System.identityHashCode(Thread.currentThread())); text.append("\n"); if (--countdown <= 0) { throw new IllegalArgumentException(); } } }), BorderLayout.NORTH); } public static void main(String[] args) { ThreadsDie gui = new ThreadsDie(); gui.pack(); gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); gui.show(); } }
The output on the textpane is something like:
Event Queue Thread: 29959477 Event Queue Thread: 29959477 Event Queue Thread: 29959477 Event Queue Thread: 23994289 Event Queue Thread: 19764978 Event Queue Thread: 1114115 Event Queue Thread: 1565898 Event Queue Thread: 11383252 Event Queue Thread: 23110255 Event Queue Thread: 21514757
Creating a new thread each time you get an exception in your Gui is not that good, since it is fairly expensive to create threads. We would like to get to the position where no exceptions are generated by our Gui at all.
Last Friday I went to a customer who had an old version of the software I am working on at the moment. When it started up, I saw several exceptions appear on the console. On questioning him about that he said: "Oh those, they always appear. The program still works though."
This makes me scared. I would rather get 259 emails in my inbox telling me that something is seriously amiss, than have my customers think that seeing exceptions on the console is acceptable.
However, how can we catch all uncaught exceptions that are generated in the Gui?
The trick lies in a class called the ThreadGroup and in the way
that JDK 1.4 now constructs the Gui threads. A Thread may belong
to a ThreadGroup. ThreadGroup has a method called
uncaughtException(Thread t, Throwable e)
that we can
override and which is called whenever an uncaught exception occurs.
The event dispatch thread still dies, but at least we know about it.
import javax.swing.*; import java.awt.*; public class ExceptionGroup extends ThreadGroup { public ExceptionGroup() { super("ExceptionGroup"); } public void uncaughtException(Thread t, Throwable e) { JOptionPane.showMessageDialog(findActiveFrame(), e.toString(), "Exception Occurred", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); } /** * I hate ownerless dialogs. With this method, we can find the * currently visible frame and attach the dialog to that, instead * of always attaching it to null. */ private Frame findActiveFrame() { Frame[] frames = JFrame.getFrames(); for (int i = 0; i < frames.length; i++) { Frame frame = frames[i]; if (frame.isVisible()) { return frame; } } return null; } }
All we now need to do is start the Gui from within a Thread that belongs to this ExceptionGroup, and we will catch all uncaught exceptions that are caused by the event dispatch thread:
import javax.swing.*; public class BetterGui { public static void main(String[] args) { ThreadGroup exceptionThreadGroup = new ExceptionGroup(); new Thread(exceptionThreadGroup, "Init thread") { public void run() { Gui gui = new Gui(); gui.pack(); gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); gui.show(); } }.start(); } }
This solution is far from perfect. In my industrial-strength exception framework (not available for general use, don't ask), the uncaught exceptions go to a central place from where they are dispatched to the correct listeners. Those might be more fancy dialogs, or emails sent to the support team, or a stacktrace in a log file.
That is it for today. I need some beauty sleep so that tomorrow I can tackle all the exceptions that we found with this approach in some existing code.
Kind regards
Heinz
P.S. Please don't forget to contact me if you would like to translate The Java(tm) Specialists' Newsletter into your native language
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.