package hirondelle.stocks.preferences; import java.util.logging.*; import javax.swing.JOptionPane; import java.awt.EventQueue; import hirondelle.stocks.util.Consts; /** * An implementation of a Java Logging API {@link Handler} * which displays a short message to the user using a <tt>JOptionPane</tt>, and * is intended only for <tt>Level.SEVERE</tt> messages. * * <P>The issue arises of how to inform the user in the case of a * <tt>Level.SEVERE</tt> message : if all * <tt>Handler</tt>s are purely textual, then in a graphical application it is * not uncommon for the user to be unaware of the problem, since the textual output * is not necessarily visible. This <tt>Handler</tt> addresses the issue by defining * a graphical logging <tt>Handler</tt>, which will always get the user's attention * by using a visual pop-up message. * * <P>This <tt>Handler</tt> is unusual for two reasons : * <ul> * <li> it is intended only for <tt>SEVERE</tt> messages, so * that the user is never overloaded with too many pop-up messages. * <li>it is intended only for programmatic use, and should not appear in the * <tt>logging.properties</tt> config file. *</ul> * * <P>The content of the message is taken from * {@link LogRecord#getMessage}, which is usually a short piece * of text. The intent is that this <tt>Handler</tt> present only the barest * facts, and that the user refer to other text-based <tt>Handler</tt> tools * for further information. * * <P>This <tt>Handler</tt> can be used both from the event dispatch thread, * and from any other thread. Thus, it may be used by any worker thread which * is experiencing difficulties. * * <P>Typically, this <tt>Handler</tt> is attached to the root <tt>Logger</tt>, * and is thus inherited by all all other <tt>Logger</tt>s. */ public final class OptionPaneExceptionHandler extends Handler { /* * Implementation Note : * A simple way to test the behavior of this class is * to attempt a File->Import with corrupted data. */ /** * Construct this <tt>Handler</tt> with default settings. * * These defaults should not be changed : *<ul> * <li><tt>Level</tt> is set to <tt>SEVERE</tt> * <li>{@link Formatter} is set to a {@link SimpleFormatter} *</ul> */ public OptionPaneExceptionHandler(){ setFormatter( new SimpleFormatter() ); setLevel(Level.SEVERE); } /** No-operation. */ @Override public void close() { } /** No-operation. */ @Override public void flush() { } /** * If <tt>aLogRecord</tt> satisfies {@link Handler#isLoggable}, * then a short message is displayed to the user using a <tt>JOptionPane</tt>. * * <P>The message is presented in a modal dialog, and will grab the focus. * * <P> Warning : if a <tt>SEVERE</tt> message is generated during startup, before * the main window is displayed, then any pop-up message generated by this class will * eventually be hidden behind the main window. */ @Override public void publish(LogRecord aLogRecord) { if ( ! isLoggable(aLogRecord) ) return; if (EventQueue.isDispatchThread()) { showGui(aLogRecord); } else { //using a SwingWorker would be a more modern style, but this still works fine too EventQueue.invokeLater( new Worker(aLogRecord) ); } } /** * Add a <tt>OptionPaneExceptionHandler</tt> to the root <tt>Logger</tt>. * *<P>Typically, the <tt>Handler</tt> attached to the root <tt>Logger</tt> is * inherited by all other <tt>Logger</tt>s. * * <P>Called both upon startup and when refreshing the * <tt>logging.properties</tt> file. In the case of startup, it is best to * call this method only after the main window is shown ; otherwise, it is likely that * any startup error messages generated by this class will be hidden * by the main window. */ public static void attachToRootLogger(){ Handler handler = new OptionPaneExceptionHandler(); Logger rootLogger = Logger.getLogger(Consts.EMPTY_STRING); rootLogger.addHandler(handler); } // PRIVATE private static final String TITLE = "Error Occured"; private void showGui(LogRecord aLogRecord){ String message = getFormatter().formatMessage(aLogRecord); JOptionPane.showMessageDialog(null, message, TITLE, JOptionPane.ERROR_MESSAGE); } private class Worker implements Runnable { Worker(LogRecord aLogRecord){ fLogRecord = aLogRecord; } @Override public void run(){ showGui(fLogRecord); } private LogRecord fLogRecord; } }