|
The Java Specialists' Newsletter
Issue 081 2003-11-25
Category:
Exceptions
Java version: Catching Exceptions in GUI Codeby Dr. Heinz M. Kabutz
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.
Thanks for reading this newsletter on our website. We also have a mailing list. That is where the real action takes place (webinars, free reports, etc.). Maybe subscribe today?
Advanced Java Courses on Crete:Java Specialists Master Course 18-21 June 2013 and
Concurrency Specialists Course 6-9 August 2013.
Catching Exceptions in GUI Code
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
Exceptions Articles
Related Java Course
Discuss at The Java Specialist Club
|