org.jdesktop.application.Application Maven / Gradle / Ivy
Show all versions of tink-app Show documentation
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.application;
import java.awt.ActiveEvent;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.PaintEvent;
import java.beans.Beans;
import java.lang.reflect.Constructor;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
/**
* The base class for Swing applications.
*
*
* This class defines a simple lifecyle for Swing applications: {@code
* initialize}, {@code startup}, {@code ready}, and {@code shutdown}.
* The {@code Application's} {@code startup} method is responsible for
* creating the initial GUI and making it visible, and the {@code
* shutdown} method for hiding the GUI and performing any other
* cleanup actions before the application exits. The {@code initialize}
* method can be used configure system properties that must be set
* before the GUI is constructed and the {@code ready}
* method is for applications that want to do a little bit of extra
* work once the GUI is "ready" to use. Concrete subclasses must
* override the {@code startup} method.
*
* Applications are started with the static {@code launch} method.
* Applications use the {@code ApplicationContext} {@link
* Application#getContext singleton} to find resources,
* actions, local storage, and so on.
*
* All {@code Application} subclasses must override {@code startup}
* and they should call {@link #exit} (which
* calls {@code shutdown}) to exit.
* Here's an example of a complete "Hello World" Application:
*
* public class MyApplication extends Application {
* JFrame mainFrame = null;
* @Override protected void startup() {
* mainFrame = new JFrame("Hello World");
* mainFrame.add(new JLabel("Hello World"));
* mainFrame.addWindowListener(new MainFrameListener());
* mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
* mainFrame.pack();
* mainFrame.setVisible(true);
* }
* @Override protected void shutdown() {
* mainFrame.setVisible(false);
* }
* private class MainFrameListener extends WindowAdapter {
* public void windowClosing(WindowEvent e) {
* exit();
* }
* }
* public static void main(String[] args) {
* Application.launch(MyApplication.class, args);
* }
* }
*
*
* The {@code mainFrame's} {@code defaultCloseOperation} is set
* to {@code DO_NOTHING_ON_CLOSE} because we're handling attempts
* to close the window by calling
* {@code ApplicationContext} {@link #exit}.
*
* Simple single frame applications like the example can be defined
* more easily with the {@link SingleFrameApplication
* SingleFrameApplication} {@code Application} subclass.
*
*
* All of the Application's methods are called (must be called) on
* the EDT.
*
*
* All but the most trivial applications should define a ResourceBundle
* in the resources subpackage with the same name as the application class (like {@code
* resources/MyApplication.properties}). This ResourceBundle contains
* resources shared by the entire application and should begin with the
* following the standard Application resources:
*
* Application.name = A short name, typically just a few words
* Application.id = Suitable for Application specific identifiers, like file names
* Application.title = A title suitable for dialogs and frames
* Application.version = A version string that can be incorporated into messages
* Application.vendor = A proper name, like Sun Microsystems, Inc.
* Application.vendorId = suitable for Application-vendor specific identifiers, like file names.
* Application.homepage = A URL like http://www.javadesktop.org
* Application.description = One brief sentence
* Application.lookAndFeel = either system, default, or a LookAndFeel class name
*
*
* The {@code Application.lookAndFeel} resource is used to initialize the
* {@code UIManager lookAndFeel} as follows:
*
* - {@code system} - the system (native) look and feel
* - {@code default} - use the JVM default, typically the cross platform look and feel
* - a LookAndFeel class name - use the specified class
*
*
* @see SingleFrameApplication
* @see ApplicationContext
* @see UIManager#setLookAndFeel
* @author Hans Muller ([email protected])
*/
@ProxyActions({"cut", "copy", "paste", "delete"})
public abstract class Application extends AbstractBean {
private static final Logger logger = Logger.getLogger(Application.class.getName());
private static Application application = null;
private final List exitListeners;
private final ApplicationContext context;
/**
* Not to be called directly, see {@link #launch launch}.
*
* Subclasses can provide a no-args construtor
* to initialize private final state however GUI
* initialization, and anything else that might refer to
* public API, should be done in the {@link #startup startup}
* method.
*/
protected Application() {
exitListeners = new CopyOnWriteArrayList();
context = new ApplicationContext();
}
/**
* Creates an instance of the specified {@code Application}
* subclass, sets the {@code ApplicationContext} {@code
* application} property, and then calls the new {@code
* Application's} {@code startup} method. The {@code launch} method is
* typically called from the Application's {@code main}:
*
* public static void main(String[] args) {
* Application.launch(MyApplication.class, args);
* }
*
* The {@code applicationClass} constructor and {@code startup} methods
* run on the event dispatching thread.
*
* @param applicationClass the {@code Application} class to launch
* @param args {@code main} method arguments
* @see #shutdown
* @see ApplicationContext#getApplication
*/
public static synchronized void launch(final Class applicationClass, final String[] args) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
try {
application = create(applicationClass);
application.initialize(args);
application.startup();
application.waitForReady();
}
catch (Exception e) {
String msg = String.format("Application %s failed to launch", applicationClass);
logger.log(Level.SEVERE, msg, e);
throw(new Error(msg, e));
}
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
/* Initializes the ApplicationContext applicationClass and application
* properties.
*
* Note that, as of Java SE 5, referring to a class literal
* doesn't force the class to be loaded. More info:
* http://java.sun.com/javase/technologies/compatibility.jsp#literal
* It's important to perform these initializations early, so that
* Application static blocks/initializers happen afterwards.
*/
static T create(Class applicationClass) throws Exception {
if (!Beans.isDesignTime()) {
/* A common mistake for privileged applications that make
* network requests (and aren't applets or web started) is to
* not configure the http.proxyHost/Port system properties.
* We paper over that issue here.
*/
try {
System.setProperty("java.net.useSystemProxies", "true");
}
catch (SecurityException ignoreException) {
// Unsigned apps can't set this property.
}
}
/* Construct the Application object. The following
* complications, relative to just calling
* applicationClass.newInstance(), allow a privileged app to
* have a private static inner Application subclass.
*/
Constructor ctor = applicationClass.getDeclaredConstructor();
if (!ctor.isAccessible()) {
try {
ctor.setAccessible(true);
}
catch (SecurityException ignore) {
// ctor.newInstance() will throw an IllegalAccessException
}
}
T application = ctor.newInstance();
/* Initialize the ApplicationContext application properties
*/
ApplicationContext ctx = application.getContext();
ctx.setApplicationClass(applicationClass);
ctx.setApplication(application);
/* Load the application resource map, notably the
* Application.* properties.
*/
ResourceMap appResourceMap = ctx.getResourceMap();
appResourceMap.putResource("platform", platform());
if (!Beans.isDesignTime()) {
/* Initialize the UIManager lookAndFeel property with the
* Application.lookAndFeel resource. If the the resource
* isn't defined we default to "system".
*/
String key = "Application.lookAndFeel";
String lnfResource = appResourceMap.getString(key);
String lnf = (lnfResource == null) ? "system" : lnfResource;
try {
if (lnf.equalsIgnoreCase("system")) {
String name = UIManager.getSystemLookAndFeelClassName();
UIManager.setLookAndFeel(name);
}
else if (!lnf.equalsIgnoreCase("default")) {
UIManager.setLookAndFeel(lnf);
}
}
catch(Exception e) {
String s = "Couldn't set LookandFeel " + key + " = \"" + lnfResource + "\"";
logger.log(Level.WARNING, s, e);
}
}
return application;
}
/* Defines the default value for the platform resource,
* either "osx" or "default".
*/
private static String platform() {
String platform = "default";
try {
String osName = System.getProperty("os.name");
if ((osName != null) && osName.toLowerCase().startsWith("mac os x")) {
platform = "osx";
}
}
catch (SecurityException ignore) {
}
return platform;
}
/* Call the ready method when the eventQ is quiet.
*/
void waitForReady() {
new DoWaitForEmptyEventQ().execute();
}
/**
* Responsible for initializations that must occur before the
* GUI is constructed by {@code startup}.
*
* This method is called by the static {@code launch} method,
* before {@code startup} is called. Subclasses that want
* to do any initialization work before {@code startup} must
* override it. The {@code initialize} method
* runs on the event dispatching thread.
*
* By default initialize() does nothing.
*
* @param args the main method's arguments.
* @see #launch
* @see #startup
* @see #shutdown
*/
protected void initialize(String[] args) {
}
/**
* Responsible for starting the application; for creating and showing
* the initial GUI.
*
* This method is called by the static {@code launch} method,
* subclasses must override it. It runs on the event dispatching
* thread.
*
* @see #launch
* @see #initialize
* @see #shutdown
*/
protected abstract void startup();
/**
* Called after the startup() method has returned and there
* are no more events on the
* {@link Toolkit#getSystemEventQueue system event queue}.
* When this method is called, the application's GUI is ready
* to use.
*
* It's usually important for an application to start up as
* quickly as possible. Applications can override this method
* to do some additional start up work, after the GUI is up
* and ready to use.
*
* @see #launch
* @see #startup
* @see #shutdown
*/
protected void ready() {
}
/**
* Called when the application {@link #exit exits}.
* Subclasses may override this method to do any cleanup
* tasks that are neccessary before exiting. Obviously, you'll want to try
* and do as little as possible at this point. This method runs
* on the event dispatching thread.
*
* @see #startup
* @see #ready
* @see #exit
* @see #addExitListener
*/
protected void shutdown() {
// TBD should call TaskService#shutdownNow() on each TaskService
}
/* An event that sets a flag when it's dispatched and another
* flag, see isEventQEmpty(), that indicates if the event queue
* was empty at dispatch time.
*/
private static class NotifyingEvent extends PaintEvent implements ActiveEvent {
private boolean dispatched = false;
private boolean qEmpty = false;
NotifyingEvent(Component c) {
super(c, PaintEvent.UPDATE, null);
}
synchronized boolean isDispatched() { return dispatched; }
synchronized boolean isEventQEmpty() { return qEmpty; }
public void dispatch() {
EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue();
synchronized(this) {
qEmpty = (q.peekEvent() == null);
dispatched = true;
notifyAll();
}
}
}
/* Keep queuing up NotifyingEvents until the event queue is
* empty when the NotifyingEvent is dispatched().
*/
private void waitForEmptyEventQ() {
boolean qEmpty = false;
JPanel placeHolder = new JPanel();
EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue();
while (!qEmpty) {
NotifyingEvent e = new NotifyingEvent(placeHolder);
q.postEvent(e);
synchronized(e) {
while (!e.isDispatched()) {
try {
e.wait();
}
catch (InterruptedException ie) {
}
}
qEmpty = e.isEventQEmpty();
}
}
}
/* When the event queue is empty, give the app a chance to do
* something, now that the GUI is "ready".
*/
private class DoWaitForEmptyEventQ extends Task {
DoWaitForEmptyEventQ() { super(Application.this); }
@Override protected Void doInBackground() {
waitForEmptyEventQ();
return null;
}
@Override protected void finished() {
ready();
}
}
/**
* Gracefully shutdown the application, calls {@code exit(null)}
* This version of exit() is convenient if the decision to exit the
* application wasn't triggered by an event.
*
* @see #exit(EventObject)
*/
public final void exit() {
exit(null);
}
/**
* Gracefully shutdown the application.
*
* If none of the {@code ExitListener.canExit()} methods return false,
* calls the {@code ExitListener.willExit()} methods, then
* {@code shutdown()}, and then exits the Application with
* {@link #end end}. Exceptions thrown while running willExit() or shutdown()
* are logged but otherwise ignored.
*
* If the caller is responding to an GUI event, it's helpful to pass the
* event along so that ExitListeners' canExit methods that want to popup
* a dialog know on which screen to show the dialog. For example:
*
* class ConfirmExit implements Application.ExitListener {
* public boolean canExit(EventObject e) {
* Object source = (e != null) ? e.getSource() : null;
* Component owner = (source instanceof Component) ? (Component)source : null;
* int option = JOptionPane.showConfirmDialog(owner, "Really Exit?");
* return option == JOptionPane.YES_OPTION;
* }
* public void willExit(EventObejct e) {}
* }
* myApplication.addExitListener(new ConfirmExit());
*
* The {@code eventObject} argument may be null, e.g. if the exit
* call was triggered by non-GUI code, and {@code canExit}, {@code
* willExit} methods must guard against the possibility that the
* {@code eventObject} argument's {@code source} is not a {@code
* Component}.
*
* @param event the EventObject that triggered this call or null
* @see #addExitListener
* @see #removeExitListener
* @see #shutdown
* @see #end
*/
public void exit(EventObject event) {
for (ExitListener listener : exitListeners) {
if (!listener.canExit(event)) {
return;
}
}
try {
for (ExitListener listener : exitListeners) {
try {
listener.willExit(event);
}
catch (Exception e) {
logger.log(Level.WARNING, "ExitListener.willExit() failed", e);
}
}
shutdown();
}
catch (Exception e) {
logger.log(Level.WARNING, "unexpected error in Application.shutdown()", e);
}
finally {
end();
}
}
/**
* Called by {@link #exit exit} to terminate the application. Calls
* {@code Runtime.getRuntime().exit(0)}, which halts the JVM.
*
* @see #exit
*/
protected void end() {
Runtime.getRuntime().exit(0);
}
/**
* Give the Application a chance to veto an attempt to exit/quit.
* An {@code ExitListener's} {@code canExit} method should return
* false if there are pending decisions that the user must make
* before the app exits. A typical {@code ExitListener} would
* prompt the user with a modal dialog.
*
* The {@code eventObject} argument will be the the value passed
* to {@link #exit(EventObject) exit()}. It may be null.
*
* The {@code willExit} method is called after the exit has
* been confirmed. An ExitListener that's going to perform
* some cleanup work should do so in {@code willExit}.
*
* {@code ExitListeners} run on the event dispatching thread.
*
* @param event the EventObject that triggered this call or null
* @see #exit(EventObject)
* @see #addExitListener
* @see #removeExitListener
*/
public interface ExitListener extends EventListener {
boolean canExit(EventObject event);
void willExit(EventObject event);
}
/**
* Add an {@code ExitListener} to the list.
*
* @param listener the {@code ExitListener}
* @see #removeExitListener
* @see #getExitListeners
*/
public void addExitListener(ExitListener listener) {
exitListeners.add(listener);
}
/**
* Remove an {@code ExitListener} from the list.
*
* @param listener the {@code ExitListener}
* @see #addExitListener
* @see #getExitListeners
*/
public void removeExitListener(ExitListener listener) {
exitListeners.remove(listener);
}
/**
* All of the {@code ExitListeners} added so far.
*
* @return all of the {@code ExitListeners} added so far.
*/
public ExitListener[] getExitListeners() {
int size = exitListeners.size();
return exitListeners.toArray(new ExitListener[size]);
}
/**
* The default {@code Action} for quitting an application,
* {@code quit} just exits the application by calling {@code exit(e)}.
*
* @param e the triggering event
* @see #exit(EventObject)
*/
@Action public void quit(ActionEvent e) {
exit(e);
}
/**
* The ApplicationContext singleton for this Application.
*
* @return the Application's ApplicationContext singleton
*/
public final ApplicationContext getContext() {
return context;
}
/**
* The {@code Application} singleton.
*
* Typically this method is only called after an Application has
* been launched however in some situations, like tests, it's useful to be
* able to get an {@code Application} object without actually
* launching. In that case, an instance of the specified class
* is constructed and configured as it would be by the
* {@link #launch launch} method. However it's {@code initialize}
* and {@code startup} methods are not run.
*
* @param applicationClass this Application's subclass
* @return the launched Application singleton.
* @see Application#launch
*/
public static synchronized T getInstance(Class applicationClass) {
if (application == null) {
/* Special case: the application hasn't been launched. We're
* constructing the applicationClass here to get the same effect
* as the NoApplication class serves for getInstance(). We're
* not launching the app, no initialize/startup/wait steps.
*/
try {
application = create(applicationClass);
}
catch (Exception e) {
String msg = String.format("Couldn't construct %s", applicationClass);
throw(new Error(msg, e));
}
}
return applicationClass.cast(application);
}
/**
* The {@code Application} singleton, or a placeholder if {@code
* launch} hasn't been called yet.
*
* Typically this method is only called after an Application has
* been launched however in some situations, like tests, it's useful to be
* able to get an {@code Application} object without actually
* launching. The placeholder Application object provides
* access to an {@code ApplicationContext} singleton and has
* the same semantics as launching an Application defined like this:
*
* public class PlaceholderApplication extends Application {
* public void startup() { }
* }
* Application.launch(PlaceholderApplication.class);
*
*
* @return the Application singleton or a placeholder
* @see Application#launch
* @see Application#getInstance(Class)
*/
public static synchronized Application getInstance() {
if (application == null) {
application = new NoApplication();
}
return application;
}
private static class NoApplication extends Application {
protected NoApplication() {
ApplicationContext ctx = getContext();
ctx.setApplicationClass(getClass());
ctx.setApplication(this);
ResourceMap appResourceMap = ctx.getResourceMap();
appResourceMap.putResource("platform", platform());
}
protected void startup() {}
}
/* Prototype support for the View type */
public void show(View view) {
Window window = (Window)view.getRootPane().getParent();
if (window != null) {
window.pack();
window.setVisible(true);
}
}
public void hide(View view) {
view.getRootPane().getParent().setVisible(false);
}
}