package passwordstore1; import java.awt.ActiveEvent; import java.awt.Component; import java.awt.EventQueue; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.PaintEvent; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadFactory; import java.util.prefs.Preferences; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; /** * Base Application class for Swing apps. Application provides the following * functionality: * *

* Application provides a handful of methods that are invoked as part of * starting the application. Subclasses need only override those they are * interested in. The following outlines the order the methods are invoked in * as well as what they are intended for. All methods are invoked on the * event dispatching thread. *

    *
  1. preInit is invoked first. preInit is provided for any initialization * that needs to be done prior to creating the UI. Application's implementation * invokes installLookAndFeel to install the system look and feel. *
  2. init is invoked after preInit. init is intended for creating the actual * UI. *
  3. ApplicationListeners are notified that the application has initialized * (appDidInit). *
  4. postInit is invoked after the listeners are notified, and is intended * for any cleanup that needs to be done. *
  5. postInitEventQueueEmpty is invoked after postInit and after the event * queue has processed all pending events, such as paint events or * revalidate requests generated during the earlier stages of * initialization. Subclasses that need to do processing after the UI is * completely showing should override this method. *
*

* * Application also provides a sequence of methods for exiting the * application. When exit is invoked the following methods are invoked: *

    *
  1. canExit is invoked, if this returns false the application will not exit. *
  2. ApplicationListeners are asked if the application can exit. If any * ApplicationListener returns false from canApplicationExit the application * will not exit. *
  3. waitForBackgroundThreads is invoked to block until any background threads * have completed. *
  4. All ApplicationListeners are notified that the application is exiting. *
  5. exiting is invoked. *
  6. Lastly, System.exit is invoked. *
*

* Concrete implementations need only override getName, but will undoubtedly * override one of the various init methods as well. */ public abstract class Application implements Thread.UncaughtExceptionHandler { private static Application APPLICATION; private static final int RUN_POST_INIT_EVENT_QUEUE_EMPTY = 0; private static final int RUN_START_ON_EDT = 1; private static final int RUN_WAIT_EVENTS = 2; private static final int UNCAUGHT_EXCEPTION = 3; private static final int RUN_WAIT_FOR_BACKGROUND_THREADS = 4; // Exception that has been thrown. This is used to track if an exception // is thrown while alerting the user to the current exception. private Throwable throwable = null; // ThreadFactory for vending threads that are automatically registered. private ThreadFactory threadFactory; // Threads we'll block on when exiting. private List> threads; // Whether or not the app has started private boolean started; // Preferecnes nodes for the app. private Preferences preferences; // ResourceBundle for the app private ResourceBundle resources; // Arbitrary user data. private Map data; // ApplicationListeners private List appListeners; /** * Returns the single Application instance. * * @return the single Application instance */ public static Application getInstance() { return APPLICATION; } /** * Creates a new Application instance. Subclasses very rarely need * put any logic in the constructor, especially not code that creates * Swing components. Override one of the various init methods instead * which are guaranteed to be invoked on the event dispatching thread. * * @throws IllegalStateException if an Application has already been created * @see init sequence */ public Application() { if (APPLICATION != null) { throw new IllegalStateException("Can only have one Application"); } threads = new LinkedList>(); APPLICATION = this; data = new HashMap(1); if (System.getProperty("nohandler") == null) { Thread.setDefaultUncaughtExceptionHandler(this); } } /** * Adds a listener for application events. * * @param listener the ApplicationListener to add */ public void addApplicationListener(ApplicationListener listener) { if (appListeners == null) { appListeners = new CopyOnWriteArrayList(); } appListeners.add(listener); } /** * Adds a listener for application events. * * @param listener the ApplicationListener to add */ public void removeApplicationListener(ApplicationListener listener) { if (appListeners != null) { appListeners.remove(listener); } } /** * Associated the specified value with the specified key. This is intended * for developers to place application specific data in. * * @param key the key to store the value in * @param value the value to associated with key */ public final void putData(Object key, Object value) { data.put(key, value); } /** * Returns the value for the specified user key. * * @param key the key used to retrieve the specified value * @return the value for the specified user key */ public final Object getData(Object key) { return data.get(key); } /** * Returns the Preferences object for the Application. * * @return the Preferences object for the Application * @see #getPreferencesKey */ public final Preferences getPreferences() { if (preferences == null) { preferences = Preferences.userNodeForPackage( getPreferencesKey()); } return preferences; } /** * Returns the Class key used to fetch the Preferences object. This * implementation returns the Application class. * * @return the key used to fetch the Preferences object * @see #getPreferences */ protected Class getPreferencesKey() { return getClass(); } /** * Returns the ResourceBundle for the Application. The ResourceBundle * is loaded using the value returned from getResourceBundleName. * * @return the ResourceBundle for the Application * @see #getResourceBundleName */ public final ResourceBundle getResourceBundle() { if (resources == null) { resources = ResourceBundle.getBundle(getResourceBundleName()); } return resources; } /** * Returns the key for loading the resources for the Application. * This implementation returns * getClass().getName().resources.Resources. * * @return the name used to locate the Applications ResourceBundle */ protected String getResourceBundleName() { return getClass().getPackage().getName() + ".resources.Resources"; } /** * Returns a ThreadFactory suitable for threads used within Swing * applications. Threads created by the returned ThreadFactory are * automatically registered with the Application, are not daemon, and * have a priority of Thread.MIN_PRIORITY. *

* When exit is invoked the Application will block until all background * threads have exited. * * @return a ThreadFactory suitable for background threads * @see #registerThread * @see #waitForBackgroundThreadsToExit */ public ThreadFactory getBackgroundThreadFactory() { if (threadFactory == null) { threadFactory = new SwingThreadFactory(); } return threadFactory; } /** * Registers a background thread with the Application. When the application * exits it will block until all background threads have completed. *

* This method is thread safe. * * @param thread the Thread to wait for completion on * @throws IllegalArgumentException if thread is null */ public final void registerThread(Thread thread) { if (thread == null) { throw new IllegalArgumentException("Thread must be non-null"); } synchronized(threads) { // Prune any bogus references Iterator> threadsIterator = threads.iterator(); while (threadsIterator.hasNext()) { if (threadsIterator.next().get() == null) { threadsIterator.remove(); } } threads.add(new WeakReference(thread)); } } /** * Returns the name of the application. * * @return the name of the Application */ public abstract String getName(); /** * Invoked from preInit to set the look and feel for the Application. This * implementation sets the look and feel to the system look and feel. * If setting the look and feel results in throwing an exception it will * be ignored. * * @see init sequence */ protected void installLookAndFeel() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (InstantiationException ex) { } catch (IllegalAccessException ex) { } catch (ClassNotFoundException ex) { } catch (UnsupportedLookAndFeelException ex) { } } /** * Invoked as part of starting the application. This method invokes * installLookAndFeel. See * init sequence for details on when this * is invoked. * * @see #installLookAndFeel */ protected void preInit() { installLookAndFeel(); } /** * Invoked as part of starting the application. See * init sequence for details on when this * is invoked. */ protected void init() { } /** * Invoked as part of starting the application. See * init sequence for details on when this * is invoked. */ protected void postInit() { } /** * Invoked as part of starting the application. See * init sequence for details on when this * is invoked. */ protected void postInitEventQueueEmpty() { } /** * Starts the Application. This method is typically invoked * directly from main. Refer to * init sequence for details on which methods * this invokes. * * @throws IllegalStateException if start has already been invoked * @see init sequence */ public final void start() { if (started) { throw new IllegalStateException("Application is already running"); } started = true; if (!EventQueue.isDispatchThread()) { SwingUtilities.invokeLater(new Handler(RUN_START_ON_EDT)); } else { startOnEDT(); } } private void startOnEDT() { preInit(); init(); fireApplicationDidInit(); postInit(); new Thread(new Handler(RUN_WAIT_EVENTS)).start(); } /** * Returns whether the application should be allowed to exit. This * is invoked from exit. A return value of false will stop the application * from exiting. * * @return whether or not the application should be allowed to exit; this * implementation unconditionally returns true * @see #exit */ protected boolean canExit() { return true; } /** * Invoked as part of exiting the application. Refer to * exit sequence for details on when this * method is invoked. */ protected void exiting() { } private boolean listenersCanExit() { if (appListeners != null) { for (ApplicationListener listener : appListeners) { if (!listener.canApplicationExit()) { return false; } } } return true; } /** * Blocks until all registered threads have completed. This is invoked * from exit. If necessary this method will invoke * createBackgroundThreadDialog to create a modal dialog that is shown * while waiting for background threads to exit. * * @see #exit * @see #createBackgroundThreadDialog */ protected void waitForBackgroundThreadsToExit() { // Do a quick check to see if we'll need to show a modal dialog. If // after 100 ms threads are still running, then show a modal dialog. if (!waitForBackgroundThreadsToExit(100)) { // spawn background thread JDialog threadDialog = createBackgroundThreadDialog(); new Thread(new Handler(RUN_WAIT_FOR_BACKGROUND_THREADS, threadDialog)). start(); threadDialog.setVisible(true); } } // Invoked on a background thread private void waitForBackgroundThreadsToExitInBackground(JDialog dialog) { waitForBackgroundThreadsToExit(Long.MAX_VALUE); // The background thread is spawned prior to making the dialog visible. // As such, we need to block until the dialog is visible. while (!dialog.isVisible()) { try { Thread.sleep(10); } catch (InterruptedException ex) { } } dialog.setVisible(false); } // Blocks until the background threads exit, or maxWaitTime milliseconds has // expired. private boolean waitForBackgroundThreadsToExit(long maxWaitTime) { boolean done = false; long start = System.currentTimeMillis(); while (!done) { Thread thread = null; synchronized (threads) { Iterator> threadsIterator = threads.iterator(); while (threadsIterator.hasNext()) { thread = threadsIterator.next().get(); if (thread != null) { threadsIterator.remove(); break; } } } if (thread != null) { try { thread.join(Math.max(10, maxWaitTime - (System.currentTimeMillis() - start))); } catch (InterruptedException ex) { } if (thread.isAlive()) { // It's still alive, reregister so that we loop back // through. registerThread(thread); } if (System.currentTimeMillis() - start >= maxWaitTime) { // More than maxWaitTime has elapsed, bail. return false; } } else { done = true; } } return true; } private void fireApplicationExiting() { if (appListeners != null) { for (ApplicationListener listener : appListeners) { listener.applicationExiting(); } } } private void fireApplicationDidInit() { if (appListeners != null) { for (ApplicationListener listener : appListeners) { listener.applicationDidInit(); } } } /** * Exits the application. Refer to exit sequence * for details on which methods this invokes. * * @see #canExit * @see #waitForBackgroundThreadsToExit */ public final void exit() { if (canExit() && listenersCanExit()) { waitForBackgroundThreadsToExit(); fireApplicationExiting(); exiting(); System.exit(0); } } /** * Invoked when an uncaught exception is encountered. This invokes * the method of the same name with the calling thread as an argument. * * @param throwable the thrown exception */ public void uncaughtException(Throwable throwable) { uncaughtException(Thread.currentThread(), throwable); } /** * Invoked when an uncaught exception is encountered. This will * show a modal dialog alerting the user, and exit the app. This does * not invoke exit. * * @param thread the thread the exception was thrown on * @param throwable the thrown exception * @see #getUncaughtExceptionDialog */ public void uncaughtException(Thread thread, final Throwable throwable) { synchronized(this) { if (this.throwable != null) { // An exception has occured while we're trying to display // the current exception, bale. System.err.println("exception thrown while altering user"); throwable.printStackTrace(); System.exit(1); } else { this.throwable = throwable; } } throwable.printStackTrace(); if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Handler(UNCAUGHT_EXCEPTION)); } else { uncaughtException0(); } } /** * Returns the dialog that is shown when an uncaught exception is * encountered. * * @see #uncaughtException * @return dialog to show when an uncaught exception is encountered */ protected JDialog getUncaughtExceptionDialog() { // PENDING: this needs to be localized. JOptionPane optionPane = new JOptionPane( "An unrecoverable error has occured. " + getName() + " will now exit", JOptionPane.ERROR_MESSAGE); return optionPane.createDialog(null, "Error"); } private void uncaughtException0() { Throwable throwable; synchronized(this) { throwable = this.throwable; } JDialog dialog = getUncaughtExceptionDialog(); dialog.setVisible(true); System.exit(1); } /** * Returns the dialog to show when waiting for any background threads * to exit. The returned dialog must be modal. * * @return dialog to shown when waiting for background threads to exit */ protected JDialog createBackgroundThreadDialog() { JDialog dialog = new JDialog((Frame)null, getName(), true); dialog.setLayout(new GridBagLayout()); // PENDING: localize this dialog.add(new JLabel("Waiting for processing to complete..."), new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(5, 5, 0, 50), 0, 0)); JProgressBar pb = new JProgressBar(); pb.setIndeterminate(true); dialog.add(pb, new GridBagConstraints(0, 1, 1, 1, 1, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); dialog.setResizable(false); dialog.pack(); dialog.setLocationRelativeTo(null); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); return dialog; } private void waitForEmptyEventQ() { boolean qEmpty = false; JPanel bogusComponent = new JPanel(); EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue(); while (!qEmpty) { NotifyingPaintEvent e = new NotifyingPaintEvent(bogusComponent); q.postEvent(e); synchronized(e) { // Wait until the event has been dispatched while (!e.isDispatched()) { try { e.wait(); } catch (InterruptedException ie) { } } // Check if the q is empty qEmpty = e.qEmpty(); } } SwingUtilities.invokeLater(new Handler(RUN_POST_INIT_EVENT_QUEUE_EMPTY)); } // Coalesced Runnable implementation to avoid numerous inner classes. private class Handler implements Runnable { private final int type; private final Object[] args; Handler(int type, Object...args) { this.type = type; this.args = args; } public void run() { switch (this.type) { case RUN_POST_INIT_EVENT_QUEUE_EMPTY: postInitEventQueueEmpty(); break; case RUN_START_ON_EDT: startOnEDT(); break; case RUN_WAIT_EVENTS: waitForEmptyEventQ(); break; case UNCAUGHT_EXCEPTION: uncaughtException0(); break; case RUN_WAIT_FOR_BACKGROUND_THREADS: waitForBackgroundThreadsToExitInBackground((JDialog)this.args[0]); break; } } } // Used in determining when the event Q is empty private class NotifyingPaintEvent extends PaintEvent implements ActiveEvent { private boolean _dispatched = false; private boolean _qEmpty; NotifyingPaintEvent(Component x) { super(x, PaintEvent.UPDATE, null); } public synchronized boolean isDispatched() { return _dispatched; } public synchronized boolean qEmpty() { return _qEmpty; } public void dispatch() { EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue(); _qEmpty = (q.peekEvent() == null); synchronized(this) { _dispatched = true; notifyAll(); } } } private static class SwingThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread thread = new Thread(r, "swing-thread"); thread.setDaemon(false); thread.setPriority(Thread.MIN_PRIORITY); Application.getInstance().registerThread(thread); return thread; } } }