pax_global_header00006660000000000000000000000064115164030620014510gustar00rootroot0000000000000052 comment=36af7b87ff60841d5232d428d5bb9232dc2ee96b bsaf-1.9/000077500000000000000000000000001151640306200122745ustar00rootroot00000000000000bsaf-1.9/configs/000077500000000000000000000000001151640306200137245ustar00rootroot00000000000000bsaf-1.9/configs/checkstyle.xml000066400000000000000000000013411151640306200166030ustar00rootroot00000000000000 bsaf-1.9/pom.xml000066400000000000000000000176151151640306200136230ustar00rootroot00000000000000 4.0.0 org.jdesktop.bsaf bsaf jar 1.9 bsaf http://kenai.com/projects/bsaf An Application Framework for Swing 2009 LGPL 2.1 http://www.gnu.org/licenses/lgpl-2.1.html repo Jira http://kenai.com/jira/browse/BSAF UTF-8 java.net-m2-repository java-net:/maven2-repository/trunk/repository/ scm:svn:https://kenai.com/svn/bsaf~main/framework/tags/bsaf-1.9 scm:svn:https://kenai.com/svn/bsaf~main/framework/tags/bsaf-1.9 scm:svn:https://kenai.com/svn/bsaf~main/framework/tags/bsaf-1.9 org.apache.maven.plugins maven-jar-plugin true true ${pom.url} org.apache.maven.plugins maven-compiler-plugin true true 1.6 1.6 true true ${project.build.sourceEncoding} org.apache.maven.plugins maven-resources-plugin ${project.build.sourceEncoding} org.apache.maven.plugins maven-surefire-plugin pertest -enableassertions LocalStorage.dir ${project.build.testOutputDirectory} org.apache.maven.plugins maven-source-plugin attach-sources jar org.apache.maven.plugins maven-javadoc-plugin attach-javadocs jar org.jvnet.wagon-svn wagon-svn RELEASE junit junit 4.7 test javax.jnlp jnlp 1.6 system ${java.home}/lib/javaws.jar org.codehaus.mojo findbugs-maven-plugin true Low Max false false true org.apache.maven.plugins maven-pmd-plugin 1.5 org.apache.maven.plugins maven-checkstyle-plugin configs/checkstyle.xml org.apache.maven.plugins maven-changes-plugin 2.3 true jira-report maven2-repository.dev.java.net Java.net Repository for Maven http://download.java.net/maven/2/ bsaf-1.9/src/000077500000000000000000000000001151640306200130635ustar00rootroot00000000000000bsaf-1.9/src/main/000077500000000000000000000000001151640306200140075ustar00rootroot00000000000000bsaf-1.9/src/main/java/000077500000000000000000000000001151640306200147305ustar00rootroot00000000000000bsaf-1.9/src/main/java/org/000077500000000000000000000000001151640306200155175ustar00rootroot00000000000000bsaf-1.9/src/main/java/org/jdesktop/000077500000000000000000000000001151640306200173425ustar00rootroot00000000000000bsaf-1.9/src/main/java/org/jdesktop/application/000077500000000000000000000000001151640306200216455ustar00rootroot00000000000000bsaf-1.9/src/main/java/org/jdesktop/application/AbstractBean.java000066400000000000000000000126461151640306200250520ustar00rootroot00000000000000package org.jdesktop.application; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import javax.swing.SwingUtilities; import javax.swing.event.SwingPropertyChangeSupport; /** * An encapsulation of the PropertyChangeSupport methods based on * java.beans.PropertyChangeSupport. PropertyChangeListeners are fired * on the event dispatching thread. * *

* Note: this class is only public because the so-called "fix" * for javadoc bug * 4780441 * still fails to correctly document public methods inherited from a package * private class. */ public class AbstractBean { private final PropertyChangeSupport pcs; public AbstractBean() { pcs = new SwingPropertyChangeSupport(this, true); } /** * Add a PropertyChangeListener to the listener list. * The listener is registered for all properties and its * {@code propertyChange} method will run on the event dispatching * thread. *

* If {@code listener} is null, no exception is thrown and no action * is taken. * * @param listener the PropertyChangeListener to be added. * @see #removePropertyChangeListener * @see java.beans.PropertyChangeSupport#addPropertyChangeListener */ public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } /** * Remove a PropertyChangeListener from the listener list. *

* If {@code listener} is null, no exception is thrown and no action * is taken. * * @param listener the PropertyChangeListener to be removed. * @see #addPropertyChangeListener * @see java.beans.PropertyChangeSupport#removePropertyChangeListener */ public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } /** * Add a PropertyChangeListener for a specific property. The listener * will be invoked only when a call on firePropertyChange names that * specific property. * The same listener object may be added more than once. For each * property, the listener will be invoked the number of times it was added * for that property. * If propertyName or listener is null, no * exception is thrown and no action is taken. * * @param propertyName The name of the property to listen on. * @param listener the PropertyChangeListener to be added * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener) */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { pcs.addPropertyChangeListener(propertyName, listener); } /** * Remove a PropertyChangeListener for a specific property. * If listener was added more than once to the same event * source for the specified property, it will be notified one less time * after being removed. * If propertyName is null, no exception is thrown and no * action is taken. * If listener is null, or was never added for the specified * property, no exception is thrown and no action is taken. * * @param propertyName The name of the property that was listened on. * @param listener The PropertyChangeListener to be removed * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(String, PropertyChangeListener) */ public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { pcs.removePropertyChangeListener(propertyName, listener); } /** * An array of all of the {@code PropertyChangeListeners} added so far. * * @return all of the {@code PropertyChangeListeners} added so far. * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners */ public PropertyChangeListener[] getPropertyChangeListeners() { return pcs.getPropertyChangeListeners(); } /** * Called whenever the value of a bound property is set. *

* If oldValue is not equal to newValue, invoke the {@code * propertyChange} method on all of the {@code * PropertyChangeListeners} added so far, on the event * dispatching thread. * * @see #addPropertyChangeListener * @see #removePropertyChangeListener * @see java.beans.PropertyChangeSupport#firePropertyChange(String, Object, Object) */ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if (oldValue != null && newValue != null && oldValue.equals(newValue)) { return; } pcs.firePropertyChange(propertyName, oldValue, newValue); } /** * Fire an existing PropertyChangeEvent *

* If the event's oldValue property is not equal to newValue, * invoke the {@code propertyChange} method on all of the {@code * PropertyChangeListeners} added so far, on the event * dispatching thread. * * @see #addPropertyChangeListener * @see #removePropertyChangeListener * @see java.beans.PropertyChangeSupport#firePropertyChange(PropertyChangeEvent e) */ protected void firePropertyChange(PropertyChangeEvent e) { pcs.firePropertyChange(e); } } bsaf-1.9/src/main/java/org/jdesktop/application/Action.java000066400000000000000000000061201151640306200237240ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.jdesktop.application.Task.BlockingScope; /** * Marks a method that will be used to define a Swing * Action object's actionPerformed * method. It also identifies the resources that * will be used to initialize the Action's properties. * Additional @Action parameters can be used * to specify the name of the bound properties (from the * same class) that indicate if the Action is to be * enabled/selected, and if the GUI should be blocked * while the Action's background {@link Task} is running. * *

* The {@link ApplicationActionMap} class creates an * ActionMap that contains one {@link ApplicationAction} * for each @Action found in a target or "actions" class. * Typically applications will use {@link * ApplicationContext#getActionMap(Class, Object) getActionMap} to * lazily construct and cache ApplicationActionMaps, rather than * constructing them directly. By default the ApplicationActionMap's * {@link ApplicationActionMap#get key} for an @Action is the * name of the method. The name parameter can be used to * specify a different key. * *

* The ApplicationAction's properties are initialized with * resources loaded from a ResourceBundle with the same name as the * actions class. The list of properties initialized this way * is documented by the {@link ApplicationAction ApplicationAction's} * constructor. * *

* The method marked with @Action, can have no parameters, * or a single ActionEvent parameter. The method's return type * can be void or {@link Task}. If the return type * is Task, the Task will be executed by the ApplicationAction's * actionPerformed method. * * @see ApplicationAction * @see ApplicationActionMap * @see ApplicationContext * @author Hans Muller (Hans.Muller@Sun.COM) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Action { /** * The action name. The method name is used as an action name if not specified. */ String name() default ""; /** * The parameter binds the enabled state of the @Action to the current value of a property. */ String enabledProperty() default ""; /** * The parameter binds the selected state of the @Action to the current value of a property. */ String selectedProperty() default ""; /** * The parameter indicates that the GUI should be blocked while the background task is running. * @see Task */ BlockingScope block() default BlockingScope.NONE; /** * This annotation is not used yet */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface Parameter { String value() default ""; } } bsaf-1.9/src/main/java/org/jdesktop/application/ActionManager.java000066400000000000000000000243421151640306200252250ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.KeyboardFocusManager; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.WeakHashMap; import java.util.logging.Logger; import javax.swing.ActionMap; import javax.swing.JComponent; /** * The application's {@code ActionManager} provides read-only cached * access to {@code ActionMaps} that contain one entry for each method * marked with the {@code @Action} annotation in a class. * * * @see ApplicationContext#getActionMap(Object) * @see ApplicationActionMap * @see ApplicationAction * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ActionManager extends AbstractBean { private static final Logger logger = Logger.getLogger(ActionManager.class.getName()); private final ApplicationContext context; private final WeakHashMap> actionMaps; private ApplicationActionMap globalActionMap = null; protected ActionManager(ApplicationContext context) { if (context == null) { throw new IllegalArgumentException("null context"); } this.context = context; actionMaps = new WeakHashMap>(); } protected final ApplicationContext getContext() { return context; } private ApplicationActionMap createActionMapChain( Class startClass, Class stopClass, Object actionsObject, ResourceMap resourceMap) { // All of the classes from stopClass to startClass, inclusive. List classes = new ArrayList(); for (Class c = startClass;; c = c.getSuperclass()) { classes.add(c); if (c.equals(stopClass)) { break; } } Collections.reverse(classes); // Create the ActionMap chain, one per class ApplicationContext ctx = getContext(); ApplicationActionMap parent = null; for (Class cls : classes) { ApplicationActionMap appAM = new ApplicationActionMap(ctx, cls, actionsObject, resourceMap); appAM.setParent(parent); parent = appAM; } return parent; } /** * The {@code ActionMap} chain for the entire {@code Application}. *

* Returns an {@code ActionMap} with the {@code @Actions} defined * in the application's {@code Application} subclass, i.e. the * the value of: *

     * ApplicationContext.getInstance().getApplicationClass()
     * 
* The remainder of the chain contains one {@code ActionMap} * for each superclass, up to {@code Application.class}. The * {@code ActionMap.get()} method searches the entire chain, so * logically, the {@code ActionMap} that this method returns contains * all of the application-global actions. *

* The value returned by this method is cached. * * @return the {@code ActionMap} chain for the entire {@code Application}. * @see #getActionMap(Class, Object) * @see ApplicationContext#getActionMap() * @see ApplicationContext#getActionMap(Class, Object) * @see ApplicationContext#getActionMap(Object) */ public ApplicationActionMap getActionMap() { if (globalActionMap == null) { ApplicationContext ctx = getContext(); Object appObject = ctx.getApplication(); Class appClass = ctx.getApplicationClass(); ResourceMap resourceMap = ctx.getResourceMap(); globalActionMap = createActionMapChain(appClass, Application.class, appObject, resourceMap); initProxyActionSupport(); // lazy initialization } return globalActionMap; } private void initProxyActionSupport() { KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); kfm.addPropertyChangeListener(new KeyboardFocusPCL()); } /** * Returns the {@code ApplicationActionMap} chain for the specified * actions class and target object. *

* The specified class can contain methods marked with * the {@code @Action} annotation. Each one will be turned * into an {@link ApplicationAction ApplicationAction} object * and all of them will be added to a single * {@link ApplicationActionMap ApplicationActionMap}. All of the * {@code ApplicationActions} invoke their {@code actionPerformed} * method on the specified {@code actionsObject}. * The parent of the returned {@code ActionMap} is the global * {@code ActionMap} that contains the {@code @Actions} defined * in this application's {@code Application} subclass. * *

* To bind an {@code @Action} to a Swing component, one specifies * the {@code @Action's} name in an expression like this: *

     * ApplicationContext ctx = Application.getInstance(MyApplication.class).getContext();
     * MyActions myActions = new MyActions();
     * myComponent.setAction(ac.getActionMap(myActions).get("myAction"));
     * 
* *

* The value returned by this method is cached. The lifetime of * the cached entry will be the same as the lifetime of the {@code * actionsObject} and the {@code ApplicationActionMap} and {@code * ApplicationActions} that refer to it. In other words, if you * drop all references to the {@code actionsObject}, including * its {@code ApplicationActions} and their {@code * ApplicationActionMaps}, then the cached {@code ActionMap} entry * will be cleared. * * @param actionsClass * @param actionsObject * @see #getActionMap() * @see ApplicationContext#getActionMap() * @see ApplicationContext#getActionMap(Class, Object) * @see ApplicationContext#getActionMap(Object) * @return the {@code ApplicationActionMap} for {@code actionsClass} and {@code actionsObject} */ public ApplicationActionMap getActionMap(Class actionsClass, Object actionsObject) { if (actionsClass == null) { throw new IllegalArgumentException("null actionsClass"); } if (actionsObject == null) { throw new IllegalArgumentException("null actionsObject"); } if (!actionsClass.isAssignableFrom(actionsObject.getClass())) { throw new IllegalArgumentException("actionsObject not instanceof actionsClass"); } synchronized (actionMaps) { WeakReference ref = actionMaps.get(actionsObject); ApplicationActionMap classActionMap = (ref != null) ? ref.get() : null; if ((classActionMap == null) || (classActionMap.getActionsClass() != actionsClass)) { ApplicationContext ctx = getContext(); Class actionsObjectClass = actionsObject.getClass(); ResourceMap resourceMap = ctx.getResourceMap(actionsObjectClass, actionsClass); classActionMap = createActionMapChain(actionsObjectClass, actionsClass, actionsObject, resourceMap); ActionMap lastActionMap = classActionMap; while (lastActionMap.getParent() != null) { lastActionMap = lastActionMap.getParent(); } lastActionMap.setParent(getActionMap()); actionMaps.put(actionsObject, new WeakReference(classActionMap)); } return classActionMap; } } private final class KeyboardFocusPCL implements PropertyChangeListener { private final TextActions textActions; KeyboardFocusPCL() { textActions = new TextActions(getContext()); } @Override public void propertyChange(PropertyChangeEvent e) { if ("permanentFocusOwner".equals(e.getPropertyName())) { JComponent oldOwner = getContext().getFocusOwner(); Object newValue = e.getNewValue(); JComponent newOwner = (newValue instanceof JComponent) ? (JComponent) newValue : null; textActions.updateFocusOwner(oldOwner, newOwner); getContext().setFocusOwner(newOwner); updateAllProxyActions(oldOwner, newOwner); } } } /* For each proxyAction in each ApplicationActionMap, if * the newFocusOwner's ActionMap includes an Action with the same * name then bind the proxyAction to it, otherwise set the proxyAction's * proxyBinding to null. [TBD: synchronize access to actionMaps] */ private void updateAllProxyActions(JComponent oldFocusOwner, JComponent newFocusOwner) { if (newFocusOwner != null) { ActionMap ownerActionMap = newFocusOwner.getActionMap(); if (ownerActionMap != null) { updateProxyActions(getActionMap(), ownerActionMap, newFocusOwner); for (WeakReference appAMRef : actionMaps.values()) { ApplicationActionMap appAM = appAMRef.get(); if (appAM == null) { continue; } updateProxyActions(appAM, ownerActionMap, newFocusOwner); } } } } /* For each proxyAction in appAM: if there's an action with the same * name in the focusOwner's ActionMap, then set the proxyAction's proxy * to the matching Action. In other words: calls to the proxyAction * (actionPerformed) will delegate to the matching Action. */ private void updateProxyActions(ApplicationActionMap appAM, ActionMap ownerActionMap, JComponent focusOwner) { for (ApplicationAction proxyAction : appAM.getProxyActions()) { String proxyActionName = proxyAction.getName(); javax.swing.Action proxy = ownerActionMap.get(proxyActionName); if (proxy != null) { proxyAction.setProxy(proxy); proxyAction.setProxySource(focusOwner); } else { proxyAction.setProxy(null); proxyAction.setProxySource(null); } } } } bsaf-1.9/src/main/java/org/jdesktop/application/Application.java000066400000000000000000000650611151640306200247630ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Copyright (C) 2010 Illya Yalovyy (yalovoy@gmail.com) * Use is subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.utils.AppHelper; 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; import javax.swing.UIManager.LookAndFeelInfo; import org.jdesktop.application.utils.OSXAdapter; import org.jdesktop.application.utils.PlatformType; /** * 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} 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
  • *
  • {@code nimbus} - use the modern cross platform look and feel Nimbus *
  • a LookAndFeel class name - use the specified class *
* * @see SingleFrameApplication * @see ApplicationContext * @see UIManager#setLookAndFeel * @author Hans Muller (Hans.Muller@Sun.COM) */ @ProxyActions({"cut", "copy", "paste", "delete"}) public abstract class Application extends AbstractBean { public static final String KEY_APPLICATION_TITLE = "Application.title"; public static final String KEY_APPLICATION_ICON = "Application.icon"; public static final String KEY_APPLICATION_VENDOR_ID = "Application.vendorId"; private static final Logger logger = Logger.getLogger(Application.class.getName()); private static Application application = null; private final List exitListeners; private final ApplicationContext context; protected boolean ready; /** * 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 initialize} and {@code startup} methods. * * When UI is ready, method {@code ready} is called. * * 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() { @Override 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. * * @param applicationClass the {@code Application} class to create * @return created application instance */ 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(); final PlatformType platform = AppHelper.getPlatform(); appResourceMap.putResource(ResourceMap.KEY_PLATFORM, platform); //Generic registration with the Mac OS X application menu if (PlatformType.OS_X.equals(platform)) { try { OSXAdapter.setQuitHandler(application, Application.class.getDeclaredMethod("handleQuit", (Class[])null)); } catch (Exception e) { logger.log(Level.SEVERE, "Cannot set Mac Os X specific handler for Quit event", e); } } 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("nimbus")) { for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { UIManager.setLookAndFeel(info.getClassName()); break; } } } 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; } /* Calls 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 necessary 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. */ @SuppressWarnings("serial") 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; } @Override 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(JPanel placeHolder) { boolean qEmpty = false; 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) { //ignore } } 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 { private final JPanel placeHolder; DoWaitForEmptyEventQ() { super(Application.this); placeHolder = new JPanel(); } @Override protected Void doInBackground() { waitForEmptyEventQ(placeHolder); return null; } @Override protected void finished() { ready = true; ready(); } } /** * Gracefully shutdowns 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); } /** * Handles quit even on Mac Os X * Developer should not use it directly * @return always true */ public boolean handleQuit() { exit(); return false; } /** * Gracefully shutdowns 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(final EventObject event) { Runnable runnable = new Runnable() { @Override public void run() { 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(); } } }; if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { try { SwingUtilities.invokeAndWait(runnable); } catch (Exception ignore) { } } } /** * 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); } /** * Gives 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. * * @see #exit(EventObject) * @see #addExitListener * @see #removeExitListener */ public interface ExitListener extends EventListener { /** * The method is called before the Application exits. * * @param event the {@code EventObject} object. It will be the the value passed * to {@link #exit(EventObject) exit()}. * @return {@code true} if application can proceed with shutdown process; {@code false} if * there are pending decisions that the user must make before the app exits. */ boolean canExit(EventObject event); /** * The method is called after the exit has been confirmed. * * @param event the {@code EventObject} object. It will be the the value passed * to {@link #exit(EventObject) exit()}. */ void willExit(EventObject event); } /** * Adds an {@code ExitListener} to the list. * * @param listener the {@code ExitListener} * @see #removeExitListener * @see #getExitListeners */ public void addExitListener(ExitListener listener) { exitListeners.add(listener); } /** * Removes 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 for this Application. * * @return the Application's ApplicationContext */ public final ApplicationContext getContext() { return context; } /** * The {@code Application} singleton. *

* This method is only called after an Application has * been launched. * * @param applicationClass this Application's subclass * @return the launched Application singleton. * @see Application#launch */ public static synchronized T getInstance(Class applicationClass) { if (Beans.isDesignTime() && application==null) { try { application = create(applicationClass); } catch (Exception ex) { String msg = String.format("Couldn't construct %s", applicationClass); Logger.getLogger(Application.class.getName()).log(Level.SEVERE, msg, ex); throw new Error(msg, ex); } } checkApplicationLaunched(); return applicationClass.cast(application); } /** * The {@code Application} singleton. *

* This method is only called after an Application has * been launched. * * @return the Application singleton or a placeholder * @see Application#launch * @see Application#getInstance(Class) */ public static synchronized Application getInstance() { if (Beans.isDesignTime() && application==null) { application = new DesignTimeApplication(); } checkApplicationLaunched(); return application; } private static void checkApplicationLaunched() throws IllegalStateException { if (application == null) { throw new IllegalStateException("Application is not launched."); } } /** * Shows the application {@code View} * @param view - View to show * @see View */ public void show(View view) { Window window = (Window) view.getRootPane().getParent(); if (window != null) { window.pack(); window.setVisible(true); } } /** * Hides the application {@code View} * @param view * @see View */ public void hide(View view) { view.getRootPane().getParent().setVisible(false); } /** * The state of the initial UI. * @return true if the initial UI is ready */ public boolean isReady() { return ready; } /** * Application placeholder class * * Instance of this class is created when client * invokes static method {@code Application.getInstance()} * @author etf * @see Application#getInstance() */ private static final class DesignTimeApplication extends Application { protected DesignTimeApplication() { ApplicationContext ctx = getContext(); ctx.setApplicationClass(getClass()); ctx.setApplication(this); ResourceMap appResourceMap = ctx.getResourceMap(); appResourceMap.setPlatform(PlatformType.DEFAULT); } @Override protected void startup() { } } } bsaf-1.9/src/main/java/org/jdesktop/application/ApplicationAction.java000066400000000000000000001014301151640306200261100ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.Icon; import javax.swing.KeyStroke; /** * The {@link javax.swing.Action} class used to implement the * @Action annotation. This class is typically not * instantiated directly, it's created as a side effect of constructing * an ApplicationActionMap: *

 * public class MyActions {
 *     @Action public void anAction() { }  // an @Action named "anAction"
 * }
 * ApplicationContext ac = ApplicationContext.getInstance();
 * ActionMap actionMap = ac.getActionMap(new MyActions());
 * myButton.setAction(actionMap.get("anAction"));
 * 
* *

* When an ApplicationAction is constructed, it initializes all of its * properties from the specified ResourceMap. Resource names * must match the {@code @Action's} name, which is the name of the * corresponding method, or the value of the optional {@code @Action} name * parameter. To initialize the text and shortDescription properties * of the action named "anAction" in the previous example, one * would define two resources: *

 * anAction.Action.text = Button/Menu/etc label text for anAction
 * anAction.Action.shortDescription = Tooltip text for anAction
 * 
* *

* A complete description of the mapping between resources and Action * properties can be found in the ApplicationAction {@link * #ApplicationAction constructor} documentation. * *

* An ApplicationAction's enabled and selected * properties can be delegated to boolean properties of the * Actions class, by specifying the corresponding property names. * This can be done with the {@code @Action} annotation, e.g.: *

 * public class MyActions {
 *     @Action(enabledProperty = "anActionEnabled")
 *     public void anAction() { } 
 *     public boolean isAnActionEnabled() {
 *         // will fire PropertyChange when anActionEnabled changes 
 *         return anActionEnabled;
 *     }
 * }
 * 
* If the MyActions class supports PropertyChange events, then then * ApplicationAction will track the state of the specified property * ("anActionEnabled" in this case) with a PropertyChangeListener. * *

* ApplicationActions can automatically block the GUI while the * actionPerformed method is running, depending on the value of * block annotation parameter. For example, if the value of block is * Task.BlockingScope.ACTION, then the action will be disabled while * the actionPerformed method runs. * *

* An ApplicationAction can have a proxy Action, i.e. * another Action that provides the actionPerformed method, * the enabled/selected properties, and values for the Action's long * and short descriptions. If the proxy property is set, this * ApplicationAction tracks all of the aforementioned properties, and * the actionPerformed method just calls the proxy's * actionPerformed method. If a proxySource is * specified, then it becomes the source of the ActionEvent that's * passed to the proxy actionPerformed method. Proxy action * dispatching is as simple as this: *

 * public void actionPerformed(ActionEvent actionEvent) {
 *     javax.swing.Action proxy = getProxy();
 *     if (proxy != null) {
 *         actionEvent.setSource(getProxySource());
 *         proxy.actionPerformed(actionEvent);
 *     }
 *     // ....
 * }
 * 
* * * @author Hans Muller (Hans.Muller@Sun.COM) * @see ApplicationContext#getActionMap(Object) * @see ResourceMap */ public class ApplicationAction extends AbstractAction { private static final Logger logger = Logger.getLogger(ApplicationAction.class.getName()); private final ApplicationActionMap appAM; private final ResourceMap resourceMap; private final String actionName; // see getName() private final Method actionMethod; // The @Action method private final String enabledProperty; // names a bound appAM.getActionsClass() property private final Method isEnabledMethod; // Method object for is/getEnabledProperty private final Method setEnabledMethod; // Method object for setEnabledProperty private final String selectedProperty; // names a bound appAM.getActionsClass() property private final Method isSelectedMethod; // Method object for is/getSelectedProperty private final Method setSelectedMethod; // Method object for setSelectedProperty private final Task.BlockingScope block; private javax.swing.Action proxy = null; private Object proxySource = null; private PropertyChangeListener proxyPCL = null; /** * Construct an ApplicationAction that implements an @Action. * *

* If a {@code ResourceMap} is provided, then all of the * {@link javax.swing.Action Action} properties are initialized * with the values of resources whose key begins with {@code baseName}. * ResourceMap keys are created by appending an @Action resource * name, like "Action.shortDescription" to the @Action's baseName * For example, Given an @Action defined like this: *

     * @Action void actionBaseName() { } 
     * 
*

* Then the shortDescription resource key would be * actionBaseName.Action.shortDescription, as in: *

     * actionBaseName.Action.shortDescription = Do perform some action
     * 
* *

* The complete set of @Action resources is: *

     * Action.icon
     * Action.text
     * Action.shortDescription
     * Action.longDescription
     * Action.smallIcon
     * Action.largeIcon
     * Action.command
     * Action.accelerator
     * Action.mnemonic
     * Action.displayedMnemonicIndex
     * 
* *

* A few the resources are handled specially: *

    *
  • Action.text
    * Used to initialize the Action properties with keys * Action.NAME, Action.MNEMONIC_KEY and * Action.DISPLAYED_MNEMONIC_INDEX. * If the resources's value contains an "&" or an "_" it's * assumed to mark the following character as the mnemonic. * If Action.mnemonic/Action.displayedMnemonic resources are * also defined (an odd case), they'll override the mnemonic * specfied with the Action.text marker character. * *
  • Action.icon
    * Used to initialize both ACTION.SMALL_ICON,LARGE_ICON. If * Action.smallIcon or Action.largeIcon resources are also defined * they'll override the value defined for Action.icon. * *
  • Action.displayedMnemonicIndexKey
    * The corresponding javax.swing.Action constant is only defined in Java SE 6. * We'll set the Action property in Java SE 5 too. *
* * @param appAM the ApplicationActionMap this action is being constructed for. * @param resourceMap initial Action properties are loaded from this ResourceMap. * @param baseName the name of the @Action * @param actionMethod unless a proxy is specified, actionPerformed calls this method. * @param enabledProperty name of the enabled property. * @param selectedProperty name of the selected property. * @param block how much of the GUI to block while this action executes. * * @see #getName * @see ApplicationActionMap#getActionsClass * @see ApplicationActionMap#getActionsObject */ public ApplicationAction(ApplicationActionMap appAM, ResourceMap resourceMap, String baseName, Method actionMethod, String enabledProperty, String selectedProperty, Task.BlockingScope block) { if (appAM == null) { throw new IllegalArgumentException("null appAM"); } if (baseName == null) { throw new IllegalArgumentException("null baseName"); } this.appAM = appAM; this.resourceMap = resourceMap; this.actionName = baseName; this.actionMethod = actionMethod; this.enabledProperty = enabledProperty; this.selectedProperty = selectedProperty; this.block = block; /* If enabledProperty is specified, lookup up the is/set methods and * verify that the former exists. */ if (enabledProperty != null) { setEnabledMethod = propertySetMethod(enabledProperty, boolean.class); isEnabledMethod = propertyGetMethod(enabledProperty); if (isEnabledMethod == null) { throw newNoSuchPropertyException(enabledProperty); } } else { this.isEnabledMethod = null; this.setEnabledMethod = null; } /* If selectedProperty is specified, lookup up the is/set methods and * verify that the former exists. */ if (selectedProperty != null) { setSelectedMethod = propertySetMethod(selectedProperty, boolean.class); isSelectedMethod = propertyGetMethod(selectedProperty); if (isSelectedMethod == null) { throw newNoSuchPropertyException(selectedProperty); } super.putValue(SELECTED_KEY, invokeBooleanMethod(appAM.getActionsObject(), isSelectedMethod)); } else { this.isSelectedMethod = null; this.setSelectedMethod = null; } if (resourceMap != null) { initActionProperties(resourceMap, baseName); } } /* Shorter convenience constructor used to create ProxyActions, * see ApplicationActionMap.addProxyAction(). */ ApplicationAction(ApplicationActionMap appAM, ResourceMap resourceMap, String actionName) { this(appAM, resourceMap, actionName, null, null, null, Task.BlockingScope.NONE); } private IllegalArgumentException newNoSuchPropertyException(String propertyName) { String actionsClassName = appAM.getActionsClass().getName(); String msg = String.format("no property named %s in %s", propertyName, actionsClassName); return new IllegalArgumentException(msg); } /** * The name of the {@code @Action} enabledProperty * whose value is returned by {@link #isEnabled isEnabled}, * or null. * * @return the name of the enabledProperty or null. * @see #isEnabled */ String getEnabledProperty() { return enabledProperty; } /** * The name of the {@code @Action} selectedProperty whose value is * returned by {@link #isSelected isSelected}, or null. * * @return the name of the selectedProperty or null. * @see #isSelected */ String getSelectedProperty() { return selectedProperty; } /** * Return the proxy for this action or null. * * @return the value of the proxy property. * @see #setProxy * @see #setProxySource * @see #actionPerformed */ public javax.swing.Action getProxy() { return proxy; } /** * Set the proxy for this action. If the proxy is non-null then * we delegate/track the following: *
    *
  • actionPerformed
    * Our actionPerformed method calls the delegate's after * the ActionEvent source to be the value of getProxySource * *
  • shortDescription
    * If the proxy's shortDescription, i.e. the value for key * {@link javax.swing.Action#SHORT_DESCRIPTION SHORT_DESCRIPTION} is not null, * then set this action's shortDescription. Most Swing components use * the shortDescription to initialize their tooltip. * *
  • longDescription
    * If the proxy's longDescription, i.e. the value for key * {@link javax.swing.Action#LONG_DESCRIPTION LONG_DESCRIPTION} is not null, * then set this action's longDescription. *
* * @param proxy * @see #setProxy * @see #setProxySource * @see #actionPerformed */ public void setProxy(javax.swing.Action proxy) { javax.swing.Action oldProxy = this.proxy; this.proxy = proxy; if (oldProxy != null) { oldProxy.removePropertyChangeListener(proxyPCL); proxyPCL = null; } if (this.proxy != null) { updateProxyProperties(); proxyPCL = new ProxyPCL(); proxy.addPropertyChangeListener(proxyPCL); } else if (oldProxy != null) { setEnabled(false); setSelected(false); } firePropertyChange("proxy", oldProxy, this.proxy); } /** * Return the value that becomes the ActionEvent source before * the ActionEvent is passed along to the proxy Action. * * @return the value of the proxySource property. * @see #getProxy * @see #setProxySource * @see ActionEvent#getSource */ public Object getProxySource() { return proxySource; } /** * Set the value that becomes the ActionEvent source before * the ActionEvent is passed along to the proxy Action. * * @param source the ActionEvent source/ * @see #getProxy * @see #getProxySource * @see ActionEvent#setSource */ public void setProxySource(Object source) { Object oldValue = this.proxySource; this.proxySource = source; firePropertyChange("proxySource", oldValue, this.proxySource); } private void maybePutDescriptionValue(String key, javax.swing.Action proxy) { Object s = proxy.getValue(key); if (s instanceof String) { putValue(key, s); } } private void updateProxyProperties() { javax.swing.Action proxy = getProxy(); if (proxy != null) { setEnabled(proxy.isEnabled()); Object s = proxy.getValue(SELECTED_KEY); setSelected((s instanceof Boolean) && (Boolean) s); maybePutDescriptionValue(javax.swing.Action.SHORT_DESCRIPTION, proxy); maybePutDescriptionValue(javax.swing.Action.LONG_DESCRIPTION, proxy); } } /* This PCL is added to the proxy action, i.e. getProxy(). We * track the following properties of the proxy action we're bound to: * enabled, selected, longDescription, shortDescription. We only * mirror the description properties if they're non-null. */ private class ProxyPCL implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if ((propertyName == null) || "enabled".equals(propertyName) || "selected".equals(propertyName) || javax.swing.Action.SHORT_DESCRIPTION.equals(propertyName) || javax.swing.Action.LONG_DESCRIPTION.equals(propertyName)) { updateProxyProperties(); } } } /* Init all of the javax.swing.Action properties for the @Action * named actionName. */ private void initActionProperties(ResourceMap resourceMap, String baseName) { boolean iconOrNameSpecified = false; // true if Action's icon/name properties set // Action.text => Action.NAME,MNEMONIC_KEY,DISPLAYED_MNEMONIC_INDEX_KEY String text = resourceMap.getString(baseName + ".Action.text"); if (text != null) { MnemonicText.configure(this, text); iconOrNameSpecified = true; } // Action.mnemonic => Action.MNEMONIC_KEY Integer mnemonic = resourceMap.getKeyCode(baseName + ".Action.mnemonic"); if (mnemonic != null) { putValue(javax.swing.Action.MNEMONIC_KEY, mnemonic); } // Action.mnemonic => Action.DISPLAYED_MNEMONIC_INDEX_KEY Integer index = resourceMap.getInteger(baseName + ".Action.displayedMnemonicIndex"); if (index != null) { putValue(DISPLAYED_MNEMONIC_INDEX_KEY, index); } // Action.accelerator => Action.ACCELERATOR_KEY KeyStroke key = resourceMap.getKeyStroke(baseName + ".Action.accelerator"); if (key != null) { putValue(javax.swing.Action.ACCELERATOR_KEY, key); } // Action.icon => Action.SMALL_ICON,LARGE_ICON_KEY Icon icon = resourceMap.getIcon(baseName + ".Action.icon"); if (icon != null) { putValue(javax.swing.Action.SMALL_ICON, icon); putValue(LARGE_ICON_KEY, icon); iconOrNameSpecified = true; } // Action.smallIcon => Action.SMALL_ICON Icon smallIcon = resourceMap.getIcon(baseName + ".Action.smallIcon"); if (smallIcon != null) { putValue(javax.swing.Action.SMALL_ICON, smallIcon); iconOrNameSpecified = true; } // Action.largeIcon => Action.LARGE_ICON Icon largeIcon = resourceMap.getIcon(baseName + ".Action.largeIcon"); if (largeIcon != null) { putValue(LARGE_ICON_KEY, largeIcon); iconOrNameSpecified = true; } // Action.shortDescription => Action.SHORT_DESCRIPTION String shortDescription = resourceMap.getString(baseName + ".Action.shortDescription"); if (shortDescription != null && !shortDescription.isEmpty()) { putValue(javax.swing.Action.SHORT_DESCRIPTION, resourceMap.getString(baseName + ".Action.shortDescription")); } // Action.longDescription => Action.LONG_DESCRIPTION putValue(javax.swing.Action.LONG_DESCRIPTION, resourceMap.getString(baseName + ".Action.longDescription")); // Action.command => Action.ACTION_COMMAND_KEY putValue(javax.swing.Action.ACTION_COMMAND_KEY, resourceMap.getString(baseName + ".Action.command")); // If no visual was defined for this Action, i.e. no text // and no icon, then we default Action.NAME if (!iconOrNameSpecified) { putValue(javax.swing.Action.NAME, actionName); } } private String propertyMethodName(String prefix, String propertyName) { return prefix + propertyName.substring(0, 1).toUpperCase(java.util.Locale.ENGLISH) + propertyName.substring(1); } private Method propertyGetMethod(String propertyName) { String[] getMethodNames = { propertyMethodName("is", propertyName), propertyMethodName("get", propertyName) }; Class actionsClass = appAM.getActionsClass(); for (String name : getMethodNames) { try { return actionsClass.getMethod(name); } catch (NoSuchMethodException ignore) { } } return null; } private Method propertySetMethod(String propertyName, Class type) { Class actionsClass = appAM.getActionsClass(); try { return actionsClass.getMethod(propertyMethodName("set", propertyName), type); } catch (NoSuchMethodException ignore) { return null; } } /** * * The name of this Action. This string begins with the name * the corresponding @Action method (unless the name * @Action parameter was specified). * *

* This name is used as a prefix to look up action resources, * and the ApplicationContext Framework uses it as the key for this * Action in ApplicationActionMaps. * *

* Note: this property should not confused with the {@link * javax.swing.Action#NAME Action.NAME} key. That key is actually * used to initialize the text properties of Swing * components, which is why we call the corresponding * ApplicationAction resource "Action.text", as in: *

 
     * myCloseButton.Action.text = Close 
     * 
* * * @return the (read-only) value of the name property */ public String getName() { return actionName; } /** * The resourceMap for this Action. * * @return the (read-only) value of the resourceMap property */ public ResourceMap getResourceMap() { return resourceMap; } /** * * Provides parameter values to @Action methods. By default, parameter * values are selected based exclusively on their type: * * * * * * * * * * * * * * * * * * * * * * * * * *
Parameter TypeParameter Value
ActionEventactionEvent
javax.swing.Actionthis ApplicationAction object
ActionMapthe ActionMap that contains this Action
ResourceMapthe ResourceMap of the the ActionMap that contains this Action
ApplicationContextthe value of ApplicationContext.getInstance()
* *

* ApplicationAction subclasses may also select values based on * the value of the Action.Parameter annotation, which is * passed along as the pKey argument to this method: *

     * @Action public void doAction(@Action.Parameter("myKey") String myParameter) {
     *    // The value of myParameter is computed by:
     *    // getActionArgument(String.class, "myKey", actionEvent)
     * }
     * 
* *

* If pType and pKey aren't recognized, this method * calls {@link #actionFailed} with an IllegalArgumentException. * * * @param pType parameter type * @param pKey the value of the @Action.Parameter annotation * @param actionEvent the ActionEvent that trigged this Action */ protected Object getActionArgument(Class pType, String pKey, ActionEvent actionEvent) { Object argument = null; if (pType == ActionEvent.class) { argument = actionEvent; } else if (pType == javax.swing.Action.class) { argument = this; } else if (pType == ActionMap.class) { argument = appAM; } else if (pType == ResourceMap.class) { argument = resourceMap; } else if (pType == ApplicationContext.class) { argument = appAM.getContext(); } else if (pType == Application.class) { argument = appAM.getContext().getApplication(); } else { Exception e = new IllegalArgumentException("unrecognized @Action method parameter"); actionFailed(e); } return argument; } private Task.InputBlocker createInputBlocker(Task task, ActionEvent event) { Object target = event.getSource(); if (block == Task.BlockingScope.ACTION) { target = this; } return new DefaultInputBlocker(task, block, target, this); } private void noProxyActionPerformed(ActionEvent actionEvent) { Object taskObject = null; /* Create the arguments array for actionMethod by * calling getActionArgument() for each parameter. */ Annotation[][] allPAnnotations = actionMethod.getParameterAnnotations(); Class[] pTypes = actionMethod.getParameterTypes(); Object[] arguments = new Object[pTypes.length]; for (int i = 0; i < pTypes.length; i++) { String pKey = null; for (Annotation pAnnotation : allPAnnotations[i]) { if (pAnnotation instanceof Action.Parameter) { pKey = ((Action.Parameter) pAnnotation).value(); break; } } arguments[i] = getActionArgument(pTypes[i], pKey, actionEvent); } /* Call target.actionMethod(arguments). If the return value * is a Task, then execute it. */ try { Object target = appAM.getActionsObject(); taskObject = actionMethod.invoke(target, arguments); } catch (Exception e) { actionFailed(e); } if (taskObject instanceof Task) { Task task = (Task) taskObject; if (task.getInputBlocker() == null) { task.setInputBlocker(createInputBlocker(task, actionEvent)); } ApplicationContext ctx = appAM.getContext(); ctx.getTaskService().execute(task); } } /** * This method implements this Action's behavior. *

* If there's a proxy Action then call its actionPerformed * method. Otherwise, call the @Action method with parameter * values provided by {@code getActionArgument()}. If anything goes wrong * call {@code actionFailed()}. * * @param actionEvent @{inheritDoc} * @see #setProxy * @see #getActionArgument * @see Task */ public void actionPerformed(ActionEvent actionEvent) { javax.swing.Action proxy = getProxy(); if (proxy != null) { actionEvent.setSource(getProxySource()); proxy.actionPerformed(actionEvent); } else if (actionMethod != null) { noProxyActionPerformed(actionEvent); } } /** * If the proxy action is null and {@code enabledProperty} was * specified, then return the value of the enabled property's * is/get method applied to our ApplicationActionMap's * {@code actionsObject}. * Otherwise return the value of this Action's enabled property. * * @return {@inheritDoc} * @see #setProxy * @see #setEnabled * @see ApplicationActionMap#getActionsObject */ @Override public boolean isEnabled() { if ((getProxy() != null) || (isEnabledMethod == null)) { return super.isEnabled(); } else { try { Object b = isEnabledMethod.invoke(appAM.getActionsObject()); return (Boolean) b; } catch (Exception e) { throw newInvokeError(isEnabledMethod, e); } } } /** * If the proxy action is null and {@code enabledProperty} was * specified, then set the value of the enabled property by * invoking the corresponding {@code set} method on our * ApplicationActionMap's {@code actionsObject}. * Otherwise set the value of this Action's enabled property. * * @param enabled {@inheritDoc} * @see #setProxy * @see #isEnabled * @see ApplicationActionMap#getActionsObject */ @Override public void setEnabled(boolean enabled) { if ((getProxy() != null) || (setEnabledMethod == null)) { super.setEnabled(enabled); } else { try { setEnabledMethod.invoke(appAM.getActionsObject(), enabled); } catch (Exception e) { throw newInvokeError(setEnabledMethod, e, enabled); } } } /** * If the proxy action is null and {@code selectedProperty} was * specified, then return the value of the selected property's * is/get method applied to our ApplicationActionMap's {@code actionsObject}. * Otherwise return the value of this Action's enabled property. * * @return true if this Action's JToggleButton is selected * @see #setProxy * @see #setSelected * @see ApplicationActionMap#getActionsObject */ public boolean isSelected() { if ((getProxy() != null) || (isSelectedMethod == null)) { Object v = getValue(SELECTED_KEY); return (v instanceof Boolean) && (Boolean) v; } else { return invokeBooleanMethod(appAM.getActionsObject(), isSelectedMethod); } } private Boolean invokeBooleanMethod(Object obj, Method method) { try { Object b = method.invoke(obj); return (Boolean) b; } catch (Exception e) { throw newInvokeError(method, e); } } /** * If the proxy action is null and {@code selectedProperty} was * specified, then set the value of the selected property by * invoking the corresponding {@code set} method on our * ApplicationActionMap's {@code actionsObject}. * Otherwise set the value of this Action's selected property. * * @param selected this Action's JToggleButton's value * @see #setProxy * @see #isSelected * @see ApplicationActionMap#getActionsObject */ public void setSelected(boolean selected) { if ((getProxy() != null) || (setSelectedMethod == null)) { super.putValue(SELECTED_KEY, Boolean.valueOf(selected)); } else { try { super.putValue(SELECTED_KEY, Boolean.valueOf(selected)); if (selected != isSelected()) { setSelectedMethod.invoke(appAM.getActionsObject(), selected); } } catch (Exception e) { throw newInvokeError(setSelectedMethod, e, selected); } } } /** * Keeps the {@code @Action selectedProperty} in sync when * the value of {@code key} is {@code Action.SELECTED_KEY}. * * @param key {@inheritDoc} * @param value {@inheritDoc} */ public void putValue(String key, Object value) { if (SELECTED_KEY.equals(key) && (value instanceof Boolean)) { setSelected((Boolean) value); } else { super.putValue(key, value); } } /* Throw an Error because invoking Method m on the actionsObject, * with the specified arguments, failed. */ private Error newInvokeError(Method m, Exception e, Object... args) { StringBuilder argsString = new StringBuilder((args.length == 0) ? "" : args[0].toString()); for (int i = 1; i < args.length; i++) { argsString.append(", ").append(args[i].toString()); } String actionsClassName = appAM.getActionsObject().getClass().getName(); String msg = String.format("%s.%s(%s) failed", actionsClassName, m, argsString.toString()); return new Error(msg, e); } /* Forward the @Action class's PropertyChangeEvent e to this * Action's PropertyChangeListeners using actionPropertyName instead * the original @Action class's property name. This method is used * by ApplicationActionMap#ActionsPCL to forward @Action * enabledProperty and selectedProperty changes. */ void forwardPropertyChangeEvent(PropertyChangeEvent e, String actionPropertyName) { if ("selected".equals(actionPropertyName) && (e.getNewValue() instanceof Boolean)) { putValue(SELECTED_KEY, e.getNewValue()); } firePropertyChange(actionPropertyName, e.getOldValue(), e.getNewValue()); } /* Log enough output for a developer to figure out * what went wrong. */ private void actionFailed(Exception e) { // TBD Log an error // e.printStackTrace(); throw new Error(e); } /** * Returns a string representation of this * ApplicationAction that should be useful for debugging. * If the action is enabled it's name is enclosed by parentheses; * if it's selected then a "+" appears after the name. If the * action will appear with a text label, then that's included too. * If the action has a proxy, then we append the string for * the proxy action. * * @return A string representation of this ApplicationAction */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getName()); sb.append(" "); boolean enabled = isEnabled(); if (!enabled) { sb.append("("); } sb.append(getName()); Object selectedValue = getValue(SELECTED_KEY); if (selectedValue instanceof Boolean) { if ((Boolean) selectedValue) { sb.append("+"); } } if (!enabled) { sb.append(")"); } Object nameValue = getValue(javax.swing.Action.NAME); // [getName()].Action.text if (nameValue instanceof String) { sb.append(" \""); sb.append((String) nameValue); sb.append("\""); } proxy = getProxy(); if (proxy != null) { sb.append(" Proxy for: "); sb.append(proxy.toString()); } return sb.toString(); } } bsaf-1.9/src/main/java/org/jdesktop/application/ApplicationActionMap.java000066400000000000000000000230021151640306200265440ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.ActionMap; /** * An {@link javax.swing.ActionMap ActionMap} class where each entry * corresponds to an @Action method from a single * actionsClass (i.e. a class that contains one or more * @Actions). Each entry's key is the @Action's * name (the method name by default), and the value is an * {@link ApplicationAction} that calls the @Actions method. * For example, the code below prints "Hello World": *

 * public class HelloWorldActions {
 *     public @Action void Hello() { System.out.print("Hello "); }
 *     public @Action void World() { System.out.println("World"); }
 * }
 * // ...
 * ApplicationActionMap appAM = new ApplicationActionMap(SimpleActions.class);
 * ActionEvent e = new ActionEvent("no src", ActionEvent.ACTION_PERFORMED, "no cmd");
 * appAM.get("Hello").actionPerformed(e);
 * appAM.get("World").actionPerformed(e);
 * 
* *

* If a ResourceMap is provided then each * ApplicationAction's ({@link javax.swing.Action#putValue * putValue}, {@link javax.swing.Action#getValue getValue}) properties * are initialized from the ResourceMap. * * @see ApplicationAction * @see ResourceMap * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationActionMap extends ActionMap { private final ApplicationContext context; private final ResourceMap resourceMap; private final Class actionsClass; private final Object actionsObject; private final List proxyActions; /** * Creates {@code ApplicationActionMap} object. *

* The created action map will contain actions which are defined in the {@code actionsObject} and all * its ancestors up to the {@code actionsClass}. If {@code actionsClass} is a type of the {@code actionsObject} then * actions only from this class will be added to the map. * * @param context the Application context * @param actionsClass a super class for the {@code actionsObject}. Actions will be retrieved starting from this class. * @param actionsObject the object to be scanned for the actions. * @param resourceMap the {@code ResourceMap} to be used for those actions */ public ApplicationActionMap(ApplicationContext context, Class actionsClass, Object actionsObject, ResourceMap resourceMap) { if (context == null) { throw new IllegalArgumentException("null context"); } if (actionsClass == null) { throw new IllegalArgumentException("null actionsClass"); } if (actionsObject == null) { throw new IllegalArgumentException("null actionsObject"); } if (!(actionsClass.isInstance(actionsObject))) { throw new IllegalArgumentException("actionsObject not an instanceof actionsClass"); } this.context = context; this.actionsClass = actionsClass; this.actionsObject = actionsObject; this.resourceMap = resourceMap; this.proxyActions = new ArrayList(); addAnnotationActions(resourceMap); maybeAddActionsPCL(); } /** * Returns the {@code ApplicationContext} * @return the {@code ApplicationContext} */ public final ApplicationContext getContext() { return context; } /** * Returns the base class for actions retrieval * @return the base class for actions retrieval */ public final Class getActionsClass() { return actionsClass; } /** * Returns the object with actions * @return the object with actions */ public final Object getActionsObject() { return actionsObject; } /** * All of the {@code @ProxyActions} recursively defined by this * {@code ApplicationActionMap} and its parent ancestors. *

* Returns a read-only list of the {@code @ProxyActions} defined * by this {@code ApplicationActionMap's} {@code actionClass} * and, recursively, by this {@code ApplicationActionMap's} parent. * If there are no proxyActions, an empty list is returned. * * @return a list of all the proxyActions for this {@code ApplicationActionMap} */ public List getProxyActions() { // TBD: proxyActions that shadow should be merged ArrayList allProxyActions = new ArrayList(proxyActions); ActionMap parent = getParent(); while (parent != null) { if (parent instanceof ApplicationActionMap) { allProxyActions.addAll(((ApplicationActionMap) parent).proxyActions); } parent = parent.getParent(); } return Collections.unmodifiableList(allProxyActions); } private String aString(String s, String emptyValue) { return (s.length() == 0) ? emptyValue : s; } private void putAction(String key, ApplicationAction action) { if (get(key) != null) { // TBD log a warning - two actions with the same key } put(key, action); } /* Add Actions for each actionsClass method with an @Action * annotation and for the class's @ProxyActions annotation */ private void addAnnotationActions(ResourceMap resourceMap) { Class actionsClass = getActionsClass(); // @Action for (Method m : actionsClass.getDeclaredMethods()) { Action action = m.getAnnotation(Action.class); if (action != null) { String methodName = m.getName(); String enabledProperty = aString(action.enabledProperty(), null); String selectedProperty = aString(action.selectedProperty(), null); String actionName = aString(action.name(), methodName); Task.BlockingScope block = action.block(); ApplicationAction appAction = new ApplicationAction(this, resourceMap, actionName, m, enabledProperty, selectedProperty, block); putAction(actionName, appAction); } } // @ProxyActions ProxyActions proxyActionsAnnotation = actionsClass.getAnnotation(ProxyActions.class); if (proxyActionsAnnotation != null) { for (String actionName : proxyActionsAnnotation.value()) { ApplicationAction appAction = new ApplicationAction(this, resourceMap, actionName); appAction.setEnabled(false); // will track the enabled property of the Action it's bound to putAction(actionName, appAction); proxyActions.add(appAction); } } } /* If any of the ApplicationActions need to track an * enabled or selected property defined in actionsClass, then add our * PropertyChangeListener. If none of the @Actions in actionClass * provide an enabledProperty or selectedProperty argument, then * we don't need to do this. */ private void maybeAddActionsPCL() { boolean needsPCL = false; Object[] keys = keys(); if (keys != null) { for (Object key : keys) { javax.swing.Action value = get(key); if (value instanceof ApplicationAction) { ApplicationAction actionAdapter = (ApplicationAction) value; if ((actionAdapter.getEnabledProperty() != null) || (actionAdapter.getSelectedProperty() != null)) { needsPCL = true; break; } } } if (needsPCL) { try { Class actionsClass = getActionsClass(); Method m = actionsClass.getMethod("addPropertyChangeListener", PropertyChangeListener.class); m.invoke(getActionsObject(), new ActionsPCL()); } catch (Exception e) { String s = "addPropertyChangeListener undefined " + actionsClass; throw new Error(s, e); } } } } /* When the value of an actionsClass @Action enabledProperty or * selectedProperty changes, forward the PropertyChangeEvent to * the ApplicationAction object itself. */ private class ActionsPCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent event) { String propertyName = event.getPropertyName(); Object[] keys = keys(); if (keys != null) { for (Object key : keys) { javax.swing.Action value = get(key); if (value instanceof ApplicationAction) { ApplicationAction appAction = (ApplicationAction) value; if (propertyName.equals(appAction.getEnabledProperty())) { appAction.forwardPropertyChangeEvent(event, "enabled"); } else if (propertyName.equals(appAction.getSelectedProperty())) { appAction.forwardPropertyChangeEvent(event, "selected"); } } } } } } } bsaf-1.9/src/main/java/org/jdesktop/application/ApplicationContext.java000066400000000000000000000422521151640306200263250ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; import javax.swing.JComponent; /** * A singleton that manages shared objects, like actions, resources, and tasks, * for {@code Applications}. *

* {@link Application Applications} use {@code ApplicationContext}, * via {@link Application#getContext}, to access global values and services. * The majority of the Swing Application Framework API can be accessed through {@code * ApplicationContext}. * * @see Application * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationContext extends AbstractBean { private static final Logger logger = Logger.getLogger(ApplicationContext.class.getName()); private final List taskServices; private final List taskServicesReadOnly; private ResourceManager resourceManager; private ActionManager actionManager; private LocalStorage localStorage; private SessionStorage sessionStorage; private Application application = null; private Class applicationClass = null; private JComponent focusOwner = null; private Clipboard clipboard = null; private TaskMonitor taskMonitor = null; protected ApplicationContext() { resourceManager = new ResourceManager(this); actionManager = new ActionManager(this); localStorage = new LocalStorage(this); sessionStorage = new SessionStorage(this); taskServices = new CopyOnWriteArrayList(); taskServices.add(new TaskService("default")); taskServicesReadOnly = Collections.unmodifiableList(taskServices); } /** * Returns the application's class or null if the application * hasn't been launched and this property hasn't been set. Once * the application has been launched, the value returned by this * method is the same as {@code getApplication().getClass()}. * * @return the application's class or null * @see #setApplicationClass * @see #getApplication */ public final synchronized Class getApplicationClass() { return applicationClass; } /** * Called by * {@link Application#launch Application.launch()} to * record the application's class. *

* This method is only intended for testing, or design time * configuration. Normal applications shouldn't need to * call it directly. * * @param applicationClass * @see #getApplicationClass */ public final synchronized void setApplicationClass(Class applicationClass) { if (this.application != null) { throw new IllegalStateException("application has been launched"); } this.applicationClass = applicationClass; } /** * The {@code Application} singleton, or null if {@code launch} hasn't * been called yet. * * @return the launched Application singleton. * @see Application#launch */ public final synchronized Application getApplication() { return application; } /* Called by Application.launch(). */ synchronized void setApplication(Application application) { if (this.application != null) { throw new IllegalStateException("application has already been launched"); } this.application = application; } /** * The application's {@code ResourceManager} provides * read-only cached access to resources in ResourceBundles via the * {@link ResourceMap ResourceMap} class. * * @return this application's ResourceManager. * @see #getResourceMap(Class, Class) */ public final ResourceManager getResourceManager() { return resourceManager; } /** * Change this application's {@code ResourceManager}. An * {@code ApplicationContext} subclass that * wanted to fundamentally change the way {@code ResourceMaps} were * created and cached could replace this property in its constructor. *

* Throws an IllegalArgumentException if resourceManager is null. * * @param resourceManager the new value of the resourceManager property. * @see #getResourceMap(Class, Class) * @see #getResourceManager */ protected void setResourceManager(ResourceManager resourceManager) { if (resourceManager == null) { throw new IllegalArgumentException("null resourceManager"); } Object oldValue = this.resourceManager; this.resourceManager = resourceManager; firePropertyChange("resourceManager", oldValue, this.resourceManager); } /** * Returns a {@link ResourceMap#getParent chain} of two or * more ResourceMaps. The first encapsulates the ResourceBundles * defined for the specified class, and its parent * encapsulates the ResourceBundles defined for the entire application. *

* This is just a convenience method that calls * {@link ResourceManager#getResourceMap(Class, Class) * ResourceManager.getResourceMap()}. It's defined as: *

     * return getResourceManager().getResourceMap(cls, cls);
     * 
* * @param cls the class that defines the location of ResourceBundles * @return a {@code ResourceMap} that contains resources loaded from * {@code ResourceBundles} found in the resources subpackage of the * specified class's package. * @see ResourceManager#getResourceMap(Class) */ public final ResourceMap getResourceMap(Class cls) { return getResourceManager().getResourceMap(cls, cls); } /** * Returns a {@link ResourceMap#getParent chain} of two or more * ResourceMaps. The first encapsulates the ResourceBundles * defined for the all of the classes between {@code startClass} * and {@code stopClass} inclusive. It's parent encapsulates the * ResourceBundles defined for the entire application. *

* This is just a convenience method that calls * {@link ResourceManager#getResourceMap(Class, Class) * ResourceManager.getResourceMap()}. It's defined as: *

     * return getResourceManager().getResourceMap(startClass, stopClass);
     * 
* * @param startClass the first class whose ResourceBundles will be included * @param stopClass the last class whose ResourceBundles will be included * @return a {@code ResourceMap} that contains resources loaded from * {@code ResourceBundles} found in the resources subpackage of the * specified class's package. * @see ResourceManager#getResourceMap(Class, Class) */ public final ResourceMap getResourceMap(Class startClass, Class stopClass) { return getResourceManager().getResourceMap(startClass, stopClass); } /** * Returns the {@link ResourceMap#getParent chain} of ResourceMaps * that's shared by the entire application, beginning with the one * defined for the Application class, i.e. the value of the * {@code applicationClass} property. *

* This is just a convenience method that calls * {@link ResourceManager#getResourceMap() * ResourceManager.getResourceMap()}. It's defined as: *

     * return getResourceManager().getResourceMap();
     * 
* * @return the Application's ResourceMap * @see ResourceManager#getResourceMap() * @see #getApplicationClass */ public final ResourceMap getResourceMap() { return getResourceManager().getResourceMap(); } /** * Return this application's ActionManager. * @return this application's ActionManager. * @see #getActionMap(Object) */ public final ActionManager getActionManager() { return actionManager; } /** * Change this application's {@code ActionManager}. An * {@code ApplicationContext} subclass that * wanted to fundamentally change the way {@code ActionManagers} were * created and cached could replace this property in its constructor. *

* Throws an IllegalArgumentException if actionManager is null. * * @param actionManager the new value of the actionManager property. * @see #getActionManager * @see #getActionMap(Object) */ protected void setActionManager(ActionManager actionManager) { if (actionManager == null) { throw new IllegalArgumentException("null actionManager"); } Object oldValue = this.actionManager; this.actionManager = actionManager; firePropertyChange("actionManager", oldValue, this.actionManager); } /** * Returns the shared {@code ActionMap} chain for the entire {@code Application}. *

* This is just a convenience method that calls * {@link ActionManager#getActionMap() * ActionManager.getActionMap()}. It's defined as: *

     * return getActionManager().getActionMap()
     * 
* * @return the {@code ActionMap} chain for the entire {@code Application}. * @see ActionManager#getActionMap() */ public final ApplicationActionMap getActionMap() { return getActionManager().getActionMap(); } /** * Returns the {@code ApplicationActionMap} chain for the specified * actions class and target object. *

* This is just a convenience method that calls * {@link ActionManager#getActionMap() * ActionManager.getActionMap(Class, Object)}. It's defined as: *

     * return getActionManager().getActionMap(actionsClass, actionsObject)
     * 
* * @param actionsClass * @param actionsObject * @return the {@code ActionMap} chain for the entire {@code Application}. * @see ActionManager#getActionMap(Class, Object) */ public final ApplicationActionMap getActionMap(Class actionsClass, Object actionsObject) { return getActionManager().getActionMap(actionsClass, actionsObject); } /** * Defined as {@code getActionMap(actionsObject.getClass(), actionsObject)}. * * @param actionsObject * @return the {@code ActionMap} for the specified object * @see #getActionMap(Class, Object) */ public final ApplicationActionMap getActionMap(Object actionsObject) { if (actionsObject == null) { throw new IllegalArgumentException("null actionsObject"); } return getActionManager().getActionMap(actionsObject.getClass(), actionsObject); } /** * The shared {@link LocalStorage LocalStorage} object. * * @return the shared {@link LocalStorage LocalStorage} object. */ public final LocalStorage getLocalStorage() { return localStorage; } /** * The shared {@link LocalStorage LocalStorage} object. * * @param localStorage the shared {@link LocalStorage LocalStorage} object. */ protected void setLocalStorage(LocalStorage localStorage) { if (localStorage == null) { throw new IllegalArgumentException("null localStorage"); } Object oldValue = this.localStorage; this.localStorage = localStorage; firePropertyChange("localStorage", oldValue, this.localStorage); } /** * The shared {@link SessionStorage SessionStorage} object. * * @return the shared {@link SessionStorage SessionStorage} object. */ public final SessionStorage getSessionStorage() { return sessionStorage; } /** * The shared {@link SessionStorage SessionStorage} object. * * @param sessionStorage the shared {@link SessionStorage SessionStorage} object. */ protected void setSessionStorage(SessionStorage sessionStorage) { if (sessionStorage == null) { throw new IllegalArgumentException("null sessionStorage"); } Object oldValue = this.sessionStorage; this.sessionStorage = sessionStorage; firePropertyChange("sessionStorage", oldValue, this.sessionStorage); } /** * Return a shared {@code Clipboard}. * @return A shared {@code Clipboard}. */ public Clipboard getClipboard() { if (clipboard == null) { try { clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); } catch (SecurityException e) { clipboard = new Clipboard("sandbox"); } } return clipboard; } /** * Returns the application's focus owner. * @return The application's focus owner. */ public JComponent getFocusOwner() { return focusOwner; } /** * Changes the application's focus owner. * @param focusOwner new focus owner */ void setFocusOwner(JComponent focusOwner) { Object oldValue = this.focusOwner; this.focusOwner = focusOwner; firePropertyChange("focusOwner", oldValue, this.focusOwner); } private List copyTaskServices() { return new ArrayList(taskServices); } /** * Register a new TaskService with the application. The task service * then be retrieved by name via {@link ApplicationContext#getTaskService(String)}. * * @param taskService Task service to register */ public void addTaskService(TaskService taskService) { if (taskService == null) { throw new IllegalArgumentException("null taskService"); } List oldValue = null, newValue = null; boolean changed = false; synchronized (taskServices) { if (!taskServices.contains(taskService)) { oldValue = copyTaskServices(); taskServices.add(taskService); newValue = copyTaskServices(); changed = true; } } if (changed) { firePropertyChange("taskServices", oldValue, newValue); } } /** * Unregister a previously registered TaskService. The task service * is not shut down. * * @param taskService TaskService to unregister */ public void removeTaskService(TaskService taskService) { if (taskService == null) { throw new IllegalArgumentException("null taskService"); } List oldValue = null, newValue = null; boolean changed = false; synchronized (taskServices) { if (taskServices.contains(taskService)) { oldValue = copyTaskServices(); taskServices.remove(taskService); newValue = copyTaskServices(); changed = true; } } if (changed) { firePropertyChange("taskServices", oldValue, newValue); } } /** * Look up a task service by name. * * @param name Name of the task service to retrieve. * @return Task service found, or null if no service of that name found */ public TaskService getTaskService(String name) { if (name == null) { throw new IllegalArgumentException("null name"); } for (TaskService taskService : taskServices) { if (name.equals(taskService.getName())) { return taskService; } } return null; } /** * Returns the default TaskService, i.e. the one named "default": * return getTaskService("default"). The * {@link ApplicationAction#actionPerformed ApplicationAction actionPerformed} * method executes background Tasks on the default * TaskService. Application's can launch Tasks in the same way, e.g. *
     * Application.getInstance().getContext().getTaskService().execute(myTask);
     * 
* * @return the default TaskService. * @see #getTaskService(String) * */ public final TaskService getTaskService() { return getTaskService("default"); } /** * Returns a read-only view of the complete list of TaskServices. * * @return a list of all of the TaskServices. * @see #addTaskService * @see #removeTaskService */ public List getTaskServices() { return taskServicesReadOnly; } /** * Returns a shared TaskMonitor object. Most applications only * need one TaskMonitor for the sake of status bars and other status * indicators. * * @return the shared TaskMonitor object. */ public final TaskMonitor getTaskMonitor() { if (taskMonitor == null) { taskMonitor = new TaskMonitor(this); } return taskMonitor; } } bsaf-1.9/src/main/java/org/jdesktop/application/DefaultInputBlocker.java000066400000000000000000000400461151640306200264220ustar00rootroot00000000000000 /* * Copyright (C) 2006-2009 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Font; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.RootPaneContainer; import javax.swing.Timer; import javax.swing.event.MouseInputAdapter; import javax.swing.event.MouseInputListener; import static org.jdesktop.application.utils.SwingHelper.findRootPaneContainer; final class DefaultInputBlocker extends Task.InputBlocker { private static final Logger logger = Logger.getLogger(DefaultInputBlocker.class.getName()); private static final String PB_STRING_FORMAT_KEY = "progressBarStringFormat"; public static final String ON_ESCAPE_ACTION_KEY = "onEscape"; private JDialog modalDialog = null; DefaultInputBlocker(Task task, Task.BlockingScope scope, Object target, ApplicationAction action) { super(task, scope, target, action); } private void setActionTargetBlocked(boolean f) { javax.swing.Action action = (javax.swing.Action) getTarget(); action.setEnabled(!f); } private void setComponentTargetBlocked(boolean f) { Component c = (Component) getTarget(); c.setEnabled(!f); // Note: can't set the cursor on a disabled component } /* Accumulates a list of all of the descendants of root whose name * begins with "BlockingDialog" */ private void blockingDialogComponents(Component root, List rv) { String rootName = root.getName(); if ((rootName != null) && rootName.startsWith("BlockingDialog")) { rv.add(root); } if (root instanceof Container) { for (Component child : ((Container) root).getComponents()) { blockingDialogComponents(child, rv); } } } private List blockingDialogComponents(Component root) { List rv = new ArrayList(); blockingDialogComponents(root, rv); return rv; } /* Inject resources from both the Task's ResourceMap and the * ApplicationAction's ResourceMap. We add the action's name * prefix to all of the components before the second step. */ private void injectBlockingDialogComponents(Component root) { ResourceMap taskResourceMap = getTask().getResourceMap(); if (taskResourceMap != null) { taskResourceMap.injectComponents(root); } ApplicationAction action = getAction(); if (action != null) { ResourceMap actionResourceMap = action.getResourceMap(); String actionName = action.getName(); for (Component c : blockingDialogComponents(root)) { c.setName(actionName + "." + c.getName()); } actionResourceMap.injectComponents(root); } } /* Creates a dialog whose visuals are initialized from the * following Task resources: * BlockingDialog.title * BlockingDialog.optionPane.icon * BlockingDialog.optionPane.message * BlockingDialog.cancelButton.text * BlockingDialog.cancelButton.icon * BlockingDialog.progressBar.stringPainted * * If the Task has an Action then use the actionName as a prefix * and look up the resources again, in the action's ResourceMap * (that's the @Action's ApplicationActionMap ResourceMap really): * actionName.BlockingDialog.title * actionName.BlockingDialog.optionPane.icon * actionName.BlockingDialog.optionPane.message * actionName.BlockingDialog.cancelButton.text * actionName.BlockingDialog.cancelButton.icon * actionName.BlockingDialog.progressBar.stringPainted */ private JDialog createBlockingDialog() { JOptionPane optionPane = new JOptionPane(); /* If the task can be canceled, then add the cancel * button. Otherwise clear the default OK button. */ if (getTask().getUserCanCancel()) { JButton cancelButton = new JButton(); cancelButton.setName("BlockingDialog.cancelButton"); ActionListener doCancelTask = new ActionListener() { @Override public void actionPerformed(ActionEvent ignore) { getTask().cancel(true); } }; cancelButton.addActionListener(doCancelTask); optionPane.setOptions(new Object[]{cancelButton}); } else { optionPane.setOptions(new Object[]{}); // no OK button } /* Replace default action assigned to ESC key stroke * */ optionPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ON_ESCAPE_ACTION_KEY); /* Create the JDialog. If the task can be canceled, then * map closing the dialog window to canceling the task. * * BSAF-77 related: Dialog should appear centered over the window * ancestor of the target component, not over the component itself. * Therefore, use findRootPaneContainer and cast to Component. */ Component dialogOwner = (Component) getTarget(); String taskTitle = getTask().getTitle(); String dialogTitle = (taskTitle == null) ? "BlockingDialog" : taskTitle; final JDialog dialog = optionPane.createDialog((Component) findRootPaneContainer(dialogOwner), dialogTitle); dialog.setModal(true); dialog.setName("BlockingDialog"); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); WindowListener dialogCloseListener = new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { if (getTask().getUserCanCancel()) { getTask().cancel(true); dialog.setVisible(false); } } }; dialog.addWindowListener(dialogCloseListener); optionPane.setName("BlockingDialog.optionPane"); injectBlockingDialogComponents(dialog); /* Reset the JOptionPane's message property after injecting * an initial value for the message string. */ recreateOptionPaneMessage(optionPane); dialog.pack(); return dialog; } /* Replace the default message panel with one that where the * message text can be selected and that includes a status bar for * task progress. We inject resources here because the * JOptionPane#setMessage() doesn't add the panel to the JOptionPane * immediately. */ private void recreateOptionPaneMessage(JOptionPane optionPane) { Object message = optionPane.getMessage(); if (message instanceof String) { Font font = optionPane.getFont(); final JTextArea textArea = new JTextArea((String) message); textArea.setFont(font); int lh = textArea.getFontMetrics(font).getHeight(); Insets margin = new Insets(0, 0, lh, 24); // top left bottom right textArea.setMargin(margin); textArea.setEditable(false); textArea.setWrapStyleWord(true); textArea.setBackground(optionPane.getBackground()); JPanel panel = new JPanel(new BorderLayout()); panel.add(textArea, BorderLayout.CENTER); final JProgressBar progressBar = new JProgressBar(); progressBar.setName("BlockingDialog.progressBar"); progressBar.setIndeterminate(true); PropertyChangeListener taskPCL = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent e) { if ("progress".equals(e.getPropertyName())) { progressBar.setIndeterminate(false); progressBar.setValue((Integer) e.getNewValue()); updateStatusBarString(progressBar); } else if ("message".equals(e.getPropertyName())) { textArea.setText((String) e.getNewValue()); } } }; getTask().addPropertyChangeListener(taskPCL); panel.add(progressBar, BorderLayout.SOUTH); injectBlockingDialogComponents(panel); /* The initial value of the progressBar string is the format. * We save the format string in a client property. The format * String will be applied four values (see below). The default * format String is in resources/Application.properties, it's: * "%02d:%02d, %02d:%02d remaining" * FIXED: BSAF-12 */ if (progressBar.getClientProperty(PB_STRING_FORMAT_KEY) == null) { progressBar.putClientProperty(PB_STRING_FORMAT_KEY, progressBar.getString()); } progressBar.setString(""); optionPane.setMessage(panel); } } private void updateStatusBarString(JProgressBar progressBar) { if (!progressBar.isStringPainted()) { return; } final String fmt = (String) progressBar.getClientProperty(PB_STRING_FORMAT_KEY); if (progressBar.getValue() <= 0) { progressBar.setString(""); } else if (fmt == null) { progressBar.setString(null); } else { double pctComplete = progressBar.getValue() / 100.0; long durSeconds = getTask().getExecutionDuration(TimeUnit.SECONDS); long durMinutes = durSeconds / 60; long remSeconds = (long) (0.5 + ((double) durSeconds / pctComplete)) - durSeconds; long remMinutes = remSeconds / 60; String s = String.format(fmt, durMinutes, durSeconds - (durMinutes * 60), remMinutes, remSeconds - (remMinutes * 60)); progressBar.setString(s); } } private void showBusyGlassPane(boolean f) { /* * Use SwingHelper.findRootPaneContainer to find the nearest * RootPaneContainer ancestor. * FIXED: BSAF-77 */ RootPaneContainer rpc = findRootPaneContainer((Component) getTarget()); if (rpc != null) { if (f) { JMenuBar menuBar = rpc.getRootPane().getJMenuBar(); if (menuBar != null) { menuBar.putClientProperty(this, menuBar.isEnabled()); menuBar.setEnabled(false); } JComponent glassPane = new BusyGlassPane(); InputVerifier retainFocusWhileVisible = new InputVerifier() { @Override public boolean verify(JComponent c) { return !c.isVisible(); } }; glassPane.setInputVerifier(retainFocusWhileVisible); Component oldGlassPane = rpc.getGlassPane(); rpc.getRootPane().putClientProperty(this, oldGlassPane); rpc.setGlassPane(glassPane); glassPane.setVisible(true); glassPane.revalidate(); } else { JMenuBar menuBar = rpc.getRootPane().getJMenuBar(); if (menuBar != null) { boolean enabled = (Boolean) menuBar.getClientProperty(this); menuBar.putClientProperty(this, null); menuBar.setEnabled(enabled); } Component oldGlassPane = (Component) rpc.getRootPane().getClientProperty(this); rpc.getRootPane().putClientProperty(this, null); if (!oldGlassPane.isVisible()) { rpc.getGlassPane().setVisible(false); } rpc.setGlassPane(oldGlassPane); // sets oldGlassPane.visible } } } /* Note: unfortunately, the busy cursor is reset when the modal * dialog is shown. */ private static class BusyGlassPane extends JPanel { BusyGlassPane() { super(null, false); setVisible(false); setOpaque(false); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); MouseInputListener blockMouseEvents = new MouseInputAdapter() { }; addMouseMotionListener(blockMouseEvents); addMouseListener(blockMouseEvents); } } /* If an action was specified then return the value of the * actionName.BlockingDialogTimer.delay resource from the action's * resourceMap. Otherwise return the value of the * BlockingDialogTimer.delay resource from the Task's ResourceMap. * The latter's default in defined in resources/Application.properties. */ private int blockingDialogDelay() { Integer delay = null; String key = "BlockingDialogTimer.delay"; ApplicationAction action = getAction(); if (action != null) { ResourceMap actionResourceMap = action.getResourceMap(); String actionName = action.getName(); delay = actionResourceMap.getInteger(actionName + "." + key); } ResourceMap taskResourceMap = getTask().getResourceMap(); if ((delay == null) && (taskResourceMap != null)) { delay = taskResourceMap.getInteger(key); } return (delay == null) ? 0 : delay; } private void showBlockingDialog(boolean f) { if (f) { if (modalDialog != null) { String msg = String.format("unexpected InputBlocker state [%s] %s", f, this); logger.warning(msg); modalDialog.dispose(); } modalDialog = createBlockingDialog(); ActionListener showModalDialog = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (modalDialog != null) { // already dismissed modalDialog.setVisible(true); } } }; Timer showModalDialogTimer = new Timer(blockingDialogDelay(), showModalDialog); showModalDialogTimer.setRepeats(false); showModalDialogTimer.start(); } else { if (modalDialog != null) { modalDialog.dispose(); modalDialog = null; } else { String msg = String.format("unexpected InputBlocker state [%s] %s", f, this); logger.warning(msg); } } } @Override protected void block() { switch (getScope()) { case ACTION: setActionTargetBlocked(true); break; case COMPONENT: setComponentTargetBlocked(true); break; case WINDOW: case APPLICATION: showBusyGlassPane(true); showBlockingDialog(true); break; } } @Override protected void unblock() { switch (getScope()) { case ACTION: setActionTargetBlocked(false); break; case COMPONENT: setComponentTargetBlocked(false); break; case WINDOW: case APPLICATION: showBusyGlassPane(false); showBlockingDialog(false); break; } } } bsaf-1.9/src/main/java/org/jdesktop/application/FrameView.java000066400000000000000000000055451151640306200244060ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.Image; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.JRootPane; import static org.jdesktop.application.Application.KEY_APPLICATION_ICON; import static org.jdesktop.application.Application.KEY_APPLICATION_TITLE; public class FrameView extends View { public static final String MAIN_FRAME_NAME = "mainFrame"; private static final Logger logger = Logger.getLogger(FrameView.class.getName()); private JFrame frame = null; public FrameView(Application application) { super(application); } /** * Return the JFrame used to show this View * *

* This method may be called at any time; the JFrame is created lazily * and cached. For example: *

     *  @Override protected void startup() {
     *     getFrame().setJMenuBar(createMenuBar());
     *     show(createMainPanel());
     * }
     * 
* * @return this application's main frame */ public JFrame getFrame() { if (frame == null) { ResourceMap resourceMap = getContext().getResourceMap(); String title = resourceMap.getString(KEY_APPLICATION_TITLE); frame = new JFrame(title); frame.setName(MAIN_FRAME_NAME); if (resourceMap.containsKey(KEY_APPLICATION_ICON)) { Image icon = resourceMap.getImageIcon(KEY_APPLICATION_ICON).getImage(); frame.setIconImage(icon); } } return frame; } /** * Sets the JFrame use to show this View *

* This method should be called from the startup method by a * subclass that wants to construct and initialize the main frame * itself. Most applications can rely on the fact that {code * getFrame} lazily constructs the main frame and initializes * the {@code frame} property. *

* If the main frame property was already initialized, either * implicitly through a call to {@code getFrame} or by * explicitly calling this method, an IllegalStateException is * thrown. If {@code frame} is null, an IllegalArgumentException * is thrown. *

* This property is bound. * * * * @param frame the new value of the frame property * @see #getFrame */ public void setFrame(JFrame frame) { if (frame == null) { throw new IllegalArgumentException("null JFrame"); } if (this.frame != null) { throw new IllegalStateException("frame already set"); } this.frame = frame; firePropertyChange("frame", null, this.frame); } @Override public JRootPane getRootPane() { return getFrame().getRootPane(); } } bsaf-1.9/src/main/java/org/jdesktop/application/LocalStorage.java000066400000000000000000000524641151640306200251020ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.jdesktop.application.Application.KEY_APPLICATION_VENDOR_ID; import org.jdesktop.application.utils.AppHelper; import org.jdesktop.application.utils.PlatformType; import javax.jnlp.*; import java.awt.*; import java.beans.*; import java.io.*; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; /** * Access to per application, per user, local file storage. * * @see ApplicationContext#getLocalStorage * @see SessionStorage * @author Hans Muller (Hans.Muller@Sun.COM) */ public class LocalStorage extends AbstractBean { private static Logger logger = Logger.getLogger(LocalStorage.class.getName()); private final ApplicationContext context; private long storageLimit = -1L; private LocalIO localIO = null; private final File unspecifiedFile = new File("unspecified"); private File directory = unspecifiedFile; protected LocalStorage(ApplicationContext context) { if (context == null) { throw new IllegalArgumentException("null context"); } this.context = context; } // FIXME - documentation protected final ApplicationContext getContext() { return context; } private void checkFileName(String fileName) { if (fileName == null) { throw new IllegalArgumentException("null fileName"); } } /** * Opens an input stream to read from the entry * specified by the {@code name} parameter. * If the named entry cannot be opened for reading * then a {@code IOException} is thrown. * * @param fileName the storage-dependent name * @return an {@code InputStream} object * @throws IOException if the specified name is invalid, * or an input stream cannot be opened */ public InputStream openInputFile(String fileName) throws IOException { checkFileName(fileName); return getLocalIO().openInputFile(fileName); } /** * Opens an output stream to write to the entry * specified by the {@code name} parameter. * If the named entry cannot be opened for writing * then a {@code IOException} is thrown. * If the named entry does not exist it can be created. * The entry will be recreated if already exists. * * @param fileName the storage-dependent name * @return an {@code OutputStream} object * @throws IOException if the specified name is invalid, * or an output stream cannot be opened */ public OutputStream openOutputFile(final String fileName) throws IOException { return openOutputFile(fileName, false); } /** * Opens an output stream to write to the entry * specified by the {@code name} parameter. * If the named entry cannot be opened for writing * then a {@code IOException} is thrown. * If the named entry does not exist it can be created. * You can decide whether data will be appended via append parameter. * * @param fileName the storage-dependent name * @param append if true, then bytes will be written * to the end of the output entry rather than the beginning * @return an {@code OutputStream} object * @throws IOException if the specified name is invalid, * or an output stream cannot be opened */ public OutputStream openOutputFile(String fileName, boolean append) throws IOException { checkFileName(fileName); return getLocalIO().openOutputFile(fileName, append); } /** * Deletes the entry specified by the {@code name} parameter. * * @param fileName the storage-dependent name * @throws IOException if the specified name is invalid, * or an internal entry cannot be deleted */ public boolean deleteFile(String fileName) throws IOException { checkFileName(fileName); return getLocalIO().deleteFile(fileName); } /* If an exception occurs in the XMLEncoder/Decoder, we want * to throw an IOException. The exceptionThrow listener method * doesn't throw a checked exception so we just set a flag * here and check it when the encode/decode operation finishes */ private static class AbortExceptionListener implements ExceptionListener { public Exception exception = null; @Override public void exceptionThrown(Exception e) { if (exception == null) { exception = e; } } } private static boolean persistenceDelegatesInitialized = false; /** * Saves the {@code bean} to the local storage * @param bean the object ot be saved * @param fileName the targen file name * @throws IOException */ public void save(Object bean, final String fileName) throws IOException { AbortExceptionListener el = new AbortExceptionListener(); XMLEncoder e = null; /* Buffer the XMLEncoder's output so that decoding errors don't * cause us to trash the current version of the specified file. */ ByteArrayOutputStream bst = new ByteArrayOutputStream(); try { e = new XMLEncoder(bst); if (!persistenceDelegatesInitialized) { e.setPersistenceDelegate(Rectangle.class, new RectanglePD()); persistenceDelegatesInitialized = true; } e.setExceptionListener(el); e.writeObject(bean); } finally { if (e != null) { e.close(); } } if (el.exception != null) { throw new LSException("save failed \"" + fileName + "\"", el.exception); } OutputStream ost = null; try { ost = openOutputFile(fileName); ost.write(bst.toByteArray()); } finally { if (ost != null) { ost.close(); } } } /** * Loads the been from the local storage * @param fileName name of the file to be read from * @return loaded object * @throws IOException */ public Object load(String fileName) throws IOException { InputStream ist; try { ist = openInputFile(fileName); } catch (IOException e) { return null; } AbortExceptionListener el = new AbortExceptionListener(); XMLDecoder d = null; try { d = new XMLDecoder(ist); d.setExceptionListener(el); Object bean = d.readObject(); if (el.exception != null) { throw new LSException("load failed \"" + fileName + "\"", el.exception); } return bean; } finally { if (d != null) { d.close(); } } } // private void closeStream(Closeable st, String fileName) throws IOException { // if (st != null) { // try { // st.close(); // } catch (java.io.IOException e) { // throw new LSException("close failed \"" + fileName + "\"", e); // } // } // } /** * Gets the limit of the local storage * @return the limit of the local storage */ public long getStorageLimit() { return storageLimit; } /** * Sets the limit of the lical storage * @param storageLimit the limit of the lical storage */ public void setStorageLimit(long storageLimit) { if (storageLimit < -1L) { throw new IllegalArgumentException("invalid storageLimit"); } long oldValue = this.storageLimit; this.storageLimit = storageLimit; firePropertyChange("storageLimit", oldValue, this.storageLimit); } private String getId(String key, String def) { ResourceMap appResourceMap = getContext().getResourceMap(); String id = appResourceMap.getString(key); if (id == null) { logger.log(Level.WARNING, "unspecified resource " + key + " using " + def); id = def; } else if (id.trim().length() == 0) { logger.log(Level.WARNING, "empty resource " + key + " using " + def); id = def; } return id; } private String getApplicationId() { return getId("Application.id", getContext().getApplicationClass().getSimpleName()); } private String getVendorId() { return getId(KEY_APPLICATION_VENDOR_ID, "UnknownApplicationVendor"); } /** * Returns the directory where the local storage is located * @return the directory where the local storage is located */ public File getDirectory() { if (directory == unspecifiedFile) { directory = null; String userHome = null; try { userHome = System.getProperty("user.home"); } catch (SecurityException ignore) { } if (userHome != null) { final String applicationId = getApplicationId(); final PlatformType osId = AppHelper.getPlatform(); if (osId == PlatformType.WINDOWS) { File appDataDir = null; try { String appDataEV = System.getenv("APPDATA"); if ((appDataEV != null) && (appDataEV.length() > 0)) { appDataDir = new File(appDataEV); } } catch (SecurityException ignore) { } String vendorId = getVendorId(); if ((appDataDir != null) && appDataDir.isDirectory()) { // ${APPDATA}\{vendorId}\${applicationId} String path = vendorId + "\\" + applicationId + "\\"; directory = new File(appDataDir, path); } else { // ${userHome}\Application Data\${vendorId}\${applicationId} String path = "Application Data\\" + vendorId + "\\" + applicationId + "\\"; directory = new File(userHome, path); } } else if (osId == PlatformType.OS_X) { // ${userHome}/Library/Application Support/${applicationId} String path = "Library/Application Support/" + applicationId + "/"; directory = new File(userHome, path); } else { // ${userHome}/.${applicationId}/ String path = "." + applicationId + "/"; directory = new File(userHome, path); } } } return directory; } /** * Sets the location of the local storage * @param directory the location of the local storage */ public void setDirectory(File directory) { File oldValue = this.directory; this.directory = directory; firePropertyChange("directory", oldValue, this.directory); } //TODO: Use IOException instead /* Papers over the fact that the String,Throwable IOException * constructor was only introduced in Java 6. */ private static class LSException extends IOException { public LSException(String s, Throwable e) { super(s); initCause(e); } public LSException(String s) { super(s); } } /* There are some (old) Java classes that aren't proper beans. Rectangle * is one of these. When running within the secure sandbox, writing a * Rectangle with XMLEncoder causes a security exception because * DefaultPersistenceDelegate calls Field.setAccessible(true) to gain * access to private fields. This is a workaround for that problem. * A bug has been filed, see JDK bug ID 4741757 */ private static class RectanglePD extends DefaultPersistenceDelegate { public RectanglePD() { super(new String[]{"x", "y", "width", "height"}); } @Override protected Expression instantiate(Object oldInstance, Encoder out) { Rectangle oldR = (Rectangle) oldInstance; Object[] constructorArgs = new Object[]{ oldR.x, oldR.y, oldR.width, oldR.height }; return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs); } } private synchronized LocalIO getLocalIO() { if (localIO == null) { localIO = getPersistenceServiceIO(); if (localIO == null) { localIO = new LocalFileIO(); } } return localIO; } private abstract class LocalIO { /** * Opens an input stream to read from the entry * specified by the {@code name} parameter. * If the named entry cannot be opened for reading * then a {@code IOException} is thrown. * * @param fileName the storage-dependent name * @return an {@code InputStream} object * @throws IOException if the specified name is invalid, * or an input stream cannot be opened */ public abstract InputStream openInputFile(String fileName) throws IOException; /** * Opens an output stream to write to the entry * specified by the {@code name} parameter. * If the named entry cannot be opened for writing * then a {@code IOException} is thrown. * If the named entry does not exist it can be created. * The entry will be recreated if already exists. * * @param fileName the storage-dependent name * @return an {@code OutputStream} object * @throws IOException if the specified name is invalid, * or an output stream cannot be opened */ public OutputStream openOutputFile(final String fileName) throws IOException { return openOutputFile(fileName, false); } /** * Opens an output stream to write to the entry * specified by the {@code name} parameter. * If the named entry cannot be opened for writing * then a {@code IOException} is thrown. * If the named entry does not exist it can be created. * You can decide whether data will be appended via append parameter. * * @param fileName the storage-dependent name * @param append if true, then bytes will be written * to the end of the output entry rather than the beginning * @return an {@code OutputStream} object * @throws IOException if the specified name is invalid, * or an output stream cannot be opened */ public abstract OutputStream openOutputFile(final String fileName, boolean append) throws IOException; /** * Deletes the entry specified by the {@code name} parameter. * * @param fileName the storage-dependent name * @throws IOException if the specified name is invalid, * or an internal entry cannot be deleted */ public abstract boolean deleteFile(String fileName) throws IOException; } private final class LocalFileIO extends LocalIO { @Override public InputStream openInputFile(String fileName) throws IOException { File path = getFile(fileName); try { return new BufferedInputStream(new FileInputStream(path)); } catch (IOException e) { throw new LSException("couldn't open input file \"" + fileName + "\"", e); } } @Override public OutputStream openOutputFile(String name, boolean append) throws IOException { try { File file = getFile(name); File dir = file.getParentFile(); if (!dir.isDirectory() && !dir.mkdirs()) { throw new IOException("couldn't create directory " + dir); } return new BufferedOutputStream(new FileOutputStream(file, append)); } catch (SecurityException exception) { throw new IOException("could not write to entry: " + name, exception); } } @Override public boolean deleteFile(String fileName) throws IOException { File path = new File(getDirectory(), fileName); return path.delete(); } private File getFile(String name) throws IOException { if (name == null) { throw new IOException("name is not set"); } return new File(getDirectory(), name); } } /* Determine if we're a web started application and the * JNLP PersistenceService is available without forcing * the JNLP API to be class-loaded. We don't want to * require apps that aren't web started to bundle javaws.jar */ private LocalIO getPersistenceServiceIO() { try { Class smClass = Class.forName("javax.jnlp.ServiceManager"); Method getServiceNamesMethod = smClass.getMethod("getServiceNames"); String[] serviceNames = (String[]) getServiceNamesMethod.invoke(null); boolean psFound = false; boolean bsFound = false; for (String serviceName : serviceNames) { if (serviceName.equals("javax.jnlp.BasicService")) { bsFound = true; } else if (serviceName.equals("javax.jnlp.PersistenceService")) { psFound = true; } } if (bsFound && psFound) { return new PersistenceServiceIO(); } } catch (Exception ignore) { // either the classes or the services can't be found } return null; } private final class PersistenceServiceIO extends LocalIO { private BasicService bs; private PersistenceService ps; private String initFailedMessage(String s) { return getClass().getName() + " initialization failed: " + s; } PersistenceServiceIO() { try { bs = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService"); ps = (PersistenceService) ServiceManager.lookup("javax.jnlp.PersistenceService"); } catch (UnavailableServiceException e) { logger.log(Level.SEVERE, initFailedMessage("ServiceManager.lookup"), e); bs = null; ps = null; } } private void checkBasics(String s) throws IOException { if ((bs == null) || (ps == null)) { throw new IOException(initFailedMessage(s)); } } private URL fileNameToURL(String name) throws IOException { if (name == null) { throw new IOException("name is not set"); } try { return new URL(bs.getCodeBase(), name); } catch (MalformedURLException e) { throw new LSException("invalid filename \"" + name + "\"", e); } } @Override public InputStream openInputFile(String fileName) throws IOException { checkBasics("openInputFile"); URL fileURL = fileNameToURL(fileName); try { return new BufferedInputStream(ps.get(fileURL).getInputStream()); } catch (Exception e) { throw new LSException("openInputFile \"" + fileName + "\" failed", e); } } @Override public OutputStream openOutputFile(String fileName, boolean append) throws IOException { checkBasics("openOutputFile"); URL fileURL = fileNameToURL(fileName); try { FileContents fc = null; try { fc = ps.get(fileURL); } catch (FileNotFoundException e) { /* Verify that the max size for new PersistenceService * files is >= 100K (2^17) before opening one. */ long maxSizeRequest = 131072L; long maxSize = ps.create(fileURL, maxSizeRequest); if (maxSize >= maxSizeRequest) { fc = ps.get(fileURL); } } if ((fc != null) && (fc.canWrite())) { return new BufferedOutputStream(fc.getOutputStream(!append)); } else { throw new IOException("unable to create FileContents object"); } } catch (Exception e) { throw new LSException("openOutputFile \"" + fileName + "\" failed", e); } } @Override public boolean deleteFile(String fileName) throws IOException { checkBasics("deleteFile"); URL fileURL = fileNameToURL(fileName); try { ps.delete(fileURL); return true; } catch (Exception e) { throw new LSException("openInputFile \"" + fileName + "\" failed", e); } } } } bsaf-1.9/src/main/java/org/jdesktop/application/MnemonicText.java000066400000000000000000000113431151640306200251240ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.event.KeyEvent; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import javax.swing.AbstractButton; import javax.swing.JLabel; /** * An internal helper class that configures the text and mnemonic * properties for instances of AbstractButton, JLabel, and * javax.swing.Action. It's used like this: *

 * MnemonicText.configure(myButton, "Save &As")
 * 
* The configure method unconditionally sets three properties on the * target object: *
    *
  • the label text, "Save As" *
  • the mnemonic key code, VK_A *
  • the index of the mnemonic character, 5 *
* If the mnemonic marker character isn't present, then the second * two properties are cleared to VK_UNDEFINED (0) and -1 respectively. *

*/ class MnemonicText { private MnemonicText() { } // not used public static void configure(Object target, String markedText) { String text = markedText; int mnemonicIndex = -1; int mnemonicKey = KeyEvent.VK_UNDEFINED; // TBD: mnemonic marker char should be an application resource int markerIndex = mnemonicMarkerIndex(markedText, '&'); if (markerIndex == -1) { markerIndex = mnemonicMarkerIndex(markedText, '_'); } if (markerIndex != -1) { text = text.substring(0, markerIndex) + text.substring(markerIndex + 1); mnemonicIndex = markerIndex; CharacterIterator sci = new StringCharacterIterator(markedText, markerIndex); mnemonicKey = mnemonicKey(sci.next()); } if (target instanceof javax.swing.Action) { configureAction((javax.swing.Action) target, text, mnemonicKey, mnemonicIndex); } else if (target instanceof AbstractButton) { configureButton((AbstractButton) target, text, mnemonicKey, mnemonicIndex); } else if (target instanceof JLabel) { configureLabel((JLabel) target, text, mnemonicKey, mnemonicIndex); } else { throw new IllegalArgumentException("unrecognized target type " + target); } } private static int mnemonicMarkerIndex(String s, char marker) { if ((s == null) || (s.length() < 2)) { return -1; } CharacterIterator sci = new StringCharacterIterator(s); int i = 0; while (i != -1) { i = s.indexOf(marker, i); if (i != -1) { sci.setIndex(i); char c1 = sci.previous(); sci.setIndex(i); char c2 = sci.next(); boolean isQuote = (c1 == '\'') && (c2 == '\''); boolean isSpace = Character.isWhitespace(c2); if (!isQuote && !isSpace && (c2 != CharacterIterator.DONE)) { return i; } } if (i != -1) { i += 1; } } return -1; } /* A general purpose way to map from a char to a KeyCode is needed. An * AWT RFE has been filed: * http://bt2ws.central.sun.com/CrPrint?id=6559449 * CR 6559449 java/classes_awt Support for converting from char to KeyEvent VK_ keycode */ private static int mnemonicKey(char c) { int vk = (int) c; if ((vk >= 'a') && (vk <= 'z')) { vk -= ('a' - 'A'); } return vk; } /* This javax.swing.Action constants is only * defined in Mustang (1.6), see * http://download.java.net/jdk6/docs/api/javax/swing/Action.html */ private static final String DISPLAYED_MNEMONIC_INDEX_KEY = "SwingDisplayedMnemonicIndexKey"; private static void configureAction(javax.swing.Action target, String text, int key, int index) { target.putValue(javax.swing.Action.NAME, text); if (key != KeyEvent.VK_UNDEFINED) { target.putValue(javax.swing.Action.MNEMONIC_KEY, key); } if (index != -1) { target.putValue(DISPLAYED_MNEMONIC_INDEX_KEY, index); } } private static void configureButton(AbstractButton target, String text, int key, int index) { target.setText(text); if (key != KeyEvent.VK_UNDEFINED) { target.setMnemonic(key); } if (index != -1) { target.setDisplayedMnemonicIndex(index); } } private static void configureLabel(JLabel target, String text, int key, int index) { target.setText(text); if (key != KeyEvent.VK_UNDEFINED) { target.setDisplayedMnemonic(key); } if (index != -1) { target.setDisplayedMnemonicIndex(index); } } } bsaf-1.9/src/main/java/org/jdesktop/application/ProxyActions.java000066400000000000000000000032031151640306200251500ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Defines a list of the proxy action names *

* The proxy actions perform their * action not on the component they're bound to (menu items and * toolbar buttons), but on the component that currently * has the keyboard focus. Their enabled state tracks the * selection value of the component with the keyboard focus, * as well as the contents of the system clipboard. * The proxy actions work in conjunction with the action * map associated with the component that has focus. *

* So you can create an action in any object (whether it is a component or not), * and use that action anywhere. But as the focus changes, that one action binds * itself to the same named action in the component tree that the focused control has. *

* For example, in the case of Copy, the proxy action can be created anywhere * in the application and attached to menus, buttons, or whatever you want. * When the focus is on a control that has an ActionMap that contains a 'copy' entry, * the proxy action will bind to that action and the menu/button/whatever will invoke * the action of the component with focus. * * @author Hans Muller (Hans.Muller@Sun.COM) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ProxyActions { String[] value() default {}; } bsaf-1.9/src/main/java/org/jdesktop/application/Resource.java000066400000000000000000000016431151640306200243030ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Marks the field as a resource to be injected. *

* In order to inject the resources for this class, * the resources must be defined in a class resource file. * You should name field resources in the resource file using the class name followed by a period (.) and the key: *

 *  <classname>.<fieldname>
 * 
* * @author Hans Muller (Hans.Muller@Sun.COM) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Resource { /** * Key for resource injection. If not specified the name of the field will be used. */ String key() default ""; } bsaf-1.9/src/main/java/org/jdesktop/application/ResourceConverter.java000066400000000000000000000276671151640306200262110ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * A base class for converting arbitrary types to and from Strings, as well as * a registry of ResourceConverter implementations. *

* The supportsType method defines what types a ResourceConverter supports. * By default it returns true for classes that are equal to the constructor's * type argument. The parseType methods converts a string * the ResourceConverter's supported type, and the toString does the * inverse, it converts a supported type to a String. Concrete ResourceConverter * subclasses must override parseType() and, in most cases, the * toString method as well. *

* This class maintains a registry of ResourceConverters. * The forType method returns the first ResourceConverter that * supports a particular type, new ResourceConverters can be added with * register(). A small set of generic ResourceConverters are * registered by default. They support the following types: *

    *
  • Boolean
  • *
  • Integer
  • *
  • Float
  • *
  • Double
  • *
  • Long
  • *
  • Short
  • *
  • Byte
  • *
  • MessageFormat
  • *
  • URL
  • *
  • URI
  • *
*

* The Boolean ResourceConverter returns true for "true", "on", "yes", * false otherwise. The other primitive type ResourceConverters rely on * the corresponding static parseType method, * e.g. Integer.parseInt(). The MessageFormat * ResourceConverter just creates MessageFormat object with the string * as its constructor argument. The URL/URI converters just apply * the corresponding constructor to the resource string. * * @author Hans Muller (Hans.Muller@Sun.COM) * @see ResourceMap */ public abstract class ResourceConverter { protected final Class type; /** * Convert string to object * @param s the string to be parsed * @param r the {@code ResourceMap} * @return the object which was created from the string * @throws org.jdesktop.application.ResourceConverter.ResourceConverterException */ public abstract Object parseString(String s, ResourceMap r) throws ResourceConverterException; /** * Null safe toString operation. * @param obj the object to be converted to String * @return result of obj.toString or "null" */ public String toString(Object obj) { return (obj == null) ? "null" : obj.toString(); } //TODO: it should be static, or even moved to the utility class protected ResourceConverter(Class type) { if (type == null) { throw new IllegalArgumentException("null type"); } this.type = type; } private ResourceConverter() { type = null; } // not used /** * Checks whether {@code testType} can be converted with this converter. * @param testType * @return {@code true} if {@code testType} can be converted with this converter. */ public boolean supportsType(Class testType) { return type.equals(testType); } public static class ResourceConverterException extends Exception { private final String badString; private String maybeShorten(String s) { int n = s.length(); return (n < 128) ? s : s.substring(0, 128) + "...[" + (n - 128) + " more characters]"; } public ResourceConverterException(String message, String badString, Throwable cause) { super(message, cause); this.badString = maybeShorten(badString); } public ResourceConverterException(String message, String badString) { super(message); this.badString = maybeShorten(badString); } @Override public String toString() { StringBuffer sb = new StringBuffer(super.toString()); sb.append(" string: \""); sb.append(badString); sb.append("\""); return sb.toString(); } } /** * Registers a {@code ResourceConverter} * @param resourceConverter the resource converter to be registered */ public static void register(ResourceConverter resourceConverter) { if (resourceConverter == null) { throw new IllegalArgumentException("null resourceConverter"); } resourceConverters.add(resourceConverter); } /** * Returns {@code ResourceConverter} for the specified type * @param type the type converter must be found for * @return the converter for specified type or {@code null} if no converter is found for the {@code type} */ public static ResourceConverter forType(Class type) { if (type == null) { throw new IllegalArgumentException("null type"); } for (ResourceConverter sc : resourceConverters) { if (sc.supportsType(type)) { return sc; } } return null; } private static ResourceConverter[] resourceConvertersArray = { new BooleanResourceConverter("true", "on", "yes"), new IntegerResourceConverter(), new MessageFormatResourceConverter(), new FloatResourceConverter(), new DoubleResourceConverter(), new LongResourceConverter(), new ShortResourceConverter(), new ByteResourceConverter(), new URLResourceConverter(), new URIResourceConverter() }; private static List resourceConverters = new ArrayList(Arrays.asList(resourceConvertersArray)); private static class BooleanResourceConverter extends ResourceConverter { private final String[] trueStrings; BooleanResourceConverter(String... trueStrings) { super(Boolean.class); this.trueStrings = trueStrings; } @Override public Object parseString(String s, ResourceMap ignore) { s = s.trim(); for (String trueString : trueStrings) { if (s.equalsIgnoreCase(trueString)) { return Boolean.TRUE; } } return Boolean.FALSE; } @Override public boolean supportsType(Class testType) { return testType.equals(Boolean.class) || testType.equals(boolean.class); } } private static abstract class NumberResourceConverter extends ResourceConverter { private final Class primitiveType; NumberResourceConverter(Class type, Class primitiveType) { super(type); this.primitiveType = primitiveType; } protected abstract Number parseString(String s) throws NumberFormatException; @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { try { return parseString(s); } catch (NumberFormatException e) { throw new ResourceConverterException("invalid " + type.getSimpleName(), s, e); } } @Override public boolean supportsType(Class testType) { return testType.equals(type) || testType.equals(primitiveType); } } private static class FloatResourceConverter extends NumberResourceConverter { FloatResourceConverter() { super(Float.class, float.class); } @Override protected Number parseString(String s) throws NumberFormatException { return Float.parseFloat(s); } } private static class DoubleResourceConverter extends NumberResourceConverter { DoubleResourceConverter() { super(Double.class, double.class); } @Override protected Number parseString(String s) throws NumberFormatException { return Double.parseDouble(s); } } private static abstract class INumberResourceConverter extends ResourceConverter { private final Class primitiveType; INumberResourceConverter(Class type, Class primitiveType) { super(type); this.primitiveType = primitiveType; } protected abstract Number parseString(String s, int radix) throws NumberFormatException; @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { try { String[] nar = s.split("&"); // number ampersand radix int radix = (nar.length == 2) ? Integer.parseInt(nar[1]) : -1; return parseString(nar[0], radix); } catch (NumberFormatException e) { throw new ResourceConverterException("invalid " + type.getSimpleName(), s, e); } } @Override public boolean supportsType(Class testType) { return testType.equals(type) || testType.equals(primitiveType); } } private static class ByteResourceConverter extends INumberResourceConverter { ByteResourceConverter() { super(Byte.class, byte.class); } @Override protected Number parseString(String s, int radix) throws NumberFormatException { return (radix == -1) ? Byte.decode(s) : Byte.parseByte(s, radix); } } private static class IntegerResourceConverter extends INumberResourceConverter { IntegerResourceConverter() { super(Integer.class, int.class); } @Override protected Number parseString(String s, int radix) throws NumberFormatException { return (radix == -1) ? Integer.decode(s) : Integer.parseInt(s, radix); } } private static class LongResourceConverter extends INumberResourceConverter { LongResourceConverter() { super(Long.class, long.class); } @Override protected Number parseString(String s, int radix) throws NumberFormatException { return (radix == -1) ? Long.decode(s) : Long.parseLong(s, radix); } } private static class ShortResourceConverter extends INumberResourceConverter { ShortResourceConverter() { super(Short.class, short.class); } @Override protected Number parseString(String s, int radix) throws NumberFormatException { return (radix == -1) ? Short.decode(s) : Short.parseShort(s, radix); } } private static class MessageFormatResourceConverter extends ResourceConverter { MessageFormatResourceConverter() { super(MessageFormat.class); } @Override public Object parseString(String s, ResourceMap ignore) { return new MessageFormat(s); } } private static class URLResourceConverter extends ResourceConverter { URLResourceConverter() { super(URL.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { try { return new URL(s); } catch (MalformedURLException e) { throw new ResourceConverterException("invalid URL", s, e); } } } private static class URIResourceConverter extends ResourceConverter { URIResourceConverter() { super(URI.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { try { return new URI(s); } catch (URISyntaxException e) { throw new ResourceConverterException("invalid URI", s, e); } } } } bsaf-1.9/src/main/java/org/jdesktop/application/ResourceManager.java000066400000000000000000000503261151640306200256000ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.utils.PlatformType; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; /** * The application's {@code ResourceManager} provides * read-only cached access to resources in {@code ResourceBundles} via the * {@link ResourceMap ResourceMap} class. {@code ResourceManager} is a * property of the {@code ApplicationContext} and most applications * look up resources relative to it, like this: *

 * ApplicationContext appContext = Application.getInstance().getContext();
 * ResourceMap resourceMap = appContext.getResourceMap(MyClass.class);
 * String msg = resourceMap.getString("msg");
 * Icon icon = resourceMap.getIcon("icon");
 * Color color = resourceMap.getColor("color");
 * 
* {@link ApplicationContext#getResourceMap(Class) ApplicationContext.getResourceMap()} * just delegates to its {@code ResourceManager}. The {@code ResourceMap} * in this example contains resources from the ResourceBundle named * {@code MyClass}, and the rest of the * chain contains resources shared by the entire application. *

* Resources for a class are defined by an eponymous {@code ResourceBundle} * in a {@code resources} subpackage. The Application class itself * may also provide resources. A complete * description of the naming conventions for ResourceBundles is provided * by the {@link #getResourceMap(Class) getResourceMap()} method. *

* The mapping from classes and {@code Application} to a list * ResourceBundle names is handled by two protected methods: * {@link #getClassBundleNames(Class) getClassBundleNames}, * {@link #getApplicationBundleNames() getApplicationBundleNames}. * Subclasses could override these methods to append additional * ResourceBundle names to the default lists. * * @see ApplicationContext#getResourceManager * @see ApplicationContext#getResourceMap * @see ResourceMap * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ResourceManager extends AbstractBean { private static final Logger logger = Logger.getLogger(ResourceManager.class.getName()); private final Map resourceMaps; private final ApplicationContext context; private List applicationBundleNames = null; private ResourceMap appResourceMap = null; /** * Construct a {@code ResourceManager}. Typically applications * will not create a ResourceManager directly, they'll retrieve * the shared one from the {@code ApplicationContext} with: *

     * Application.getInstance().getContext().getResourceManager()
     * 
* Or just look up {@code ResourceMaps} with the ApplicationContext * convenience method: *
     * Application.getInstance().getContext().getResourceMap(MyClass.class)
     * 
* * FIXME - @param javadoc * @param context * @see ApplicationContext#getResourceManager * @see ApplicationContext#getResourceMap */ protected ResourceManager(ApplicationContext context) { if (context == null) { throw new IllegalArgumentException("null context"); } this.context = context; resourceMaps = new ConcurrentHashMap(); } // FIXME - documentation protected final ApplicationContext getContext() { return context; } /* Returns a read-only list of the ResourceBundle names for all of * the classes from startClass to (including) stopClass. The * bundle names for each class are #getClassBundleNames(Class). * The list is in priority order: resources defined in bundles * earlier in the list shadow resources with the same name that * appear bundles that come later. */ private List allBundleNames(Class startClass, Class stopClass) { List bundleNames = new ArrayList(); Class limitClass = stopClass.getSuperclass(); // could be null for (Class c = startClass; c != limitClass; c = c.getSuperclass()) { bundleNames.addAll(getClassBundleNames(c)); } return Collections.unmodifiableList(bundleNames); } private String bundlePackageName(String bundleName) { int i = bundleName.lastIndexOf("."); return (i == -1) ? "" : bundleName.substring(0, i); } /* Creates a parent chain of ResourceMaps for the specfied * ResourceBundle names. One ResourceMap is created for each * subsequence of ResourceBundle names with a common bundle * package name, i.e. with a common resourcesDir. The parent * of the final ResourceMap in the chain is root. */ private ResourceMap createResourceMapChain(ClassLoader cl, ResourceMap root, ListIterator names) { if (!names.hasNext()) { return root; } else { String bundleName0 = names.next(); String rmBundlePackage = bundlePackageName(bundleName0); List rmNames = new ArrayList(); rmNames.add(bundleName0); while (names.hasNext()) { String bundleName = names.next(); if (rmBundlePackage.equals(bundlePackageName(bundleName))) { rmNames.add(bundleName); } else { names.previous(); break; } } ResourceMap parent = createResourceMapChain(cl, root, names); return createResourceMap(cl, parent, rmNames); } } /* Lazily creates the Application ResourceMap chain, * appResourceMap. If the Application hasn't been launched yet, * i.e. if the ApplicationContext applicationClass property hasn't * been set yet, then the ResourceMap just corresponds to * Application.class. */ private ResourceMap getApplicationResourceMap() { if (appResourceMap == null) { List appBundleNames = getApplicationBundleNames(); Class appClass = getContext().getApplicationClass(); if (appClass == null) { logger.warning("getApplicationResourceMap(): no Application class"); appClass = Application.class; } ClassLoader classLoader = appClass.getClassLoader(); appResourceMap = createResourceMapChain(classLoader, null, appBundleNames.listIterator()); } return appResourceMap; } /* Lazily creates the ResourceMap chain for the the class from * startClass to stopClass. */ private ResourceMap getClassResourceMap(Class startClass, Class stopClass) { String classResourceMapKey = startClass.getName() + stopClass.getName(); ResourceMap classResourceMap = resourceMaps.get(classResourceMapKey); if (classResourceMap == null) { List classBundleNames = allBundleNames(startClass, stopClass); ClassLoader classLoader = startClass.getClassLoader(); ResourceMap appRM = getResourceMap(); classResourceMap = createResourceMapChain(classLoader, appRM, classBundleNames.listIterator()); resourceMaps.put(classResourceMapKey, classResourceMap); } return classResourceMap; } /** * Returns a {@link ResourceMap#getParent chain} of {@code ResourceMaps} * that encapsulate the {@code ResourceBundles} for each class * from {@code startClass} to (including) {@code stopClass}. The * final link in the chain is Application ResourceMap chain, i.e. * the value of {@link #getResourceMap() getResourceMap()}. *

* The ResourceBundle names for the chain of ResourceMaps * are defined by {@link #getClassBundleNames} and * {@link #getApplicationBundleNames}. Collectively they define the * standard location for {@code ResourceBundles} for a particular * class as the {@code resources} subpackage. For example, the * ResourceBundle for the single class {@code com.myco.MyScreen}, would * be named {@code com.myco.resources.MyScreen}. Typical * ResourceBundles are ".properties" files, so: {@code * com/foo/bar/resources/MyScreen.properties}. The following table * is a list of the ResourceMaps and their constituent * ResourceBundles for the same example: *

* * * * * * * * * * * * * * * * * * * * * * * * * *
ResourceMap chain for class MyScreen in MyApp
ResourceMapResourceBundle namesTypical ResourceBundle files
1class: com.myco.MyScreencom.myco.resources.MyScreencom/myco/resources/MyScreen.properties
2/td> * application: com.myco.MyAppcom.myco.resources.MyAppcom/myco/resources/MyApp.properties
3application: javax.swing.application.Applicationjavax.swing.application.resources.Applicationjavax.swing.application.resources.Application.properties
* *

Note that inner classes are searched for by "simple" name - eg, * for a class MyApp$InnerClass, the resource bundle must be named * InnerClass.properties. See the notes on {@link #classBundleBaseName classBundleBaseName}

* *

* None of the ResourceBundles are required to exist. If more than one * ResourceBundle contains a resource with the same name then * the one earlier in the list has precedence *

* ResourceMaps are constructed lazily and cached. One ResourceMap * is constructed for each sequence of classes in the same package. * * @param startClass the first class whose ResourceBundles will be included * @param stopClass the last class whose ResourceBundles will be included * @return a {@code ResourceMap} chain that contains resources loaded from * {@code ResourceBundles} found in the resources subpackage for * each class. * @see #getClassBundleNames * @see #getApplicationBundleNames * @see ResourceMap#getParent * @see ResourceMap#getBundleNames */ public ResourceMap getResourceMap(Class startClass, Class stopClass) { if (startClass == null) { throw new IllegalArgumentException("null startClass"); } if (stopClass == null) { throw new IllegalArgumentException("null stopClass"); } if (!stopClass.isAssignableFrom(startClass)) { throw new IllegalArgumentException("startClass is not a subclass, or the same as, stopClass"); } return getClassResourceMap(startClass, stopClass); } /** * Return the ResourcedMap chain for the specified class. This is * just a convenince method, it's the same as: * getResourceMap(cls, cls). * * @param cls the class that defines the location of ResourceBundles * @return a {@code ResourceMap} that contains resources loaded from * {@code ResourceBundles} found in the resources subpackage of the * specified class's package. * @see #getResourceMap(Class, Class) */ public final ResourceMap getResourceMap(Class cls) { if (cls == null) { throw new IllegalArgumentException("null class"); } return getResourceMap(cls, cls); } /** * Returns the chain of ResourceMaps that's shared by the entire application, * beginning with the resources defined for the application's class, i.e. * the value of the ApplicationContext * {@link ApplicationContext#getApplicationClass applicationClass} property. * If the {@code applicationClass} property has not been set, e.g. because * the application has not been {@link Application#launch launched} yet, * then a ResourceMap for just {@code Application.class} is returned. * * @return the Application's ResourceMap * @see ApplicationContext#getResourceMap() * @see ApplicationContext#getApplicationClass */ public ResourceMap getResourceMap() { return getApplicationResourceMap(); } /** * The names of the ResourceBundles to be shared by the entire * application. The list is in priority order: resources defined * by the first ResourceBundle shadow resources with the the same * name that come later. *

* The default value for this property is a list of {@link * #getClassBundleNames per-class} ResourceBundle names, beginning * with the {@code Application's} class and of each of its * superclasses, up to {@code Application.class}. * For example, if the Application's class was * {@code com.foo.bar.MyApp}, and MyApp was a subclass * of {@code SingleFrameApplication.class}, then the * ResourceBundle names would be: *

    *
  1. com.foo.bar.resources.MyApp
  2. *
  3. javax.swing.application.resources.SingleFrameApplication
  4. *
  5. javax.swing.application.resources.Application
  6. *
*

* The default value of this property is computed lazily and * cached. If it's reset, then all ResourceMaps cached by * {@code getResourceMap} will be updated. * * @return names of the ResourceBundles * @see #setApplicationBundleNames * @see #getResourceMap * @see #getClassBundleNames * @see ApplicationContext#getApplication */ public List getApplicationBundleNames() { /* Lazily compute an initial value for this property, unless the * application's class hasn't been specified yet. In that case * we just return a placeholder based on Application.class. */ if (applicationBundleNames == null) { Class appClass = getContext().getApplicationClass(); if (appClass == null) { return allBundleNames(Application.class, Application.class); // placeholder } else { applicationBundleNames = allBundleNames(appClass, Application.class); } } return applicationBundleNames; } /** * Specify the names of the ResourceBundles to be shared by the entire * application. More information about the property is provided * by the {@link #getApplicationBundleNames} method. * * @param bundleNames * @see #setApplicationBundleNames */ public void setApplicationBundleNames(List bundleNames) { if (bundleNames != null) { for (String bundleName : bundleNames) { if ((bundleName == null) || (bundleNames.size() == 0)) { throw new IllegalArgumentException("invalid bundle name \"" + bundleName + "\""); } } } Object oldValue = applicationBundleNames; if (bundleNames != null) { applicationBundleNames = Collections.unmodifiableList(new ArrayList(bundleNames)); } else { applicationBundleNames = null; } resourceMaps.clear(); firePropertyChange("applicationBundleNames", oldValue, applicationBundleNames); } /* Convert a class name to an eponymous resource bundle in the * resources subpackage. For example, given a class named * com.foo.bar.MyClass, the ResourceBundle name would be * "com.foo.bar.resources.MyClass" If MyClass is an inner class, * only its "simple name" is used. For example, given an * inner class named com.foo.bar.OuterClass$InnerClass, the * ResourceBundle name would be "com.foo.bar.resources.InnerClass". * Although this could result in a collision, creating more * complex rules for inner classes would be a burden for * developers. */ private String classBundleBaseName(Class cls) { String className = cls.getName(); StringBuffer sb = new StringBuffer(); int i = className.lastIndexOf('.'); if (i > 0) { sb.append(className.substring(0, i)); sb.append(".resources."); sb.append(cls.getSimpleName()); } else { sb.append("resources."); sb.append(cls.getSimpleName()); } return sb.toString(); } /** * Map from a class to a list of the names of the * {@code ResourceBundles} specific to the class. * The list is in priority order: resources defined * by the first ResourceBundle shadow resources with the * the same name that come later. *

* By default this method returns one ResourceBundle * whose name is the same as the class's name, but in the * {@code "resources"} subpackage. *

* For example, given a class named * {@code com.foo.bar.MyClass}, the ResourceBundle name would * be {@code "com.foo.bar.resources.MyClass"}. If MyClass is * an inner class, only its "simple name" is used. For example, * given an inner class named {@code com.foo.bar.OuterClass$InnerClass}, * the ResourceBundle name would be * {@code "com.foo.bar.resources.InnerClass"}. *

* This method is used by the {@code getResourceMap} methods * to compute the list of ResourceBundle names * for a new {@code ResourceMap}. ResourceManager subclasses * can override this method to add additional class-specific * ResourceBundle names to the list. * * @param cls the named ResourceBundles are specific to {@code cls}. * @return the names of the ResourceBundles to be loaded for {@code cls} * @see #getResourceMap * @see #getApplicationBundleNames */ protected List getClassBundleNames(Class cls) { String bundleName = classBundleBaseName(cls); return Collections.singletonList(bundleName); } /** * Called by {@link #getResourceMap} to construct {@code ResourceMaps}. * By default this method is effectively just: *

     * return new ResourceMap(parent, classLoader, bundleNames);
     * 
* Custom ResourceManagers might override this method to construct their * own ResourceMap subclasses. * @param classLoader the ClassLoader to be used to load the ResourceBundle * @param parent parent ResourceMap or null * @param bundleNames names of the ResourceBundle to be loaded * @return a new resource map */ protected ResourceMap createResourceMap(ClassLoader classLoader, ResourceMap parent, List bundleNames) { return new ResourceMap(parent, classLoader, bundleNames); } /** * The value of the special Application ResourceMap resource * named "platform". * * @return the value of the platform resource * @see #setPlatform */ public PlatformType getPlatform() { return getResourceMap().getPlatform(); } /** * Defines the value of the special Application ResourceMap resource * named "platform". This resource can be used to define platform * specific resources. For example: *
     * myLabel.text.osx = A value that's appropriate for OSX
     * myLabel.text.default = A value for other platforms
     * myLabel.text = myLabel.text.${platform}
     * 
*

* By default the value of this resource is "osx" if the * underlying operating environment is Apple OSX or "default". * To distinguish other platforms one can reset this property * based on the value of the {@code "os.name"} system property. *

* This method should be called as early as possible, typically * in the Application {@link Application#initialize initialize} * method. * * @param platform * @see #getPlatform * @see System#getProperty */ public void setPlatform(PlatformType platform) { if (platform == null) { throw new IllegalArgumentException("null platform"); } getResourceMap().setPlatform(platform); } } bsaf-1.9/src/main/java/org/jdesktop/application/ResourceMap.java000066400000000000000000001750001151640306200247400ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.ResourceConverter.ResourceConverterException; import org.jdesktop.application.utils.PlatformType; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Event; import java.awt.Font; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractButton; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.KeyStroke; import javax.swing.border.EmptyBorder; /** * A read-only encapsulation of one or more ResourceBundles that adds * automatic string conversion, support for field and Swing component * property injection, string resource variable substitution, and chaining. *

* ResourceMaps are typically obtained with the {@code ApplicationContext} * {@link ApplicationContext#getResourceMap getResourceMap} method * which lazily creates per Application, package, and class ResourceMaps that * are linked together with the ResourceMap parent property. *

* An individual ResourceMap provides read-only access to all of the * resources defined by the ResourceBundles named when the ResourceMap * was created as well as all of its parent ResourceMaps. Resources * are retrieved with the getObject method which requires both * the name of the resource and its expected type. The latter is used * to convert strings if neccessary. * Converted values are cached. As a convenience, getObject * wrapper methods for common GUI types, like getFont, * and getColor, are provided. *

* The getObject method scans raw string resource values * for ${resourceName} variable substitutions before * performing string conversion. Variables named this way can * refer to String resources defined anywhere in their ResourceMap * or any parent ResourceMap. The special variable ${null} * means that the value of the resource will be null. *

* ResourceMaps can be used to "inject" resource values into Swing * component properties and into object fields. The * injectComponents method uses Component names ({@link * Component#setName}) to match resources names with properties. The * injectFields method sets fields that have been tagged with * the @Resource annotation to the value of resources * with the same name. * * @author Hans Muller (Hans.Muller@Sun.COM) * @see #injectComponents * @see #injectFields * @see ResourceConverter * @see ResourceBundle */ public class ResourceMap { private static Logger logger = Logger.getLogger(ResourceMap.class.getName()); public static final String KEY_PLATFORM = "platform"; private final static Object NULL_RESOURCE = "null resource"; private final ClassLoader classLoader; private final ResourceMap parent; private final List bundleNames; private final String resourcesDir; private Map bundlesMapP = null; // see getBundlesMap() private Locale locale = Locale.getDefault(); // ... private Set bundlesMapKeysP = null; // set getBundlesMapKeys() private boolean bundlesLoaded = false; // ResourceBundles are loaded lazily private PlatformType platform; /** * Creates a ResourceMap that contains all of the resources * defined in the named {@link ResourceBundle}s as well as * (recursively) the parent ResourceMap. The parent * may be null. Typically just one ResourceBundle is specified * however one might name additional ResourceBundles that contain * platform or Swing look and feel specific resources. When multiple * bundles are named, a resource defined in bundlen will * overide the same resource defined in bundles0..n-1. * In other words bundles named later in the argument list take * precendence over the bundles named earlier. *

* ResourceBundles are loaded with the specified ClassLoader. If * classLoader is null, an IllegalArgumentException is * thrown. *

* At least one bundleName must be specified and all of the * bundleNames must be non-empty strings, or an * IllegalArgumentException is thrown. The bundles are * listed in priority order, highest priority first. In other * words, resources in the the first ResourceBundle named first, * shadow resources with the same name later in the list. *

* All of the bundleNames * must share a common package prefix. The package prefix * implicitly specifies the resources directory * (see {@link #getResourcesDir}). For example, the resources * directory for bundle names "myapp.resources.foo" and * "myapp.resources.bar", would be "myapp/resources/". If * bundle names don't share a common package prefix, then * an IllegalArgumentException is thrown. * * @param parent parent ResourceMap or null * @param classLoader the ClassLoader to be used to load the ResourceBundle * @param bundleNames names of the ResourceBundle to be loaded * @throws IllegalArgumentException if classLoader or any bundleName is * null, if no bundleNames are specified, if any bundleName is an * empty (zero length) String, or if all of the bundleNames don't * have a common package prefix * @see ResourceBundle * @see #getParent * @see #getClassLoader * @see #getResourcesDir * @see #getBundleNames */ public ResourceMap(ResourceMap parent, ClassLoader classLoader, List bundleNames) { if (classLoader == null) { throw new IllegalArgumentException("null ClassLoader"); } if ((bundleNames == null) || (bundleNames.size() == 0)) { throw new IllegalArgumentException("no bundle specified"); } for (String bn : bundleNames) { if ((bn == null) || (bn.length() == 0)) { throw new IllegalArgumentException("invalid bundleName: \"" + bn + "\""); } } String bpn = bundlePackageName(bundleNames.get(0)); for (String bn : bundleNames) { if (!bpn.equals(bundlePackageName(bn))) { throw new IllegalArgumentException("bundles not colocated: \"" + bn + "\" != \"" + bpn + "\""); } } this.parent = parent; this.classLoader = classLoader; this.bundleNames = Collections.unmodifiableList(new ArrayList(bundleNames)); this.resourcesDir = bpn.replace(".", "/") + "/"; } private String bundlePackageName(String bundleName) { int i = bundleName.lastIndexOf("."); return (i == -1) ? "" : bundleName.substring(0, i); } /** * Just a convenience version of the constructor for the common case * where there's only one bundle name. Defined as: * this(parent, classLoader, Arrays.asList(bundleNames)). * @param parent * @param classLoader * @param bundleNames */ public ResourceMap(ResourceMap parent, ClassLoader classLoader, String... bundleNames) { this(parent, classLoader, Arrays.asList(bundleNames)); } /** * Returns the parent ResourceMap, or null. Logically, this ResourceMap * contains all of the resources defined here and (recursively) in the * parent. * * @return the parent ResourceMap or null */ public ResourceMap getParent() { return parent; } /** * Returns the names of the ResourceBundles that define the * resources contained by this ResourceMap. * * @return the names of the ResourceBundles in this ResourceMap */ public List getBundleNames() { return bundleNames; } /** * Returns the ClassLoader used to load the ResourceBundles for this * ResourceMap. * * @return the classLoader constructor argument */ public ClassLoader getClassLoader() { return classLoader; } /** * Returns the resources directory that contains all of the ResourceBundles * in this ResourceMap. It can be used with the the classLoader property * to load files from the resources directory. For example: *

     * String filename = myResourceMap.getResourcesDir() + "myIcon.png";
     * URL url = myResourceMap.getClassLoader().getResource(filename);
     * new ImageIcon(iconURL);
     * 
* * @return the the resources directory for this ResourceMap */ public String getResourcesDir() { return resourcesDir; } /* Lazily flattens all of the ResourceBundles named in bundleNames * into a single Map - bundlesMapP. The bundleNames list is in * priority order, the first entry shadows later entries. */ private synchronized Map getBundlesMap() { // If the default locale has changed, then reload Locale defaultLocale = Locale.getDefault(); if (locale != defaultLocale) { bundlesLoaded = false; locale = defaultLocale; } if (!bundlesLoaded) { String resourceSuffix = getPlatform().getResourceSuffix(); Map bundlesMap = new ConcurrentHashMap(); for (int i = bundleNames.size() - 1; i >= 0; i--) { populateResourceMap(bundleNames.get(i), bundlesMap); if (!resourceSuffix.isEmpty()) populateResourceMap(bundleNames.get(i)+"_"+resourceSuffix, bundlesMap); } bundlesMapP = bundlesMap; bundlesLoaded = true; } return bundlesMapP; } private void populateResourceMap(String bundleName, Map bundlesMap) { try { ResourceBundle bundle = ResourceBundle.getBundle(bundleName, locale, classLoader); Enumeration keys = bundle.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); bundlesMap.put(key, bundle.getObject(key)); } } catch (MissingResourceException ignore) { /* bundleName is just a location to check, it's not * guaranteed to name a ResourceBundle */ } } private void checkNullKey(String key) { if (key == null) { throw new IllegalArgumentException("null key"); } } private synchronized Set getBundlesMapKeys() { if (bundlesMapKeysP == null) { Set allKeys = new HashSet(getResourceKeySet()); ResourceMap parent = getParent(); if (parent != null) { allKeys.addAll(parent.keySet()); } bundlesMapKeysP = Collections.unmodifiableSet(allKeys); } return bundlesMapKeysP; } /** * Return a unmodifiable {@link Set} that contains all of the keys in * this ResourceMap and (recursively) its parent ResourceMaps. * * @return all of the keys in this ResourceMap and its parent * @see #getParent */ public Set keySet() { return getBundlesMapKeys(); } /** * Returns true if this resourceMap or its parent (recursively) contains * the specified key. * * @return true if this resourceMap or its parent contains the specified key. * @see #getParent * @see #keySet */ public boolean containsKey(String key) { checkNullKey(key); if (containsResourceKey(key)) { return true; } else { ResourceMap parent = getParent(); return (parent != null) && parent.containsKey(key); } } public PlatformType getPlatform() { if (platform != null) return platform; if (parent != null) return parent.getPlatform(); return PlatformType.DEFAULT; } public void setPlatform(PlatformType platform) { if(platform == null) throw new IllegalArgumentException("Platform could not be null."); if (this.platform != null) throw new IllegalStateException("The platform attribute is already set for this resource map."); this.platform = platform; } /** * Unchecked exception thrown by {@link #getObject} when resource lookup * fails, for example because string conversion fails. This is * not a missing resource exception. If a resource isn't defined * for a particular key, getObject does not throw an exception. * * @see #getObject */ public static class LookupException extends RuntimeException { private final Class type; private final String key; /** * Constructs an instance of this class with some useful information * about the failure. * * @param msg the detail message * @param type the type of the resource * @param key the name of the resource */ public LookupException(String msg, String key, Class type) { super(String.format("%s: resource %s, type %s", msg, key, type)); this.key = key; this.type = type; } /** * Returns the type of the resource for which lookup failed. * @return the resource type */ public Class getType() { return type; } /** * Returns the type of the name of resource for which lookup failed. * @return the resource name */ public String getKey() { return key; } } /** * By default this method is used by {@code keySet} to * get the names of the resources defined in this ResourceMap. * This method lazily loads the ResourceBundles named * by the constructor. *

* The protected {@code getResource}, {@code putResource}, and * {@code containsResourceKey}, {@code getResourceKeySet} abstract * the internal representation of this ResourceMap's list of * {@code ResourceBundles}. Most applications can ignore them. * * @return the names of the resources defined in this ResourceMap * @see #getResource * @see #putResource * @see #containsResourceKey */ protected Set getResourceKeySet() { Map bundlesMap = getBundlesMap(); if (bundlesMap == null) { return Collections.emptySet(); } else { return bundlesMap.keySet(); } } /** * By default this method is used by {@code getObject} to see * if a resource is defined by this ResourceMap. This method lazily * loads the ResourceBundles named by the constructor. *

* The protected {@code getResource}, {@code putResource}, and * {@code containsResourceKey}, {@code getResourceKeySet} abstract * the internal representation of this ResourceMap's list of * {@code ResourceBundles}. Most applications can ignore them. *

* If {@code key} is null, an IllegalArgumentException is thrown. * * @param key the name of the resource * @return true if a resource named {@code key} is defined in this ResourceMap * @see #getResource * @see #putResource * @see #getResourceKeySet */ protected boolean containsResourceKey(String key) { checkNullKey(key); Map bundlesMap = getBundlesMap(); return (bundlesMap != null) && bundlesMap.containsKey(key); } /** * By default this method is used by {@code getObject} to look up * resource values in the internal representation of the {@code * ResourceBundles} named when this ResourceMap was constructed. * If a resource named {@code key} is {@link #containsResourceKey defined} * then its value is returned, otherwise null. * The {@code getResource} method lazily loads the * ResourceBundles named by the constructor. *

* The protected {@code getResource}, {@code putResource}, and * {@code containsResourceKey}, {@code getResourceKeySet} abstract * the internal representation of this ResourceMap's list of * {@code ResourceBundles}. Most applications can ignore them. *

* If {@code key} is null, an IllegalArgumentException is thrown. * * @param key the name of the resource * @return the value of the resource named {@code key} (can be null) * @see #putResource * @see #containsResourceKey * @see #getResourceKeySet */ protected Object getResource(String key) { checkNullKey(key); Map bundlesMap = getBundlesMap(); Object value = (bundlesMap != null) ? bundlesMap.get(key) : null; return (value == NULL_RESOURCE) ? null : value; } /** * By default this method is used by {@code getObject} to cache * values that have been retrieved, evaluated (as in ${key} * expressions), and string converted. A subclass could override * this method to defeat caching or to refine the caching strategy. * The {@code putResource} method lazily loads ResourceBundles. *

* The protected {@code getResource}, {@code putResource}, and * {@code containsResourceKey}, {@code getResourceKeySet} abstract * the internal representation of this ResourceMap's list of * {@code ResourceBundles}. Most applications can ignore them. *

* If {@code key} is null, an IllegalArgumentException is thrown. * * @param key the name of the resource * @param value the value of the resource (can be null) * @see #getResource * @see #containsResourceKey * @see #getResourceKeySet */ protected void putResource(String key, Object value) { checkNullKey(key); if (KEY_PLATFORM.equals(key)) { setPlatform((PlatformType) value); } else { Map bundlesMap = getBundlesMap(); if (bundlesMap != null) { bundlesMap.put(key, (value == null) ? NULL_RESOURCE : value); } } } /** * Returns the value of the resource named key, or null * if no resource with that name exists. A resource exists if * it's defined in this ResourceMap or (recursively) in the * ResourceMap's parent. *

* String resources may contain variables that name other * resources. Each ${variable-key} variable is replaced * with the value of a string resource named * variable-key. For example, given the following * resources: *

     * Application.title = My Application
     * ErrorDialog.title = Error: ${application.title}
     * WarningDialog.title = Warning: ${application.title}
     * 
* The value of "WarningDialog.title" would be * "Warning: My Application". To include "${" in a * resource, insert a backslash before the "$". For example, the * value of escString in the example below, would * be "${hello}": *
     * escString = \\${hello}
     * 
* Note that, in a properties file, the backslash character is * used for line continuation, so we've had to escape that too. * If the value of a resource is the special variable ${null}, * then the resource will be removed from this ResourceMap. *

* The value returned by getObject will be of the specified type. If a * string valued resource exists for key, and type is not * String.class, the value will be converted using a * ResourceConverter and the ResourceMap entry updated with the * converted value. *

* If the named resource exists and an error occurs during lookup, * then a ResourceMap.LookupException is thrown. This can * happen if string conversion fails, or if resource parameters * can't be evaluated, or if the existing resource is of the wrong * type. *

* An IllegalArgumentException is thrown if key or type are null. * * @param key resource name * @param type resource type * @return the value of the resource * @see #getParent * @see ResourceConverter#forType * @see ResourceMap.LookupException * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key or type are null */ public Object getObject(String key, Class type) { checkNullKey(key); if (type == null) { throw new IllegalArgumentException("null type"); } if (type.isPrimitive()) { if (type == Boolean.TYPE) { type = Boolean.class; } else if (type == Character.TYPE) { type = Character.class; } else if (type == Byte.TYPE) { type = Byte.class; } else if (type == Short.TYPE) { type = Short.class; } else if (type == Integer.TYPE) { type = Integer.class; } else if (type == Long.TYPE) { type = Long.class; } else if (type == Float.TYPE) { type = Float.class; } else if (type == Double.TYPE) { type = Double.class; } } Object value = null; ResourceMap resourceMapNode = this; /* Find the ResourceMap bundlesMap that contains a non-null * value for the specified key, first check this ResourceMap, * then its parents. */ while (resourceMapNode != null) { if (resourceMapNode.containsResourceKey(key)) { value = resourceMapNode.getResource(key); break; } resourceMapNode = resourceMapNode.getParent(); } /* If we've found a String expression then replace * any ${key} variables, and then reset the * the original resourceMapNode entry. */ if ((value instanceof String) && ((String) value).contains("${")) { value = evaluateStringExpression((String) value); resourceMapNode.putResource(key, value); } /* If the value we've found in resourceMapNode is * the expected type, then we're done. If the expected * type is primitive and the value is the corresponding * object type then we're done too. Otherwise, * if it's a String, then try and convert the String * and replace the original resourceMapNode entry, * otherwise return null. */ if (value != null) { Class valueClass = value.getClass(); if (!type.isAssignableFrom(valueClass)) { if (value instanceof String) { ResourceConverter stringConverter = ResourceConverter.forType(type); if (stringConverter != null) { String sValue = (String) value; try { value = stringConverter.parseString(sValue, resourceMapNode); resourceMapNode.putResource(key, value); } catch (ResourceConverterException e) { String msg = "string conversion failed"; LookupException lfe = new LookupException(msg, key, type); lfe.initCause(e); throw lfe; } } else { String msg = "no StringConverter for required type"; throw new LookupException(msg, key, type); } } else { String msg = "named resource has wrong type"; throw new LookupException(msg, key, type); } } } return value; } /* Given the following resources: * * hello = Hello * world = World * place = ${world} * * The value of evaluateStringExpression("${hello} ${place}") * would be "Hello World". The value of ${null} is null. */ private String evaluateStringExpression(String expr) { if (expr.trim().equals("${null}")) { return null; } StringBuffer value = new StringBuffer(); int i0 = 0, i1; while ((i1 = expr.indexOf("${", i0)) != -1) { if ((i1 == 0) || ((i1 > 0) && (expr.charAt(i1 - 1) != '\\'))) { int i2 = expr.indexOf("}", i1); if ((i2 != -1) && (i2 > i1 + 2)) { String k = expr.substring(i1 + 2, i2); String v = getString(k); value.append(expr.substring(i0, i1)); if (v != null) { value.append(v); } else { String msg = String.format("no value for \"%s\" in \"%s\"", k, expr); throw new LookupException(msg, k, String.class); } i0 = i2 + 1; // skip trailing "}" } else { String msg = String.format("no closing brace in \"%s\"", expr); throw new LookupException(msg, "", String.class); } } else { // we've found an escaped variable - "\${" value.append(expr.substring(i0, i1 - 1)); value.append("${"); i0 = i1 + 2; // skip past "${" } } value.append(expr.substring(i0)); return value.toString(); } /** * If no arguments are specified, return the String value * of the resource named key. This is * equivalent to calling getObject(key, String.class) * If arguments are provided, then the type of the resource * named key is assumed to be * {@link String#format(String, Object...) format} string, * which is applied to the arguments if it's non null. * For example, given the following resources *

     * hello = Hello %s
     * 
* then the value of getString("hello", "World") would * be "Hello World". * * @param key * @param args * @return the String value of the resource named key * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @see #getObject * @see String#format(String, Object...) */ public String getString(String key, Object... args) { if (args.length == 0) { return (String) getObject(key, String.class); } else { String format = (String) getObject(key, String.class); return (format == null) ? null : String.format(format, args); } } /** * A convenience method that's shorthand for calling: * getObject(key, Boolean.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Boolean value of the resource named key * @see #getObject */ public final Boolean getBoolean(String key) { return (Boolean) getObject(key, Boolean.class); } /** * A convenience method that's shorthand for calling: * getObject(key, Integer.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Integer value of the resource named key * @see #getObject */ public final Integer getInteger(String key) { return (Integer) getObject(key, Integer.class); } /** * A convenience method that's shorthand for calling: * getObject(key, Long.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Long value of the resource named key * @see #getObject */ public final Long getLong(String key) { return (Long) getObject(key, Long.class); } /** * A convenience method that's shorthand for calling: * getObject(key, Short.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Short value of the resource named key * @see #getObject */ public final Short getShort(String key) { return (Short) getObject(key, Short.class); } /** * A convenience method that's shorthand for calling: * getObject(key, Byte.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Byte value of the resource named key * @see #getObject */ public final Byte getByte(String key) { return (Byte) getObject(key, Byte.class); } /** * A convenience method that's shorthand for calling: * getObject(key, Float.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Float value of the resource named key * @see #getObject */ public final Float getFloat(String key) { return (Float) getObject(key, Float.class); } /** * A convenience method that's shorthand for calling: * getObject(key, Double.class). * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the Double value of the resource named key * @see #getObject */ public final Double getDouble(String key) { return (Double) getObject(key, Double.class); } /** * * A convenience method that's shorthand for calling: * getObject(key, Icon.class). This method * relies on the ImageIcon ResourceConverter that's registered * by this class. See {@link #getImageIcon} for more information. * * * @param key the name of the resource * @return the Icon value of the resource named key * @see #getObject * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null */ public final Icon getIcon(String key) { return (Icon) getObject(key, Icon.class); } /** * * A convenience method that's shorthand for calling: * getObject(key, ImageIcon.class). This method * relies on the ImageIcon ResourceConverter that's registered * by this class. *

* If the resource named key is a String, it should name * an image file to be found in the resources subdirectory that * also contains the ResourceBundle (typically a ".properties" * file) that was used to create the corresponding ResourceMap. *

* For example, given the ResourceMap produced by * Application.getClass(com.mypackage.MyClass.class), * and a ResourceBundle called MyClass.properties * in com.mypackage.resources: *

     * openIcon = myOpenIcon.png
     * 
* then resourceMap.getIcon("openIcon") would load * the image file called "myOpenIcon.png" from the resources * subdirectory, effectively like this: *
     * String filename = myResourceMap.getResourcesDir() + "myOpenIcon.png";
     * URL url = myResourceMap.getClassLoader().getResource(filename);
     * new ImageIcon(iconURL);
     * 
* * * @param key the name of the resource * @return the ImageIcon value of the resource named key * @see #getObject * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null */ public final ImageIcon getImageIcon(String key) { return (ImageIcon) getObject(key, ImageIcon.class); } /** * * A convenience method that's shorthand for calling: * getObject(key, Font.class). This method relies * on the Font ResourceConverter that's registered by this class. * Font resources may be defined with strings that are * recognized by {@link Font#decode}, * face-STYLE-size. * For example: *
     * myFont = Arial-PLAIN-12
     * 
* * * @param key the name of the resource * @return the Font value of the resource named key * @see #getObject * @see ResourceConverter#forType * @see Font#decode * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalResourceConverteron if key is null */ public final Font getFont(String key) { return (Font) getObject(key, Font.class); } /** * * A convenience method that's shorthand for calling: * getObject(key, Color.class). This method relies on the * Color ResourceConverter that's registered by this class. It defines * an improved version of Color.decode() * that supports colors with an alpha channel and comma * separated RGB[A] values. Legal format for color resources are: *
     * myHexRGBColor = #RRGGBB 
     * myHexAlphaRGBColor = #AARRGGBB
     * myRGBColor = R, G, B
     * myAlphaRGBColor = R, G, B, A
     * 
* The first two examples, with the leading "#" encode the color * with 3 or 4 hex values and the latter with integer values between * 0 and 255. In both cases the value represented by "A" is the * color's (optional) alpha channel. * * * @param key the name of the resource * @return the Color value of the resource named key * @see #getObject * @see ResourceConverter#forType * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException ResourceConverter is null */ public final Color getColor(String key) { return (Color) getObject(key, Color.class); } /** * * A convenience method that's shorthand for calling: * getObject(key, KeyStroke.class). This method relies on the * KeyStroke ResourceConverter that's registered by this class and * uses {@link KeyStroke#getKeyStroke(String s)} to convert strings. * * For example, pressed F reports the "F" key, and control * pressed F reports Control-F. See the KeyStroke JavaDoc for * more information. * * @param key the name of the resource * @return the KeyStroke value of the resource named key * @see #getObject * @see KeyStroke#getKeyStroke * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null */ public final KeyStroke getKeyStroke(String key) { return (KeyStroke) getObject(key, KeyStroke.class); } /** * A convenience method that's shorthand for calling: * getKeyStroke(key).getKeyCode(). If there's * no resource named key then null is returned. * * @param key the name of the resource * @throws LookupException if an error occurs during lookup or string conversion * @throws IllegalArgumentException if key is null * @return the KeyCode value of the resource named key * @see #getObject */ public Integer getKeyCode(String key) { KeyStroke ks = getKeyStroke(key); return (ks != null) ? ks.getKeyCode() : null; } /** * Unchecked exception thrown by {@link #injectComponent} and * {@link #injectComponents} when a property value specified by * a resource can not be set. * * @see #injectComponent * @see #injectComponents */ public static class PropertyInjectionException extends RuntimeException { private final String key; private final Component component; private final String propertyName; /** * Constructs an instance of this class with some useful information * about the failure. * * @param msg the detail message * @param key the name of the resource * @param component the component whose property couldn't be set * @param propertyName the name of the component property */ public PropertyInjectionException(String msg, String key, Component component, String propertyName) { super(String.format("%s: resource %s, property %s, component %s", msg, key, propertyName, component)); this.key = key; this.component = component; this.propertyName = propertyName; } /** * Returns the the name of resource whose value was to be used to set the property * @return the resource name */ public String getKey() { return key; } /** * Returns the component whose property could not be set * @return the component */ public Component getComponent() { return component; } /** * Returns the the name of property that could not be set * @return the property name */ public String getPropertyName() { return propertyName; } } private void injectComponentProperty(Component component, PropertyDescriptor pd, String key) { Method setter = pd.getWriteMethod(); Class type = pd.getPropertyType(); if ((setter != null) && (type != null) && containsKey(key)) { Object value = getObject(key, type); String propertyName = pd.getName(); try { // Note: this could be generalized, we could delegate // to a component property injector. if ("text".equals(propertyName) && (component instanceof AbstractButton)) { MnemonicText.configure(component, (String) value); } else if ("text".equals(propertyName) && (component instanceof JLabel)) { MnemonicText.configure(component, (String) value); } else { setter.invoke(component, value); } } catch (Exception e) { String pdn = pd.getName(); String msg = "property setter failed"; RuntimeException re = new PropertyInjectionException(msg, key, component, pdn); re.initCause(e); throw re; } } else if (type != null) { String pdn = pd.getName(); String msg = "no value specified for resource"; throw new PropertyInjectionException(msg, key, component, pdn); } else if (setter == null) { String pdn = pd.getName(); String msg = "can't set read-only property"; throw new PropertyInjectionException(msg, key, component, pdn); } } private void injectComponentProperties(Component component) { String componentName = component.getName(); if (componentName != null) { /* Optimization: punt early if componentName doesn't * appear in any componentName.propertyName resource keys */ boolean matchingResourceFound = false; for (String key : keySet()) { int i = key.lastIndexOf("."); if ((i != -1) && componentName.equals(key.substring(0, i))) { matchingResourceFound = true; break; } } if (!matchingResourceFound) { return; } BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(component.getClass()); } catch (IntrospectionException e) { String msg = "introspection failed"; RuntimeException re = new PropertyInjectionException(msg, null, component, null); re.initCause(e); throw re; } PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); if ((pds != null) && (pds.length > 0)) { for (String key : keySet()) { int i = key.lastIndexOf("."); String keyComponentName = (i == -1) ? null : key.substring(0, i); if (componentName.equals(keyComponentName)) { if ((i + 1) == key.length()) { /* key has no property name suffix, e.g. "myComponentName." * This is probably a mistake. */ String msg = "component resource lacks property name suffix"; logger.warning(msg); break; } String propertyName = key.substring(i + 1); boolean matchingPropertyFound = false; for (PropertyDescriptor pd : pds) { if (pd.getName().equals(propertyName)) { injectComponentProperty(component, pd, key); matchingPropertyFound = true; break; } } if (!matchingPropertyFound) { String msg = String.format( "[resource %s] component named %s doesn't have a property named %s", key, componentName, propertyName); logger.warning(msg); } } } } } } /** * Set each property in target to the value of * the resource named componentName.propertyName, * where componentName is the value of the * target component's name property, i.e. the value of * target.getName(). The type of the resource must * match the type of the corresponding property. Properties * that aren't defined by a resource aren't set. *

* For example, given a button configured like this: *

     * myButton = new JButton();
     * myButton.setName("myButton");
     * 
* And a ResourceBundle properties file with the following * resources: *
     * myButton.text = Hello World
     * myButton.foreground = 0, 0, 0 
     * myButton.preferredSize = 256, 256
     * 
* Then injectComponent(myButton) would initialize * myButton's text, foreground, and preferredSize properties * to Hello World, new Color(0,0,0), and * new Dimension(256,256) respectively. *

* This method calls {@link #getObject} to look up resources * and it uses {@link Introspector#getBeanInfo} to find * the target component's properties. *

* If target is null an IllegalArgumentException is thrown. If a * resource is found that matches the target component's name but * the corresponding property can't be set, an (unchecked) {@link * PropertyInjectionException} is thrown. * * * * @param target the Component to inject * @see #injectComponents * @see #getObject * @see ResourceConverter#forType * @throws LookupException if an error occurs during lookup or string conversion * @throws PropertyInjectionException if a property specified by a resource can't be set * @throws IllegalArgumentException if target is null */ public void injectComponent(Component target) { if (target == null) { throw new IllegalArgumentException("null target"); } injectComponentProperties(target); } /** * Applies {@link #injectComponent} to each Component in the * hierarchy with root root. * * @param root the root of the component hierarchy * @throws PropertyInjectionException if a property specified by a resource can't be set * @throws IllegalArgumentException if target is null * @see #injectComponent */ public void injectComponents(Component root) { injectComponent(root); if (root instanceof JMenu) { /* Warning: we're bypassing the popupMenu here because * JMenu#getPopupMenu creates it; doesn't seem right * to do so at injection time. Unfortunately, this * means that attempts to inject the popup menu's * "label" property will fail. */ JMenu menu = (JMenu) root; for (Component child : menu.getMenuComponents()) { injectComponents(child); } } else if (root instanceof Container) { Container container = (Container) root; for (Component child : container.getComponents()) { injectComponents(child); } } } /** * Unchecked exception thrown by {@link #injectFields} when * an error occurs while attempting to set a field (a field that * had been marked with @Resource). * * @see #injectFields */ public static class InjectFieldException extends RuntimeException { private final Field field; private final Object target; private final String key; /** * Constructs an instance of this class with some useful information * about the failure. * * @param msg the detail message * @param field the Field we were attempting to set * @param target the object whose field we were attempting to set * @param key the name of the resource */ public InjectFieldException(String msg, Field field, Object target, String key) { super(String.format("%s: resource %s, field %s, target %s", msg, key, field, target)); this.field = field; this.target = target; this.key = key; } /** * Return the Field whose value couldn't be set. * @return the field whose value couldn't be set */ public Field getField() { return field; } /** * Return the Object whose Field we were attempting to set * @return the Object whose Field we were attempting to set */ public Object getTarget() { return target; } /** * Returns the type of the name of resource for which lookup failed. * @return the resource name */ public String getKey() { return key; } } private void injectField(Field field, Object target, String key) { Class type = field.getType(); if (type.isArray()) { type = type.getComponentType(); Pattern p = Pattern.compile(key + "\\[([\\d]+)\\]"); // matches key[12] for (String arrayElementKey : keySet()) { Matcher m = p.matcher(arrayElementKey); if (m.matches()) { /* field's value is an array, arrayElementKey is a resource * name of the form "MyClass.myArray[12]" and m.group(1) * matches the array index. Set the index element * of the field's array to the value of the resource. */ Object value = getObject(arrayElementKey, type); if (!field.isAccessible()) { field.setAccessible(true); } try { int index = Integer.parseInt(m.group(1)); Array.set(field.get(target), index, value); } /* Array.set throws IllegalArgumentException, ArrayIndexOutOfBoundsException * field.get throws IllegalAccessException(Checked), IllegalArgumentException * Integer.parseInt throws NumberFormatException (Checked) */ catch (Exception e) { String msg = "unable to set array element"; InjectFieldException ife = new InjectFieldException(msg, field, target, key); ife.initCause(e); throw ife; } } } } else { // field is not an array Object value = getObject(key, type); if (value != null) { if (!field.isAccessible()) { field.setAccessible(true); } try { field.set(target, value); } /* Field.set throws IllegalAccessException, IllegalArgumentException, * ExceptionInInitializerError */ catch (Exception e) { String msg = "unable to set field's value"; InjectFieldException ife = new InjectFieldException(msg, field, target, key); ife.initCause(e); throw ife; } } } } /** * Set each field with a @Resource annotation in the target object, * to the value of a resource whose name is the simple name of the target * class followed by "." followed by the name of the field. If the * key @Resource parameter is specified, then a resource with that name * is used instead. Array valued fields can also be initialized * with resources whose names end with "[index]". For example: *

     * class MyClass {
     *   @Resource String sOne; 
     *   @Resource(key="sTwo") String s2; 
     *   @Resource int[] numbers = new int[2];
     * }
     * 
* Given the previous class and the following resource file: *
     * MyClass.sOne = One
     * sTwo = Two
     * MyClass.numbers[0] = 10
     * MyClass.numbers[1] = 11
     * 
* Then injectFields(new MyClass()) would initialize the MyClass * sOne field to "One", the s2 field to "Two", and the * two elements of the numbers array to 10 and 11. *

* If target is null an IllegalArgumentException is * thrown. If an error occurs during resource lookup, then an * unchecked LookupException is thrown. If a target field marked * with @Resource can't be set, then an unchecked * InjectFieldException is thrown. * * @param target the object whose fields will be initialized * @throws LookupException if an error occurs during lookup or string conversion * @throws InjectFieldException if a field can't be set * @throws IllegalArgumentException if target is null * @see #getObject */ public void injectFields(Object target) { if (target == null) { throw new IllegalArgumentException("null target"); } Class targetType = target.getClass(); if (targetType.isArray()) { throw new IllegalArgumentException("array target"); } String keyPrefix = targetType.getSimpleName() + "."; for (Field field : targetType.getDeclaredFields()) { Resource resource = field.getAnnotation(Resource.class); if (resource != null) { String rKey = resource.key(); String key = (rKey.length() > 0) ? rKey : keyPrefix + field.getName(); injectField(field, target, key); } } } /* Register ResourceConverters that are defined in this class * and documented here. */ static { ResourceConverter[] stringConverters = { new ColorStringConverter(), new IconStringConverter(), new ImageStringConverter(), new FontStringConverter(), new KeyStrokeStringConverter(), new DimensionStringConverter(), new PointStringConverter(), new RectangleStringConverter(), new InsetsStringConverter(), new EmptyBorderStringConverter() }; for (ResourceConverter sc : stringConverters) { ResourceConverter.register(sc); } } /* If path doesn't have a leading "/" then the resourcesDir * is prepended, otherwise the leading "/" is removed. */ private static String resourcePath(final String path, ResourceMap resourceMap) { if (path == null) { return null; } else if (path.startsWith("/")) { return (path.length() > 1) ? path.substring(1) : null; } else { return resourceMap.getResourcesDir() + path; } } private static ImageIcon loadImageIcon(String s, ResourceMap resourceMap) throws ResourceConverterException { String rPath = resourcePath(s, resourceMap); if (rPath == null) { String msg = String.format("invalid image/icon path \"%s\"", s); throw new ResourceConverterException(msg, s); } URL url = resourceMap.getClassLoader().getResource(rPath); if (url != null) { return new ImageIcon(url); } else { String msg = String.format("couldn't find Icon resource \"%s\"", s); throw new ResourceConverterException(msg, s); } } private static class FontStringConverter extends ResourceConverter { FontStringConverter() { super(Font.class); } /* Just delegates to Font.decode. * Typical string is: face-STYLE-size, for example "Arial-PLAIN-12" */ @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { return Font.decode(s); } } private static class ColorStringConverter extends ResourceConverter { ColorStringConverter() { super(Color.class); } // private void error(String msg, String s, Exception e) throws ResourceConverterException { // throw new ResourceConverterException(msg, s, e); // } /* An improved version of Color.decode() that supports colors * with an alpha channel and comma separated RGB[A] values. * Legal format for color resources are: * "#RRGGBB", "#AARRGGBB", "R, G, B", "R, G, B, A" * Thanks to Romain Guy for the code. */ @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { final Color color; if (s.startsWith("#")) { switch (s.length()) { // RGB/hex color case 7: color = Color.decode(s); break; // ARGB/hex color case 9: int alpha = Integer.decode(s.substring(0, 3)); int rgb = Integer.decode("#" + s.substring(3)); color = new Color(alpha << 24 | rgb, true); break; default: throw new ResourceConverterException("invalid #RRGGBB or #AARRGGBB color string", s); } } else { String[] parts = s.split(","); if (parts.length < 3 || parts.length > 4) { throw new ResourceConverterException("invalid R, G, B[, A] color string", s); } try { // with alpha component if (parts.length == 4) { int r = Integer.parseInt(parts[0].trim()); int g = Integer.parseInt(parts[1].trim()); int b = Integer.parseInt(parts[2].trim()); int a = Integer.parseInt(parts[3].trim()); color = new Color(r, g, b, a); } else { int r = Integer.parseInt(parts[0].trim()); int g = Integer.parseInt(parts[1].trim()); int b = Integer.parseInt(parts[2].trim()); color = new Color(r, g, b); } } catch (NumberFormatException e) { throw new ResourceConverterException("invalid R, G, B[, A] color string", s, e); } } return color; } } private static class IconStringConverter extends ResourceConverter { IconStringConverter() { super(Icon.class); } @Override public Object parseString(String s, ResourceMap resourceMap) throws ResourceConverterException { return loadImageIcon(s, resourceMap); } @Override public boolean supportsType(Class testType) { return testType.equals(Icon.class) || testType.equals(ImageIcon.class); } } private static class ImageStringConverter extends ResourceConverter { ImageStringConverter() { super(Image.class); } @Override public Object parseString(String s, ResourceMap resourceMap) throws ResourceConverterException { return loadImageIcon(s, resourceMap).getImage(); } } private static class KeyStrokeStringConverter extends ResourceConverter { KeyStrokeStringConverter() { super(KeyStroke.class); } @Override public Object parseString(String s, ResourceMap ignore) { if (s.contains("shortcut")) { int k = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); s = s.replaceAll("shortcut", (k == Event.META_MASK) ? "meta" : "control"); } return KeyStroke.getKeyStroke(s); } } /* String s is assumed to contain n number substrings separated by * commas. Return a list of those integers or null if there are too * many, too few, or if a substring can't be parsed. The format * of the numbers is specified by Double.valueOf(). */ private static List parseDoubles(String s, int n, String errorMsg) throws ResourceConverterException { String[] doubleStrings = s.split(",", n + 1); if (doubleStrings.length != n) { throw new ResourceConverterException(errorMsg, s); } else { List doubles = new ArrayList(n); for (String doubleString : doubleStrings) { try { doubles.add(Double.valueOf(doubleString)); } catch (NumberFormatException e) { throw new ResourceConverterException(errorMsg, s, e); } } return doubles; } } private static class DimensionStringConverter extends ResourceConverter { DimensionStringConverter() { super(Dimension.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { List xy = parseDoubles(s, 2, "invalid x,y Dimension string"); Dimension d = new Dimension(); d.setSize(xy.get(0), xy.get(1)); return d; } } private static class PointStringConverter extends ResourceConverter { PointStringConverter() { super(Point.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { List xy = parseDoubles(s, 2, "invalid x,y Point string"); Point p = new Point(); p.setLocation(xy.get(0), xy.get(1)); return p; } } private static class RectangleStringConverter extends ResourceConverter { RectangleStringConverter() { super(Rectangle.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { List xywh = parseDoubles(s, 4, "invalid x,y,width,height Rectangle string"); Rectangle r = new Rectangle(); r.setFrame(xywh.get(0), xywh.get(1), xywh.get(2), xywh.get(3)); return r; } } private static class InsetsStringConverter extends ResourceConverter { InsetsStringConverter() { super(Insets.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { List tlbr = parseDoubles(s, 4, "invalid top,left,bottom,right Insets string"); return new Insets(tlbr.get(0).intValue(), tlbr.get(1).intValue(), tlbr.get(2).intValue(), tlbr.get(3).intValue()); } } private static class EmptyBorderStringConverter extends ResourceConverter { EmptyBorderStringConverter() { super(EmptyBorder.class); } @Override public Object parseString(String s, ResourceMap ignore) throws ResourceConverterException { List tlbr = parseDoubles(s, 4, "invalid top,left,bottom,right EmptyBorder string"); return new EmptyBorder(tlbr.get(0).intValue(), tlbr.get(1).intValue(), tlbr.get(2).intValue(), tlbr.get(3).intValue()); } } } bsaf-1.9/src/main/java/org/jdesktop/application/SessionStorage.java000066400000000000000000000503651151640306200254710ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.session.SplitPaneProperty; import org.jdesktop.application.session.WindowProperty; import org.jdesktop.application.session.TableProperty; import org.jdesktop.application.session.TabbedPaneProperty; import org.jdesktop.application.session.PropertySupport; import java.applet.Applet; import java.awt.Component; import java.awt.Container; import java.awt.Window; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.swing.*; /** * Support for storing GUI state that persists between Application sessions. *

* This class simplifies the common task of saving a little bit of an * application's GUI "session" state when the application shuts down, * and then restoring that state when the application is restarted. * Session state is stored on a per component basis, and only for * components with a {@link java.awt.Component#getName name} and for * which a {@code PropertySupport} object has been defined and registeres. * SessionState Properties that preserve the {@code bounds} {@code Rectangle} * for Windows, the {@code dividerLocation} for {@code JSliderPanes} and the * {@code selectedIndex} for {@code JTabbedPanes} are defined by default. The * {@code ApplicationContext} {@link * ApplicationContext#getSessionStorage getSessionStorage} method * provides a shared {@code SessionStorage} object. *

* A typical Application saves session state in its * {@link Application#shutdown shutdown()} method, and then restores * session state in {@link Application#startup startup()}: *

 * public class MyApplication extends Application {
 *     @Override protected void shutdown() {
 *         getContext().getSessionStorage().save(mainFrame, "session.xml");
 *     }
 *     @Override protected void startup() {
 *         ApplicationContext appContext = getContext();
 *         appContext.setVendorId("Sun");
 *         appContext.setApplicationId("SessionStorage1");
 *         // ... create the GUI rooted by JFrame mainFrame
 *         appContext.getSessionStorage().restore(mainFrame, "session.xml");
 *     }
 *     // ...
 * }
 * 
* In this example, the bounds of {@code mainFrame} as well the * session state for any of its {@code JSliderPane} or {@code * JTabbedPane} will be saved when the application shuts down, and * restored when the applications starts up again. Note: error * handling has been omitted from the example. *

* Session state is stored locally, relative to the user's * home directory, by the {@code LocalStorage} * {@link LocalStorage#save save} and {@link LocalStorage#save load} * methods. The {@code startup} method must set the * {@code ApplicationContext} {@code vendorId} and {@code applicationId} * properties to ensure that the correct * {@link LocalStorage#getDirectory local directory} is selected on * all platforms. For example, on Windows XP, the full pathname * for filename {@code "session.xml"} is typically: *

 * ${userHome}\Application Data\${vendorId}\${applicationId}\session.xml
 * 
* Where the value of {@code ${userHome}} is the the value of * the Java System property {@code "user.home"}. On Solaris or * Linux the file is: *
 * ${userHome}/.${applicationId}/session.xml
 * 
* and on OSX: *
 * ${userHome}/Library/Application Support/${applicationId}/session.xml
 * 
* * @see ApplicationContext#getSessionStorage * @see LocalStorage */ public class SessionStorage { private static Logger logger = Logger.getLogger(SessionStorage.class.getName()); private final Map propertyMap; private final ApplicationContext context; /** * Constructs a SessionStorage object. The following {@link * PropertySupport PropertySupport} objects are registered by default: *

* * * * * * * * * * * * * * * * * * * * * * * * * * *
Base Component TypePropertySupportPropertySupport Value
WindowWindowPropertyWindowState
JTabbedPaneTabbedPanePropertyTabbedPaneState
JSplitPaneSplitPanePropertySplitPaneState
JTableTablePropertyTableState
*

* Applications typically would not create a {@code SessionStorage} * object directly, they'd use the shared ApplicationContext value: *

     * ApplicationContext ctx = Application.getInstance(MyApplication.class).getContext();
     * SessionStorage ss = ctx.getSesssionStorage();
     * 
* * * @param context * @see ApplicationContext#getSessionStorage * @see #getProperty(Class) * @see #getProperty(Component) */ protected SessionStorage(ApplicationContext context) { if (context == null) { throw new IllegalArgumentException("null context"); } this.context = context; propertyMap = new HashMap(); propertyMap.put(Window.class, new WindowProperty()); propertyMap.put(JTabbedPane.class, new TabbedPaneProperty()); propertyMap.put(JSplitPane.class, new SplitPaneProperty()); propertyMap.put(JTable.class, new TableProperty()); } /** * Returns {@link ApplicationContext} which was used during creation of this * {@code SessionStorage} object. * @return the application context for this session storage object */ protected final ApplicationContext getContext() { return context; } private void checkSaveRestoreArgs(Component root, String fileName) { if (root == null) { throw new IllegalArgumentException("null root"); } if (fileName == null) { throw new IllegalArgumentException("null fileName"); } } /* At some point we may replace this with a more complex scheme. */ private String getComponentName(Component c) { return c.getName(); } /* Returns a string that uniquely identifies this component, or null * if Component c doesn't have a name per getComponentName(). The * pathname is basically the name of all of the components, starting * with c, separated by "/". This path is the reverse of what's * typical, the first path element is c's name, rather than the name * of c's root Window or Applet. That way pathnames can be * distinguished without comparing much of the string. The names * of intermediate components *can* be null, we substitute * "[type][z-order]" for the name. Here's an example: * * JFrame myFrame = new JFrame(); * JPanel p = new JPanel() {}; // anonymous JPanel subclass * JButton myButton = new JButton(); * myButton.setName("myButton"); * p.add(myButton); * myFrame.add(p); * * getComponentPathname(myButton) => * "myButton/AnonymousJPanel0/null.contentPane/null.layeredPane/JRootPane0/myFrame" * * Notes about name usage in AWT/Swing: JRootPane (inexplicably) assigns * names to it's children (layeredPane, contentPane, glassPane); * all AWT components lazily compute a name. If we hadn't assigned the * JFrame a name, it's name would have been "frame0". */ private String getComponentPathname(Component c) { String name = getComponentName(c); if (name == null) { return null; } StringBuilder path = new StringBuilder(name); while ((c.getParent() != null) && !(c instanceof Window) && !(c instanceof Applet)) { c = c.getParent(); name = getComponentName(c); if (name == null) { int n = c.getParent().getComponentZOrder(c); if (n >= 0) { Class cls = c.getClass(); name = cls.getSimpleName(); if (name.length() == 0) { name = "Anonymous" + cls.getSuperclass().getSimpleName(); } name = name + n; } else { // Implies that the component tree is changing // while we're computing the path. Punt. logger.warning("Couldn't compute pathname for " + c); return null; } } path.append("/").append(name); } return path.toString(); } /* Recursively walk the component tree, breadth first, storing the * state - PropertySupport.getSessionState() - of named components under * their pathname (the key) in stateMap. * * Note: the breadth first tree-walking code here should remain * structurally identical to restoreTree(). */ private void saveTree(List roots, Map stateMap) { List allChildren = new ArrayList(); for (Component root : roots) { if (root != null) { PropertySupport p = getProperty(root); if (p != null) { String pathname = getComponentPathname(root); if (pathname != null) { Object state = p.getSessionState(root); if (state != null) { stateMap.put(pathname, state); } } } } if (root instanceof Container) { Component[] children = ((Container) root).getComponents(); if ((children != null) && (children.length > 0)) { Collections.addAll(allChildren, children); } } } if (allChildren.size() > 0) { saveTree(allChildren, stateMap); } } /** * Saves the state of each named component in the specified hierarchy to * a file using {@link LocalStorage#save LocalStorage.save(fileName)}. * Each component is visited in breadth-first order: if a {@code PropertySupport} * {@link #getProperty(Component) exists} for that component, * and the component has a {@link java.awt.Component#getName name}, then * its {@link PropertySupport#getSessionState state} is saved. *

* Component names can be any string however they must be unique * relative to the name's of the component's siblings. Most Swing * components do not have a name by default, however there are * some exceptions: JRootPane (inexplicably) assigns names to it's * children (layeredPane, contentPane, glassPane); and all AWT * components lazily compute a name, so JFrame, JDialog, and * JWindow also have a name by default. *

* The type of sessionState values (i.e. the type of values * returned by {@code PropertySupport.getSessionState}) must be one those * supported by {@link java.beans.XMLEncoder XMLEncoder} and * {@link java.beans.XMLDecoder XMLDecoder}, for example beans * (null constructor, read/write properties), primitives, and * Collections. Java bean classes and their properties must be * public. Typically beans defined for this purpose are little * more than a handful of simple properties. The JDK 6 * @ConstructorProperties annotation can be used to eliminate * the need for writing set methods in such beans, e.g. *

     * public class FooBar {
     *     private String foo, bar;
     *     // Defines the mapping from constructor params to properties
     *     @ConstructorProperties({"foo", "bar"})
     *     public FooBar(String foo, String bar) {
     *         this.foo = foo;
     *         this.bar = bar;
     *     }
     *     public String getFoo() { return foo; }  // don't need setFoo
     *     public String getBar() { return bar; }  // don't need setBar
     * }
     * 
* * @param root the root of the Component hierarchy to be saved. * @param fileName the {@code LocalStorage} filename. * @throws IOException * @see #restore * @see ApplicationContext#getLocalStorage * @see LocalStorage#save * @see #getProperty(Component) */ public void save(Component root, String fileName) throws IOException { checkSaveRestoreArgs(root, fileName); Map stateMap = new HashMap(); saveTree(Collections.singletonList(root), stateMap); LocalStorage lst = getContext().getLocalStorage(); lst.save(stateMap, fileName); } /* Recursively walk the component tree, breadth first, restoring the * state - PropertySupport.setSessionState() - of named components for which * there's a non-null entry under the component's pathName in * stateMap. * * Note: the breadth first tree-walking code here should remain * structurally identical to saveTree(). */ private void restoreTree(List roots, Map stateMap) { List allChildren = new ArrayList(); for (Component root : roots) { if (root != null) { PropertySupport p = getProperty(root); if (p != null) { String pathname = getComponentPathname(root); if (pathname != null) { Object state = stateMap.get(pathname); if (state != null) { p.setSessionState(root, state); } else { logger.warning("No saved state for " + root); } } } } if (root instanceof Container) { Component[] children = ((Container) root).getComponents(); if ((children != null) && (children.length > 0)) { Collections.addAll(allChildren, children); } } } if (allChildren.size() > 0) { restoreTree(allChildren, stateMap); } } /** * Restores each named component in the specified hierarchy * from the session state loaded from * a file using {@link LocalStorage#save LocalStorage.load(fileName)}. * Each component is visited in breadth-first order: if a * {@link #getProperty(Component) PropertySupport} exists for that component, * and the component has a {@link java.awt.Component#getName name}, then * its state is {@link PropertySupport#setSessionState restored}. * * @param root the root of the Component hierarchy to be restored. * @param fileName the {@code LocalStorage} filename. * @throws IOException * @see #save * @see ApplicationContext#getLocalStorage * @see LocalStorage#save * @see #getProperty(Component) */ public void restore(Component root, String fileName) throws IOException { checkSaveRestoreArgs(root, fileName); LocalStorage lst = getContext().getLocalStorage(); Map stateMap = (Map) (lst.load(fileName)); if (stateMap != null) { restoreTree(Collections.singletonList(root), stateMap); } } private void checkClassArg(Class cls) { if (cls == null) { throw new IllegalArgumentException("null class"); } } /** * Returns the {@code PropertySupport} object that was * {@link #putProperty registered} for the specified class * or a superclass. If no PropertySupport has been registered, * return null. To lookup the session state {@code PropertySupport} * for a {@code Component} use {@link #getProperty(Component)}. *

* * @exception IllegalArgumentException if {@code cls} is null * @param cls the class to which the returned {@code PropertySupport} applies * @return the {@code PropertySupport} registered with {@code putProperty} for * the specified class or the first one registered for a superclass * of {@code cls}. * @see #getProperty(Component) * @see #putProperty * @see #save * @see #restore */ public PropertySupport getProperty(Class cls) { checkClassArg(cls); while (cls != null) { PropertySupport p = propertyMap.get(cls); if (p != null) { return p; } cls = cls.getSuperclass(); } return null; } /** * Register a {@code PropertySupport} for the specified class. *

* One can clear the {@code PropertySupport} for a class by setting the entry to null: *

     * sessionStorage.putProperty(myClass.class, null);
     * 
* * Register a custom {@code PropertySupport}: *
     * ApplicationContext ctx = Application.getInstance(MyApplication.class).getContext();
     * SessionStorage ss = ctx.getSesssionStorage();
     * ctx.putProperty(JTable.class, new ExtendedTableProperty());
     * 
* * @exception IllegalArgumentException if {@code cls} is null. * @param cls the class to which {@code propertySupport} applies. * @param propertySupport the {@code PropertySupport} object to register or null. * @see #getProperty(Component) * @see #getProperty(Class) * @see #save * @see #restore */ public void putProperty(Class cls, PropertySupport propertySupport) { checkClassArg(cls); // Remove property support for the clazz in case property argument is null if (propertySupport == null) { propertyMap.remove(cls); return; } propertyMap.put(cls, propertySupport); } /** * If a {@code sessionState PropertySupport} object exists for the * specified Component return it, otherwise return null. This method * is used by the {@link #save save} and {@link #restore restore} methods * to lookup the {@code sessionState PropertySupport} object for each component * to whose session state is to be saved or restored. *

* The {@code putProperty} method registers a PropertySupport object for * a class. One can specify a PropertySupport object for a single Swing * component by setting the component's client property, like this: *

     * myJComponent.putClientProperty(PropertySupport.class, myPropertySupport);
     * 
* One can also create components that implement the * {@code PropertySupport} interface directly. * * @param component the component to retrive the {@code PropertySupport} from * @return if {@code component} implements {@code PropertySupport}, then * {@code component}, if {@code component} is a {@code JComponent} with a * {@code PropertySupport} valued * {@link javax.swing.JComponent#getClientProperty client property} under * (client property key) {@code PropertySupport}, then * return that, otherwise return the value of * {@code getProperty(component.getClass())}. *

* @exception IllegalArgumentException if {@code Component component} is null. * @see javax.swing.JComponent#putClientProperty * @see #getProperty(Class) * @see #putProperty * @see #save * @see #restore */ public final PropertySupport getProperty(Component component) { if (component == null) { throw new IllegalArgumentException("null component"); } if (component instanceof PropertySupport) { return (PropertySupport) component; } else { PropertySupport p = null; if (component instanceof JComponent) { Object v = ((JComponent) component).getClientProperty(PropertySupport.class); p = (v instanceof PropertySupport) ? (PropertySupport) v : null; } return (p != null) ? p : getProperty(component.getClass()); } } } bsaf-1.9/src/main/java/org/jdesktop/application/SingleFrameApplication.java000066400000000000000000000401161151640306200270720ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Copyright (C) 2010 Illya Yalovyy. All rights reserved. * Use is subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.utils.SwingHelper; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * An application base class for simple GUIs with one primary JFrame. *

* This class takes care of component property injection, exit processing, * and saving/restoring session state in a way that's appropriate for * simple single-frame applications. The application's JFrame is created * automatically, with a WindowListener that calls exit() when the * window is closed. Session state is stored when the application * shuts down, and restored when the GUI is shown. *

* To use {@code SingleFrameApplication}, one need only override * {@code startup}, create the GUI's main panel, and apply * {@code show} to that. Here's an example: *

 *class MyApplication extends SingleFrameApplication {
 *    @Override protected void startup() {
 *        show(new JLabel("Hello World"));
 *    }
 *}
 * 
* The call to {@code show} in this example creates a JFrame (named * "mainFrame"), that contains the "Hello World" JLabel. Before the * frame is made visible, the properties of all of the components in * the hierarchy are initialized with * {@link ResourceMap#injectComponents ResourceMap.injectComponents} * and then restored from saved session state (if any) with * {@link SessionStorage#restore SessionStorage.restore}. * When the application shuts down, session state is saved. *

* A more realistic tiny example would rely on a ResourceBundle for * the JLabel's string and the main frame's title. The automatic * injection step only initializes the properties of named * components, so: *

 * class MyApplication extends SingleFrameApplication {
 *     @Override protected void startup() {
 *         JLabel label = new JLabel();
 *         label.setName("label");
 *         show(label);
 *     }
 * }
 * 
* The ResourceBundle should contain definitions for all of the * standard Application resources, as well the main frame's title * and the label's text. Note that the JFrame that's implicitly * created by the {@code show} method is named "mainFrame". *
 * # resources/MyApplication.properties
 * Application.id = MyApplication
 * Application.title = My Hello World Application
 * Application.version = 1.0
 * Application.vendor = Illya Yalovyy
 * Application.vendorId = Etf
 * Application.homepage = http://kenai.com/projects/bsaf
 * Application.description =  An example of SingleFrameApplication
 * Application.lookAndFeel = system
 * Application.icon=app_icon.png
 *
 * mainFrame.title = ${Application.title} ${Application.version}
 * label.text = Hello World
 * 
*/ public abstract class SingleFrameApplication extends Application { private static final String INITIALIZATION_MARKER="SingleFrameApplication.initRootPaneContainer"; private static final Logger logger = Logger.getLogger(SingleFrameApplication.class.getName()); /** * Return the JFrame used to show this application. *

* The frame's name is set to "mainFrame", its title is * initialized with the value of the {@code Application.title} * resource and a {@code WindowListener} is added that calls * {@code exit} when the user attempts to close the frame. * *

* This method may be called at any time; the JFrame is created lazily * and cached. For example: *

     * protected void startup() {
     *     getMainFrame().setJMenuBar(createMenuBar());
     *     show(createMainPanel());
     * }
     * 
* * @return this application's main frame * @see #setMainFrame * @see #show * @see JFrame#setName * @see JFrame#setTitle * @see JFrame#addWindowListener */ public final JFrame getMainFrame() { return getMainView().getFrame(); } /** * Sets the JFrame use to show this application. *

* This method should be called from the startup method by a * subclass that wants to construct and initialize the main frame * itself. Most applications can rely on the fact that {code * getMainFrame} lazily constructs the main frame and initializes * the {@code mainFrame} property. *

* If the main frame property was already initialized, either * implicitly through a call to {@code getMainFrame} or by * explicitly calling this method, an IllegalStateException is * thrown. If {@code mainFrame} is null, an IllegalArgumentException * is thrown. *

* This property is bound. * * @param mainFrame the new value of the mainFrame property * @see #getMainFrame */ protected final void setMainFrame(JFrame mainFrame) { getMainView().setFrame(mainFrame); } private String sessionFilename(Window window) { if (window == null) { return null; } else { String name = window.getName(); return (name == null) ? null : name + ".session.xml"; } } /** * Initialize the hierarchy with the specified root by * injecting resources. *

* By default the {@code show} methods * {@link ResourceMap#injectComponents inject resources} before * initializing the JFrame or JDialog's size, location, * and restoring the window's session state. If the application * is showing a window whose resources have already been injected, * or that shouldn't be initialized via resource injection, * this method can be overridden to defeat the default * behavior. * * @param root the root of the component hierarchy * @see ResourceMap#injectComponents * @see #show(JComponent) * @see #show(JFrame) * @see #show(JDialog) */ protected void configureWindow(Window root) { getContext().getResourceMap().injectComponents(root); } private void initRootPaneContainer(RootPaneContainer c) { JComponent rootPane = c.getRootPane(); // These initializations are only done once if (rootPane.getClientProperty(INITIALIZATION_MARKER) != null) { return; } rootPane.putClientProperty(INITIALIZATION_MARKER, Boolean.TRUE); // Inject resources Container root = rootPane.getParent(); if (root instanceof Window) { configureWindow((Window) root); } // If this is the mainFrame, then close == exit JFrame mainFrame = getMainFrame(); if (c == mainFrame) { mainFrame.addWindowListener(new MainFrameListener()); mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); } else if (root instanceof Window) { // close == save session state Window window = (Window) root; window.addHierarchyListener(new SecondaryWindowListener()); } // If this is a JFrame monitor "normal" (not maximized) bounds if (root instanceof JFrame) { root.addComponentListener(new FrameBoundsListener()); } if (root instanceof Window) { Window window = (Window) root; // If the window's bounds don't appear to have been set, do it if (!root.isValid() || (root.getWidth() == 0) || (root.getHeight() == 0)) { window.pack(); } // Restore session state String filename = sessionFilename((Window) root); if (filename != null) { try { getContext().getSessionStorage().restore(root, filename); } catch (Exception e) { String msg = String.format("couldn't restore session [%s]", filename); logger.log(Level.WARNING, msg, e); } } // If window location is default and size is not too big // the window should be centered Point defaultLocation = SwingHelper.defaultLocation(window); if (!window.isLocationByPlatform() && (root.getX() == defaultLocation.getX()) && (root.getY() == defaultLocation.getY())) { Dimension screenSize = window.getToolkit().getScreenSize(); Dimension windowSIze = window.getSize(); if (screenSize.getWidth()/windowSIze.getWidth()>1.25 && screenSize.getHeight()/windowSIze.getHeight()>1.25) { Component owner = window.getOwner(); if (owner == null) { owner = (window != mainFrame) ? mainFrame : null; } window.setLocationRelativeTo(owner); // center the window } } } } /** * Show the specified component in the {@link #getMainFrame main frame}. * Typical applications will call this method after constructing their * main GUI panel in the {@code startup} method. *

* Before the main frame is made visible, the properties of all of * the components in the hierarchy are initialized with {@link * ResourceMap#injectComponents ResourceMap.injectComponents} and * then restored from saved session state (if any) with {@link * SessionStorage#restore SessionStorage.restore}. When the * application shuts down, session state is saved. *

* Note that the name of the lazily created main frame (see * {@link #getMainFrame getMainFrame}) is set by default. * Session state is only saved for top level windows with * a valid name and then only for component descendants * that are named. *

* Throws an IllegalArgumentException if {@code c} is null * * @param c the main frame's contentPane child */ protected void show(JComponent c) { if (c == null) { throw new IllegalArgumentException("null JComponent"); } JFrame f = getMainFrame(); f.getContentPane().add(c, BorderLayout.CENTER); initRootPaneContainer(f); f.setVisible(true); } /** * Initialize and show the JDialog. *

* This method is intended for showing "secondary" windows, like * message dialogs, about boxes, and so on. Unlike the {@code mainFrame}, * dismissing a secondary window will not exit the application. *

* Session state is only automatically saved if the specified * JDialog has a name, and then only for component descendants * that are named. *

* Throws an IllegalArgumentException if {@code c} is null * * @param c the main frame's contentPane child * @see #show(JComponent) * @see #show(JFrame) * @see #configureWindow */ public void show(JDialog c) { if (c == null) { throw new IllegalArgumentException("null JDialog"); } initRootPaneContainer(c); c.setVisible(true); } /** * Initialize and show the secondary JFrame. *

* This method is intended for showing "secondary" windows, like * message dialogs, about boxes, and so on. Unlike the {@code mainFrame}, * dismissing a secondary window will not exit the application. *

* Session state is only automatically saved if the specified * JFrame has a name, and then only for component descendants * that are named. *

* Throws an IllegalArgumentException if {@code c} is null * * @param c * @see #show(JComponent) * @see #show(JDialog) * @see #configureWindow */ public void show(JFrame c) { if (c == null) { throw new IllegalArgumentException("null JFrame"); } initRootPaneContainer(c); c.setVisible(true); } private void saveSession(Window window) { String filename = sessionFilename(window); if (filename != null) { try { getContext().getSessionStorage().save(window, filename); } catch (IOException e) { logger.log(Level.WARNING, "couldn't save session", e); } } } private boolean isVisibleWindow(Window w) { return w.isVisible() && ((w instanceof JFrame) || (w instanceof JDialog) || (w instanceof JWindow)); } /** * Return all of the visible JWindows, JDialogs, and JFrames per * Window.getWindows() on Java SE 6 */ private List getVisibleSecondaryWindows() { List rv = new ArrayList(); for (Window window : Window.getWindows()) { if (isVisibleWindow(window)) { rv.add(window); } } return rv; } /** * Save session state for the component hierarchy rooted by * the mainFrame. SingleFrameApplication subclasses that override * shutdown need to remember call {@code super.shutdown()}. */ @Override protected void shutdown() { if (isReady()) { for (Window window : getVisibleSecondaryWindows()) { saveSession(window); } } } private class MainFrameListener extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { exit(e); } } /* Although it would have been simpler to listen for changes in * the secondary window's visibility per either a * PropertyChangeEvent on the "visible" property or a change in * visibility per ComponentListener, neither listener is notified * if the secondary window is disposed. * HierarchyEvent.SHOWING_CHANGED does report the change in all * cases, so we use that. */ private class SecondaryWindowListener implements HierarchyListener { @Override public void hierarchyChanged(HierarchyEvent e) { if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { if (e.getSource() instanceof Window) { Window secondaryWindow = (Window) e.getSource(); if (!secondaryWindow.isShowing()) { saveSession(secondaryWindow); } } } } } /* In order to properly restore a maximized JFrame, we need to * record it's normal (not maximized) bounds. They're recorded * under a rootPane client property here, so that they've can be * session-saved by WindowProperty#getSessionState(). */ private static class FrameBoundsListener implements ComponentListener { private void maybeSaveFrameSize(ComponentEvent e) { if (e.getComponent() instanceof JFrame) { JFrame f = (JFrame) e.getComponent(); if ((f.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0) { SwingHelper.putWindowNormalBounds(f, f.getBounds()); } } } @Override public void componentResized(ComponentEvent e) { maybeSaveFrameSize(e); } @Override public void componentMoved(ComponentEvent e) { /* maybeSaveFrameSize(e); */ } @Override public void componentHidden(ComponentEvent e) { } @Override public void componentShown(ComponentEvent e) { } } /* Prototype support for the View type */ private FrameView mainView = null; /** * Gets the main view of the application * @return the main view of the application */ public FrameView getMainView() { if (mainView == null) { mainView = new FrameView(this); } return mainView; } @Override public void show(View view) { if ((mainView == null) && (view instanceof FrameView)) { mainView = (FrameView) view; } RootPaneContainer c = (RootPaneContainer) view.getRootPane().getParent(); initRootPaneContainer(c); ((Window) c).setVisible(true); } } bsaf-1.9/src/main/java/org/jdesktop/application/Task.java000066400000000000000000001322021151640306200234120ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.Component; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; /** * A type of {@link SwingWorker} that represents an application * background task. Tasks add descriptive properties that can * be shown to the user, a new set of methods for customizing * task completion, support for blocking input to the GUI while * the Task is executing, and a {@code TaskListener} that * enables one to monitor the three key SwingWorker methods: {@code * doInBackground}, {@code process} and {@code done}. * *

* When a Task completes, the {@code final done} method invokes * one of {@code succeeded}, {@code cancelled}, {@code interrupted}, * or {@code failed}. The {@code final done} method invokes * {@code finished} when the completion method returns or throws * an exception. * *

* Tasks should provide localized values for the {@code title}, * {@code description}, and {@code message} properties in a * ResourceBundle for the Task subclass. A * {@link ResourceMap} is * loaded automatically using the Task subclass as the * {@code startClass} and Task.class the {@code stopClass}. * This ResourceMap is also used to look up format strings used in * calls to {@link #message message}, which is used to set * the {@code message} property. * *

* For example: given a Task called {@code MyTask} defined like this: *

 
 * class MyTask extends Task { 
 *     protected MyResultType doInBackground() { 
 *         message("startMessage", getPlannedSubtaskCount()); 
 *         // do the work ... if an error is encountered:
 *             message("errorMessage");
 *         message("finishedMessage", getActualSubtaskCount(), getFailureCount()); 
 *         // .. return the result
 *     } 
 * } 
 * 
* Typically the resources for this class would be defined in the * MyTask ResourceBundle, @{code resources/MyTask.properties}: *
 * title = My Task 
 * description = A task of mine for my own purposes.   
 * startMessage = Starting: working on %s subtasks...  
 * errorMessage = An unexpected error occurred, skipping subtask
 * finishedMessage = Finished: completed %1$s subtasks, %2$s failures 
 * 
* *

* Task subclasses can override resource values in their own ResourceBundles: *

 * class MyTaskSubclass extends MyTask {
 * }
 * # resources/MyTaskSubclass.properties
 * title = My Task Subclass 
 * description = An appropriate description
 * # ... all other resources are inherited
 * 
* *

* Tasks can specify that input to the GUI is to be blocked while * they're being executed. The {@code inputBlocker} property specifies * what part of the GUI is to be blocked and how that's accomplished. * The {@code inputBlocker} is set automatically when an {@code @Action} * method that returns a Task specifies a {@link BlockingScope} value * for the {@code block} annotation parameter. To customize the way * blocking is implemented you can define your own {@code Task.InputBlocker}. * For example, assume that {@code busyGlassPane} is a component * that consumes (and ignores) keyboard and mouse input: *

 * class MyInputBlocker extends InputBlocker {
 *     BusyIndicatorInputBlocker(Task task) {
 *         super(task, Task.BlockingScope.WINDOW, myGlassPane);
 *     }
 *     protected void block() {
 *         myFrame.setGlassPane(myGlassPane);
 *         busyGlassPane.setVisible(true);
 *     }
 *     protected void unblock() {
 *       busyGlassPane.setVisible(false);
 *     }
 * }
 * // ...
 * myTask.setInputBlocker(new MyInputBlocker(myTask));
 * 
*

* All of the settable properties in this class are bound, i.e. a * PropertyChangeEvent is fired when the value of the property changes. * As with the {@code SwingWorker} superclass, all * {@code PropertyChangeListeners} run on the event dispatching thread. * This is also true of {@code TaskListeners}. * *

* Unless specified otherwise specified, this class is thread-safe. * All of the Task properties can be get/set on any thread. * @param the result type returned by this {@code SwingWorker's} * {@code doInBackground} and {@code get} methods * @param the type used for carrying out intermediate results by this * {@code SwingWorker's} {@code publish} and {@code process} methods * * @author Hans Muller (Hans.Muller@Sun.COM) * @see ApplicationContext * @see ResourceMap * @see TaskListener * @see TaskEvent */ public abstract class Task extends SwingWorker { private static final Logger logger = Logger.getLogger(Task.class.getName()); public static final String PROP_DESCRIPTION = "description"; public static final String PROP_INPUTBLOCKER = "inputBlocker"; public static final String PROP_MESSAGE = "message"; public static final String PROP_TASKSERVICE = "taskService"; public static final String PROP_TITLE = "title"; public static final String PROP_USERCANCANCEL = "userCanCancel"; public static final String PROP_COMPLETED = "completed"; public static final String PROP_DONE = "done"; public static final String PROP_STARTED = "started"; private final Application application; private String resourcePrefix; private ResourceMap resourceMap; private List> taskListeners; private InputBlocker inputBlocker; private String title = null; private String description = null; private long messageTime = -1L; private String message = null; private long startTime = -1L; private long doneTime = -1L; private boolean userCanCancel = true; private boolean progressPropertyIsValid = false; private TaskService taskService = null; /** * Specifies to what extent the GUI should be blocked a Task * is executed by a TaskService. Input blocking is carried * out by the Task's {@link #getInputBlocker inputBlocker}. * * @see Task.InputBlocker * @see Action#block */ public enum BlockingScope { /** * Don't block the GUI while this Task is executing. */ NONE, /** * Block an {@link ApplicationAction Action} while the * task is executing, typically by temporarily disabling it. */ ACTION, /** * Block a component while the * task is executing, typically by temporarily disabling it. */ COMPONENT, /** * Block a top level window while the task is executing, * typically by showing a window-modal dialog. */ WINDOW, /** * Block all of the application's top level windows, * typically by showing a application-modal dialog. */ APPLICATION } private void initTask(ResourceMap resourceMap, String prefix) { this.resourceMap = resourceMap; if ((prefix == null) || (prefix.length() == 0)) { resourcePrefix = ""; } else if (prefix.endsWith(".")) { resourcePrefix = prefix; } else { resourcePrefix = prefix + "."; } if (resourceMap != null) { title = resourceMap.getString(resourceName(PROP_TITLE)); description = resourceMap.getString(resourceName(PROP_DESCRIPTION)); message = resourceMap.getString(resourceName(PROP_MESSAGE)); if (message != null) { messageTime = System.currentTimeMillis(); } } addPropertyChangeListener(new StatePCL()); taskListeners = new CopyOnWriteArrayList>(); } private ResourceMap defaultResourceMap(Application application) { return application.getContext().getResourceMap(getClass(), Task.class); } /** * Warning: This constructor is deprecated. It will be removed * in a future release. This constructor was a way for developers * to initialize a Task's title/description/message properties, * and it's InputBlocker's visual properties, from an alternative * ResourceMap. This feature is now supported with the * InputBlocker's resourceMap property. *

* Construct a {@code Task}. If the {@code resourceMap} parameter * is not null, then the {@code title}, {@code description}, and * {@code message} properties are initialized from resources. The * {@code resourceMap} is also used to lookup localized messages * defined with the {@link #message message} method. In both * cases, if the value of {@code resourcePrefix} is not null or an * empty string {@code ""}, resource names must have the name of * the {@code resourcePrefix} parameter, followed by a ".", as a * prefix * * @param application * @param resourceMap the ResourceMap for the Task's user properties, can be null * @param resourcePrefix prefix for resource names, can be null * @see #getResourceMap * @see #setTitle * @see #setDescription * @see #setMessage * @see #resourceName * @see ApplicationContext#getResourceMap * @deprecated */ @Deprecated public Task(Application application, ResourceMap resourceMap, String resourcePrefix) { this.application = application; initTask(resourceMap, resourcePrefix); } /** * Warning: This constructor is deprecated. It will be removed * in a future release. This constructor was a way for developers * to initialize a Task's title/description/message properties, * and it's InputBlocker's visual properties, from an alternative * ResourceMap. This feature is now supported with the * InputBlocker's resourceMap property. *

* Construct a {@code Task} with the specified resource name * prefix, whose ResourceMap is the value of * ApplicationContext.getInstance().getResourceMap(this.getClass(), Task.class) * . The {@code resourcePrefix} is used to construct * the resource names for the intial values of the * {@code title}, {@code description}, and {@code message} Task properties * and for message {@link java.util.Formatter format} strings. * * @param application * @param resourcePrefix prefix for resource names, can be null * @see #getResourceMap * @see #setTitle * @see #setDescription * @see #setMessage * @see #resourceName * @see ApplicationContext#getResourceMap * @deprecated */ @Deprecated public Task(Application application, String resourcePrefix) { this.application = application; initTask(defaultResourceMap(application), resourcePrefix); } /** * Construct a {@code Task} with an empty ("") resource name * prefix, whose ResourceMap is the value of * ApplicationContext.getInstance().getResourceMap(this.getClass(), * Task.class). * @param application */ public Task(Application application) { this.application = application; initTask(defaultResourceMap(application), ""); } public final Application getApplication() { return application; } public final ApplicationContext getContext() { return getApplication().getContext(); } /** * Returns the TaskService that this Task has been submitted to, * or null. This property is set when a task is executed by a * TaskService, cleared when the task is done. *

* This is a read-only bound property. * * @return the value of the taskService property. * @see TaskService#execute * @see #done */ public synchronized TaskService getTaskService() { return taskService; } /** * Set when a task is executed by a TaskService, cleared when * the task is done and all of its completion methods have run. */ synchronized void setTaskService(TaskService taskService) { TaskService oldTaskService, newTaskService; synchronized (this) { oldTaskService = this.taskService; this.taskService = taskService; newTaskService = this.taskService; } firePropertyChange(PROP_TASKSERVICE, oldTaskService, newTaskService); } /** * Returns a Task resource name with the specified suffix. Task resource * names are the simple name of the constructor's {@code resourceClass} * parameter, followed by ".", followed by {@code suffix}. If the * resourceClass parameter was null, then this method returns an empty string. *

* This method would only be of interest to subclasses that wanted to look * up additional Task resources (beyond {@code title}, {@code message}, etc..) * using the same naming convention. * * @param suffix the resource name's suffix * @return a Task resource name * @see #getResourceMap * @see #message */ protected final String resourceName(String suffix) { return resourcePrefix + suffix; } /** * Returns the {@code ResourceMap} used by the constructor to * initialize the {@code title}, {@code message}, etc properties, * and by the {@link #message message} method to look up format * strings. * * @return this Task's {@code ResourceMap} * @see #resourceName */ public final ResourceMap getResourceMap() { return resourceMap; } /** * Return the value of the {@code title} property. * The default value of this property is the value of the * {@link #getResourceMap resourceMap's} {@code title} resource. *

* Returns a brief one-line description of the this Task that would * be useful for describing this task to the user. The default * value of this property is null. * * @return the value of the {@code title} property. * @see #setTitle * @see #setDescription * @see #setMessage */ public synchronized String getTitle() { return title; } /** * Set the {@code title} property. * The default value of this property is the value of the * {@link #getResourceMap resourceMap's} {@code title} resource. *

* The title is a brief one-line description of the this Task that * would be useful for describing it to the user. The {@code * title} should be specific to this Task, for example "Loading * image sunset.png" is better than "Image Loader". Similarly the * title isn't intended for ephemeral messages, like "Loaded 27.3% * of sunset.png". The {@link #setMessage message} property is * for reporting the Task's current status. * * @param title a brief one-line description of the this Task. * @see #getTitle * @see #setDescription * @see #setMessage */ protected void setTitle(String title) { String oldTitle, newTitle; synchronized (this) { oldTitle = this.title; this.title = title; newTitle = this.title; } firePropertyChange(PROP_TITLE, oldTitle, newTitle); } /** * Return the value of the {@code description} property. * The default value of this property is the value of the * {@link #getResourceMap resourceMap's} {@code description} resource. *

* A longer version of the Task's title; a few sentences that * describe what the Task is for in terms that an application * user would understand. * * @return the value of the {@code description} property. * @see #setDescription * @see #setTitle * @see #setMessage */ public synchronized String getDescription() { return description; } /** * Set the {@code description} property. * The default value of this property is the value of the * {@link #getResourceMap resourceMap's} {@code description} resource. *

* The description is a longer version of the Task's title. * It should be a few sentences that describe what the Task is for, * in terms that an application user would understand. * * @param description a few sentences that describe what this Task is for. * @see #getDescription * @see #setTitle * @see #setMessage */ protected void setDescription(String description) { String oldDescription, newDescription; synchronized (this) { oldDescription = this.description; this.description = description; newDescription = this.description; } firePropertyChange(PROP_DESCRIPTION, oldDescription, newDescription); } /** * Returns the length of time this Task has run. If the task hasn't * started yet (i.e. if its state is still {@code StateValue.PENDING}), * then this method returns 0. Otherwise it returns the duration in the * specified time units. For example, to learn how many seconds a * Task has run so far: *

     * long nSeconds = myTask.getExecutionDuration(TimeUnit.SECONDS);
     * 
* * @param unit the time unit of the return value * @return the length of time this Task has run. * @see #execute */ public long getExecutionDuration(TimeUnit unit) { long startTime, doneTime, dt; synchronized (this) { startTime = this.startTime; doneTime = this.doneTime; } if (startTime == -1L) { dt = 0L; } else if (doneTime == -1L) { dt = System.currentTimeMillis() - startTime; } else { dt = doneTime - startTime; } return unit.convert(Math.max(0L, dt), TimeUnit.MILLISECONDS); } /** * Return the value of the {@code message} property. * The default value of this property is the value of the * {@link #getResourceMap resourceMap's} {@code message} resource. *

* Returns a short, one-line, message that explains what the task * is up to in terms appropriate for an application user. * * @return a short one-line status message. * @see #setMessage * @see #getMessageDuration */ public String getMessage() { return message; } /** * Set the {@code message} property. * The default value of this property is the value of the * {@link #getResourceMap resourceMap's} {@code message} resource. *

* Returns a short, one-line, message that explains what the task is * up to in terms appropriate for an application user. This message * should reflect that Task's dynamic state and can be reset as * frequently one could reasonably expect a user to understand. * It should not repeat the information in the Task's title and * should not convey any information that the user shouldn't ignore. *

* For example, a Task whose {@code doInBackground} method loaded * a photo from a web service might set this property to a new * value each time a new internal milestone was reached, e.g.: *

     * loadTask.setTitle("Loading photo from http://photos.com/sunset");
     * // ...
     * loadTask.setMessage("opening connection to photos.com");
     * // ...
     * loadTask.setMessage("reading thumbnail image file sunset.png");
     * // ... etc
     * 
*

* Each time this property is set, the {@link #getMessageDuration * messageDuration} property is reset. Since status messages are * intended to be ephemeral, application GUI elements like status * bars may want to clear messages after 20 or 30 seconds have * elapsed. *

* Localized messages that require paramters can be constructed * with the {@link #message message} method. * * @param message a short one-line status message. * @see #getMessage * @see #getMessageDuration * @see #message */ protected void setMessage(String message) { String oldMessage, newMessage; synchronized (this) { oldMessage = this.message; this.message = message; newMessage = this.message; messageTime = System.currentTimeMillis(); } firePropertyChange(PROP_MESSAGE, oldMessage, newMessage); } /** * Set the message property to a string generated with {@code * String.format} and the specified arguments. The {@code * formatResourceKey} names a resource whose value is a format * string. See the Task class javadoc for an example. *

* Note that if the no arguments are specified, this method is * comparable to: *

     * setMessage(getResourceMap().getString(resourceName(formatResourceKey)));
     *
*

* If a {@code ResourceMap} was not specified for this Task, * then set the {@code message} property to {@code formatResourceKey}. * * @param formatResourceKey the suffix of the format string's resource name. * @param args the arguments referred to by the placeholders in the format string * @see #setMessage * @see ResourceMap#getString(String, Object...) * @see java.text.MessageFormat */ protected final void message(String formatResourceKey, Object... args) { ResourceMap resourceMap = getResourceMap(); if (resourceMap != null) { setMessage(resourceMap.getString(resourceName(formatResourceKey), args)); } else { setMessage(formatResourceKey); } } /** * Returns the length of time that has elapsed since the {@code * message} property was last set. * * @param unit units for the return value * @return elapsed time since the {@code message} property was last set. * @see #setMessage */ public long getMessageDuration(TimeUnit unit) { long messageTime; synchronized (this) { messageTime = this.messageTime; } long dt = (messageTime == -1L) ? 0L : Math.max(0L, System.currentTimeMillis() - messageTime); return unit.convert(dt, TimeUnit.MILLISECONDS); } /** * Returns the value of the {@code userCanCancel} property. * The default value of this property is true. *

* Generic GUI components, like a Task progress dialog, can use this * property to decide if they should provide a way for the * user to cancel this task. * * @return true if the user can cancel this Task. * @see #setUserCanCancel */ public synchronized boolean getUserCanCancel() { return userCanCancel; } /** * Sets the {@code userCanCancel} property. * The default value of this property is true. *

* Generic GUI components, like a Task progress dialog, can use this * property to decide if they should provide a way for the * user to cancel this task. For example, the value of this * property might be bound to the enabled property of a * cancel button. *

* This property has no effect on the {@link #cancel} cancel * method. It's just advice for GUI components that display * this Task. * * @param userCanCancel true if the user should be allowed to cancel this Task. * @see #getUserCanCancel */ protected void setUserCanCancel(boolean userCanCancel) { boolean oldValue, newValue; synchronized (this) { oldValue = this.userCanCancel; this.userCanCancel = userCanCancel; newValue = this.userCanCancel; } firePropertyChange(PROP_USERCANCANCEL, oldValue, newValue); } /** * Returns true if the {@link #setProgress progress} property has * been set. Some Tasks don't update the progress property * because it's difficult or impossible to determine how what * percentage of the task has been completed. GUI elements that * display Task progress, like an application status bar, can use this * property to set the @{link JProgressBar#indeterminate * indeterminate} @{code JProgressBar} property. *

* A task that does keep the progress property up to * date should initialize it to 0, to ensure that {@code * isProgressPropertyValid} is always true. * * @return true if the {@link #setProgress progress} property has been set. * @see #setProgress */ public synchronized boolean isProgressPropertyValid() { return progressPropertyIsValid; } /** * A convenience method that sets the {@code progress} property to the following * ratio normalized to 0 .. 100. *

     * value - min / max - min
     * 
* * @param value a value in the range min ... max, inclusive * @param min the minimum value of the range * @param max the maximum value of the range * @see #setProgress(int) */ protected final void setProgress(int value, int min, int max) { if (min >= max) { throw new IllegalArgumentException("invalid range: min >= max"); } if ((value < min) || (value > max)) { throw new IllegalArgumentException("invalid value"); } float percentage = (float) (value - min) / (float) (max - min); setProgress(Math.round(percentage * 100.0f)); } /** * A convenience method that sets the {@code progress} property to * percentage * 100. * * @param percentage a value in the range 0.0 ... 1.0 inclusive * @see #setProgress(int) */ protected final void setProgress(float percentage) { if ((percentage < 0.0) || (percentage > 1.0)) { throw new IllegalArgumentException("invalid percentage"); } setProgress(Math.round(percentage * 100.0f)); } /** * A convenience method that sets the {@code progress} property to the following * ratio normalized to 0 .. 100. *
     * value - min / max - min
     * 
* * @param value a value in the range min ... max, inclusive * @param min the minimum value of the range * @param max the maximum value of the range * @see #setProgress(int) */ protected final void setProgress(float value, float min, float max) { if (min >= max) { throw new IllegalArgumentException("invalid range: min >= max"); } if ((value < min) || (value > max)) { throw new IllegalArgumentException("invalid value"); } float percentage = (value - min) / (max - min); setProgress(Math.round(percentage * 100.0f)); } /** * Equivalent to {@code getState() == StateValue.PENDING}. *

* When a pending Task's state changes to {@code StateValue.STARTED} * a PropertyChangeEvent for the "started" property is fired. Similarly * when a started Task's state changes to {@code StateValue.DONE}, a * "done" PropertyChangeEvent is fired. */ public final boolean isPending() { return getState() == StateValue.PENDING; } /** * Equivalent to {@code getState() == StateValue.STARTED}. *

* When a pending Task's state changes to {@code StateValue.STARTED} * a PropertyChangeEvent for the "started" property is fired. Similarly * when a started Task's state changes to {@code StateValue.DONE}, a * "done" PropertyChangeEvent is fired. * @return true if the state is {@code STARTED} */ public final boolean isStarted() { return getState() == StateValue.STARTED; } /** * {@inheritDoc} *

* This method fires the TaskListeners' {@link TaskListener#process process} * method. If you override {@code process} and do not call * {@code super.process(values)}, then the TaskListeners will not run. * * @param values @{inheritDoc} */ @Override protected void process(List values) { fireProcessListeners(values); } @Override protected final void done() { setTaskService(null); } /** * Called when this Task has been cancelled by {@link #cancel(boolean)}. *

* This method runs on the EDT. It does nothing by default. * * @see #done */ protected void cancelled() { } /** * Called when this Task has successfully completed, i.e. when * its {@code get} method returns a value. Tasks that compute * a value should override this method. *

*

* This method runs on the EDT. It does nothing by default. * * @param result the value returned by the {@code get} method * @see #done * @see #get * @see #failed */ protected void succeeded(T result) { } /** * Called if the Task's Thread is interrupted but not * explicitly cancelled. *

* This method runs on the EDT. It does nothing by default. * * @param e the {@code InterruptedException} thrown by {@code get} * @see #cancel * @see #done * @see #get */ protected void interrupted(InterruptedException e) { } /** * Called when an execution of this Task fails and an * {@code ExecutionExecption} is thrown by {@code get}. *

* This method runs on the EDT. It Logs an error message by default. * * @param cause the {@link Throwable#getCause cause} of the {@code ExecutionException} * @see #done * @see #get * @see #failed */ protected void failed(Throwable cause) { String msg = String.format("%s failed: %s", this, cause); logger.log(Level.SEVERE, msg, cause); } /** * Called unconditionally (in a {@code finally} clause) after one * of the completion methods, {@code succeeded}, {@code failed}, * {@code cancelled}, or {@code interrupted}, runs. Subclasses * can override this method to cleanup before the {@code done} * method returns. *

* This method runs on the EDT. It does nothing by default. * * @see #done * @see #get * @see #failed */ protected void finished() { } /** * Adds a {@code TaskListener} to this Task. The listener will * be notified when the Task's state changes to {@code STARTED}, * each time the {@code process} method is called, and when the * Task's state changes to {@code DONE}. All of the listener * methods will run on the event dispatching thread. * * @param listener the {@code TaskListener} to be added * @see #removeTaskListener */ public void addTaskListener(TaskListener listener) { if (listener == null) { throw new IllegalArgumentException("null listener"); } taskListeners.add(listener); } /** * Removes a {@code TaskListener} from this Task. If the specified * listener doesn't exist, this method does nothing. * * @param listener the {@code TaskListener} to be added * @see #addTaskListener */ public void removeTaskListener(TaskListener listener) { if (listener == null) { throw new IllegalArgumentException("null listener"); } taskListeners.remove(listener); } /** * Returns a copy of this Task's {@code TaskListeners}. * * @return a copy of this Task's {@code TaskListeners}. * @see #addTaskListener * @see #removeTaskListener */ public TaskListener[] getTaskListeners() { return taskListeners.toArray(new TaskListener[taskListeners.size()]); } /* This method is guaranteed to run on the EDT, it's called * from SwingWorker.process(). */ private void fireProcessListeners(List values) { TaskEvent> event = new TaskEvent>(this, values); for (TaskListener listener : taskListeners) { listener.process(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireDoInBackgroundListeners() { TaskEvent event = new TaskEvent(this, null); for (TaskListener listener : taskListeners) { listener.doInBackground(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireSucceededListeners(T result) { TaskEvent event = new TaskEvent(this, result); for (TaskListener listener : taskListeners) { listener.succeeded(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireCancelledListeners() { TaskEvent event = new TaskEvent(this, null); for (TaskListener listener : taskListeners) { listener.cancelled(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireInterruptedListeners(InterruptedException e) { TaskEvent event = new TaskEvent(this, e); for (TaskListener listener : taskListeners) { listener.interrupted(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireFailedListeners(Throwable e) { TaskEvent event = new TaskEvent(this, e); for (TaskListener listener : taskListeners) { listener.failed(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireFinishedListeners() { TaskEvent event = new TaskEvent(this, null); for (TaskListener listener : taskListeners) { listener.finished(event); } } /* This method runs on the EDT because it's called from * StatePCL (see below). */ private void fireCompletionListeners() { try { if (isCancelled()) { fireCancelledListeners(); } else { try { fireSucceededListeners(get()); } catch (InterruptedException e) { fireInterruptedListeners(e); } catch (ExecutionException e) { fireFailedListeners(e.getCause()); } } } finally { fireFinishedListeners(); } } private class StatePCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if ("state".equals(propertyName)) { StateValue state = (StateValue) (e.getNewValue()); switch (state) { case STARTED: taskStarted(); break; case DONE: taskDone(); break; } } else if ("progress".equals(propertyName)) { synchronized (Task.this) { progressPropertyIsValid = true; } } } private void taskStarted() { synchronized (Task.this) { startTime = System.currentTimeMillis(); } firePropertyChange(PROP_STARTED, false, true); fireDoInBackgroundListeners(); } private void taskDone() { synchronized (Task.this) { doneTime = System.currentTimeMillis(); } try { removePropertyChangeListener(this); firePropertyChange(PROP_DONE, false, true); } finally { // execute succeeded only when SwingWorker is done. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { if (isCancelled()) { cancelled(); } else { try { succeeded(get()); } catch (InterruptedException e) { interrupted(e); } catch (ExecutionException e) { failed(e.getCause()); } } } finally { finished(); try { fireCompletionListeners(); } finally { firePropertyChange(PROP_COMPLETED, false, true); } } } }); } } } /** * Return this task's InputBlocker. *

* This is a bound property. * * @return this task's InputBlocker * @see #setInputBlocker */ public final InputBlocker getInputBlocker() { return inputBlocker; } /** * Set this task's InputBlocker. The InputBlocker defines * to what extent the GUI should be blocked while the Task * is executed by a TaskService. It is not used by the Task * directly, it's used by the TaskService that executes the Task. *

* This property may only be set before the Task is * {@link TaskService#execute submitted} to a TaskService for * execution. If it's called afterwards, an IllegalStateException * is thrown. *

* This is a bound property. * * @param inputBlocker * @see #getInputBlocker */ public final void setInputBlocker(InputBlocker inputBlocker) { if (getTaskService() != null) { throw new IllegalStateException("task already being executed"); } InputBlocker oldInputBlocker, newInputBlocker; synchronized (this) { oldInputBlocker = this.inputBlocker; this.inputBlocker = inputBlocker; newInputBlocker = this.inputBlocker; } firePropertyChange(PROP_INPUTBLOCKER, oldInputBlocker, newInputBlocker); } /** * Specifies to what extent input to the Application's GUI should * be blocked while this Task is being executed and provides * a pair of methods, {@code block} and {@code unblock} that * do the work of blocking the GUI. For the sake of input blocking, * a Task begins executing when it's {@link TaskService#execute submitted} * to a {@code TaskService}, and it finishes executing after * the Task's completion methods have been called. *

* The InputBlocker's {@link Task.BlockingScope * BlockingScope} and the blocking {@code target} object define * what part of the GUI's input will be blocked: *

*
Task.BlockingScope.NONE
Don't block input. The blocking * target is ignored in this case.

*
Task.BlockingScope.ACTION
Disable the target * {@link javax.swing.Action Action} while the Task is executing.

*
Task.BlockingScope.COMPONENT
Disable the target * {@link java.awt.Component} Component while the Task is executing.

*
Task.BlockingScope.WINDOW
Block the Window ancestor of the * target Component while the Task is executing.

*
Task.BlockingScope.Application
Block the entire Application * while the Task is executing. The blocking target is ignored * in this case.

*
*

* Input blocking begins when the {@code block} method is called and * ends when {@code unblock} is called. Each method is only * called once, typically by the {@code TaskService}. * * @see Task#getInputBlocker * @see Task#setInputBlocker * @see TaskService * @see Action */ public static abstract class InputBlocker extends AbstractBean { private final Task task; private final BlockingScope scope; private final Object target; private final ApplicationAction action; /** * Construct an InputBlocker with four immutable properties. If * the Task is null or if the Task has already been * executed by a TaskService, then an exception is thrown. * If scope is {@code BlockingScope.ACTION} then target must be * a {@link javax.swing.Action Action}. If scope is * {@code BlockingScope.WINDOW} or {@code BlockingScope.COMPONENT} * then target must be a Component. * * @param task block input while this Task is executing * @param scope how much of the GUI will be blocked * @param target the GUI element that will be blocked * @param action the {@code @Action} that triggered running the task, or null * @see TaskService#execute */ public InputBlocker(Task task, BlockingScope scope, Object target, ApplicationAction action) { if (task == null) { throw new IllegalArgumentException("null task"); } if (task.getTaskService() != null) { throw new IllegalStateException("task already being executed"); } switch (scope) { case ACTION: if (!(target instanceof javax.swing.Action)) { throw new IllegalArgumentException("target not an Action"); } break; case COMPONENT: case WINDOW: if (!(target instanceof Component)) { throw new IllegalArgumentException("target not a Component"); } break; } this.task = task; this.scope = scope; this.target = target; this.action = action; } /** * Construct an InputBlocker. If {@code target} is an * {@code ApplicationAction}, it becomes the InputBlocker's * {@code action}. If the Task is null or if the Task has already been * executed by a TaskService, then an exception is thrown. * * @param task block input while this Task is executing * @param scope how much of the GUI will be blocked * @param target the GUI element that will be blocked * @see TaskService#execute */ public InputBlocker(Task task, BlockingScope scope, Object target) { this(task, scope, target, (target instanceof ApplicationAction) ? (ApplicationAction) target : null); } /** * The {@code block} method will block input while this Task * is being executed by a TaskService. * * @return the value of the read-only Task property * @see #block * @see #unblock */ public final Task getTask() { return task; } /** * Defines the extent to which the GUI is blocked while * the task is being executed. * * @return the value of the read-only blockingScope property * @see #block * @see #unblock */ public final BlockingScope getScope() { return scope; } /** * Specifies the GUI element that will be blocked while * the task is being executed. *

* This property may be null. * * @return the value of the read-only target property * @see #getScope * @see #block * @see #unblock */ public final Object getTarget() { return target; } /** * The ApplicationAction ({@code @Action}) that caused * the task to be executed. The DefaultInputBlocker uses * the action's {@code name} and {@code resourceMap} to * configure its blocking dialog if {@code scope} is * {@code BlockingScope.WINDOW}. *

* This property may be null. * * @return the value of the read-only action property * @see #getScope * @see #block * @see #unblock * @see ApplicationAction#getName * @see ApplicationAction#getResourceMap */ public final ApplicationAction getAction() { return action; } /** * Block input to the GUI per the {@code scope} and {@code target} * properties. This method will only be called once. * * @see #unblock * @see TaskService#execute */ protected abstract void block(); /** * Unblock input to the GUI by undoing whatever the {@code block} * method did. This method will only be called once. * * @see #block * @see TaskService#execute */ protected abstract void unblock(); } } bsaf-1.9/src/main/java/org/jdesktop/application/TaskEvent.java000066400000000000000000000016271151640306200244220ustar00rootroot00000000000000package org.jdesktop.application; import java.util.EventObject; /** * An encapsulation of the value produced one of the {@code Task} execution * methods: {@code doInBackground()}, {@code process}, {@code done}. The source * of a {@code TaskEvent} is the {@code Task} that produced the value. * * @param * @see TaskListener * @see Task */ public class TaskEvent extends EventObject { private final T value; /** * Returns the value this event represents. * * @return the {@code value} constructor argument. */ public final T getValue() { return value; } /** * Construct a {@code TaskEvent}. * * @param source the {@code Task} that produced the value. * @param value the value, null if type {@code T} is {@code Void}. */ public TaskEvent(Task source, T value) { super(source); this.value = value; } } bsaf-1.9/src/main/java/org/jdesktop/application/TaskListener.java000066400000000000000000000131011151640306200251140ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.util.List; /** * Listener used for observing {@code Task} execution. * A {@code TaskListener} is particularly * useful for monitoring the the intermediate results * {@link Task#publish published} by a Task in situations * where it's not practical to override the Task's * {@link Task#process process} method. Note that if * what you really want to do is monitor a Task's state * and progress, a PropertyChangeListener is probably more * appropriate. *

* The Task class runs all TaskListener methods on the event dispatching * thread and the source of all TaskEvents is the Task object. * @param the result type returned by this {@code SwingWorker's} * {@code doInBackground} and {@code get} methods * @param the type used for carrying out intermediate results by this * {@code SwingWorker's} {@code publish} and {@code process} methods * @see Task#addTaskListener * @see Task#removeTaskListener * @see Task#addPropertyChangeListener * * @author Hans Muller (Hans.Muller@Sun.COM) */ public interface TaskListener { /** * Called just before the Task's {@link Task#doInBackground * doInBackground} method is called, i.e. just before the task * begins running. The {@code event's} source is the Task and its * value is null. * * @param event a TaskEvent whose source is the {@code Task} object, value is null * @see Task#doInBackground * @see TaskEvent#getSource */ void doInBackground(TaskEvent event); /** * Called each time the Task's {@link Task#process process} method is called. * The value of the event is the list of values passed to the process method. * * @param event a TaskEvent whose source is the {@code Task} object and whose * value is a list of the values passed to the {@code Task.process()} method * @see Task#doInBackground * @see Task#process * @see TaskEvent#getSource * @see TaskEvent#getValue */ void process(TaskEvent> event); /** * Called after the Task's {@link Task#succeeded succeeded} * completion method is called. The event's value is the value * returned by the Task's {@code get} method, i.e. the value that * is computed by {@link Task#doInBackground}. * * @param event a TaskEvent whose source is the {@code Task} object, and * whose value is the value returned by {@code Task.get()}. * @see Task#succeeded * @see TaskEvent#getSource * @see TaskEvent#getValue */ void succeeded(TaskEvent event); /** * Called after the Task's {@link Task#failed failed} completion * method is called. The event's value is the Throwable passed to * {@code Task.failed()}. * * @param event a TaskEvent whose source is the {@code Task} object, and * whose value is the Throwable passed to {@code Task.failed()}. * @see Task#failed * @see TaskEvent#getSource * @see TaskEvent#getValue */ void failed(TaskEvent event); /** * Called after the Task's {@link Task#cancelled cancelled} method * is called. The {@code event's} source is the Task and its * value is null. * * @param event a TaskEvent whose source is the {@code Task} object, value is null * @see Task#cancelled * @see Task#get * @see TaskEvent#getSource */ void cancelled(TaskEvent event); /** * Called after the Task's {@link Task#interrupted interrupted} method is called. * The {@code event's} source is the Task and its value is * the InterruptedException passed to {@code Task.interrupted()}. * * @param event a TaskEvent whose source is the {@code Task} object, and * whose value is the InterruptedException passed to {@code Task.interrupted()}. * @see Task#interrupted * @see TaskEvent#getSource * @see TaskEvent#getValue */ void interrupted(TaskEvent event); /** * Called after the Task's {@link Task#finished finished} method is called. * The {@code event's} source is the Task and its value is null. * * @param event a TaskEvent whose source is the {@code Task} object, value is null. * @see Task#interrupted * @see TaskEvent#getSource */ void finished(TaskEvent event); /** * Convenience class that stubs all of the TaskListener interface * methods. Using TaskListener.Adapter can simplify building * TaskListeners. * @param the result type returned by this {@code SwingWorker's} * {@code doInBackground} and {@code get} methods * @param the type used for carrying out intermediate results by this * {@code SwingWorker's} {@code publish} and {@code process} methods */ class Adapter implements TaskListener { @Override public void doInBackground(TaskEvent event) { } @Override public void process(TaskEvent> event) { } @Override public void succeeded(TaskEvent event) { } @Override public void failed(TaskEvent event) { } @Override public void cancelled(TaskEvent event) { } @Override public void interrupted(TaskEvent event) { } @Override public void finished(TaskEvent event) { } } } bsaf-1.9/src/main/java/org/jdesktop/application/TaskMonitor.java000066400000000000000000000275131151640306200247720ustar00rootroot00000000000000package org.jdesktop.application; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.SwingWorker.StateValue; /** * This class is intended to serve as the model for GUI components, * like status bars, that display the state of an application's * background tasks. {@code TaskMonitor} provides an overview of all * the ApplicationContext's Tasks, as well as the state of a single * {@code foreground} Task. * *

* The value of {@link #getTasks getTasks()} is a list of all of the * {@code Tasks} whose state is not {@link * Task#isDone DONE} for all of the * ApplicationContext's {@code TaskServices}. In other words: all of * the ApplicationContext's background tasks that haven't finished * executing. Each time a new TaskService Task is executed it's added * to the list; when the Task finishes it's removed. Each time the * list changes {@code PropertyChangeListeners} are fired. * Applications that wish to create a detailed visualization of all * Tasks should monitor the TaskMonitor {@code "tasks"} property. * *

* Users are often only interested in the status of a single * foreground task, typically the one associated with GUI * element they're working with, or with the most recent command * they've issued. The TaskMonitor's PropertyChangeListener is * notified each time a property of the {@link #setForegroundTask * foregroundTask} changes. Additionally the TaskMonitor fires * synthetic PropertyChangeEvents for properties named "pending", * "started", and "done" when the corresponding Task {@code state} * property changes occur. * *

* TaskMonitor manages a queue of new Tasks. The * foregroundTask is automatically set to the first new Task, and when * that Task finishes, the next Task in the queue, and so on. * Applications can set the foregroundTask explicitly, to better * reflect what the user is doing. For example, a tabbed browsing GUI * that launched one Task per tab might set the foreground Task each * time the user selected a tab. To prevent the foregroundTask * property from (ever) being reset automatically, one must set {@link * #setAutoUpdateForegroundTask autoUpdateForegroundTask} to false. * *

* This class is not thread-safe. All of its methods must be called * on the event dispatching thread (EDT) and all of its listeners will * run on the EDT. * * * @author Hans Muller (Hans.Muller@Sun.COM) * @see ApplicationContext#getTaskServices * @see TaskService#getTasks * @see TaskService#execute */ public class TaskMonitor extends AbstractBean { public static final String PROP_FOREGROUND_TASK = "foregroundTask"; private final PropertyChangeListener applicationPCL; private final PropertyChangeListener taskServicePCL; private final PropertyChangeListener taskPCL; private final LinkedList taskQueue; private boolean autoUpdateForegroundTask = true; private Task foregroundTask = null; /** * Construct a TaskMonitor. * @param context */ public TaskMonitor(ApplicationContext context) { applicationPCL = new ApplicationPCL(); taskServicePCL = new TaskServicePCL(); taskPCL = new TaskPCL(); taskQueue = new LinkedList(); context.addPropertyChangeListener(applicationPCL); for (TaskService taskService : context.getTaskServices()) { taskService.addPropertyChangeListener(taskServicePCL); } } /** * The TaskMonitor's PropertyChangeListeners are fired each time * any property of the the {@code foregroundTask} changes. By * default this property is set to the first Task to be executed * and then, when that Task finishes, reset to the next most * recently executed Task. If the {@code * autoUpdateForegroundTask} is false, then the foregroundTask * property is not reset automatically. * * @param foregroundTask the task whose properties are reflected by this class * @see #setAutoUpdateForegroundTask * @see #getForegroundTask */ public void setForegroundTask(Task foregroundTask) { final Task oldTask = this.foregroundTask; if (oldTask != null) { oldTask.removePropertyChangeListener(taskPCL); } this.foregroundTask = foregroundTask; Task newTask = this.foregroundTask; if (newTask != null) { newTask.addPropertyChangeListener(taskPCL); } firePropertyChange(PROP_FOREGROUND_TASK, oldTask, newTask); } /** * Indicates the {@code Task} whose status the ApplicationContext's GUI wants * to be displayed, typically in the main window's status bar. * * * @return the value of the foregroundTask property. * @see #setForegroundTask */ public Task getForegroundTask() { return foregroundTask; } /** * True if the {@code foregroundTask} property should be automatically * reset to the oldest Task in the queue when it finishes running. *

* This property is true by default. * * @return true if the foregroundTask should be set automatically. * @see #setAutoUpdateForegroundTask * @see #setForegroundTask */ public boolean getAutoUpdateForegroundTask() { return autoUpdateForegroundTask; } /** * True if the {@code foregroundTask} property should be automatically * reset to the oldest Task in the queue when it finishes running. An * application that wants explicit control over the Task being monitored * can set this property to false. *

* This property is true by default. * * @param autoUpdateForegroundTask true if the foregroundTask should be set automatically * @see #getAutoUpdateForegroundTask */ public void setAutoUpdateForegroundTask(boolean autoUpdateForegroundTask) { boolean oldValue = this.autoUpdateForegroundTask; this.autoUpdateForegroundTask = autoUpdateForegroundTask; firePropertyChange("autoUpdateForegroundTask", oldValue, this.autoUpdateForegroundTask); } private List copyTaskQueue() { synchronized (taskQueue) { if (taskQueue.isEmpty()) { return Collections.emptyList(); } else { return new ArrayList(taskQueue); } } } /** * All of the Application Tasks whose {@code state} is not {@code DONE}. *

* Each time the list of Tasks changes, a PropertyChangeEvent for the * property named "tasks" is fired. Applications that want to monitor all * background Tasks should monitor the tasks property. * * @return a list of all Tasks that aren't {@code DONE} */ public List getTasks() { return copyTaskQueue(); } /* Called on the EDT, each time a TaskService's list of tasks changes, * i.e. each time a new Task is executed and each time a Task's * state changes to DONE. */ private void updateTasks(List oldTasks, List newTasks) { boolean tasksChanged = false; // has the "tasks" property changed? List oldTaskQueue = copyTaskQueue(); // Remove each oldTask that's not in the newTasks list from taskQueue for (Task oldTask : oldTasks) { if (!(newTasks.contains(oldTask))) { if (taskQueue.remove(oldTask)) { tasksChanged = true; } } } // Add each newTask that's not in the oldTasks list to the taskQueue for (Task newTask : newTasks) { if (!(taskQueue.contains(newTask))) { taskQueue.addLast(newTask); tasksChanged = true; } } // Remove any tasks that are DONE for the sake of tasksChanged Iterator tasks = taskQueue.iterator(); while (tasks.hasNext()) { Task task = tasks.next(); if (task.isDone()) { tasks.remove(); tasksChanged = true; } } // Maybe fire the "tasks" PCLs if (tasksChanged) { List newTaskQueue = copyTaskQueue(); firePropertyChange("tasks", oldTaskQueue, newTaskQueue); } if (autoUpdateForegroundTask && (getForegroundTask() == null)) { setForegroundTask(taskQueue.isEmpty() ? null : taskQueue.getLast()); } } /* Each time an ApplicationContext TaskService is added or removed, we * remove our taskServicePCL from the old ones, add it to the new * ones. In a typical application, this will happen infrequently * and the number of TaskServices will be small, often just one. * This listener runs on the EDT. */ private class ApplicationPCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if ("taskServices".equals(propertyName)) { List oldList = (List) e.getOldValue(); List newList = (List) e.getNewValue(); for (TaskService oldTaskService : oldList) { oldTaskService.removePropertyChangeListener(taskServicePCL); } for (TaskService newTaskService : newList) { newTaskService.addPropertyChangeListener(taskServicePCL); } } } } /* Each time a TaskService's list of Tasks (the "tasks" property) changes, * update the taskQueue (the "tasks" property) and possibly the * foregroundTask property. See updateTasks(). * This listener runs on the EDT. */ private class TaskServicePCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if ("tasks".equals(propertyName)) { List oldList = (List) e.getOldValue(); List newList = (List) e.getNewValue(); updateTasks(oldList, newList); } } } /* Each time a property of the foregroundTask that's also a * TaskMonitor property changes, update the TaskMonitor's state * and fire a TaskMonitor ProprtyChangeEvent. * This listener runs on the EDT. */ private class TaskPCL implements PropertyChangeListener { private void fireStateChange(Task task, String propertyName) { firePropertyChange(new PropertyChangeEvent(task, propertyName, false, true)); } @Override public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); Task task = (Task) (e.getSource()); if ((task != null) && (task == getForegroundTask())) { firePropertyChange(e); if ("state".equals(propertyName)) { StateValue newState = (StateValue) (e.getNewValue()); switch (newState) { case PENDING: fireStateChange(task, "pending"); break; case STARTED: fireStateChange(task, "started"); break; case DONE: fireStateChange(task, "done"); } } if (Task.PROP_COMPLETED.equals(propertyName)) { if (autoUpdateForegroundTask) { setForegroundTask(taskQueue.isEmpty() ? null : taskQueue.getLast()); } } } } } } bsaf-1.9/src/main/java/org/jdesktop/application/TaskService.java000066400000000000000000000203001151640306200247260ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Copyright (C) 2010 Illya Yalovyy. All rights reserved. * Use is subject to license terms. */ package org.jdesktop.application; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.swing.SwingUtilities; /** * The service for executing tasks *

* Methods descriptions are copied from {@link ExecutorService} *

*/ public class TaskService extends AbstractBean { private final String name; private final ExecutorService executorService; private final List tasks; private final PropertyChangeListener taskPCL; /** * Creates a new {@code TaskService} * @param name the name of the task service * @param executorService the executor to be used to run tasks. */ public TaskService(String name, ExecutorService executorService) { if (name == null) { throw new IllegalArgumentException("null name"); } if (executorService == null) { throw new IllegalArgumentException("null executorService"); } this.name = name; this.executorService = executorService; this.tasks = new ArrayList(); this.taskPCL = new TaskPCL(); } /** * Creates a new {@code TaskService} with default executor. * The default executor is a ThreadPoolExecutor with core pool size = 3, * maximum pool size = 10, threads live time = 1 second and queue of type * {@link LinkedBlockingQueue}. */ public TaskService(String name) { this(name, new ThreadPoolExecutor( 3, // corePool size 10, // maximumPool size 1L, TimeUnit.SECONDS, // non-core threads time to live new LinkedBlockingQueue())); } /** * Gets the name of this task service * @return this task service's name */ public final String getName() { return name; } private List copyTasksList() { synchronized (tasks) { if (tasks.isEmpty()) { return Collections.emptyList(); } else { return new ArrayList(tasks); } } } private class TaskPCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if ("done".equals(propertyName)) { Task task = (Task) (e.getSource()); if (task.isDone()) { List oldTaskList, newTaskList; synchronized (tasks) { oldTaskList = copyTasksList(); tasks.remove(task); task.removePropertyChangeListener(taskPCL); newTaskList = copyTasksList(); } firePropertyChange("tasks", oldTaskList, newTaskList); Task.InputBlocker inputBlocker = task.getInputBlocker(); if (inputBlocker != null) { inputBlocker.unblock(); } } } } } private void maybeBlockTask(Task task) { final Task.InputBlocker inputBlocker = task.getInputBlocker(); if (inputBlocker == null) { return; } if (inputBlocker.getScope() != Task.BlockingScope.NONE) { if (SwingUtilities.isEventDispatchThread()) { inputBlocker.block(); } else { Runnable doBlockTask = new Runnable() { @Override public void run() { inputBlocker.block(); } }; SwingUtilities.invokeLater(doBlockTask); } } } /** * Executes the task. * * @param task the task to be executed */ public void execute(Task task) { if (task == null) { throw new IllegalArgumentException("null task"); } if (!task.isPending() || (task.getTaskService() != null)) { throw new IllegalArgumentException("task has already been executed"); } task.setTaskService(this); // TBD: what if task has already been submitted? List oldTaskList, newTaskList; synchronized (tasks) { oldTaskList = copyTasksList(); tasks.add(task); newTaskList = copyTasksList(); task.addPropertyChangeListener(taskPCL); } firePropertyChange("tasks", oldTaskList, newTaskList); maybeBlockTask(task); executorService.execute(task); } /** * Returns the list of tasks which are executing by this service * @return the list of tasks which are executing by this service */ public List getTasks() { return copyTasksList(); } /** * Initiates an orderly shutdown in which previously submitted * tasks are executed, but no new tasks will be accepted. * Invocation has no additional effect if already shut down. * * @throws SecurityException if a security manager exists and * shutting down this ExecutorService may manipulate * threads that the caller is not permitted to modify * because it does not hold {@link * java.lang.RuntimePermission}("modifyThread"), * or the security manager's checkAccess method * denies access. */ public final void shutdown() { executorService.shutdown(); } /** * Attempts to stop all actively executing tasks, halts the * processing of waiting tasks, and returns a list of the tasks that were * awaiting execution. * *

There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. For example, typical * implementations will cancel via {@link Thread#interrupt}, so any * task that fails to respond to interrupts may never terminate. * * @return list of tasks that never commenced execution * @throws SecurityException if a security manager exists and * shutting down this ExecutorService may manipulate * threads that the caller is not permitted to modify * because it does not hold {@link * java.lang.RuntimePermission}("modifyThread"), * or the security manager's checkAccess method * denies access. */ public final List shutdownNow() { return executorService.shutdownNow(); } /** * Returns true if this executor has been shut down. * * @return true if this executor has been shut down */ public final boolean isShutdown() { return executorService.isShutdown(); } /** * Returns true if all tasks have completed following shut down. * Note that isTerminated is never true unless * either shutdown or shutdownNow was called first. * @return true if all tasks have completed following shut down */ public final boolean isTerminated() { return executorService.isTerminated(); } /** * Blocks until all tasks have completed execution after a shutdown * request, or the timeout occurs, or the current thread is * interrupted, whichever happens first. * * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument * @return true if this executor terminated and * false if the timeout elapsed before termination * @throws InterruptedException if interrupted while waiting */ public final boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return executorService.awaitTermination(timeout, unit); } } bsaf-1.9/src/main/java/org/jdesktop/application/TextActions.java000066400000000000000000000241741151640306200247650ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.text.Caret; import javax.swing.text.DefaultEditorKit; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.FlavorEvent; import java.awt.datatransfer.FlavorListener; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; /** * An ActionMap class that defines cut/copy/paste/delete. *

* This class only exists to paper over limitations in the standard JTextComponent * cut/copy/paste/delete javax.swing.Actions. The standard cut/copy Actions don't * keep their enabled property in sync with having the focus and (for copy) having * a non-empty text selection. The standard paste Action's enabled property doesn't * stay in sync with the current contents of the clipboard. The paste/copy/delete * actions must also track the JTextComponent editable property. *

* The new cut/copy/paste/delete are installed lazily, when a JTextComponent gets * the focus, and before any other focus-change related work is done. See * updateFocusOwner(). * * @author Hans Muller (Hans.Muller@Sun.COM) * @author Scott Violet (Scott.Violet@Sun.COM) */ class TextActions extends AbstractBean { private static final String MARKER_ACTION_KEY = "TextActions.markerAction"; private final ApplicationContext context; private final CaretListener textComponentCaretListener; private final PropertyChangeListener textComponentPCL; private final javax.swing.Action markerAction; private boolean copyEnabled = false; // see setCopyEnabled private boolean cutEnabled = false; // see setCutEnabled private boolean pasteEnabled = false; // see setPasteEnabled private boolean deleteEnabled = false; // see setDeleteEnabled private boolean selectAllEnabled = false; // see setSelectAllEnabled public TextActions(ApplicationContext context) { this.context = context; markerAction = new javax.swing.AbstractAction() { @Override public void actionPerformed(ActionEvent e) { } }; textComponentCaretListener = new TextComponentCaretListener(); textComponentPCL = new TextComponentPCL(); getClipboard().addFlavorListener(new ClipboardListener()); } private ApplicationContext getContext() { return context; } private JComponent getFocusOwner() { return getContext().getFocusOwner(); } private Clipboard getClipboard() { return getContext().getClipboard(); } /* Called by the KeyboardFocus PropertyChangeListener in ApplicationContext, * before any other focus-change related work is done. */ void updateFocusOwner(JComponent oldOwner, JComponent newOwner) { if (oldOwner instanceof JTextComponent) { JTextComponent text = (JTextComponent) oldOwner; text.removeCaretListener(textComponentCaretListener); text.removePropertyChangeListener(textComponentPCL); } if (newOwner instanceof JTextComponent) { JTextComponent text = (JTextComponent) newOwner; maybeInstallTextActions(text); updateTextActions(text); text.addCaretListener(textComponentCaretListener); text.addPropertyChangeListener(textComponentPCL); } else if (newOwner == null) { setCopyEnabled(false); setCutEnabled(false); setPasteEnabled(false); setDeleteEnabled(false); setSelectAllEnabled(false); } } private final class ClipboardListener implements FlavorListener { @Override public void flavorsChanged(FlavorEvent e) { JComponent c = getFocusOwner(); if (c instanceof JTextComponent) { updateTextActions((JTextComponent) c); } } } private final class TextComponentCaretListener implements CaretListener { @Override public void caretUpdate(CaretEvent e) { updateTextActions((JTextComponent) (e.getSource())); } } private final class TextComponentPCL implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if ((propertyName == null) || "editable".equals(propertyName)) { updateTextActions((JTextComponent) (e.getSource())); } } } private void updateTextActions(JTextComponent text) { Caret caret = text.getCaret(); final int dot = caret.getDot(); final int mark = caret.getMark(); boolean selection = (dot != mark); boolean editable = text.isEditable(); setCopyEnabled(selection); setCutEnabled(editable && selection); setDeleteEnabled(editable && selection); final int length = text.getDocument().getLength(); setSelectAllEnabled(editable && (Math.abs(mark - dot) != length)); try { boolean data = getClipboard().isDataFlavorAvailable(DataFlavor.stringFlavor); setPasteEnabled(editable && data); } catch (IllegalStateException e) { //ignore setPasteEnabled(editable); } } // TBD: what if text.getActionMap is null, or if it's parent isn't the UI-installed actionMap private void maybeInstallTextActions(JTextComponent text) { ActionMap actionMap = text.getActionMap(); if (actionMap.get(MARKER_ACTION_KEY) == null) { actionMap.put(MARKER_ACTION_KEY, markerAction); ActionMap textActions = getContext().getActionMap(getClass(), this); for (Object key : textActions.keys()) { actionMap.put(key, textActions.get(key)); } } } /* This method lifted from JTextComponent.java */ private int getCurrentEventModifiers() { int modifiers = 0; AWTEvent currentEvent = EventQueue.getCurrentEvent(); if (currentEvent instanceof InputEvent) { modifiers = ((InputEvent) currentEvent).getModifiers(); } else if (currentEvent instanceof ActionEvent) { modifiers = ((ActionEvent) currentEvent).getModifiers(); } return modifiers; } private void invokeTextAction(JTextComponent text, String actionName) { ActionMap actionMap = text.getActionMap().getParent(); long eventTime = EventQueue.getMostRecentEventTime(); int eventMods = getCurrentEventModifiers(); ActionEvent actionEvent = new ActionEvent(text, ActionEvent.ACTION_PERFORMED, actionName, eventTime, eventMods); actionMap.get(actionName).actionPerformed(actionEvent); } @Action(enabledProperty = "cutEnabled") public void cut(ActionEvent e) { Object src = e.getSource(); if (src instanceof JTextComponent) { invokeTextAction((JTextComponent) src, "cut"); } } public boolean isCutEnabled() { return cutEnabled; } public void setCutEnabled(boolean cutEnabled) { boolean oldValue = this.cutEnabled; this.cutEnabled = cutEnabled; firePropertyChange("cutEnabled", oldValue, this.cutEnabled); } @Action(enabledProperty = "copyEnabled") public void copy(ActionEvent e) { Object src = e.getSource(); if (src instanceof JTextComponent) { invokeTextAction((JTextComponent) src, "copy"); } } public boolean isCopyEnabled() { return copyEnabled; } public void setCopyEnabled(boolean copyEnabled) { boolean oldValue = this.copyEnabled; this.copyEnabled = copyEnabled; firePropertyChange("copyEnabled", oldValue, this.copyEnabled); } @Action(enabledProperty = "pasteEnabled") public void paste(ActionEvent e) { Object src = e.getSource(); if (src instanceof JTextComponent) { invokeTextAction((JTextComponent) src, "paste"); } } public boolean isPasteEnabled() { return pasteEnabled; } public void setPasteEnabled(boolean pasteEnabled) { boolean oldValue = this.pasteEnabled; this.pasteEnabled = pasteEnabled; firePropertyChange("pasteEnabled", oldValue, this.pasteEnabled); } @Action(enabledProperty = "deleteEnabled") public void delete(ActionEvent e) { Object src = e.getSource(); if (src instanceof JTextComponent) { /* The deleteNextCharAction is bound to the delete key in * text components. The name appears to be a misnomer, * however it's really a compromise. Calling the method * by a more accurate name, * "IfASelectionExistsThenDeleteItOtherwiseDeleteTheNextCharacter" * would be rather unwieldy. */ invokeTextAction((JTextComponent) src, DefaultEditorKit.deleteNextCharAction); } } public boolean isDeleteEnabled() { return deleteEnabled; } public void setDeleteEnabled(boolean deleteEnabled) { boolean oldValue = this.deleteEnabled; this.deleteEnabled = deleteEnabled; firePropertyChange("deleteEnabled", oldValue, this.deleteEnabled); } @Action(enabledProperty = "selectAllEnabled", name = "select-all") public void selectAll(ActionEvent e) { Object src = e.getSource(); if (src instanceof JTextComponent) { invokeTextAction((JTextComponent) src, DefaultEditorKit.selectAllAction); } } public boolean isSelectAllEnabled() { return selectAllEnabled; } public void setSelectAllEnabled(boolean selectAllEnabled) { boolean oldValue = this.selectAllEnabled; this.selectAllEnabled = selectAllEnabled; firePropertyChange("selectAllEnabled", oldValue, this.selectAllEnabled); } } bsaf-1.9/src/main/java/org/jdesktop/application/View.java000066400000000000000000000232601151640306200234250ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import java.awt.BorderLayout; import java.awt.Container; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JToolBar; /** * A View encapsulates a top-level Application GUI component, like a JFrame * or an Applet, and its main GUI elements: a menu bar, tool bar, component, * and a status bar. All of the elements are optional (although a View without * a main component would be unusual). Views have a {@code JRootPane}, which * is the root component for all of the Swing Window types as well as JApplet. * Setting a View property, like {@code menuBar} or {@code toolBar}, just * adds a component to the rootPane in a way that's defined by the View subclass. * By default the View elements are arranged in a conventional way: *

    *
  • {@code menuBar} - becomes the rootPane's JMenuBar *
  • {@code toolBar} - added to {@code BorderLayout.NORTH} of the rootPane's contentPane *
  • {@code component} - added to {@code BorderLayout.CENTER} of the rootPane's contentPane *
  • {@code statusBar} - added to {@code BorderLayout.SOUTH} of the rootPane's contentPane *
*

* To show or hide a View you call the corresponding Application methods. Here's a simple * example: *

 * class MyApplication extends SingleFrameApplication {
 *     @ppOverride protected void startup() {
 *         View view = getMainView();
 *         view.setComponent(createMainComponent());
 *         view.setMenuBar(createMenuBar());
 *         show(view);
 *     }
 * }
 * 
*

* The advantage of Views over just configuring a JFrame or JApplet * directly, is that a View is more easily moved to an alternative * top level container, like a docking framework. * * @see JRootPane * @see Application#show(View) * @see Application#hide(View) */ public class View extends AbstractBean { private static final Logger logger = Logger.getLogger(View.class.getName()); private final Application application; private ResourceMap resourceMap = null; private JRootPane rootPane = null; private JComponent component = null; private JMenuBar menuBar = null; private List toolBars = Collections.emptyList(); private JComponent toolBarsPanel = null; private JComponent statusBar = null; /** * Construct an empty View object for the specified Application. * * @param application the Application responsible for showing/hiding this View * @see Application#show(View) * @see Application#hide(View) */ public View(Application application) { if (application == null) { throw new IllegalArgumentException("null application"); } this.application = application; } /** * Returns the {@code Application} that's responsible for showing/hiding this View. * * @return the Application that owns this View * @see #getContext * @see Application#show(View) * @see Application#hide(View) */ public final Application getApplication() { return application; } /** * Gets the {@code ApplicationContext} for the {@code * Application} that's responsible for showing/hiding this View. * This method is just shorthand for {@code getApplication().getContext()}. * * @return the Application that owns this View * @see #getApplication * @see Application#show(View) * @see Application#hide(View) */ public final ApplicationContext getContext() { return getApplication().getContext(); } /** * Gets {@code ResourceMap} for this View. * This method invokes {@code getContext().getResourceMap(getClass(), View.class)}. * The result is cached. * * @return The {@code ResourceMap} for this View * @see #getContext */ public ResourceMap getResourceMap() { if (resourceMap == null) { resourceMap = getContext().getResourceMap(getClass(), View.class); } return resourceMap; } /** * Gets the {@code JRootPane} for this View. All of the components for this * View must be added to its rootPane. Most applications will do so * by setting the View's {@code component}, {@code menuBar}, {@code toolBar}, * and {@code statusBar} properties. * * @return The {@code rootPane} for this View * @see #setComponent * @see #setMenuBar * @see #setToolBar * @see #setStatusBar */ public JRootPane getRootPane() { if (rootPane == null) { rootPane = new JRootPane(); rootPane.setOpaque(true); } return rootPane; } private void replaceContentPaneChild(JComponent oldChild, JComponent newChild, String constraint) { Container contentPane = getRootPane().getContentPane(); if (oldChild != null) { contentPane.remove(oldChild); } if (newChild != null) { contentPane.add(newChild, constraint); } } /** * Returns the main {@link JComponent} for this View. * * @return The {@code component} for this View * @see #setComponent */ public JComponent getComponent() { return component; } /** * Sets the single main Component for this View. It's added to the * {@code BorderLayout.CENTER} of the rootPane's contentPane. If * the component property was already set, the old component is removed * first. *

* This is a bound property. The default value is null. * * @param component The {@code component} for this View * @see #getComponent */ public void setComponent(JComponent component) { JComponent oldValue = this.component; this.component = component; replaceContentPaneChild(oldValue, this.component, BorderLayout.CENTER); firePropertyChange("component", oldValue, this.component); } /** * Returns the main {@link JMenuBar} for this View. * * @return The {@code menuBar} for this View * @see #setMenuBar */ public JMenuBar getMenuBar() { return menuBar; } /** * Sets the menu bar for this View. *

* This is a bound property. The default value is null. * * @param menuBar The {@code menuBar} for this View * @see #getMenuBar */ public void setMenuBar(JMenuBar menuBar) { JMenuBar oldValue = getMenuBar(); this.menuBar = menuBar; getRootPane().setJMenuBar(menuBar); firePropertyChange("menuBar", oldValue, menuBar); } /** * Returns the list of tool bars for this View * * @return The list of tool bars */ public List getToolBars() { return toolBars; } /** * Sets the tool bars for this View *

* This is a bound property. The default value is an empty list. * * @param toolBars * @see #setToolBar(JToolBar) * @see #getToolBars() */ public void setToolBars(List toolBars) { if (toolBars == null) { throw new IllegalArgumentException("null toolbars"); } List oldValue = getToolBars(); this.toolBars = Collections.unmodifiableList(new ArrayList(toolBars)); JComponent oldToolBarsPanel = this.toolBarsPanel; JComponent newToolBarsPanel = null; if (this.toolBars.size() == 1) { newToolBarsPanel = toolBars.get(0); } else if (this.toolBars.size() > 1) { newToolBarsPanel = new JPanel(); for (JComponent toolBar : this.toolBars) { newToolBarsPanel.add(toolBar); } } replaceContentPaneChild(oldToolBarsPanel, newToolBarsPanel, BorderLayout.NORTH); firePropertyChange("toolBars", oldValue, this.toolBars); } /** * Gets the first tool bar for this View * * @return The first {@link JToolBar} for this View * @see #setToolBars * @see #getToolBars * @see #setToolBar */ public final JToolBar getToolBar() { List toolBars = getToolBars(); return (toolBars.size() == 0) ? null : toolBars.get(0); } /** * Sets the only tool bar for this View. *

* This is a bound property. * * @param toolBar The {@link JToolBar} for this view. If {@code null} resets the tool bar. * @see #getToolBar() * @see #setToolBars(List) * @see #getToolBars() */ public final void setToolBar(JToolBar toolBar) { JToolBar oldValue = getToolBar(); List toolBars = Collections.emptyList(); if (toolBar != null) { toolBars = Collections.singletonList(toolBar); } setToolBars(toolBars); firePropertyChange("toolBar", oldValue, toolBar); } /** * Returns the Status bar for this View. * * @return The status bar {@link JComponent} for this View */ public JComponent getStatusBar() { return statusBar; } /** * Sets the status bar for this View. The status bar is a generic {@link JComponent}. * * @param statusBar The status bar {@link JComponent} for this View */ public void setStatusBar(JComponent statusBar) { JComponent oldValue = this.statusBar; this.statusBar = statusBar; replaceContentPaneChild(oldValue, this.statusBar, BorderLayout.SOUTH); firePropertyChange("statusBar", oldValue, this.statusBar); } }bsaf-1.9/src/main/java/org/jdesktop/application/session/000077500000000000000000000000001151640306200233305ustar00rootroot00000000000000bsaf-1.9/src/main/java/org/jdesktop/application/session/PropertySupport.java000066400000000000000000000033221151640306200274140ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import java.awt.Component; /** * Defines the {@code sessionState} property. The value of this * property is the GUI state that should be preserved across * sessions for the specified component. The type of sessionState * values just one those supported by * {@link java.beans.XMLEncoder XMLEncoder} and * {@link java.beans.XMLDecoder XMLDecoder}, for example beans * (null constructor, read/write properties), primitives, and * Collections. * * @see org.jdesktop.application.SessionStorage#putProperty * @see org.jdesktop.application.SessionStorage#getProperty(Class) * @see org.jdesktop.application.SessionStorage#getProperty(Component) */ public interface PropertySupport { /** * Return the value of the {@code sessionState} property, typically * a Java bean or a Collection the defines the {@code Component} state * that should be preserved across Application sessions. This * value will be stored with {@link java.beans.XMLEncoder XMLEncoder}, * loaded with {@link java.beans.XMLDecoder XMLDecoder}, and * passed to {@code setSessionState} to restore the Component's * state. * * @param c the Component. * @return the {@code sessionState} object for Component {@code c}. * @see #setSessionState */ Object getSessionState(Component c); /** * Restore Component {@code c's} {@code sessionState} from the specified * object. * * @param c the Component. * @param state the value of the {@code sessionState} property. * @see #getSessionState */ void setSessionState(Component c, Object state); } bsaf-1.9/src/main/java/org/jdesktop/application/session/SplitPaneProperty.java000066400000000000000000000063701151640306200276450ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import java.awt.Component; import javax.swing.JSplitPane; /** * A {@code sessionState} property for JSplitPane. *

* This class defines how the session state for {@code JSplitPanes} * is {@link WindowProperty#getSessionState saved} and * and {@link WindowProperty#setSessionState restored} in * terms of a property called {@code sessionState}. The * JSplitPane's {@code dividerLocation} is saved and restored * if its {@code orientation} hasn't changed. *

* {@code SplitPaneProperty} is registered for {@code * JSplitPane.class} by default, so this class applies to * JSplitPane and any subclass of JSplitPane. One can * override the default with the {@link org.jdesktop.application.SessionStorage#putProperty putProperty} * method. * * @see SplitPaneState * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class SplitPaneProperty implements PropertySupport { private void checkComponent(Component component) { if (component == null) { throw new IllegalArgumentException("null component"); } if (!(component instanceof JSplitPane)) { throw new IllegalArgumentException("invalid component"); } } /** * Returns a {@link SplitPaneState SplitPaneState} object * for {@code JSplitPane c}. If the split pane's * {@code dividerLocation} is -1, indicating that either * the divider hasn't been moved, or it's been reset, * then return null. *

* Throws an {@code IllegalArgumentException} if {@code Component c} * isn't a non-null {@code JSplitPane}. * * @param c the {@code JSplitPane} whose dividerLocation will * recoreded in a {@code SplitPaneState} object. * @return the {@code SplitPaneState} object * @see #setSessionState * @see SplitPaneState */ @Override public Object getSessionState(Component c) { checkComponent(c); JSplitPane p = (JSplitPane) c; return new SplitPaneState(p.getUI().getDividerLocation(p), p.getOrientation()); } /** * Restore the {@code JSplitPane's} {@code dividerLocation} * property if its {@link JSplitPane#getOrientation orientation} * has not changed. *

* Throws an {@code IllegalArgumentException} if {@code c} is * not a {@code JSplitPane} or if {@code state} is non-null * but not an instance of {@link SplitPaneState}. * * @param c the JSplitPane whose state is to be restored * @param state the {@code SplitPaneState} to be restored * @see #getSessionState * @see SplitPaneState */ @Override public void setSessionState(Component c, Object state) { checkComponent(c); if (state == null) return; if (state instanceof SplitPaneState) { JSplitPane p = (JSplitPane) c; SplitPaneState sps = (SplitPaneState) state; if (p.getOrientation() == sps.getOrientation()) { p.setDividerLocation(sps.getDividerLocation()); } } else { throw new IllegalArgumentException("invalid state"); } } } bsaf-1.9/src/main/java/org/jdesktop/application/session/SplitPaneState.java000066400000000000000000000035301151640306200270740ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import javax.swing.JSplitPane; /** * This Java Bean records the {@code dividerLocation} and {@code * orientation} properties of a {@code JSplitPane}. A {@code * SplitPaneState} object created by {@link * SplitPaneProperty#getSessionState} and used to restore the * selected tab by {@link SplitPaneProperty#setSessionState}. * * @see SplitPaneProperty * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class SplitPaneState { private int dividerLocation = -1; private int orientation = JSplitPane.HORIZONTAL_SPLIT; private void checkOrientation(int orientation) { if ((orientation != JSplitPane.HORIZONTAL_SPLIT) && (orientation != JSplitPane.VERTICAL_SPLIT)) { throw new IllegalArgumentException("invalid orientation"); } } public SplitPaneState() { super(); } public SplitPaneState(int dividerLocation, int orientation) { super(); checkOrientation(orientation); if (dividerLocation < -1) { throw new IllegalArgumentException("invalid dividerLocation"); } this.dividerLocation = dividerLocation; this.orientation = orientation; } public int getDividerLocation() { return dividerLocation; } public void setDividerLocation(int dividerLocation) { if (dividerLocation < -1) { throw new IllegalArgumentException("invalid dividerLocation"); } this.dividerLocation = dividerLocation; } public int getOrientation() { return orientation; } public void setOrientation(int orientation) { checkOrientation(orientation); this.orientation = orientation; } } bsaf-1.9/src/main/java/org/jdesktop/application/session/TabbedPaneProperty.java000066400000000000000000000061441151640306200277320ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import java.awt.Component; import javax.swing.JTabbedPane; /** * A {@code sessionState} property for JTabbedPane. *

* This class defines how the session state for {@code JTabbedPanes} * is {@link WindowProperty#getSessionState saved} and * and {@link WindowProperty#setSessionState restored} in * terms of a property called {@code sessionState}. The * JTabbedPane's {@code selectedIndex} is saved and restored * if the number of tabs ({@code tabCount}) hasn't changed. *

* {@code TabbedPaneProperty} is registered for {@code * JTabbedPane.class} by default, so this class applies to * JTabbedPane and any subclass of JTabbedPane. One can * override the default with the {@link org.jdesktop.application.SessionStorage#putProperty putProperty} * method. * * @see TabbedPaneState * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class TabbedPaneProperty implements PropertySupport { private void checkComponent(Component component) { if (component == null) { throw new IllegalArgumentException("null component"); } if (!(component instanceof JTabbedPane)) { throw new IllegalArgumentException("invalid component"); } } /** * Returns a {@link TabbedPaneState TabbedPaneState} object * for {@code JTabbedPane c}. *

* Throws an {@code IllegalArgumentException} if {@code Component c} * isn't a non-null {@code JTabbedPane}. * * @param c the {@code JTabbedPane} whose selectedIndex will * recoreded in a {@code TabbedPaneState} object. * @return the {@code TabbedPaneState} object * @see #setSessionState * @see TabbedPaneState */ @Override public Object getSessionState(Component c) { checkComponent(c); JTabbedPane p = (JTabbedPane) c; return new TabbedPaneState(p.getSelectedIndex(), p.getTabCount()); } /** * Restore the {@code JTabbedPane's} {@code selectedIndex} * property if the number of {@link JTabbedPane#getTabCount tabs} * has not changed. *

* Throws an {@code IllegalArgumentException} if {@code c} is * not a {@code JTabbedPane} or if {@code state} is non-null * but not an instance of {@link TabbedPaneState}. * * @param c the JTabbedPane whose state is to be restored * @param state the {@code TabbedPaneState} to be restored * @see #getSessionState * @see TabbedPaneState */ @Override public void setSessionState(Component c, Object state) { checkComponent(c); if (state == null) return; if (state instanceof TabbedPaneState){ JTabbedPane p = (JTabbedPane) c; TabbedPaneState tps = (TabbedPaneState) state; if (p.getTabCount() == tps.getTabCount()) { p.setSelectedIndex(tps.getSelectedIndex()); } } else { throw new IllegalArgumentException("invalid state"); } } } bsaf-1.9/src/main/java/org/jdesktop/application/session/TabbedPaneState.java000066400000000000000000000032771151640306200271720ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; /** * This Java Bean record the {@code selectedIndex} and {@code * tabCount} properties of a {@code JTabbedPane}. A {@code * TabbedPaneState} object created by {@link * TabbedPaneProperty#getSessionState} and used to restore the * selected tab by {@link TabbedPaneProperty#setSessionState}. * * @see TabbedPaneProperty * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class TabbedPaneState { private int selectedIndex; private int tabCount; public TabbedPaneState() { super(); selectedIndex = -1; tabCount = 0; } public TabbedPaneState(int selectedIndex, int tabCount) { super(); if (tabCount < 0) { throw new IllegalArgumentException("invalid tabCount"); } if ((selectedIndex < -1) || (selectedIndex > tabCount)) { throw new IllegalArgumentException("invalid selectedIndex"); } this.selectedIndex = selectedIndex; this.tabCount = tabCount; } public int getSelectedIndex() { return selectedIndex; } public void setSelectedIndex(int selectedIndex) { if (selectedIndex < -1) { throw new IllegalArgumentException("invalid selectedIndex"); } this.selectedIndex = selectedIndex; } public int getTabCount() { return tabCount; } public void setTabCount(int tabCount) { if (tabCount < 0) { throw new IllegalArgumentException("invalid tabCount"); } this.tabCount = tabCount; } } bsaf-1.9/src/main/java/org/jdesktop/application/session/TableProperty.java000066400000000000000000000074331151640306200267760ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import java.awt.Component; import javax.swing.JTable; import javax.swing.table.TableColumn; /** * A {@code sessionState} property for JTable *

* This class defines how the session state for {@code JTables} * is {@link WindowProperty#getSessionState saved} and * and {@link WindowProperty#setSessionState restored} in * terms of a property called {@code sessionState}. * We save and restore the width of each resizable * {@code TableColumn}, if the number of columns haven't * changed. *

* {@code TableProperty} is registered for {@code * JTable.class} by default, so this class applies to * JTable and any subclass of JTable. One can * override the default with the {@link org.jdesktop.application.SessionStorage#putProperty putProperty} * method. * * @see TableState * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class TableProperty implements PropertySupport { private void checkComponent(Component component) { if (component == null) { throw new IllegalArgumentException("null component"); } if (!(component instanceof JTable)) { throw new IllegalArgumentException("invalid component"); } } /** * Returns a {@link TableState TableState} object * for {@code JTable c} or null, if none of the JTable's * columns are {@link TableColumn#getResizable resizable}. * A width of -1 is used to mark {@code TableColumns} * that are not resizable. *

* Throws an {@code IllegalArgumentException} if {@code Component c} * isn't a non-null {@code JTable}. * * @param c the {@code JTable} whose columnWidths will be * saved in a {@code TableState} object. * @return the {@code TableState} object or null * @see #setSessionState * @see TableState */ @Override public Object getSessionState(Component c) { checkComponent(c); JTable table = (JTable) c; int[] columnWidths = new int[table.getColumnCount()]; boolean resizableColumnExists = false; for (int i = 0; i < columnWidths.length; i++) { TableColumn tc = table.getColumnModel().getColumn(i); columnWidths[i] = (tc.getResizable()) ? tc.getWidth() : -1; if (tc.getResizable()) { resizableColumnExists = true; } } return (resizableColumnExists) ? new TableState(columnWidths) : null; } /** * Restore the width of each resizable {@code TableColumn}, if * the number of columns haven't changed. *

* Throws an {@code IllegalArgumentException} if {@code c} is * not a {@code JTable} or if {@code state} is not an instance * of {@link TableState}. * * @param c the JTable whose column widths are to be restored * @param state the {@code TableState} to be restored * @see #getSessionState * @see TableState */ @Override public void setSessionState(Component c, Object state) { checkComponent(c); if (!(state instanceof TableState)) { throw new IllegalArgumentException("invalid state"); } JTable table = (JTable) c; int[] columnWidths = ((TableState) state).getColumnWidths(); if (table.getColumnCount() == columnWidths.length) { for (int i = 0; i < columnWidths.length; i++) { if (columnWidths[i] != -1) { TableColumn tc = table.getColumnModel().getColumn(i); if (tc.getResizable()) { tc.setPreferredWidth(columnWidths[i]); } } } } } } bsaf-1.9/src/main/java/org/jdesktop/application/session/TableState.java000066400000000000000000000023111151640306200262200ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; /** * This Java Bean records the {@code columnWidths} for all * of the columns in a JTable. A width of -1 is used to * mark {@code TableColumns} that are not resizable. * * @see TableProperty * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class TableState { private int[] columnWidths = new int[0]; private int[] copyColumnWidths(int[] columnWidths) { if (columnWidths == null) { throw new IllegalArgumentException("invalid columnWidths"); } int[] copy = new int[columnWidths.length]; System.arraycopy(columnWidths, 0, copy, 0, columnWidths.length); return copy; } public TableState() { super(); } public TableState(int[] columnWidths) { super(); this.columnWidths = copyColumnWidths(columnWidths); } public int[] getColumnWidths() { return copyColumnWidths(columnWidths); } public void setColumnWidths(int[] columnWidths) { this.columnWidths = copyColumnWidths(columnWidths); } } bsaf-1.9/src/main/java/org/jdesktop/application/session/WindowProperty.java000066400000000000000000000125501151640306200272120ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import java.awt.Component; import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Window; import javax.swing.JFrame; import org.jdesktop.application.utils.SwingHelper; import static org.jdesktop.application.utils.SwingHelper.isResizable; import static org.jdesktop.application.utils.SwingHelper.computeVirtualGraphicsBounds; /** * A {@code sessionState} property for Window. *

* This class defines how the session state for {@code Windows} * is {@link WindowProperty#getSessionState saved} and * and {@link WindowProperty#setSessionState restored} in * terms of a property called {@code sessionState}. The * Window's {@code bounds Rectangle} is saved and restored * if the dimensions of the Window's screen have not changed. *

* {@code WindowProperty} is registered for {@code Window.class} by * default, so this class applies to the AWT {@code Window}, * {@code Dialog}, and {@code Frame} class, as well as their * Swing counterparts: {@code JWindow}, {@code JDialog}, and * {@code JFrame}. * * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore * @see WindowState */ public class WindowProperty implements PropertySupport { private void checkComponent(Component component) { if (component == null) { throw new IllegalArgumentException("null component"); } if (!(component instanceof Window)) { throw new IllegalArgumentException("invalid component"); } } private int getScreenCount() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length; } /** * Returns a {@link WindowState WindowState} object * for {@code Window c}. *

* Throws an {@code IllegalArgumentException} if {@code Component c} * isn't a non-null {@code Window}. * * @param c the {@code Window} whose bounds will be stored * in a {@code WindowState} object. * @return the {@code WindowState} object * @see #setSessionState * @see WindowState */ @Override public Object getSessionState(Component c) { checkComponent(c); int frameState = Frame.NORMAL; if (c instanceof Frame) { frameState = ((Frame) c).getExtendedState(); } GraphicsConfiguration gc = c.getGraphicsConfiguration(); Rectangle gcBounds = (gc == null) ? null : gc.getBounds(); Rectangle frameBounds = c.getBounds(); /* If this is a JFrame created by FrameView and it's been maximized, * retrieve the frame's normal (not maximized) bounds. More info: * see FrameStateListener#windowStateChanged in FrameView. */ if ((c instanceof JFrame) && (0 != (frameState & Frame.MAXIMIZED_BOTH))) { frameBounds = SwingHelper.getWindowNormalBounds((JFrame)c); } if (frameBounds.isEmpty()) return null; return new WindowState(frameBounds, gcBounds, getScreenCount(), frameState); } /** * Restore the {@code Window's} bounds if the dimensions of its * screen ({@code GraphicsConfiguration}) haven't changed, the * number of screens hasn't changed, and the * {@link Window#isLocationByPlatform isLocationByPlatform} * property, which indicates that native Window manager should * pick the Window's location, is false. More precisely: *

* If {@code state} is non-null, and Window {@code c's} * {@code GraphicsConfiguration} * {@link GraphicsConfiguration#getBounds bounds} matches * the {@link WindowState#getGraphicsConfigurationBounds WindowState's value}, * and Window {@code c's} * {@link Window#isLocationByPlatform isLocationByPlatform} * property is false, then set the Window's to the * {@link WindowState#getBounds saved value}. *

* Throws an {@code IllegalArgumentException} if {@code c} is * not a {@code Window} or if {@code state} is non-null * but not an instance of {@link WindowState}. * * @param c the Window whose state is to be restored * @param state the {@code WindowState} to be restored * @see #getSessionState * @see WindowState */ @Override public void setSessionState(Component c, Object state) { checkComponent(c); if ((state != null) && !(state instanceof WindowState)) { throw new IllegalArgumentException("invalid state"); } Window w = (Window) c; WindowState windowState = (WindowState) state; SwingHelper.putWindowNormalBounds(w, windowState.getBounds()); if (!w.isLocationByPlatform() && (state != null)) { Rectangle gcBounds0 = windowState.getGraphicsConfigurationBounds(); if (gcBounds0 != null && isResizable(w)) { if (computeVirtualGraphicsBounds().contains(gcBounds0.getLocation())) { w.setBounds(windowState.getBounds()); } else { w.setSize(windowState.getBounds().getSize()); } } if (w instanceof Frame) { ((Frame) w).setExtendedState(windowState.getFrameState()); } } } } bsaf-1.9/src/main/java/org/jdesktop/application/session/WindowState.java000066400000000000000000000043471151640306200264530ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.session; import java.awt.Frame; import java.awt.Rectangle; /** * This Java Bean defines the {@code Window} state preserved across * sessions: the Window's {@code bounds}, and the bounds of the * Window's {@code GraphicsConfiguration}, i.e. the bounds of the * screen that the Window appears on. If the Window is actually a * Frame, we also store its extendedState. {@code WindowState} objects * are stored and restored by the {@link WindowProperty WindowProperty} * class. * * @see WindowProperty * @see org.jdesktop.application.SessionStorage#save * @see org.jdesktop.application.SessionStorage#restore */ public class WindowState { private final Rectangle bounds; private Rectangle gcBounds = null; private int screenCount; private int frameState = Frame.NORMAL; public WindowState() { super(); bounds = new Rectangle(); } public WindowState(Rectangle bounds, Rectangle gcBounds, int screenCount, int frameState) { super(); if (bounds == null) { throw new IllegalArgumentException("null bounds"); } if (screenCount < 1) { throw new IllegalArgumentException("invalid screenCount"); } this.bounds = bounds; this.gcBounds = gcBounds; // can be null this.screenCount = screenCount; this.frameState = frameState; } public Rectangle getBounds() { return new Rectangle(bounds); } public void setBounds(Rectangle bounds) { this.bounds.setBounds(bounds); } public int getScreenCount() { return screenCount; } public void setScreenCount(int screenCount) { this.screenCount = screenCount; } public int getFrameState() { return frameState; } public void setFrameState(int frameState) { this.frameState = frameState; } public Rectangle getGraphicsConfigurationBounds() { return (gcBounds == null) ? null : new Rectangle(gcBounds); } public void setGraphicsConfigurationBounds(Rectangle gcBounds) { this.gcBounds = (gcBounds == null) ? null : new Rectangle(gcBounds); } }bsaf-1.9/src/main/java/org/jdesktop/application/utils/000077500000000000000000000000001151640306200230055ustar00rootroot00000000000000bsaf-1.9/src/main/java/org/jdesktop/application/utils/AppHelper.java000066400000000000000000000025231151640306200255320ustar00rootroot00000000000000package org.jdesktop.application.utils; import java.security.AccessController; import java.security.PrivilegedAction; /** * Class containing help methods on application level. * @author Vity */ public final class AppHelper { private static PlatformType activePlatformType = null; private AppHelper() { } /** * Determines a platform type the application is running on. * @return current platform type */ public static PlatformType getPlatform() { if (activePlatformType != null) return activePlatformType; activePlatformType = PlatformType.DEFAULT; PrivilegedAction doGetOSName = new PrivilegedAction() { @Override public String run() { return System.getProperty("os.name"); } }; String osName = AccessController.doPrivileged(doGetOSName); if (osName != null) { osName = osName.toLowerCase(); for (PlatformType platformType : PlatformType.values()) { for (String pattern : platformType.getPatterns()) { if (osName.startsWith(pattern)) { return activePlatformType = platformType; } } } } return activePlatformType = PlatformType.DEFAULT; } } bsaf-1.9/src/main/java/org/jdesktop/application/utils/OSXAdapter.java000066400000000000000000000251031151640306200256230ustar00rootroot00000000000000/* File: OSXAdapter.java Abstract: Hooks existing preferences/about/quit functionality from an existing Java app into handlers for the Mac OS X application menu. Uses a Proxy object to dynamically implement the com.apple.eawt.ApplicationListener interface and register it with the com.apple.eawt.Application object. This allows the complete project to be both built and run on any platform without any stubs or placeholders. Useful for developers looking to implement Mac OS X features while supporting multiple platforms with minimal impact. Version: 2.0 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright � 2003-2007 Apple, Inc., All Rights Reserved */ package org.jdesktop.application.utils; import java.lang.reflect.*; public class OSXAdapter implements InvocationHandler { protected Object targetObject; protected Method targetMethod; protected String proxySignature; static Object macOSXApplication; // Pass this method an Object and Method equipped to perform application shutdown logic // The method passed should return a boolean stating whether or not the quit should occur public static void setQuitHandler(Object target, Method quitHandler) { setHandler(new OSXAdapter("handleQuit", target, quitHandler)); } // Pass this method an Object and Method equipped to display application info // They will be called when the About menu item is selected from the application menu public static void setAboutHandler(Object target, Method aboutHandler) { boolean enableAboutMenu = (target != null && aboutHandler != null); if (enableAboutMenu) { setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); } // If we're setting a handler, enable the About menu item by calling // com.apple.eawt.Application reflectively try { Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class }); enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) }); } catch (Exception ex) { System.err.println("OSXAdapter could not access the About Menu"); ex.printStackTrace(); } } // Pass this method an Object and a Method equipped to display application options // They will be called when the Preferences menu item is selected from the application menu public static void setPreferencesHandler(Object target, Method prefsHandler) { boolean enablePrefsMenu = (target != null && prefsHandler != null); if (enablePrefsMenu) { setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); } // If we're setting a handler, enable the Preferences menu item by calling // com.apple.eawt.Application reflectively try { Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class }); enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) }); } catch (Exception ex) { System.err.println("OSXAdapter could not access the About Menu"); ex.printStackTrace(); } } // Pass this method an Object and a Method equipped to handle document events from the Finder // Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the // application bundle's Info.plist public static void setFileHandler(Object target, Method fileHandler) { setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) { // Override OSXAdapter.callTarget to send information on the // file to be opened public boolean callTarget(Object appleEvent) { if (appleEvent != null) { try { Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null); String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null); this.targetMethod.invoke(this.targetObject, new Object[] { filename }); } catch (Exception ex) { } } return true; } }); } // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener public static void setHandler(OSXAdapter adapter) { try { Class applicationClass = Class.forName("com.apple.eawt.Application"); if (macOSXApplication == null) { macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null); } Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass }); // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter); addListenerMethod.invoke(macOSXApplication, new Object[] { osxAdapterProxy }); } catch (ClassNotFoundException cnfe) { System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")"); } catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods System.err.println("Mac OS X Adapter could not talk to EAWT:"); ex.printStackTrace(); } } // Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example), // the Object that will ultimately perform the task, and the Method to be called on that Object protected OSXAdapter(String proxySignature, Object target, Method handler) { this.proxySignature = proxySignature; this.targetObject = target; this.targetMethod = handler; } // Override this method to perform any operations on the event // that comes with the various callbacks // See setFileHandler above for an example public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { Object result = targetMethod.invoke(targetObject, (Object[])null); if (result == null) { return true; } return Boolean.valueOf(result.toString()).booleanValue(); } // InvocationHandler implementation // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (isCorrectMethod(method, args)) { boolean handled = callTarget(args[0]); setApplicationEventHandled(args[0], handled); } // All of the ApplicationListener methods are void; return null regardless of what happens return null; } // Compare the method that was called to the intended method when the OSXAdapter instance was created // (e.g. handleAbout, handleQuit, handleOpenFile, etc.) protected boolean isCorrectMethod(Method method, Object[] args) { return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1); } // It is important to mark the ApplicationEvent as handled and cancel the default behavior // This method checks for a boolean result from the proxy method and sets the event accordingly protected void setApplicationEventHandled(Object event, boolean handled) { if (event != null) { try { Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class }); // If the target method returns a boolean, use that as a hint setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) }); } catch (Exception ex) { System.err.println("OSXAdapter was unable to handle an ApplicationEvent: " + event); ex.printStackTrace(); } } } }bsaf-1.9/src/main/java/org/jdesktop/application/utils/PlatformType.java000066400000000000000000000017671151640306200263110ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.utils; /** * * @author Illya Yalovyy */ public enum PlatformType { DEFAULT ("Default", ""), SOLARIS ("Solaris", "sol", "solaris"), FREE_BSD ("FreeBSD", "bsd", "FreeBSD"), LINUX ("Linux", "lin", "linux"), OS_X ("Mac OS X", "osx", "mac os x"), WINDOWS ("Windows", "win", "windows"); private final String name; private final String resourceSuffix; private final String[] patterns; private PlatformType(String name, String resourcePrefix, String... patterns) { this.name = name; this.resourceSuffix = resourcePrefix; this.patterns = patterns; } public String getName() { return name; } public String[] getPatterns() { return patterns.clone(); } public String getResourceSuffix() { return resourceSuffix; } @Override public String toString() { return name; } } bsaf-1.9/src/main/java/org/jdesktop/application/utils/SwingHelper.java000066400000000000000000000102461151640306200261020ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application.utils; import java.awt.Component; import java.awt.Dialog; import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import javax.swing.JFrame; import javax.swing.JPopupMenu; import javax.swing.RootPaneContainer; /** * Utility class for Swing Application Framework (BSAF) * * @author Illya Yalovyy * @author Eric Heumann * * @since 1.9 */ public final class SwingHelper { private static final String WINDOW_STATE_NORMAL_BOUNDS = "WindowState.normalBounds"; private SwingHelper() { } /** * Calculates virtual graphic bounds. * On multiscreen systems all screens are united into one virtual screen. * @return the graphic bounds */ public static Rectangle computeVirtualGraphicsBounds() { Rectangle virtualBounds = new Rectangle(); GraphicsEnvironment ge = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice[] gs = ge.getScreenDevices(); for (GraphicsDevice gd : gs) { GraphicsConfiguration gc = gd.getDefaultConfiguration(); virtualBounds = virtualBounds.union(gc.getBounds()); } return virtualBounds; } /** * Checks whether the window supports resizing * @param window the {@code Window} to be checked * @return true if the window supports resizing */ public static boolean isResizable(Window window) { boolean resizable = true; if (window instanceof Frame) { resizable = ((Frame) window).isResizable(); } else if (window instanceof Dialog) { resizable = ((Dialog) window).isResizable(); } return resizable; } /** * Calculates default location for the specified window. * @return default location for the window * @param window the window location is calculated for. * It should not be null. */ public static Point defaultLocation(Window window) { GraphicsConfiguration gc = window.getGraphicsConfiguration(); Rectangle bounds = gc.getBounds(); Insets insets = window.getToolkit().getScreenInsets(gc); int x = bounds.x + insets.left; int y = bounds.y + insets.top; return new Point(x, y); } /** * Finds the nearest RootPaneContainer of the provided Component. * Primarily, if a JPopupMenu (such as used by JMenus when they are visible) has no parent, * the search continues with the JPopupMenu's invoker instead. Fixes BSAF-77 * * @return a RootPaneContainer for the provided component * @param root the Component */ public static RootPaneContainer findRootPaneContainer(Component root) { while (root != null) { if (root instanceof RootPaneContainer) { return (RootPaneContainer) root; } else if (root instanceof JPopupMenu && root.getParent() == null) { root = ((JPopupMenu) root).getInvoker(); } else { root = root.getParent(); } } return null; } /** * Gets {@code Window} bounds from the client property * @param window the source {@code Window} * @return bounds from the client property */ public static Rectangle getWindowNormalBounds(Window window) { if (window instanceof JFrame) { Object res = ((JFrame) window).getRootPane().getClientProperty(WINDOW_STATE_NORMAL_BOUNDS); if (res instanceof Rectangle) { return (Rectangle) res; } } return null; } /** * Puts {@code Window} bounds to client property. * @param window the target {@code Window} * @param bounds bounds */ public static void putWindowNormalBounds(Window window, Rectangle bounds) { if (window instanceof JFrame) { ((JFrame) window).getRootPane().putClientProperty(WINDOW_STATE_NORMAL_BOUNDS, bounds); } } } bsaf-1.9/src/main/resources/000077500000000000000000000000001151640306200160215ustar00rootroot00000000000000bsaf-1.9/src/main/resources/COPYING000066400000000000000000000647301151640306200170660ustar00rootroot00000000000000Copyright (c) 2005-2006 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, U.S.A. All rights reserved. Use is subject to license terms below. Sun, Sun Microsystems and the Sun logo are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. Copyright (c) 2009-2010 Illya Yalovyy , Use is subject to license terms below. Notice: This product is covered by U.S. export control laws and may be subject to the export or import laws in other countries. These laws may restrict the fields of use for this software and may require you to secure government authorization. GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice bsaf-1.9/src/main/resources/org/000077500000000000000000000000001151640306200166105ustar00rootroot00000000000000bsaf-1.9/src/main/resources/org/jdesktop/000077500000000000000000000000001151640306200204335ustar00rootroot00000000000000bsaf-1.9/src/main/resources/org/jdesktop/application/000077500000000000000000000000001151640306200227365ustar00rootroot00000000000000bsaf-1.9/src/main/resources/org/jdesktop/application/resources/000077500000000000000000000000001151640306200247505ustar00rootroot00000000000000bsaf-1.9/src/main/resources/org/jdesktop/application/resources/Application.properties000066400000000000000000000037461151640306200313430ustar00rootroot00000000000000 # The following Application resources provide generally useful # information that can be used in automatically generated dialogs, top # level window titles, etc. These resources match the JNLP information # element, more about that here: # http://java.sun.com/j2se/1.4.2/docs/guide/jws/developersguide/syntax.html#information Application.title=[Application.title not specified] Application.vendor=[Application.vendor not specified] Application.homepage=[Application.homepage not specified] Application.description.short=[Application.description.short not specified] Application.description=[Application.description not specified] Application.icon=icons/bsaf_icon_16.png # Default resources for InputBlocker's modal dialog ) BlockingDialog.title = Busy BlockingDialog.optionPane.message = Please wait... BlockingDialog.cancelButton.text = &Cancel BlockingDialog.progressBar.stringPainted = true BlockingDialog.progressBar.string = %02d:%02d, %02d:%02d remaining BlockingDialogTimer.delay = 250 # @Action resources for cut/copy/paste/delete/quit actions. cut.Action.text = Cu&t cut.Action.icon = icons/cut.png cut.Action.accelerator = shortcut X cut.Action.shortDescription = Move the current selection to the clipboard copy.Action.text = &Copy copy.Action.icon = icons/copy.png copy.Action.accelerator = shortcut C copy.Action.shortDescription = Copy the current selection to the clipboard paste.Action.text = &Paste paste.Action.icon = icons/paste.png paste.Action.accelerator = shortcut V paste.Action.shortDescription = Paste the contents of the clipboard at the current insertion point delete.Action.text = &Delete delete.Action.icon = icons/delete.png delete.Action.accelerator = DELETE delete.Action.shortDescription = Delete current selection select-all.Action.text=Select &All #accelerator is swing based - this is a proxy action select-all.Action.shortDescription=Selects the entire text quit.Action.text = E&xit quit.Action.accelerator = shortcut Q quit.Action.shortDescription = Exit the application bsaf-1.9/src/main/resources/org/jdesktop/application/resources/icons/000077500000000000000000000000001151640306200260635ustar00rootroot00000000000000bsaf-1.9/src/main/resources/org/jdesktop/application/resources/icons/bsaf_icon_16.png000066400000000000000000000011451151640306200310230ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8ő?kQƟb?$H"C2d28C8.1Nnm%.BAos}k${CC<,s`f$M(Ѻf&DjώEc[mf$R|Mz9d2fgSsӻ`:xhV}`}掠DtMJ "dvU5qrR)RY f'GG'7`fa A'>Z(ꚝ;k!:rwwCd2OOc<` I_BEj3W ߪ\J֜Z ܬ=J|.o0c.?Y`ee DD ֺ%c =h݃րyO03ɧJi(%!B+%DQR 璒8 ?CCIENDB`bsaf-1.9/src/main/resources/org/jdesktop/application/resources/icons/copy.png000066400000000000000000000061411151640306200275450ustar00rootroot00000000000000PNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_F|IDATxڌMKBAJZaiD>( DP-EЊڔDJιwkŜ3rsv|q`D&W?MS]aL6R%n$ulKTHqxvz5$Ұj֚֚鵫 "tF]6TpdԖRG[v Ƣ!!Z@ju|ʈOM(wvחI.||mKH2gs ә 3[>`A4A4AVx+BuךAH+ ns'1tiq.>M [>B)Uv2Hm`"s:y\L) $7vzWx/Tnf&IENDB`bsaf-1.9/src/main/resources/org/jdesktop/application/resources/icons/cut.png000066400000000000000000000064571151640306200274000ustar00rootroot00000000000000PNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_FJIDATxڜKQ1EC+ta!7S&ޘHt1 0AD)iSfbDW CƊdͩvEd/<{yU6CDY6CT{kVWwjsP{}kI{EWϸhlu ȿ/}/AQR 4~X%"kMgg>&=0IENDB`bsaf-1.9/src/main/resources/org/jdesktop/application/resources/icons/delete.png000066400000000000000000000067251151640306200300450ustar00rootroot00000000000000PNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_FIDATxTOhu_Z \̪p8lb!@A]Ɩ0mGIP 3Vwi!mBlIK,Md44  o Ur 2/벫 >4ػBjEvuwx~2<&&QeOO/ exp Ba 'xMLRV{R3=/}g;z'Ow7\L_a嫝8oP.aOL1|DZps x>?ɋ7B6ؾAe JE7O,,~~T30ƴ/G}MӶfkJ%)Ư݃˒fƘ7!C^5ȭC$nQ݄'OZf  Jm}F %IENDB`bsaf-1.9/src/main/resources/org/jdesktop/application/resources/icons/paste.png000066400000000000000000000063271151640306200277150ustar00rootroot00000000000000PNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_FIDATx|kA?lҺ "xi(4h[Eh WoM$H"*Df~=lM6! D^:^n- sxJD/LG7>9GP Nsb]vuq "feRJQJYMY.BvY76e|bR+-lfPDl/'ϱ3sܽs̥; @YÚ:fsc۟:96ƽ|[{n6t4}RGS * resources/Controller.properties * resources/black1x1.png * * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationActionMapTest { public static class SimpleActions { @Action public void trivialAction() { } // no properties in Controller.properties @Action public void allActionProperties() { } // all possible properties in Controller.properties @Action(name = "alternateActionName") public void notTheActionName() { } // See testActionMnemonicProperties @Action public void checkActionMnemonics0() { } @Action public void checkActionMnemonics1() { } @Action public void checkActionMnemonics5() { } // See testActionPerformed() public int actionCounter = 0; @Action public void incrementActionCounter() { actionCounter += 1; } // See testActionEnabled() private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } private boolean actionEnabled = true; public boolean isActionEnabled() { return actionEnabled; } public void setActionEnabled(boolean newValue) { boolean oldValue = actionEnabled; if (oldValue != newValue) { this.actionEnabled = newValue; pcs.firePropertyChange("actionEnabled", oldValue, newValue); } } @Action(enabledProperty = "actionEnabled") public void testActionEnabled() { } // see testActionParametersA() public ActionEvent lastActionEvent = null; @Action void actionParametersA(ActionEvent e) { lastActionEvent = e; } @Action void actionParametersB(javax.swing.Action a) { a.putValue("testKey", "testValue"); } @Action void actionParametersAB(ActionEvent e, javax.swing.Action a) { lastActionEvent = e; a.putValue("testKey", "testValue"); } @Action void actionParametersZ( ActionEvent actionEvent, javax.swing.Action action, ActionMap actionMap, ResourceMap resourceMap) { lastActionEvent = actionEvent; action.putValue("testKey", "testValue"); actionMap.put("testKey", action); assertNotNull("actionParametersZ resourceMap parameter", resourceMap); assertEquals("actionParametersZ", resourceMap.getString("actionParametersZ.text")); } // see testJavaDocExample PrintWriter helloWorldWriter = null; @Action void Hello() { helloWorldWriter.print("Hello "); } @Action void World() { helloWorldWriter.print("World"); } } private ResourceMap emptyResourceMap() { ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, "noSuchBundle"); return resourceMap; } private ResourceMap resourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.Controller"; /* If the ResourceBundle can't be found, getBundle() will throw an exception. * ResourceMap isn't supposed to complain if it can't find a * ResourceBundle however the tests that follow expect * Basic ResourceBundle to exist. */ ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, bundleBaseName); return resourceMap; } private ApplicationActionMap simpleActionMap(ResourceMap resourceMap) { SimpleActions actionsObject = new SimpleActions(); ApplicationContext ctx = new ApplicationContext(); return new ApplicationActionMap(ctx, SimpleActions.class, actionsObject, resourceMap); } private ApplicationActionMap simpleActionMap() { return simpleActionMap(emptyResourceMap()); } @Test public void testGetActionsObject() { ApplicationActionMap appAM = simpleActionMap(); boolean isSimpleActionsObject = appAM.getActionsObject() instanceof SimpleActions; assertTrue("appAM.getActionsObject() instanceof SimpleActionsObject", isSimpleActionsObject); boolean isSingleton = appAM.getActionsObject() == appAM.getActionsObject(); assertTrue("appAM.getActionsObject() returns singleton", isSingleton); } public static class NoActions { NoActions(String s) {} // ApplicationActionMap can't auto-construct this one } @Test public void testTrivialAction() { ApplicationActionMap appAM = simpleActionMap(); checkName(appAM, "trivialAction", "trivialAction"); } private String appAMGetString(String actionKey) { return "ApplicationActionMap.get(\"" + actionKey + "\")"; } private javax.swing.Action checkAction(ApplicationActionMap appAM, String actionKey) { javax.swing.Action action = appAM.get(actionKey); assertNotNull(appAMGetString(actionKey), action); return action; } private void checkName(ApplicationActionMap appAM, String actionKey, String expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); String value = (String) (action.getValue(javax.swing.Action.NAME)); assertEquals(appAMGetString(actionKey) + ".getValue(javax.swing.Action.NAME)", expectedValue, value); } private void checkShortDescription(ApplicationActionMap appAM, String actionKey, String expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); String value = (String) (action.getValue(javax.swing.Action.SHORT_DESCRIPTION)); assertEquals(appAMGetString(actionKey) + ".getValue(javax.swing.Action.SHORT_DESCRIPTION)", expectedValue, value); } private void checkLongDescription(ApplicationActionMap appAM, String actionKey, String expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); String value = (String) (action.getValue(javax.swing.Action.LONG_DESCRIPTION)); assertEquals(appAMGetString(actionKey) + ".getValue(javax.swing.Action.LONG_DESCRIPTION)", expectedValue, value); } private void checkIcon(ApplicationActionMap appAM, String actionKey) { javax.swing.Action action = checkAction(appAM, actionKey); Icon icon = (Icon) (action.getValue(javax.swing.Action.SMALL_ICON)); String msg = appAMGetString(actionKey) + ".getValue(javax.swing.Action.SMALL_ICON)"; assertNotNull(msg, icon); assertEquals(msg + ".getIconWidth()", 1, icon.getIconWidth()); assertEquals(msg + ".getIconHeight()", 1, icon.getIconHeight()); } private void checkNullIcon(ApplicationActionMap appAM, String actionKey) { javax.swing.Action action = checkAction(appAM, actionKey); Icon icon = (Icon) (action.getValue(javax.swing.Action.SMALL_ICON)); String msg = appAMGetString(actionKey) + ".getValue(javax.swing.Action.SMALL_ICON)"; assertNull(msg, icon); } private void checkCommandKey(ApplicationActionMap appAM, String actionKey, String expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); String value = (String) (action.getValue(javax.swing.Action.ACTION_COMMAND_KEY)); assertEquals(appAMGetString(actionKey) + ".getValue(javax.swing.Action.ACTION_COMMAND_KEY)", expectedValue, value); } private void checkAcceleratorKey(ApplicationActionMap appAM, String actionKey, KeyStroke expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); KeyStroke value = (KeyStroke) (action.getValue(javax.swing.Action.ACCELERATOR_KEY)); assertEquals(appAMGetString(actionKey) + ".getValue(javax.swing.Action.ACCELERATOR_KEY)", expectedValue, value); } private void checkMnemonicKey(ApplicationActionMap appAM, String actionKey, Integer expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); Integer value = (Integer) (action.getValue(javax.swing.Action.MNEMONIC_KEY)); assertEquals(appAMGetString(actionKey) + ".getValue(javax.swing.Action.MNEMONIC_KEY)", expectedValue, value); } /* This javax.swing.Action constants is only * defined in Mustang (1.6), see * http://download.java.net/jdk6/docs/api/javax/swing/Action.html */ private static final String DISPLAYED_MNEMONIC_INDEX_KEY = "SwingDisplayedMnemonicIndexKey"; private void checkMnemonicIndex(ApplicationActionMap appAM, String actionKey, Integer expectedValue) { javax.swing.Action action = checkAction(appAM, actionKey); Integer value = (Integer) (action.getValue(/*javax.swing.Action.*/DISPLAYED_MNEMONIC_INDEX_KEY)); assertEquals(appAMGetString(actionKey) + ".getValue(/*javax.swing.Action.*/DISPLAYED_MNEMONIC_INDEX_KEY)", expectedValue, value); } @Test public void testActionProperties() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); String allActionProperties = "allActionProperties"; checkName(appAM, allActionProperties, "All"); checkShortDescription(appAM, allActionProperties, "short"); checkLongDescription(appAM, allActionProperties, "long"); checkIcon(appAM, allActionProperties); checkCommandKey(appAM, allActionProperties, "AllCommand"); KeyStroke controlA = KeyStroke.getKeyStroke("control A"); checkAcceleratorKey(appAM, allActionProperties, controlA); checkMnemonicKey(appAM, allActionProperties, new Integer(controlA.getKeyCode())); String checkActionMnemonics0 = "checkActionMnemonics0"; } @Test public void testActionMnemonicProperties() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); String[] actionMapKeys = {"checkActionMnemonics0", "checkActionMnemonics1", "checkActionMnemonics5"}; String[] actionNames = {"File", "Exit", "Save As"}; int[] mnemonicKeys = {KeyEvent.VK_F, KeyEvent.VK_X, KeyEvent.VK_A}; int[] mnemonicIndices = {0, 1, 5}; for (int i = 0; i < actionMapKeys.length; i++) { String actionMapKey = actionMapKeys[i]; checkName(appAM, actionMapKey, actionNames[i]); checkMnemonicKey(appAM, actionMapKey, mnemonicKeys[i]); checkMnemonicIndex(appAM, actionMapKey, mnemonicIndices[i]); checkShortDescription(appAM, actionMapKey, null); checkLongDescription(appAM, actionMapKey, null); checkCommandKey(appAM, actionMapKey, null); } } @Test public void testActionAnnotationKeyProperty() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); String alternateActionName = "alternateActionName"; checkName(appAM, alternateActionName, "All"); checkShortDescription(appAM, alternateActionName, "short"); checkLongDescription(appAM, alternateActionName, "long"); checkIcon(appAM, alternateActionName); checkCommandKey(appAM, alternateActionName, "AllCommand"); KeyStroke controlA = KeyStroke.getKeyStroke("control A"); checkAcceleratorKey(appAM, alternateActionName, controlA); KeyStroke A = KeyStroke.getKeyStroke("A"); checkMnemonicKey(appAM, alternateActionName, new Integer(A.getKeyCode())); } @Test public void testActionPerformed() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); SimpleActions actionsObject = (SimpleActions) (appAM.getActionsObject()); javax.swing.Action action = checkAction(appAM, "incrementActionCounter"); ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "not used"); actionsObject.actionCounter = 0; action.actionPerformed(event); action.actionPerformed(event); action.actionPerformed(event); assertEquals("Called SimpleActionsObject.incrementActionCounter() 3X", 3, actionsObject.actionCounter); // TBD - verify that the event gets passed along if the @Action method // has an event parameter } /** * Verify that setting the "enabled" property of the * javax.swing.Action created for @Action SimpleActions.testActionEnabled, * calls SimpleActions.setActionEnabled(). */ @Test public void testActionEnabledBasics() { ApplicationActionMap appAM = simpleActionMap(); SimpleActions actionsObject = (SimpleActions) (appAM.getActionsObject()); javax.swing.Action action = checkAction(appAM, "testActionEnabled"); action.setEnabled(false); assertFalse(action.isEnabled()); assertFalse(actionsObject.isActionEnabled()); action.setEnabled(true); assertTrue(action.isEnabled()); assertTrue(actionsObject.isActionEnabled()); } /** * Verify that setting the SimpleActions.actionEnabled property * changes the action object's enabled property and fires its * PropertyChangeListener. */ @Test public void testActionEnabled() { ApplicationActionMap appAM = simpleActionMap(); SimpleActions actionsObject = (SimpleActions) (appAM.getActionsObject()); javax.swing.Action action = checkAction(appAM, "testActionEnabled"); PropertyChangeListener actionEnabledChangesToFalse = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { assertEquals("PropertyChangeEvent.propertyName", "enabled", event.getPropertyName()); assertTrue("PropertyChangeEvent.oldValue", (Boolean) (event.getOldValue())); assertFalse("PropertyChangeEvent.newValue", (Boolean) (event.getNewValue())); } }; action.addPropertyChangeListener(actionEnabledChangesToFalse); String msg = "SimpleActionsObject.testActionEnabled Action - action.isEnabled() "; assertTrue(msg + "before calling actionsObject.setActionEnabled(false)", action.isEnabled()); actionsObject.setActionEnabled(false); assertFalse(msg + "after calling actionsObject.setActionEnabled(false)", action.isEnabled()); } // TBD check the ApplicationActionMap constructor that takes an actionsObject // TBD check the ApplicationActionMap constructor with null ResourceMap private ActionEvent createActionEvent() { return new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "testActionParameters"); } /* @Action void actionParametersA(ActionEvent e) // see SimpleActions * * Verify that the Action created for SimpleActions.actionParametersA() * passes the ActionEvent parameter along. */ @Test public void testActionParametersA() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); // verify that the Action is in the SimpleActions ActionMap javax.swing.Action action = appAM.get("actionParametersA"); assertNotNull("appAM.get(actionParametersA)", action); // call the actionPerformedMethod and check the parameters SimpleActions simpleActions = (SimpleActions) (appAM.getActionsObject()); simpleActions.lastActionEvent = null; ActionEvent actionEventA = createActionEvent(); action.actionPerformed(actionEventA); assertEquals(actionEventA, simpleActions.lastActionEvent); } /* @Action void actionParametersB(javax.swing.Action a) { * * Verify that the Action created for SimpleActions.actionParametersB() * passes the Action parameter along. */ @Test public void testActionParametersB() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); // verify that the Action is in the SimpleActions ActionMap javax.swing.Action action = appAM.get("actionParametersB"); assertNotNull("appAM.get(actionParametersB)", action); // call the actionPerformedMethod and check the parameters action.putValue("testKey", null); ActionEvent actionEventB = createActionEvent(); action.actionPerformed(actionEventB); assertEquals("testValue", action.getValue("testKey")); } /* @Action void actionParametersAB(ActionEvent e, Action a) * * Verify that the Action created for SimpleActions.actionParametersAB() * passes the ActionEvent and Action parameters along. */ @Test public void testActionParametersAB() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); // verify that the Action is in the SimpleActions ActionMap javax.swing.Action action = appAM.get("actionParametersAB"); assertNotNull("appAM.get(actionParametersAB)", action); // call the actionPerformedMethod and check the parameters ActionEvent actionEventAB = createActionEvent(); action.putValue("testKey", null); SimpleActions simpleActions = (SimpleActions) (appAM.getActionsObject()); simpleActions.lastActionEvent = null; action.actionPerformed(actionEventAB); assertEquals(actionEventAB, simpleActions.lastActionEvent); assertEquals("testValue", action.getValue("testKey")); } /* * @Action void actionParametersZ( * ActionEvent actionEvent, javax.swing.Action action, ActionMap actionMap, resourceMap resourceMap) * * Verify that the Action created for SimpleActions.actionParametersZ() * passes all of the specified parameters along. */ @Test public void testActionParametersZ() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); // verify that the Action is in the SimpleActions ActionMap javax.swing.Action action = appAM.get("actionParametersZ"); assertNotNull("appAM.get(actionParametersZ)", action); // call the actionPerformedMethod and check the parameters ActionEvent actionEventZ = createActionEvent(); action.putValue("testKey", null); SimpleActions simpleActions = (SimpleActions) (appAM.getActionsObject()); simpleActions.lastActionEvent = null; action.actionPerformed(actionEventZ); assertEquals(actionEventZ, simpleActions.lastActionEvent); assertEquals("testValue", action.getValue("testKey")); assertEquals(action, appAM.get("testKey")); } /* Check an example from the ApplicationActionMap class javadoc. */ @Test public void testJavaDocExample1() { ApplicationActionMap appAM = simpleActionMap(resourceMap()); SimpleActions simpleActions = (SimpleActions) (appAM.getActionsObject()); StringWriter sw = new StringWriter(); simpleActions.helloWorldWriter = new PrintWriter(sw); ActionEvent actionEvent = new ActionEvent("no source", ActionEvent.ACTION_PERFORMED, ""); appAM.get("Hello").actionPerformed(actionEvent); appAM.get("World").actionPerformed(actionEvent); assertEquals("Hello World", sw.toString()); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationActionsTest.java000066400000000000000000000115031151640306200271670ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.*; import org.junit.BeforeClass; import org.junit.Test; import java.awt.event.ActionEvent; import javax.swing.JCheckBox; /** * Test the Actions that are defined by the Application class: * quit, cut, copy, paste, delete. Depends on ResourceBundle * resources/DefaultActionsApplication.properties * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationActionsTest { public static class DefaultActionsApplication extends WaitForStartupApplication { boolean deleteCalled = false; boolean selected = true; @Action public void delete() { deleteCalled = true; } @Action(selectedProperty="selected") public void selectableAction() { } public boolean isSelected() { return selected; } } @BeforeClass public static void unitSetup() { DefaultActionsApplication.launchAndWait(DefaultActionsApplication.class); } private String actionText(ApplicationAction action) { return (String) action.getValue(ApplicationAction.NAME); } private String actionShortDescription(ApplicationAction action) { return (String) action.getValue(ApplicationAction.SHORT_DESCRIPTION); } private void checkDefaultAction(String actionName, ApplicationAction action) { assertNotNull(actionName, action); assertNotNull(actionName + ".Action.text", actionText(action)); assertNotNull(actionName + ".Action.shortDescription", actionShortDescription(action)); } private ApplicationActionMap actionMap() { return Application.getInstance(DefaultActionsApplication.class).getContext().getActionMap(); } /** * Verify that the quit, cut, copy, paste, and delete actions exist, * and that they all text, shortDescription properties. */ @Test public void testBasics() { String[] actionNames = {"quit", "cut", "copy", "paste", "delete"}; ApplicationActionMap appAM = actionMap(); assertNotNull("Global ActionMap", appAM); for (String actionName : actionNames) { ApplicationAction action = (ApplicationAction) (appAM.get(actionName)); checkDefaultAction(actionName, action); } } /** * Verify that the quit action resources defined in * resources/DefaultActionsApplication.properties override * the defaults inherited from the Application ResourceBundle. */ @Test public void testApplicationResourceOverrides() { ApplicationActionMap appAM = actionMap(); assertSame("global ActionMap.actionsClass", DefaultActionsApplication.class, appAM.getActionsClass()); ApplicationAction action = (ApplicationAction) (appAM.get("quit")); assertEquals("quit.Action.text", "Q", actionText(action)); assertEquals("quit.Action.shortDescription", "Q", actionShortDescription(action)); } private DefaultActionsApplication application() { return Application.getInstance(DefaultActionsApplication.class); } /** * Verify that the DefaultActionsApplication.delete @Action * shadows (but doesn't replace) the ProxyAction defined by * the Application class. */ @Test public void testApplicationActionOverrides() { ApplicationActionMap appAM = actionMap(); ApplicationAction action = (ApplicationAction) (appAM.get("delete")); assertEquals("delete.Action.text", "D", actionText(action)); assertEquals("delete.Action.shortDescription", "D", actionShortDescription(action)); ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "not used"); action.actionPerformed(event); assertTrue("DefaultActionsApplication.deleteCalled", application().deleteCalled); /* Looking up the "delete" action in this ActionMap should produce * the default one, i.e. the one defined by Application. Its resources * should have been overidden as above, but it should be a separate * Action object. */ ApplicationActionMap parentAM = (ApplicationActionMap) actionMap().getParent(); ApplicationAction parentAction = (ApplicationAction) (parentAM.get("delete")); assertEquals("delete.Action.text", "D", actionText(parentAction)); assertEquals("delete.Action.shortDescription", "D", actionShortDescription(parentAction)); assertNotSame(action, parentAction); } @Test public void testSelectableAction() { ApplicationActionMap actionMap = actionMap(); JCheckBox checkBox = new JCheckBox(actionMap.get("selectableAction")); assertTrue(checkBox.isSelected()); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationDefaultLNFResourceTest.java000066400000000000000000000027331151640306200312300ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import javax.swing.*; /** * The Application.lookAndFeel resource defined as "default", which * indicates that we should load the JVM default look and feel. * That's metal on all platforms except OSX, where it's the native L&F. * * This test depends on resources/AppilcationDefaultLNF.properties * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationDefaultLNFResourceTest { public static class ApplicationDefaultLNF extends WaitForStartupApplication { } @Before public void methodSetup() { ApplicationDefaultLNF.launchAndWait(ApplicationDefaultLNF.class); } @Test public void testApplicationLookAndFeelResource() { LookAndFeel lnf = UIManager.getLookAndFeel(); String osName = System.getProperty("os.name"); if ((osName != null) && (osName.toLowerCase().startsWith("mac os x"))) { assertTrue("OSX default L&F is native", lnf.isNativeLookAndFeel()); } else { String defaultLNFName = UIManager.getCrossPlatformLookAndFeelClassName(); assertFalse("not native L&F", lnf.isNativeLookAndFeel()); assertEquals("cross platform L&F name", defaultLNFName, lnf.getClass().getName()); } } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationEndTest.java000066400000000000000000000037351151640306200263050ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import javax.swing.SwingUtilities; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; /** * Verify overriding Application#end() defeats the default call to * System.exit(). * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationEndTest { private static boolean isAppLaunched = false; /* If the JVM were to actually shutdown during the Application#exit call, * we'll throw an Error here. */ private static class ShutdownHookError extends Thread { public void run() { throw new Error("JVM shutdown unexpectedly"); } } public static class EndApplication extends WaitForStartupApplication { boolean endCalled = false; boolean shutdownRanOnEDT; @Override protected void end() { endCalled = true; // default was System.exit(0); } //Application.shutdown() javadoc says: "This method runs on the event dispatching thread." @Override protected void shutdown() { shutdownRanOnEDT = SwingUtilities.isEventDispatchThread(); super.shutdown(); } } private EndApplication application() { return Application.getInstance(EndApplication.class); } @Before public void methodSetup() { EndApplication.launchAndWait(EndApplication.class); isAppLaunched = true; Runtime rt = Runtime.getRuntime(); Thread hook = new ShutdownHookError(); rt.addShutdownHook(hook); application().exit(); rt.removeShutdownHook(hook); } @Test public void testEndCalled() { assertTrue("shutdown() ran on the EDT", ((EndApplication) Application.getInstance()).shutdownRanOnEDT); assertTrue(application().endCalled); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationMotifLNFResourceTest.java000066400000000000000000000031701151640306200307160ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import org.junit.Before; import org.junit.Test; import javax.swing.*; /** * Checks that by defining the Application.lookAndFeel resource * to be "com.sun.java.swing.plaf.motif.MotifLookAndFeel" causes * the UIManager.lookAndFeel property to be initialized to the * Motif L&F. * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationMotifLNFResourceTest { /* Application.lookAndFeel resource is explicity defined to be "system". */ public static class ApplicationMotifLNF extends WaitForStartupApplication { } @Before public void methodSetup() { ApplicationMotifLNF.launchAndWait(ApplicationMotifLNF.class); } @Test public void testApplicationLookAndFeelResource() { ApplicationContext ctx = Application.getInstance(ApplicationMotifLNF.class).getContext(); String lnfResource = ctx.getResourceMap().getString("Application.lookAndFeel"); assertEquals("Application.lookAndFeel resource", "com.sun.java.swing.plaf.motif.MotifLookAndFeel", lnfResource); LookAndFeel lnf = UIManager.getLookAndFeel(); @SuppressWarnings("all") // ... MotifLookAndFeel is Sun proprietary API and may be removed in a future release Class motifLNFClass = com.sun.java.swing.plaf.motif.MotifLookAndFeel.class; assertSame("UIManager.getLookAndFeel().getClass", motifLNFClass, lnf.getClass()); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationNimbusLNFResourceTest.java000066400000000000000000000023121151640306200310720ustar00rootroot00000000000000/* * Copyright (C) 2010 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import javax.swing.*; /** * Checks special support for Nimbus LnF * * @author Illya Yalovyy */ public class ApplicationNimbusLNFResourceTest { /* Application.lookAndFeel resource is explicitly defined to be "nimbus". */ public static class ApplicationNimbusLNF extends WaitForStartupApplication { } @Before public void methodSetup() { ApplicationNimbusLNF.launchAndWait(ApplicationNimbusLNF.class); } @Test public void testApplicationLookAndFeelResource() { ApplicationContext ctx = Application.getInstance(ApplicationNimbusLNF.class).getContext(); String lnfResource = ctx.getResourceMap().getString("Application.lookAndFeel"); assertNotNull(lnfResource); assertEquals("Application.lookAndFeel resource", "nimbus", lnfResource); LookAndFeel lnf = UIManager.getLookAndFeel(); assertEquals("UIManager.getLookAndFeel", "Nimbus", lnf.getName()); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationNoLNFResourceTest.java000066400000000000000000000020651151640306200302160ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import javax.swing.*; /** * No Application.lookAndFeel resource defined, so we default to * "system" (unlike the JVM itself) which means to use the * system (native) look and feel. * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationNoLNFResourceTest { public static class ApplicationNoLNF extends WaitForStartupApplication { } @Before public void methodSetup() { ApplicationNoLNF.launchAndWait(ApplicationNoLNF.class); } @Test public void testApplicationLookAndFeelResource() { LookAndFeel lnf = UIManager.getLookAndFeel(); // On Linux sestemLaF could not be native assertTrue("Look and Feel should be native", UIManager.getSystemLookAndFeelClassName().equals(lnf.getClass().getName())); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationPrivateCtorTest.java000066400000000000000000000023541151640306200300350ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; /** * * Verify that a privileged Application (one that's not running * in the secure sandbox) can have a private constructor. * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationPrivateCtorTest{ /* Support for private (not static) inner classes isn't provided * by Application.launch() because then we'd have to find a way to * pass an instance of the enclosing class along. */ private static class PrivateApplication extends WaitForStartupApplication { public boolean ok = false; private PrivateApplication() { ok = true; } } @Before public void methodSetup() { PrivateApplication.launchAndWait(PrivateApplication.class); } /** * Verify that a privileged app use an Application with a private * constructor. */ @Test public void testPrivateConstructor() { PrivateApplication app = Application.getInstance(PrivateApplication.class); assertTrue(app.ok); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationSystemLNFResourceTest.java000066400000000000000000000030071151640306200311230ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import javax.swing.*; /** * Checks that explicitly defining the Application.lookAndFeel resource * to be "system" causes the UIManager.lookAndFeel property to be * initialized to the sytem look and feel. * This test depends on resources/AppilcationSystemLNF.properties * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationSystemLNFResourceTest { /* Application.lookAndFeel resource is explicity defined to be "system". */ public static class ApplicationSystemLNF extends WaitForStartupApplication { } @Before public void methodSetup() { ApplicationSystemLNF.launchAndWait(ApplicationSystemLNF.class); } @Test public void testApplicationLookAndFeelResource() { ApplicationContext ctx = Application.getInstance(ApplicationSystemLNF.class).getContext(); String lnfResource = ctx.getResourceMap().getString("Application.lookAndFeel"); assertEquals("Application.lookAndFeel resource", "system", lnfResource); LookAndFeel lnf = UIManager.getLookAndFeel(); // On Linux sestemLaF could not be native assertTrue("Look and Feel should be native", UIManager.getSystemLookAndFeelClassName().equals(lnf.getClass().getName())); } } bsaf-1.9/src/test/java/org/jdesktop/application/ApplicationTest.java000066400000000000000000000340251151640306200256520ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.jdesktop.application.utils.PlatformType; import java.awt.event.ActionEvent; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import javax.swing.SwingUtilities; import org.jdesktop.application.utils.PlatformType; /** * ApplicationTest.java * * This test depends on ResourceBundles and an image file: *

 * resources/
 * resources/black1x1.png
 * 
* * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ApplicationTest { public static class SimpleApplication extends WaitForStartupApplication { public boolean startupOnEDT; @Override protected void startup() { startupOnEDT = SwingUtilities.isEventDispatchThread(); super.startup(); } @Action() public void simpleAppAction() { } } @Before public void methodSetup() { SimpleApplication.launchAndWait(SimpleApplication.class); } private ApplicationContext getApplicationContext() { return Application.getInstance(SimpleApplication.class).getContext(); } @Test public void testLaunch() { ApplicationContext ac = getApplicationContext(); Application app = ac.getApplication(); boolean isSimpleApp = app instanceof SimpleApplication; assertTrue("ApplicationContext.getApplication()", isSimpleApp); Class appClass = ac.getApplicationClass(); assertSame("ApplicationContext.getApplicationClass()", SimpleApplication.class, appClass); assertTrue("SimpleApplication.startup() ran to completion", ((SimpleApplication) app).isStarted()); assertTrue("SimpleApplication.startup() ran on the EDT", ((SimpleApplication) app).startupOnEDT); } @Test public void testGetResourceMap() { ApplicationContext ac = getApplicationContext(); String bundleBaseName = getClass().getPackage().getName() + ".resources."; /* Check the Application ResourceMap chain */ { ResourceMap appRM = ac.getResourceMap(); assertNotNull("Application ResourceMap", appRM); /* Application ResourceMap rm should have a null parent * and three bundles: */ String[] expectedBundleNames = { bundleBaseName + "SimpleApplication", bundleBaseName + "WaitForStartupApplication", bundleBaseName + "Application" }; String[] actualBundleNames = appRM.getBundleNames().toArray(new String[0]); assertArrayEquals(expectedBundleNames, actualBundleNames); } /* Check the ResourceMap for getClass() */ { ResourceMap rm = ac.getResourceMap(getClass()); assertNotNull(rm); assertEquals(bundleBaseName + "ApplicationTest", rm.getBundleNames().get(0)); } } /** * Verify that the platform resource was initialized to "osx" or "default" * and that it can be reset. */ @Test public void testPlatformResource() { ApplicationContext ctx = getApplicationContext(); ResourceManager rm = ctx.getResourceManager(); PlatformType platform = rm.getPlatform(); String osName = System.getProperty("os.name").toLowerCase(); boolean isPlatformSet = false; for (String ptr : platform.getPatterns()) { if (osName.startsWith(ptr)) { isPlatformSet = true; break; } } assertTrue(isPlatformSet); try { ctx.getResourceManager().setPlatform(PlatformType.FREE_BSD); fail("It is forbidden to change the platform of a resource map."); } catch (Exception ignore) { } String currentPlatform = rm.getResourceMap().getString("currentPlatform"); assertEquals(platform.getName(), currentPlatform); } private void checkActionName(String msg, javax.swing.Action action, String expectedValue) { String value = (String) (action.getValue(javax.swing.Action.NAME)); assertEquals(msg + ".getValue(javax.swing.Action.NAME)", expectedValue, value); } public static class SimpleController { @Action() public void simpleControllerAction() { } } /** * Verify that the ActionMap for SimplController.class contains an Action * for "simpleControllerAction" and a parent ActionMap, defined by * SimpleControllerApp, that contains "simpleControllerAppAction". */ @Test public void testGetActionMap() { SimpleController sc = new SimpleController(); /* There should be four ActionMaps in the parent chain for sc, based on: * 0 - SimpleController.class * 1 - SimpleApplication.class * 1 - WaitForStartupApplication.class * 2 - Application.class // parent of this one should be null */ Class[] actionsClasses = { SimpleController.class, SimpleApplication.class, WaitForStartupApplication.class, Application.class }; ApplicationActionMap actionMap = getApplicationContext().getActionMap(sc); int n = 0; for (Class actionsClass : actionsClasses) { assertNotNull("ActionMap " + actionsClass + " " + n, actionMap); assertSame("ActionMap actionsClass " + n, actionsClass, actionMap.getActionsClass()); actionMap = (ApplicationActionMap) actionMap.getParent(); } assertNull("Application actionMap parent", actionMap); actionMap = getApplicationContext().getActionMap(sc); String simpleControllerAction = "simpleControllerAction"; String gamString = "Application.getActionMap(simpleController)"; String gscaString = gamString + ".get(\"" + simpleControllerAction + "\")"; javax.swing.Action scAction = actionMap.get(simpleControllerAction); assertNotNull(gscaString, scAction); checkActionName(gscaString, scAction, simpleControllerAction); String simpleAppAction = "simpleAppAction"; String gsaaString = gamString + ".get(\"" + simpleAppAction + "\")"; javax.swing.Action saAction = actionMap.get(simpleAppAction); assertNotNull(gsaaString, saAction); checkActionName(gsaaString, saAction, simpleAppAction); String noSuchAction = "noSuchAction"; String gnsaString = gamString + ".get(\"" + noSuchAction + "\")"; javax.swing.Action nsAction = actionMap.get(noSuchAction); assertNull(gnsaString, nsAction); } /** * Check the ActionMap returned by ApplicationContext.getActionMap(), * i.e. the global ApplicationMap. */ @Test public void testGetAppActionMap() { /* In this case there should be just three ActionMaps in the * parent chain: * 0 - SimpleApplication.class * 1 - WaitForStartupApplication.class * 2 - Application.class // parent of this one should be null */ Class[] actionsClasses = { SimpleApplication.class, WaitForStartupApplication.class, Application.class }; ApplicationActionMap actionMap = getApplicationContext().getActionMap(); int n = 0; for (Class actionsClass : actionsClasses) { assertNotNull("ActionMap " + actionsClass + " " + n, actionMap); assertSame("ActionMap actionsClass " + n, actionsClass, actionMap.getActionsClass()); actionMap = (ApplicationActionMap) actionMap.getParent(); } assertNull("Application actionMap parent", actionMap); actionMap = getApplicationContext().getActionMap(); String simpleControllerAction = "simpleControllerAction"; String gamString = "Application.getActionMap()"; String gscaString = gamString + ".get(\"" + simpleControllerAction + "\")"; javax.swing.Action scAction = actionMap.get(simpleControllerAction); assertNull(gscaString, scAction); String simpleAppAction = "simpleAppAction"; String gsaaString = gamString + ".get(\"" + simpleAppAction + "\")"; javax.swing.Action saAction = actionMap.get(simpleAppAction); assertNotNull(gsaaString, saAction); checkActionName(gsaaString, saAction, simpleAppAction); } public static class StatefulController { private int n = 0; @Action() public void one() { n = 1; } @Action() public void two() { n = 2; } public int getN() { return n; } } private void checkSCActionMap(ApplicationActionMap appAM, Object actionsObject, Class actionsClass) { String msg = "ActionMap for " + actionsClass; assertNotNull(msg, appAM); assertSame(msg + ".getActionsObject()", actionsObject, appAM.getActionsObject()); assertSame(msg + ".getActionsClass()", actionsClass, appAM.getActionsClass()); assertNotNull(msg + ".getAction(\"one\")", appAM.get("one")); assertNotNull(msg + ".getAction(\"two\")", appAM.get("two")); } /** * Verify that getActionMap() caches per target actionsObject, not per @Actions * class. */ @Test public void testGetActionMapPerObject() { StatefulController sc1 = new StatefulController(); StatefulController sc2 = new StatefulController(); StatefulController sc3 = new StatefulController(); ApplicationContext ac = getApplicationContext(); ApplicationActionMap am1 = ac.getActionMap(sc1); ApplicationActionMap am2 = ac.getActionMap(sc2); ApplicationActionMap am3 = ac.getActionMap(sc3); checkSCActionMap(am1, sc1, StatefulController.class); checkSCActionMap(am2, sc2, StatefulController.class); checkSCActionMap(am3, sc3, StatefulController.class); String oneActionMapPerObject = "one ActionMap per actionsObject"; assertTrue(oneActionMapPerObject, am1 != am2); assertTrue(oneActionMapPerObject, am1 != am3); assertTrue(oneActionMapPerObject, am2 != am3); ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "not used"); am1.get("one").actionPerformed(event); am2.get("two").actionPerformed(event); assertEquals("StatefulController.getN(), after calling @Action sc1.one()", 1, sc1.getN()); assertEquals("StatefulController.getN(), after calling @Action sc2.two()", 2, sc2.getN()); assertEquals("StatefulController.getN(), no @Actions called", 0, sc3.getN()); } public static class ActionMapObject { int n; ActionMapObject(int n) { this.n = n; } @Action() public void anAction() { n = -1; } } /** * Verify that ActionMaps are GC'd when their target actionsObject is * no longer referred to and their actions are no longer in use (no * longer referred to). */ @Test public void testActionMapGC() { ApplicationContext ac = getApplicationContext(); List> refs = new ArrayList>(); for (int i = 0; i < 256; i++) { ActionMapObject amo = new ActionMapObject(i); refs.add(new WeakReference(amo)); ApplicationActionMap appAM = ac.getActionMap(amo); assertNotNull(appAM); assertNotNull(appAM.get("anAction")); assertSame(amo, appAM.getActionsObject()); assertEquals(i, amo.n); } /* GC should clear all of the references to ActionMapObjects because * they're no longer strongly reachable, i.e. the framework isn't * hanging on to them. */ System.gc(); for (Reference ref : refs) { assertNull("Reference to ApplictionActionMap actionsObject", ref.get()); } } /** * Verify that an Action's target is -not- GC'd if a reference to the * Action persists even after no direct references to the * actionsObject target exist. */ @Test public void testActionMapNoGC() { ApplicationContext ac = getApplicationContext(); List> refs = new ArrayList>(); List actions = new ArrayList(); for (int i = 0; i < 256; i++) { ActionMapObject amo = new ActionMapObject(i); refs.add(new WeakReference(amo)); ApplicationActionMap appAM = ac.getActionMap(amo); assertNotNull(appAM); actions.add((ApplicationAction) (appAM.get("anAction"))); assertSame(amo, appAM.getActionsObject()); assertEquals(i, amo.n); } /* GC should -not- clear all of the references to ActionMapObjects because * the ApplicationAction objects still refer (indirectly and strongy) * to them. */ System.gc(); for (Reference ref : refs) { assertNotNull("Reference to ApplictionActionMap actionsObject", ref.get()); } ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "not used"); int i = 0; for (ApplicationAction action : actions) { action.actionPerformed(event); ActionMapObject amo = refs.get(i).get(); assertEquals("after calling ActionMapObject.anAction()", -1, amo.n); } } } bsaf-1.9/src/test/java/org/jdesktop/application/BadSessionStateTest.java000066400000000000000000000057271151640306200264510ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import javax.swing.*; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.EventObject; /** * Verify that a corrupted session.xml file will not crash * a SingleFrameApplication. * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class BadSessionStateTest { public static class BadSessionStateApplication extends WaitForStartupSFA { private String sessionFile = "mainFrame.session.xml"; Object sessionObject = null; /* An incomplete XMLEncoder/Decoder file. */ private String badContents = "\n" + ""; @Override protected void startup() { try { OutputStream ost = getContext().getLocalStorage().openOutputFile(sessionFile); PrintStream pst = new PrintStream(ost); pst.print(badContents); pst.close(); } catch (IOException e) { throw new Error("unexpected IOException", e); } show(new JLabel("Hello World")); super.startup(); } @Override protected void shutdown() { super.shutdown(); /* At this point it should be possible to read the * session.xml file without problems, since it was * just rewritten. */ try { sessionObject = getContext().getLocalStorage().load(sessionFile); } catch (IOException e) { throw new Error("couldn't load " + sessionFile, e); } } // Don't call System.exit(), exitListeners, etc @Override public void exit(EventObject event) { shutdown(); } } @Before public void methodSetup() { System.err.println("This test generates logger warnings. Ignore them."); BadSessionStateApplication.launchAndWait(BadSessionStateApplication.class); } @Test public void testBadSessionState() throws Exception { final BadSessionStateApplication app = Application.getInstance(BadSessionStateApplication.class); assertTrue("BadSessionStateApplication started", app.isReady()); Runnable doExit = new Runnable() { @Override public void run() { app.exit(); } // override doesn't call System.exit }; SwingUtilities.invokeAndWait(doExit); assertNotNull("getLocalStorage().load(sessionFile)", app.sessionObject); } } bsaf-1.9/src/test/java/org/jdesktop/application/CustomPropertySupportTest.java000066400000000000000000000154041151640306200300230ustar00rootroot00000000000000/* * Copyright (C) 2010 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.session.PropertySupport; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import javax.swing.*; import java.awt.*; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.EventObject; import java.util.Map; /** * @author Illya Yalovyy */ public class CustomPropertySupportTest { private static final String LABEL_NAME = "testLabel"; private static final String FIRST_VALUE = "1"; private static final String SECOND_VALUE = "2"; public static class CustomSessionStateApplication extends WaitForStartupSFA { final JLabel label = new JLabel(); private final String sessionFile = "mainFrame.session.xml"; Object sessionObject = null; private String sessionContents = " " + " " + " " + " " + " mainFrame " + " " + " " + " " + " 0 " + " 28 " + " 112 " + " 43 " + " " + " " + " " + " " + " 0 " + " 0 " + " 1680 " + " 1050 " + " " + " " + " " + " 1 " + " " + " " + " " + " " + " " + LABEL_NAME + "/null.contentPane/null.layeredPane/JRootPane0/mainFrame " + " " + " " + " " + FIRST_VALUE + " " + " " + " " + " " + " " + " "; @Override protected void startup() { label.setName(LABEL_NAME); getContext().getSessionStorage().putProperty(JLabel.class, new LabelProperty()); try { OutputStream ost = getContext().getLocalStorage().openOutputFile(sessionFile); PrintStream pst = new PrintStream(ost); pst.print(sessionContents); pst.close(); } catch (IOException e) { throw new Error("unexpected IOException", e); } show(label); super.startup(); } @Override protected void shutdown() { super.shutdown(); try { sessionObject = getContext().getLocalStorage().load(sessionFile); } catch (IOException e) { throw new Error("couldn't load " + sessionFile, e); } } // Don't call System.exit(), exitListeners, etc @Override public void exit(EventObject event) { shutdown(); } } public static class LabelProperty implements PropertySupport { @Override public Object getSessionState(Component c) { if (c instanceof JLabel) { JLabel jLabel = (JLabel) c; return new LabelState(jLabel.getText()); } else { throw new IllegalArgumentException("invalid component"); } } @Override public void setSessionState(Component c, Object state) { if (c instanceof JLabel && state instanceof LabelState) { LabelState labelState = (LabelState) state; JLabel jLabel = (JLabel) c; jLabel.setText(labelState.getText()); } else { throw new IllegalArgumentException("invalid component"); } } } public static class LabelState { private String text; public LabelState() { } public LabelState(String text) { this.text = text; } public String getText() { return text; } public void setText(String text) { this.text = text; } } @Before public void methodSetup() { System.err.println("This test generates logger warnings. Ignore them."); CustomSessionStateApplication.launchAndWait(CustomSessionStateApplication.class); } @Test public void testCustomSessionState() throws Exception { final CustomSessionStateApplication app = Application.getInstance(CustomSessionStateApplication.class); assertTrue("CustomSessionStateApplication started", app.isReady()); assertTrue(FIRST_VALUE.equals(app.label.getText())); Runnable doExit = new Runnable() { @Override public void run() { app.label.setText(SECOND_VALUE); app.exit(); } // override doesn't call System.exit }; SwingUtilities.invokeAndWait(doExit); assertNotNull("getLocalStorage().load(sessionFile)", app.sessionObject); Map storage = (Map) app.sessionObject; String value = null; for (Map.Entry e : storage.entrySet()) { if (e.getKey().contains(LABEL_NAME)) { value = ((LabelState) e.getValue()).getText(); } } assertTrue(SECOND_VALUE.equals(value)); } } bsaf-1.9/src/test/java/org/jdesktop/application/DefaultInputBlockerDelayTest.java000066400000000000000000000064221151640306200302740ustar00rootroot00000000000000/* * Copyright (C) 2010 Eric Heumann * Use is subject to license terms. */ package org.jdesktop.application; import java.awt.event.ActionEvent; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * Tests that when an Action is called from a button in a menu of a popup menu, * and that Action has a WINDOW or APPLICATION blocking scope, the input is successfully * blocked to the window containing the menu. * * @author Eric M. Heumann * @version April 10, 2010 */ public class DefaultInputBlockerDelayTest { /** * Launch Application. */ @Before public void testSetup() { DIBDTestApp.launchAndWait(DIBDTestApp.class); } @Test public void testNew() throws Exception { final DIBDTestApp app = Application.getInstance(DIBDTestApp.class); final javax.swing.Action goAction = app.getContext().getActionMap().get("go"); goAction.actionPerformed(new ActionEvent(app.goItem, ActionEvent.ACTION_PERFORMED, "")); Thread.sleep(40); final boolean menuEnabled = app.getMainView().getMenuBar().isEnabled(); final boolean normalGlass = app.getMainFrame().getGlassPane() == app.normalGlass; final boolean glassVisible = app.getMainFrame().getGlassPane().isVisible(); Assert.assertFalse("main frame menu is not disabled", menuEnabled); Assert.assertFalse("main frame doesn't have input blocking glass pane", normalGlass); Assert.assertTrue("main frame glass pane is not showing", glassVisible); } /** * The SingleFrameApplication. * * @author Eric M. Heumann * @version April 10, 2010 * */ private static class DIBDTestApp extends WaitForStartupSFA { private JPanel normalGlass; private JMenu goMenu; private JMenuItem goItem; /** * Starts a Task that calls its Thread to sleep for a while. * The waiting dialog of the Tasks's input blocker has a very long delay. * * @param application * @return */ @SuppressWarnings("unused") @Action (block = Task.BlockingScope.APPLICATION) public Task go(final Application application) { return new DelayInputBlockDialogTask(application); } @Override protected void startup() { normalGlass = new JPanel(); goItem = new JMenuItem(getContext().getActionMap().get("go")); goMenu = new JMenu("Menu"); goMenu.add(goItem); final JMenuBar bar = new JMenuBar(); bar.add(goMenu); getMainView().setComponent(new JLabel("Input Blocking Ttest with Delayed Wait Dialog")); getMainView().setMenuBar(bar); getMainView().getFrame().setGlassPane(normalGlass); show(getMainView()); } @Override public void ready() { super.ready(); } /** * @author Eric M. Heumann * @version April 10, 2010 * */ class DelayInputBlockDialogTask extends Task { public DelayInputBlockDialogTask(final Application application) { super(application); } @Override protected Void doInBackground() throws Exception { try { Thread.sleep(5000l); return null; } catch (final InterruptedException exc) { return null; } } } } } bsaf-1.9/src/test/java/org/jdesktop/application/EnabledPropertyInGermanLocaleTest.java000066400000000000000000000045121151640306200312450ustar00rootroot00000000000000package org.jdesktop.application; import static org.junit.Assert.assertNotNull; import org.junit.Before; import org.junit.Test; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.Locale; /** * @author Illya Yalovyy */ public class EnabledPropertyInGermanLocaleTest { public static class ActionMapObject { protected boolean interestingActionFlag = false; public static final String PROP_INTERESTINGACTIONFLAG = "interestingActionFlag"; public boolean isInterestingActionFlag() { return interestingActionFlag; } public void setInterestingActionFlag(boolean interestingActionFlag) { boolean oldInterestingActionFlag = this.interestingActionFlag; this.interestingActionFlag = interestingActionFlag; propertyChangeSupport.firePropertyChange(PROP_INTERESTINGACTIONFLAG, oldInterestingActionFlag, interestingActionFlag); } private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } @Action(enabledProperty = "interestingActionFlag") public void interestingAction() { System.out.println("Should do something useful!"); } } @Before public void methodSetup() { WaitForStartupApplication.launchAndWait(WaitForStartupApplication.class); } @Test public void testActionInGermanLocale() { Locale.setDefault(Locale.GERMAN); ApplicationContext ac = Application.getInstance().getContext(); ActionMapObject amo = new ActionMapObject(); assertNotNull(ac.getActionMap(amo).get("interestingAction")); } @Test public void testActionInTurkishLocale() { Locale.setDefault(new Locale("tr")); ApplicationContext ac = Application.getInstance().getContext(); ActionMapObject amo = new ActionMapObject(); assertNotNull(ac.getActionMap(amo).get("interestingAction")); } } bsaf-1.9/src/test/java/org/jdesktop/application/LocalStorageTest.java000066400000000000000000000116641151640306200257720ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Scanner; /** * Test the LocalStorage class. *

* This test relies on a System Property that defines the directory * into which LocalStorage test files are read/written. The property * is defined in nbproject/project.properties: *

 * test-sys-prop.LocalStorage.dir=${basedir}/${build.dir}/local-storage.tmp
 * 
* In other words, by default, files are written to the directory * whose relative name is "build/local-storage.tmp" * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class LocalStorageTest { private File localStorageDirectory; private ApplicationContext context = new ApplicationContext(); @Before public void methodSetup() { String dirString = System.getProperty("LocalStorage.dir"); if (dirString == null) { throw new Error("System property \"LocalStorage.dir\" not defined"); } File dir = new File(dirString); if (!dir.exists()) { String msg = "Creating LocalStorage tmp directory \"" + dir + "\""; System.out.println(msg); if (!dir.mkdir()) { throw new Error(msg + " -failed-"); } } else { String msg = "LocalStorage tmp directory: \"" + dir + "\""; System.out.println(msg); } if (!(dir.canRead() && dir.canWrite())) { String msg = "Can't access LocalStorage tmp directory \"" + dir + "\""; throw new Error(msg); } /* Initialize the LocalStorage directory here, to simplify debugging. */ localStorageDirectory = dir; context.getLocalStorage().setDirectory(localStorageDirectory); } public static class ABean { private boolean b = false; private String s = "not initialized"; public String getS() { return s; } public void setS(String s) { this.s = s; } public boolean isB() { return b; } public void setB(boolean b) { this.b = b; } } @Test public void testBasics() throws IOException { LocalStorage ls = context.getLocalStorage(); assertEquals("LocalStorage.getDirectory", localStorageDirectory, ls.getDirectory()); ABean aBean = new ABean(); aBean.setS("setS"); aBean.setB(true); String filename = "aBean.xml"; ls.save(aBean, filename); File file = new File(ls.getDirectory(), filename); assertTrue(filename + " exists", file.exists()); assertTrue(filename + " is readable", file.canRead()); Object o = ls.load(filename); File dir = localStorageDirectory; assertNotNull("Loaded " + dir + "/" + filename, o); assertTrue("Loaded " + dir + "/" + filename + " - ABean", o instanceof ABean); aBean = (ABean) o; assertEquals("aBean.getS()", "setS", aBean.getS()); assertEquals("aBean.getB()", true, aBean.isB()); ls.deleteFile(filename); assertTrue(filename + " was deleted", !file.exists()); } @Test public void testInputOutput() throws IOException { final LocalStorage localStorage = context.getLocalStorage(); final String f = "testFile.tmp"; try { localStorage.deleteFile(f); } catch (IOException e) { //ignore if the file does not exist } OutputStream out = localStorage.openOutputFile(f);//testing method openOutputStream out.write("start".getBytes()); out.close(); Scanner in = new Scanner(localStorage.openInputFile(f)); assertEquals("Test write/read", "start", in.nextLine()); in.close(); //test append out = localStorage.openOutputFile(f, true);//append to file out.write("appended".getBytes()); out.close(); in = new Scanner(localStorage.openInputFile(f)); assertEquals("Test write/read2", "startappended", in.nextLine()); in.close(); out = localStorage.openOutputFile(f, false);//no append out.write("start".getBytes()); out.close(); in = new Scanner(localStorage.openInputFile(f)); assertEquals("Test write/read3", "start", in.nextLine()); in.close(); try { //test delete localStorage.deleteFile(f); } catch (IOException e) { //ignore if the file does not exist } try { //file does not exists localStorage.openInputFile(f); throw new Error("Should throw IOexception - method deleteEntry does not work properly?"); } catch (IOException e) { //ignore - OK } } }bsaf-1.9/src/test/java/org/jdesktop/application/MnemonicTextTest.java000066400000000000000000000077661151640306200260350ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertEquals; import org.junit.Test; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; /** * Test the internal (package private) MnemonicText class. * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class MnemonicTextTest { /* This javax.swing.Action constants is only * defined in Mustang (1.6), see * http://download.java.net/jdk6/docs/api/javax/swing/Action.html */ private static final String DISPLAYED_MNEMONIC_INDEX_KEY = "SwingDisplayedMnemonicIndexKey"; private void checkAction(javax.swing.Action target, String text, int mnemonicKey, int mnemonicIndex) { assertEquals(text, target.getValue(javax.swing.Action.NAME)); if (mnemonicKey != KeyEvent.VK_UNDEFINED) { /* If no mnemonicKey marker is specified then these properties * aren't set at all. */ assertEquals(mnemonicKey, target.getValue(javax.swing.Action.MNEMONIC_KEY)); assertEquals(mnemonicIndex, target.getValue(DISPLAYED_MNEMONIC_INDEX_KEY)); } } private void checkButton(AbstractButton target, String text, int mnemonicKey, int mnemonicIndex) { assertEquals(text, target.getText()); assertEquals(mnemonicKey, target.getMnemonic()); assertEquals(mnemonicIndex, target.getDisplayedMnemonicIndex()); } private void checkLabel(JLabel target, String text, int mnemonicKey, int mnemonicIndex) { assertEquals(text, target.getText()); assertEquals(mnemonicKey, target.getDisplayedMnemonic()); assertEquals(mnemonicIndex, target.getDisplayedMnemonicIndex()); } private static class MnemonicData { String markedText; String text; int mnemonicKey = KeyEvent.VK_UNDEFINED; int mnemonicIndex = -1; MnemonicData(String text) { this.markedText = this.text = text; } MnemonicData(String markedText, String text, int mnemonicKey, int mnemonicIndex) { this.markedText = markedText; this.text = text; this.mnemonicKey = mnemonicKey; this.mnemonicIndex = mnemonicIndex; } } @Test public void testConfigure() { MnemonicData[] testData = { new MnemonicData(""), // text doesn't contain a valid mnemonic marker new MnemonicData("&"), // ... new MnemonicData("x"), new MnemonicData("xy"), new MnemonicData("xyz"), new MnemonicData("x&"), new MnemonicData("x& "), new MnemonicData("x&\t"), new MnemonicData("x & y"), new MnemonicData("'&'"), new MnemonicData("foo('&')"), new MnemonicData("&x & y", "x & y", KeyEvent.VK_X, 0), // *does* contain ... new MnemonicData("x & &y", "x & y", KeyEvent.VK_Y, 4), // ... new MnemonicData("&File", "File", KeyEvent.VK_F, 0), new MnemonicData("Save &As", "Save As", KeyEvent.VK_A, 5), }; JLabel l = new JLabel(); for (MnemonicData d : testData) { MnemonicText.configure(l, d.markedText); checkLabel(l, d.text, d.mnemonicKey, d.mnemonicIndex); } JButton b = new JButton(); for (MnemonicData d : testData) { MnemonicText.configure(b, d.markedText); checkButton(b, d.text, d.mnemonicKey, d.mnemonicIndex); } javax.swing.Action a = new AbstractAction("test") { public void actionPerformed(ActionEvent e) { } }; for (MnemonicData d : testData) { MnemonicText.configure(a, d.markedText); checkAction(a, d.text, d.mnemonicKey, d.mnemonicIndex); } } } bsaf-1.9/src/test/java/org/jdesktop/application/ProxyActionTest.java000066400000000000000000000255361151640306200256750ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.*; import org.junit.BeforeClass; import org.junit.Test; import javax.swing.*; import java.awt.event.ActionEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.List; import java.util.ResourceBundle; /* * This test depends on the resources/ProxyActions.properties ResourceBundle * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ProxyActionTest { @ProxyActions({"simple"}) public static class SimpleApplication extends WaitForStartupApplication { public boolean startupOnEDT; @Override protected void startup() { super.startup(); startupOnEDT = SwingUtilities.isEventDispatchThread(); } @Action() public void simpleAppAction() { } } private ApplicationContext getContext() { return Application.getInstance(SimpleApplication.class).getContext(); } @BeforeClass public static void unitSetup() { SimpleApplication.launchAndWait(SimpleApplication.class); } @ProxyActions({"testAction"}) public static class ProxyTestActions { } public static class TargetActions { private boolean actionEnabled = true; int testActionCounter = 0; @Action(enabledProperty = "testActionEnabled") public void testAction() { testActionCounter += 1; } public boolean isTestActionEnabled() { return actionEnabled; } public void setTestActionEnabled(boolean newValue) { boolean oldValue = actionEnabled; if (oldValue != newValue) { this.actionEnabled = newValue; pcs.firePropertyChange("testActionEnabled", oldValue, newValue); } } private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } } private ResourceMap resourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.ProxyTestActions"; /* If the ResourceBundle can't be found, getBundle() will throw an exception. * ResourceMap isn't supposed to complain if it can't find a * ResourceBundle however the tests that follow expect * Basic ResourceBundle to exist. */ ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, bundleBaseName); return resourceMap; } private ApplicationActionMap proxyActionMap() { Object actionsObject = new ProxyTestActions(); return new ApplicationActionMap(getContext(), ProxyTestActions.class, actionsObject, resourceMap()); } private ApplicationActionMap targetActionMap() { Object actionsObject = new TargetActions(); return new ApplicationActionMap(getContext(), TargetActions.class, actionsObject, resourceMap()); } private void checkUnboundProxyAMAction(ApplicationActionMap appAM, String actionKey, String actionName) { javax.swing.Action action = appAM.get(actionKey); String pamG = "proxyActionMap().get(\"" + actionKey + "\")"; assertTrue(pamG, action instanceof ApplicationAction); assertFalse(pamG + ".isEnabled()", action.isEnabled()); ApplicationAction appAction = (ApplicationAction) action; assertEquals(actionName, appAction.getValue(javax.swing.Action.NAME)); assertNull(pamG + ".getProxyBinding()", appAction.getProxy()); } // see resources/ProxyAction.properties @Test public void testBasics() { ApplicationActionMap appAM = proxyActionMap(); assertEquals("appAM.getProxyActions.size()", 1, appAM.getProxyActions().size()); checkUnboundProxyAMAction(appAM, "testAction", "testAction"); assertTrue(targetActionMap().get("testAction") instanceof ApplicationAction); } private static String getShortDescription(javax.swing.Action a) { return (String) (a.getValue(javax.swing.Action.SHORT_DESCRIPTION)); } private static void setShortDescription(javax.swing.Action a, String s) { a.putValue(javax.swing.Action.SHORT_DESCRIPTION, s); } private static String getLongDescription(javax.swing.Action a) { return (String) (a.getValue(javax.swing.Action.LONG_DESCRIPTION)); } private static void setLongDescription(javax.swing.Action a, String s) { a.putValue(javax.swing.Action.LONG_DESCRIPTION, s); } private void checkActionProperties(String msg, javax.swing.Action action, boolean enabled, String shortD, String longD) { assertEquals(msg + " isEnabled()", enabled, action.isEnabled()); assertEquals(msg + " shortDescription", shortD, getShortDescription(action)); assertEquals(msg + " longDescription", longD, getLongDescription(action)); } @Test public void testBinding() { ApplicationActionMap proxyAM = proxyActionMap(); ApplicationActionMap targetAM = targetActionMap(); ApplicationAction targetAction = (ApplicationAction) (targetAM.get("testAction")); targetAction.setEnabled(false); setShortDescription(targetAction, "shortDescription"); setLongDescription(targetAction, "longDescription"); String[] proxyActionKeys = {"testAction"}; /* Bind each proxyAction to targetAction and verify that proxyAction's * enabled, long/shortDescription properties change */ checkActionProperties("testAction", targetAction, false, "shortDescription", "longDescription"); for (String proxyActionKey : proxyActionKeys) { ApplicationAction proxyAction = (ApplicationAction) (proxyAM.get(proxyActionKey)); proxyAction.setProxy(targetAction); checkActionProperties("ProxyAction " + proxyActionKey, proxyAction, false, "shortDescription", "longDescription"); } // Check for "How could this happen?" failure mode: checkActionProperties("testAction", targetAction, false, "shortDescription", "longDescription"); /* Change the target action's properties and verify that the * proxyActions change too. */ targetAction.setEnabled(true); setShortDescription(targetAction, "shortD"); setLongDescription(targetAction, "longD"); checkActionProperties("testAction", targetAction, true, "shortD", "longD"); for (String proxyActionKey : proxyActionKeys) { ApplicationAction proxyAction = (ApplicationAction) (proxyAM.get(proxyActionKey)); checkActionProperties("ProxyAction " + proxyActionKey, proxyAction, true, "shortD", "longD"); } // Check for "How could this happen?" failure mode: checkActionProperties("testAction", targetAction, true, "shortD", "longD"); /* Verify that calling proxyAction.actionPerformed() calls the targetAction's * @Action method: TargetActions.testAction(); */ TargetActions targetActions = (TargetActions) (targetAM.getActionsObject()); targetActions.testActionCounter = 0; ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "not used"); for (String proxyActionKey : proxyActionKeys) { ApplicationAction proxyAction = (ApplicationAction) (proxyAM.get(proxyActionKey)); proxyAction.actionPerformed(event); } assertEquals(proxyActionKeys.length, targetActions.testActionCounter); /* Unbind the proxyActions and verify that they're disabled, and that they * no longer track the targetAction. */ for (String proxyActionKey : proxyActionKeys) { ApplicationAction proxyAction = (ApplicationAction) (proxyAM.get(proxyActionKey)); proxyAction.setProxy(null); assertFalse(proxyAction.isEnabled()); } targetAction.setEnabled(true); // shouldn't affect - now unbound - proxyActions targetActions.testActionCounter = 0; assertTrue(targetAction.isEnabled()); for (String proxyActionKey : proxyActionKeys) { ApplicationAction proxyAction = (ApplicationAction) (proxyAM.get(proxyActionKey)); proxyAction.actionPerformed(event); assertFalse(proxyAction.isEnabled()); } assertEquals(0, targetActions.testActionCounter); } private void checkProxyAction(String name, List proxyActions) { boolean matchFound = false; for (ApplicationAction pa : proxyActions) { if (pa.getName().equals(name)) { matchFound = true; break; } } assertTrue("expected proxyAction named \"" + name + "\"", matchFound); } /** * Verify that ApplicationActionMap#getProxyActions() returns * a list of proxyActions that include the cut/copy/paste/delete * proxy actions defined in the Application class, as well as the * ones added by our app subclass and by ProxyTestActions. */ @Test public void testGetProxyActions() { ApplicationContext ac = Application.getInstance(SimpleApplication.class).getContext(); // check the actions in the global SimpleApplication action map List globalProxyActions = ac.getActionMap().getProxyActions(); String[] globalProxyActionNames = { "simple", "cut", "copy", "paste", "delete" }; for (String proxyActionName : globalProxyActionNames) { checkProxyAction(proxyActionName, globalProxyActions); } // check the actions in SimpleApplication's ProxyTestActions action map Object pta = new ProxyTestActions(); List ptaProxyActions = ac.getActionMap(pta.getClass(), pta).getProxyActions(); String[] ptaProxyActionNames = { "testAction", "simple", "cut", "copy", "paste", "delete" }; for (String proxyActionName : ptaProxyActionNames) { checkProxyAction(proxyActionName, ptaProxyActions); } } } bsaf-1.9/src/test/java/org/jdesktop/application/ResourceManagerTest.java000066400000000000000000000013661151640306200264730ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import org.junit.Test; /** * [TBD] * * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ResourceManagerTest { class TestResourceManager extends ResourceManager { TestResourceManager() { super(new ApplicationContext()); } } TestResourceManager resourceManager() { return new TestResourceManager(); } @Test public void testBasics() { TestResourceManager manager = resourceManager(); ResourceMap rm = manager.getResourceMap(getClass()); // [TBD] } } bsaf-1.9/src/test/java/org/jdesktop/application/ResourceMapTest.java000066400000000000000000001013021151640306200256250ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import org.jdesktop.application.ResourceConverter.ResourceConverterException; import static org.junit.Assert.*; import org.junit.Test; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.KeyEvent; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.MessageFormat; import java.util.*; /* * ResourceMapTest.java - JUnit based test * * This test depends on ResourceBundles and an image file: *
 * resources/Basic.properties
 * resources/Parent.properties
 * resources/Child.properties
 * resources/Injection.properties
 * resources/black1x1.png
 * resources/ExprEval.properties
 * resources/Basic_zz.properties
 * resources/Basic_WindowsXP.properties
 * resources/Basic_WindowsXP_zz.properties
 * 
* * @author Hans Muller (Hans.Muller@Sun.COM) */ public class ResourceMapTest { public static final double EPSILON_DOUBLE = 0.0000000000000001; public static final float EPSILON_FLOAT = 0.0000001f; @Test public void testSupportedResourceTypes() { Class[] supportedTypes = { Boolean.class, boolean.class, Integer.class, int.class, Long.class, long.class, Short.class, short.class, Byte.class, byte.class, Float.class, float.class, Double.class, double.class, Icon.class, ImageIcon.class, Color.class, Font.class, KeyStroke.class, MessageFormat.class, Point.class, Dimension.class, Rectangle.class, Insets.class, EmptyBorder.class, URL.class, URI.class }; ClassLoader classLoader = getClass().getClassLoader(); ResourceMap rm = new ResourceMap(null, classLoader, "noBundle"); for (Class type : supportedTypes) { String msg = "default supported ResourceConverter type: " + type.getName(); assertNotNull(msg, ResourceConverter.forType(type)); } } private static class TestColor extends Color { public final String key; public TestColor(String key, int r, int g, int b) { super(r, g, b); this.key = key; } public TestColor(String key, int a, int r, int g, int b) { super(r, g, b, a); this.key = key; } } private void checkBlack1x1Icon(String accessPathString, Icon icon) { assertNotNull(accessPathString, icon); assertEquals(accessPathString + ".getIconWidth()", icon.getIconWidth(), 1); assertEquals(accessPathString + ".getIconHeight()", icon.getIconHeight(), 1); } private ResourceMap basicResourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.Basic"; /* If the ResourceBundle can't be found, getBundle() will throw an exception. * ResourceMap isn't supposed to complain if it can't find a * ResourceBundle however the tests that follow expect * Basic ResourceBundle to exist. */ ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, bundleBaseName); return resourceMap; } private void checkBasicResourceMap(ResourceMap rm) { assertFalse("containsKey(\"noSuchResource\")", rm.containsKey("noSuchResource")); assertNull("getObject(\"noSuchResource\")", rm.getObject("noSuchResource", Object.class)); String aStringResource = "aStringResource"; assertTrue("containsKey(\"aStringResource\")", rm.containsKey(aStringResource)); assertEquals("getString(\"aStringResource\")", aStringResource, rm.getString(aStringResource)); String testString = (String) (rm.getObject(aStringResource, String.class)); assertEquals("getObject(\"aStringResource\")", aStringResource, testString); String aHelloMessage = "aHelloMessage"; assertEquals("getString(\"aHelloMessage\")", "Hello World", rm.getString("aHelloMessage", "World")); String integer123 = "integer123"; String short123 = "short123"; String long123 = "long123"; String byte123 = "byte123"; String float123 = "float123"; String double123 = "double123"; assertEquals("getInteger(\"" + integer123 + "\")", rm.getInteger(integer123).intValue(), 123); assertEquals("getShort(\"" + short123 + "\")", rm.getShort(short123).shortValue(), 123); assertEquals("getLong(\"" + long123 + "\")", rm.getLong(long123).longValue(), 123L); assertEquals("getByte(\"" + byte123 + "\")", rm.getByte(byte123).byteValue(), 123); assertEquals("getFloat(\"" + float123 + "\")", rm.getFloat(float123).floatValue(), 123.0f,EPSILON_FLOAT); assertEquals("getDouble(\"" + double123 + "\")", rm.getDouble(double123).doubleValue(), 123.0,EPSILON_DOUBLE); String integer0 = "integer0"; String short0 = "short0"; String long0 = "long0"; String byte0 = "byte0"; assertEquals("getInteger(\"" + integer0 + "\")", rm.getInteger(integer0).intValue(), 0); assertEquals("getShort(\"" + short0 + "\")", rm.getShort(short0).shortValue(), 0); assertEquals("getLong(\"" + long0 + "\")", rm.getLong(long0).longValue(), 0L); assertEquals("getByte(\"" + byte0 + "\")", rm.getByte(byte0).byteValue(), 0); String integerNegative1 = "integerNegative1"; String shortNegative1 = "shortNegative1"; String longNegative1 = "longNegative1"; String byteNegative1 = "byteNegative1"; assertEquals("getInteger(\"" + integerNegative1 + "\")", rm.getInteger(integerNegative1).intValue(), -1); assertEquals("getShort(\"" + shortNegative1 + "\")", rm.getShort(shortNegative1).shortValue(), -1); assertEquals("getLong(\"" + longNegative1 + "\")", rm.getLong(longNegative1).longValue(), -1L); assertEquals("getByte(\"" + byteNegative1 + "\")", rm.getByte(byteNegative1).byteValue(), -1); try { rm.getInteger("badlyFormattedInteger"); fail("rm.getInteger(\"badlyFormattedInteger\") expected throw"); } catch (ResourceMap.LookupException e) { } /* ResourceConverterException has a little bit of logic to limit * limit the length of the message string. Check the output * log (also checking programatically here). */ String badlyFormattedGiantInteger = "badlyFormattedGiantInteger"; try { rm.getInteger(badlyFormattedGiantInteger); fail("rm.getString(\"" + badlyFormattedGiantInteger + "\") expected throw"); } catch (ResourceMap.LookupException e) { } ResourceConverter sc = ResourceConverter.forType(Integer.class); String badGiantInteger = rm.getString(badlyFormattedGiantInteger); try { sc.parseString(badGiantInteger, rm); } catch (ResourceConverterException e) { boolean f = e.getMessage().length() < badGiantInteger.length(); assertTrue("ResourceConverterException message should be truncated", f); } String[] trueKeys = {"booleanTrue", "booleanTRUE", "booleanYes", "booleanOn"}; for (String key : trueKeys) { assertTrue("getBoolean(\"" + key + "\")", rm.getBoolean(key)); } String[] falseKeys = {"booleanFalse", "booleanFALSE", "booleanNo", "booleanOff"}; for (String key : falseKeys) { assertFalse("getBoolean(\"" + key + "\")", rm.getBoolean(key)); } // Color formats are: #RRGGBB", "#AARRGGBB", "R, G, B", "R, G, B, A" TestColor[] testColors = { new TestColor("color123", 1, 2, 3), new TestColor("color345", 3, 4, 5), new TestColor("color567", 5, 6, 7), new TestColor("color5678", 8, 5, 6, 7), // TestColor(a, r, g, b) new TestColor("color556677", 0x55, 0x66, 0x77), new TestColor("color55667788", 0x55, 0x66, 0x77, 0x88) }; for (TestColor testColor : testColors) { Color c1 = rm.getColor(testColor.key); Color c2 = rm.getColor(testColor.key); assertEquals("getColor(\"" + testColor.key + "\") c1", c1, testColor); assertEquals("getColor(\"" + testColor.key + "\") c2", c2, testColor); assertTrue("getColor(\"" + testColor.key + "\") c1==c2", c2 == c2); } /* Verify that looking up an integer resource that * names a Color resourced that has already been loaded * triggers a LookupException. */ Color c123 = rm.getColor("color123"); assertEquals("unexpected failure", c123, new Color(1, 2, 3)); try { rm.getInteger("color123"); fail("rm.getInteger(\"color123\") expected throw"); } catch (ResourceMap.LookupException e) { } String fontArialPLAIN12 = "fontArialPLAIN12"; Font f1 = rm.getFont(fontArialPLAIN12); Font f2 = rm.getFont(fontArialPLAIN12); assertNotNull("getFont(\"" + fontArialPLAIN12 + "\")", f1); assertEquals("getFont(\"" + fontArialPLAIN12 + "\") f1.equals(f2)", f1, f2); // Note that ResourceMap is not required to cache values, so it's not // necessarily true that f1==f2 ImageIcon imageIcon = rm.getImageIcon("black1x1Icon"); checkBlack1x1Icon("getImageIcon(\"black1x1Icon\")", imageIcon); Icon icon = rm.getIcon("black1x1Icon"); checkBlack1x1Icon("getIcon(\"black1x1Icon\")", icon); // Verify that absolute pathnames work ImageIcon absIcon = rm.getImageIcon("AbsoluteBlack1x1Icon"); checkBlack1x1Icon("getImageIcon(\"AbsoluteBlack1x1Icon\")", absIcon); // Check the support for Point, Dimension, Rectangle, Insets, EmptyBorder { String[] resourceKeys = { "point12", "pointMinus12", "dimension56", "dimension89", "rectangle1234", "rectangleMinus5309", "insets7890", "emptyBorder8675", "emptyBorder0000" }; Object[] resourceValues = { new Point(1, 2), new Point(-1, -2), new Dimension(5, 6), new Dimension(8, 9), new Rectangle(1, 2, 3, 4), new Rectangle(-5, -3, 0, 9), new Insets(7, 8, 9, 0), new EmptyBorder(8, 6, 7, 5), new EmptyBorder(0, 0, 0, 0) }; for (int i = 0; i < resourceKeys.length; i++) { String key = resourceKeys[i]; Object expectedValue = resourceValues[i]; Class type = expectedValue.getClass(); String msg = String.format("get%s(\"%s\")", type.getSimpleName(), key); if (EmptyBorder.class.isAssignableFrom(type)) { EmptyBorder border = (EmptyBorder) rm.getObject(key, type); EmptyBorder expectedBorder = (EmptyBorder) expectedValue; assertEquals(msg, expectedBorder.getBorderInsets(), border.getBorderInsets()); } else { assertEquals(msg, expectedValue, rm.getObject(key, type)); } } } // Check the support for URLs URL url = null; try { url = new URL("http://www.sun.com"); } catch (MalformedURLException e) { throw new Error(e); } assertEquals(url, rm.getObject("sunURL", URL.class)); // Check the support for URIs URI uri = null; try { uri = new URI("mailto:users@appframework.dev.java.net"); } catch (URISyntaxException e) { throw new Error(e); } assertEquals(uri, rm.getObject("mailURI", URI.class)); } /** * Verify that the we can load a ResourceBundle and get/cache * all its resources through a ResourceMap. */ @Test public void testBasics() { checkBasicResourceMap(basicResourceMap()); } /** * Verify that an empty ResourceMap whose parent is basicResourcMap * looks the same as (just) basicResourceMap. */ @Test public void testParentResourceMap() { ClassLoader classLoader = getClass().getClassLoader(); checkBasicResourceMap(new ResourceMap(basicResourceMap(), classLoader, "noSuchBundle")); } @Test public void testMultipleBundleNames() { String b2 = getClass().getPackage().getName() + ".resources.MultiBundle"; String b1 = b2 + "_AllLocales"; String b0 = b2 + "_WindowsXP"; ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, b0, b1, b2); assertEquals(3, resourceMap.getBundleNames().size()); assertEquals(b0, resourceMap.getBundleNames().get(0)); assertEquals(b1, resourceMap.getBundleNames().get(1)); assertEquals(b2, resourceMap.getBundleNames().get(2)); assertEquals("OK", resourceMap.getString("noOverride")); assertEquals("AllLocales OK", resourceMap.getString("AllLocales_override")); assertEquals("WindowsXP OK", resourceMap.getString("WindowsXP_override")); assertEquals("XP", resourceMap.getString("WindowsXP_only")); assertEquals("AllLocales", resourceMap.getString("AllLocales_only")); } private static class TestType { public final String value; TestType(String value) { this.value = value; } } private static class TestResourceConverter extends ResourceConverter { TestResourceConverter() { super(TestType.class); } @Override public Object parseString(String s, ResourceMap ignore) { return new TestType(s); } } @Test public void testRegisterResourceConverter() { ResourceMap rm = basicResourceMap(); ResourceConverter.register(new TestResourceConverter()); assertNotNull(ResourceConverter.forType(TestType.class)); String testAddResourceConverter = "testAddResourceConverter"; assertEquals(testAddResourceConverter, rm.getString(testAddResourceConverter)); Object tt = rm.getObject(testAddResourceConverter, TestType.class); assertNotNull("getString(\"" + testAddResourceConverter + "\")", tt); Boolean b = ((TestType) tt).value.equals(testAddResourceConverter); assertTrue("getString(\"" + testAddResourceConverter + "\").value", b); } private ResourceMap childResourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.Child"; ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); return new ResourceMap(null, classLoader, bundleBaseName); } @Test public void testKeySetBasics() { Set keys = childResourceMap().keySet(); assertNotNull("childResourceMap.keySet()", keys); String[] expectedKeys = {"p1", "p2", "p3", "p4", "p5"}; assertEquals("childResourceMap().keySet().size()", expectedKeys.length, keys.size()); // TBD check that childResourceMap is read-only Set shouldBeEmpty = new HashSet(keys); assertTrue("Should remove all keys", shouldBeEmpty.removeAll(Arrays.asList(expectedKeys))); assertEquals("Should (now) be an empty set", shouldBeEmpty.size(), 0); // TBD check that the keys iterator works // and check again with empty resourcemap with child as parent } private ResourceMap parentChildResourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.Parent"; ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); return new ResourceMap(childResourceMap(), classLoader, bundleBaseName); } @Test public void testKeySetParentChild() { Set keys = parentChildResourceMap().keySet(); assertNotNull("parentChildResourceMap.keySet()", keys); String[] expectedKeys = {"p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10"}; assertEquals("parentChildResourceMap().keySet().size()", expectedKeys.length, keys.size()); Set shouldBeEmpty = new HashSet(keys); assertTrue("Should remove all keys", shouldBeEmpty.removeAll(Arrays.asList(expectedKeys))); assertEquals("Should (now) be an empty set", shouldBeEmpty.size(), 0); } private ResourceMap injectionResourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.Injection"; ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, bundleBaseName); return resourceMap; } @Test public void testInjectComponentProperties() { JLabel label = new JLabel(); label.setName("testLabel"); ResourceMap rm = injectionResourceMap(); rm.injectComponent(label); assertEquals("label.getText()", "Hello World", label.getText()); assertEquals("label.getAlignmentX()", 0.5f, label.getAlignmentX(), EPSILON_FLOAT); assertFalse("label.getEnabled()", label.isEnabled()); Color expectedColor = new Color(0x55, 0x66, 0x77); assertEquals("label.getBackground()", expectedColor, label.getBackground()); Font font = label.getFont(); assertNotNull("label.getFont()", font); Icon icon = label.getIcon(); assertNotNull("label.getIcon()", icon); assertEquals("label.getIcon().getIconWidth()", 1, icon.getIconWidth()); assertEquals("label.getIcon().getIconHeight()", 1, icon.getIconHeight()); assertEquals("label.getDisplayedMnemonicIndex()", 3, label.getDisplayedMnemonicIndex()); JLabel labelNullText = new JLabel("Hello World"); labelNullText.setName("labelNullText"); assertNotNull(labelNullText.getText()); rm.injectComponent(labelNullText); assertNull(labelNullText.getText()); } @Test public void testInjectComponentHierarchyProperties() { JFrame mainFrame = new JFrame(); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu(); JMenuItem item = new JMenuItem(); JPanel parentPanel = new JPanel(); JTextField textField1 = new JTextField(); JTextField textField2 = new JTextField(); JPanel childPanel = new JPanel(); JLabel mnemonicLabel1 = new JLabel(); JLabel mnemonicLabel2 = new JLabel(); JButton button = new JButton(); mainFrame.setName("mainFrame"); menuBar.setName("menuBar"); menu.setName("Edit.menu"); item.setName("item"); parentPanel.setName("parentPanel"); childPanel.setName("childPanel"); textField1.setName("textField1"); textField2.setName("textField2"); mnemonicLabel1.setName("mnemonicLabel1"); mnemonicLabel2.setName("mnemonicLabel2"); button.setName("button"); mainFrame.add(parentPanel); mainFrame.setJMenuBar(menuBar); menuBar.add(menu); menu.add(item); parentPanel.add(childPanel); parentPanel.add(textField1); parentPanel.add(mnemonicLabel1); childPanel.add(new JScrollPane(textField2)); childPanel.add(mnemonicLabel2); childPanel.add(button); injectionResourceMap().injectComponents(mainFrame); assertEquals("mainFrame.getTitle()", "Frame title", mainFrame.getTitle()); Image image = mainFrame.getIconImage(); assertNotNull("mainFrame.getIconImage()", image); assertEquals("mainFrame.getIconImage().getWidth()", image.getWidth(null), 1); assertEquals("mainFrame.getIconImage().getHeight()", image.getHeight(null), 1); assertEquals("menu.getMnemonic()", 68, menu.getMnemonic()); assertEquals("item.getText()", "Item text", item.getText()); assertEquals("item.getMnemonic()", 69, item.getMnemonic()); assertEquals("textField1.getText()", "textField1", textField1.getText()); assertEquals("textField2.getText()", "textField2", textField2.getText()); assertEquals("textField2.getBackground()", new Color(0, 0, 0), textField2.getBackground()); Color parentBackground = new Color(0x55, 0x00, 0x00); Color parentForeground = new Color(0x00, 0x66, 0x00); Color childForeground = new Color(0x00, 0x00, 0x77); Color childBackground = new Color(0x00, 0x00, 0x00); assertEquals("parentPanel.getBackground()", parentBackground, parentPanel.getBackground()); assertEquals("parentPanel.getForeground()", parentForeground, parentPanel.getForeground()); assertEquals("childPanel.getBackground()", childBackground, childPanel.getBackground()); assertEquals("childPanel.getForeground()", childForeground, childPanel.getForeground()); assertEquals("mnemonic", mnemonicLabel1.getText()); assertEquals("Save As", mnemonicLabel2.getText()); assertEquals("Exit", button.getText()); assertEquals(0, mnemonicLabel1.getDisplayedMnemonicIndex()); assertEquals(5, mnemonicLabel2.getDisplayedMnemonicIndex()); assertEquals(1, button.getDisplayedMnemonicIndex()); assertEquals(KeyEvent.VK_M, mnemonicLabel1.getDisplayedMnemonic()); assertEquals(KeyEvent.VK_A, mnemonicLabel2.getDisplayedMnemonic()); assertEquals(KeyEvent.VK_X, button.getMnemonic()); } private static class TestResourceAnnotation { @Resource private String stringField; @Resource protected int intField; @Resource public Color colorField; @Resource boolean booleanField; @Resource static String staticField; @Resource(key = "F1") private String stringF1Field; @Resource(key = "TestResourceAnnotation.objectField") Object objectField; @Resource int[] numbers = new int[12]; @Resource Icon[] icons = new Icon[2]; @Resource KeyStroke shortcutX; @Resource KeyStroke shortcutShiftX; } @Test public void testResourceAnnotation() { ResourceMap rm = basicResourceMap(); TestResourceAnnotation target = new TestResourceAnnotation(); for (int i = 0; i < target.numbers.length; i++) { target.numbers[i] = -1; } rm.injectFields(target); assertEquals("@Resource private String stringField;", "stringField", target.stringField); assertEquals("@Resource protected int intField;", 123, target.intField); assertEquals("@Resource public Color colorField;", new Color(0, 1, 2), target.colorField); assertTrue("@Resource boolean booleanField;", target.booleanField); assertEquals("@Resource static String staticField;", "staticField", target.staticField); assertEquals("@Resource(key=\"F1\") private String stringF1Field;", "stringF1Field", target.stringF1Field); assertEquals("@Resource(key=\"TestResourceAnnotation.objectField\")", "objectField", target.objectField); for (int i = 0; i < target.numbers.length; i++) { if ((i == 2) || (i == 3) || (i == 11)) { assertEquals("@Resource int[] numbers[" + i + "]", i, target.numbers[i]); } else { assertEquals("@Resource int[] numbers[" + i + "]", -1, target.numbers[i]); } } checkBlack1x1Icon("@Resource icons[0]", target.icons[0]); checkBlack1x1Icon("@Resrouce icons[1]", target.icons[1]); int k = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); String shortcut = (k == Event.META_MASK) ? "meta" : "control"; assertEquals(KeyStroke.getKeyStroke(shortcut + " X"), target.shortcutX); assertEquals(KeyStroke.getKeyStroke(shortcut + " shift X"), target.shortcutShiftX); } private ResourceMap expressionEvaluationResourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.ExprEval"; ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, bundleBaseName); return resourceMap; } @Test public void testExpressionEvaluation() { ResourceMap rm = expressionEvaluationResourceMap(); assertEquals("rm.getString(hello)", "Hello", rm.getString("hello")); assertEquals("rm.getString(world)", "World", rm.getString("world")); assertEquals("rm.getString(place)", "World", rm.getString("place")); String hws = "Hello World"; assertEquals("rm.getString(helloworld0)", hws, rm.getString("helloworld0")); assertEquals("rm.getString(helloworld1)", hws, rm.getString("helloworld1")); assertEquals("rm.getString(helloworld2)", hws, rm.getString("helloworld2")); assertEquals("rm.getString(helloworld3)", hws, rm.getString("helloworld3")); assertEquals("rm.getString(escHelloWorld)", "${hello} ${world}", rm.getString("escHelloWorld")); assertEquals("rm.getString(escOnly)", "${", rm.getString("escOnly")); try { rm.getString("noSuchVariableKey"); fail("rm.getString(\"noSuchVariableKey\") expected throw"); } catch (ResourceMap.LookupException e) { } try { rm.getString("noClosingBrace"); fail("rm.getString(\"noClosingBrace\") expected throw"); } catch (ResourceMap.LookupException e) { } assertNull("getString(\"justNull\")", rm.getString("justNull")); assertTrue("containsKey(\"justNull\")", rm.containsKey("justNull")); } @Test public void testResourceMapSubclass() { final HashMap myMap = new HashMap(); myMap.put("hello", "hello"); myMap.put("world", "world"); myMap.put("hello world", "${hello} ${world}"); myMap.put("HelloX", "Hello %s"); ResourceMap rm = new ResourceMap(null, getClass().getClassLoader(), "no bundles") { protected Set getResourceKeySet() { return myMap.keySet(); } protected boolean containsResourceKey(String key) { return myMap.containsKey(key); } protected Object getResource(String key) { return myMap.get(key); } protected void putResource(String key, Object value) { myMap.put(key, value); } }; assertTrue(rm.containsKey("hello")); assertTrue(rm.containsKey("world")); assertTrue(rm.containsKey("hello world")); assertEquals("hello", rm.getString("hello")); assertEquals("world", rm.getString("world")); assertEquals("hello world", rm.getString("hello world")); assertEquals("Hello World", rm.getString("HelloX", "World")); } private ResourceMap platformResourceMap() { // see basicResourceMap String bundleBaseName = getClass().getPackage().getName() + ".resources.Basic"; String osBundleName = bundleBaseName + "_WindowsXP"; /* If the ResourceBundle can't be found, getBundle() will throw an exception. * ResourceMap isn't supposed to complain if it can't find a * ResourceBundle however the tests that follow expect * Basic ResourceBundle to exist. */ ResourceBundle.getBundle(bundleBaseName); ResourceBundle.getBundle(osBundleName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, osBundleName, bundleBaseName); return resourceMap; } @Test public void testPlatformResourceMap() { ResourceMap rm = platformResourceMap(); checkBasicResourceMap(rm); assertEquals("notPlatformSpecific", rm.getString("notPlatformSpecific")); assertEquals("platformSpecific", rm.getString("platformSpecific")); } @Test public void testLocaleResource() { Locale oldLocale = Locale.getDefault(); Locale.setDefault(new Locale("zz")); ResourceMap rm = platformResourceMap(); assertEquals("notLocalized", rm.getString("notLocalized")); assertEquals("zzLocalized", rm.getString("zzLocalized")); assertEquals("zzAndPlatformSpecific", rm.getString("zzAndPlatformSpecific")); Locale.setDefault(oldLocale); } /** * Test for Issue #27 - Resource Injection fails on cached primitive types * https://appframework.dev.java.net/issues/show_bug.cgi?id=27 */ @Test public void testCachedPrimitiveResource() { ResourceMap rm = basicResourceMap(); String integer123 = "integer123"; String short123 = "short123"; String long123 = "long123"; String byte123 = "byte123"; String float123 = "float123"; String double123 = "double123"; String booleanTrue = "booleanTrue"; assertEquals(rm.getInteger(integer123).intValue(), 123); assertEquals(rm.getObject(integer123, int.class), new Integer(123)); assertEquals(rm.getShort(short123).shortValue(), 123); assertEquals(rm.getObject(short123, short.class), new Short((short) 123)); assertEquals(rm.getLong(long123).longValue(), 123L); assertEquals(rm.getObject(long123, long.class), new Long(123)); assertEquals(rm.getByte(byte123).byteValue(), 123); assertEquals(rm.getObject(byte123, byte.class), new Byte((byte) 123)); assertEquals(rm.getFloat(float123).floatValue(), 123.0f, EPSILON_FLOAT); assertEquals(rm.getObject(float123, float.class), new Float(123.0f)); assertEquals(rm.getDouble(double123).doubleValue(), 123.0, EPSILON_DOUBLE); assertEquals(rm.getObject(double123, double.class), new Double(123.0)); assertTrue(rm.getBoolean(booleanTrue)); assertEquals(Boolean.TRUE, rm.getObject(booleanTrue, boolean.class)); } private ResourceMap localeChangeTestResourceMap() { String bundleBaseName = getClass().getPackage().getName() + ".resources.LocaleChangeTest"; /* If the ResourceBundle can't be found, getBundle() will throw an exception. * ResourceMap isn't supposed to complain if it can't find a * ResourceBundle however the tests that follow expect * Basic ResourceBundle to exist. */ ResourceBundle.getBundle(bundleBaseName); ClassLoader classLoader = getClass().getClassLoader(); ResourceMap resourceMap = new ResourceMap(null, classLoader, bundleBaseName); return resourceMap; } /** * Test for Issue #10: If the default locale changes, ResourceMap * cache entries should be cleared */ @Test public void testDefaultLocaleChange() { Locale.setDefault(Locale.ENGLISH); ResourceMap rm = localeChangeTestResourceMap(); assertEquals("English string", "Hello", rm.getString("hello")); assertEquals("English variable", "Hello World", rm.getString("welcome")); Locale.setDefault(Locale.GERMAN); assertEquals("German string", "Hallo", rm.getString("hello")); assertEquals("German variable", "Hallo Welt", rm.getString("welcome")); } } bsaf-1.9/src/test/java/org/jdesktop/application/TaskMonitorTest.java000066400000000000000000000157301151640306200256630ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertTrue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; /** * * @author Illya Yalovyy */ public class TaskMonitorTest{ public static final String MESSAGE_TASK0 = "* Task 0"; public static final String MESSAGE_TASK1 = "* Task 1"; public static class SimpleApplication extends WaitForStartupApplication { public boolean startupOnEDT; } public static class LatchTask extends Task { final CountDownLatch allTasksDoneLatch; final CountDownLatch startLatch; final LatchTask other; private final String message; volatile boolean fired = false; public LatchTask(String message, CountDownLatch startLatch, CountDownLatch allTasksDoneLatch, LatchTask other) { super(Application.getInstance(SimpleApplication.class)); this.message = message; this.startLatch = startLatch; this.allTasksDoneLatch = allTasksDoneLatch; this.other = other; } @Override protected Void doInBackground() throws Exception { if (startLatch!=null) startLatch.await(500, TimeUnit.MILLISECONDS); setMessage(message); fired = true; if (other != null) { Application.getInstance().getContext().getTaskService().execute(other); } return null; } @Override protected void finished() { if (other != null) { other.startLatch.countDown(); } allTasksDoneLatch.countDown(); } } @Before public void methodSetup() { SimpleApplication.launchAndWait(SimpleApplication.class); } CountDownLatch allTasksDoneLatch = new CountDownLatch(2); CountDownLatch startLatch = new CountDownLatch(1); @Test public void testSucceeded() throws InterruptedException { TaskMonitor mon = Application.getInstance().getContext().getTaskMonitor(); RecordingPropertyChangeListener pcl = new RecordingPropertyChangeListener(); mon.addPropertyChangeListener(pcl); LatchTask t0 = new LatchTask(MESSAGE_TASK0, new CountDownLatch(1), allTasksDoneLatch, null); LatchTask t1 = new LatchTask(MESSAGE_TASK1, startLatch, allTasksDoneLatch, t0); Application.getInstance().getContext().getTaskService().execute(t1); allTasksDoneLatch.await(1000, TimeUnit.MILLISECONDS); assertTrue(t0.fired); assertTrue(t1.fired); assertTrue(pcl.messages.contains(MESSAGE_TASK0)); assertTrue(pcl.messages.contains(MESSAGE_TASK1)); } @Test public void testMessageSucceeded() throws InterruptedException { TaskMonitor mon = Application.getInstance().getContext().getTaskMonitor(); TaskService srv = Application.getInstance().getContext().getTaskService(); final CountDownLatch cdl = new CountDownLatch(1); final String MESSAGE_0 = "doInBackground"; final String MESSAGE_1 = "succeeded"; final String MESSAGE_2 = "finished"; final Set messages = Collections.synchronizedSet(new HashSet(10)); mon.addPropertyChangeListener(Task.PROP_MESSAGE, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String message = (String) evt.getNewValue(); messages.add(message); } }); srv.execute(new Task(Application.getInstance()) { @Override protected Object doInBackground() throws Exception { setMessage("doInBackground"); return null; } @Override protected void succeeded(Object result) { super.succeeded(result); setMessage("succeeded"); } @Override protected void finished() { super.finished(); setMessage("finished"); cdl.countDown(); } }); cdl.await(); assertTrue(MESSAGE_0, messages.contains(MESSAGE_0)); assertTrue(MESSAGE_1, messages.contains(MESSAGE_1)); assertTrue(MESSAGE_2, messages.contains(MESSAGE_2)); } @Test public void testMessageFailed() throws InterruptedException { TaskMonitor mon = Application.getInstance().getContext().getTaskMonitor(); TaskService srv = Application.getInstance().getContext().getTaskService(); final CountDownLatch cdl = new CountDownLatch(1); final String MESSAGE_0 = "doInBackground"; final String MESSAGE_1 = "failed"; final String MESSAGE_2 = "finished"; final Set messages = Collections.synchronizedSet(new HashSet(10)); mon.addPropertyChangeListener(Task.PROP_MESSAGE, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String message = (String) evt.getNewValue(); messages.add(message); } }); srv.execute(new Task(Application.getInstance()) { @Override protected Object doInBackground() throws Exception { setMessage("doInBackground"); Thread.sleep(100); throw new Exception("Test Exception"); } @Override protected void failed(Throwable cause) { setMessage("failed"); } @Override protected void finished() { setMessage("finished"); cdl.countDown(); } }); cdl.await(); assertTrue(MESSAGE_0, messages.contains(MESSAGE_0)); assertTrue(MESSAGE_1, messages.contains(MESSAGE_1)); assertTrue(MESSAGE_2, messages.contains(MESSAGE_2)); } private class RecordingPropertyChangeListener implements PropertyChangeListener{ List messages = new ArrayList(); public void propertyChange(PropertyChangeEvent evt) { if (TaskMonitor.PROP_FOREGROUND_TASK.equals(evt.getPropertyName())) { // Wait until TaskMonitor insert listeners. may be this process should be synchronized? startLatch.countDown(); } if (Task.PROP_MESSAGE.equals(evt.getPropertyName())){ messages.add((String) evt.getNewValue()); } } } } bsaf-1.9/src/test/java/org/jdesktop/application/TaskStateTest.java000066400000000000000000000127741151640306200253210ustar00rootroot00000000000000/* * Copyright (C) 2009 Illya Yalovyy * Use is subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; /** * * @author Illya Yalovyy */ public class TaskStateTest{ private enum State { PCL_STARTED, PCL_COMPLETED, PCL_DONE, SUCCEEDED, FAILED, FINISHED, CANCELLED, TL_DOINBACKGROUND, TL_SUCCEEDED, TL_FAILED, TL_CANCALLED, TL_FINISHED, TL_INTERRUPTED } public static class SimpleApplication extends WaitForStartupApplication { public boolean startupOnEDT; } public static class DoNothingTask extends Task { private final Exception ex; private final Queue queue; DoNothingTask(Exception ex, Queue queue) { super(Application.getInstance(SimpleApplication.class), "DoNothingTask"); this.ex = ex; this.queue = queue; } @Override protected Void doInBackground() throws Exception { if (ex!=null) throw ex; return null; } @Override protected void failed(Throwable cause) { queue.offer(State.FAILED); } @Override protected void succeeded(Void result) { queue.offer(State.SUCCEEDED); } @Override protected void finished() { queue.offer(State.FINISHED); } } private static class PropertyChangeListenerImpl implements PropertyChangeListener { private final BlockingQueue queue; public PropertyChangeListenerImpl(BlockingQueue queue) { this.queue = queue; } @Override public void propertyChange(PropertyChangeEvent evt) { if (Task.PROP_DONE.equals(evt.getPropertyName())) { queue.offer(State.PCL_DONE); } else if (Task.PROP_STARTED.equals(evt.getPropertyName())) { queue.offer(State.PCL_STARTED); } else if (Task.PROP_COMPLETED.equals(evt.getPropertyName())) { queue.offer(State.PCL_COMPLETED); } } } private static class TaslListenerImpl extends TaskListener.Adapter { private final BlockingQueue queue; public TaslListenerImpl(BlockingQueue queue) { this.queue = queue; } @Override public void cancelled(TaskEvent event) { queue.offer(State.TL_CANCALLED); } @Override public void doInBackground(TaskEvent event) { queue.offer(State.TL_DOINBACKGROUND); } @Override public void failed(TaskEvent event) { queue.offer(State.TL_FAILED); } @Override public void finished(TaskEvent event) { queue.offer(State.TL_FINISHED); } @Override public void interrupted(TaskEvent event) { queue.offer(State.TL_INTERRUPTED); } @Override public void succeeded(TaskEvent event) { queue.offer(State.TL_SUCCEEDED); } } @Before public void methodSetup() { SimpleApplication.launchAndWait(SimpleApplication.class); } @Test public void testSucceeded() throws InterruptedException { List result = runTask(null); assertSequence(result, State.SUCCEEDED); } @Test public void testFailed() throws InterruptedException { List result = runTask(new Exception("Test Exception")); assertSequence(result, State.FAILED); } private void assertSequence(List result, State expected) { assertTrue(isAfter(State.PCL_DONE, State.PCL_STARTED, result)); assertTrue(isAfter(State.PCL_DONE, State.TL_DOINBACKGROUND, result)); assertTrue(isAfter(State.PCL_COMPLETED, State.PCL_DONE, result)); assertTrue(isAfter(State.PCL_COMPLETED, State.FINISHED, result)); assertTrue(isAfter(State.PCL_COMPLETED, expected, result)); assertTrue(isAfter(State.PCL_COMPLETED, State.TL_FINISHED, result)); assertTrue(isAfter(State.FINISHED, expected, result)); assertTrue(isAfter(expected, State.PCL_DONE, result)); } private List runTask(Exception ex) throws InterruptedException { List result = new ArrayList(); BlockingQueue queue = new ArrayBlockingQueue(20); DoNothingTask task = new DoNothingTask(ex, queue); task.addPropertyChangeListener(new PropertyChangeListenerImpl(queue)); task.addTaskListener(new TaslListenerImpl(queue)); task.execute(); boolean timeout=false; while (!timeout) { State s = queue.poll(1, TimeUnit.SECONDS); if (!(timeout = s == null)) { result.add(s); } } return result; } private boolean isAfter(State st1, State st2, List list) { int idx1 = list.indexOf(st1); int idx2 = list.indexOf(st2); return idx1 > idx2; } } bsaf-1.9/src/test/java/org/jdesktop/application/TaskTest.java000066400000000000000000000161021151640306200243050ustar00rootroot00000000000000/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; /** * Test the Task classes. This test depends on the follow resource bundles: *
 * resources/DoNothingTask.properties
 * 
* * @author Hans Muller (Hans.Muller@Sun.COM) */ public class TaskTest { public static class SimpleApplication extends WaitForStartupApplication { public boolean startupOnEDT; } @Before public void methodStartup() { SimpleApplication.launchAndWait(SimpleApplication.class); } public static class DoNothingTask extends Task { DoNothingTask() { super(Application.getInstance(SimpleApplication.class), "DoNothingTask"); } protected Void doInBackground() { return null; } } /* Sanity check: verify that SimpleApp launched */ private void appSanityCheck() { Application app = Application.getInstance(SimpleApplication.class); boolean isSimpleApp = app instanceof SimpleApplication; assertTrue("Application.getInstance() should be a SimpleApp: " + app, isSimpleApp); } @Test public void testResourceLoading() { appSanityCheck(); /* Check resource loading basics */ Task task = new DoNothingTask(); ResourceMap resourceMap = task.getResourceMap(); assertNotNull("DoNothingTask.getResourceMap()", resourceMap); String resourceName = task.resourceName("justAResource"); assertEquals("DoNothingTask.justAResource", resourceName); assertEquals("just a resource", resourceMap.getString(resourceName)); assertEquals("the title", task.getTitle()); assertEquals("the description", task.getDescription()); assertEquals("the initial message", task.getMessage()); /* Check the message method */ task.message("messageParam0"); assertEquals("message 0", task.getMessage()); task.message("messageParam1", "foo"); assertEquals("message 1 foo", task.getMessage()); task.message("messageParam2", "foo", "bar"); assertEquals("message 1 foo 2 bar", task.getMessage()); } public static class NoResourcesTask extends Task { NoResourcesTask() { super(Application.getInstance(SimpleApplication.class), null, ""); } protected Void doInBackground() { return null; } } /* If a resourceClass constructor parameter isn't specified, then * no resourceMap should be created, and title/description/message * should be null. */ @Test public void testNoArgsConstructor() { appSanityCheck(); Task task = new NoResourcesTask(); assertNull("NoResourcesTask.getResourceMap()", task.getResourceMap()); assertEquals("noPrefix", task.resourceName("noPrefix")); assertNull(task.getTitle()); assertNull(task.getDescription()); assertNull(task.getMessage()); } static int CANCELLED = 1; static int INTERRUPTED = 2; static int FAILED = 3; static int SUCCEEDED = 4; public static class SleepTask extends Task { private int completionMethod = -1; synchronized int getCompletionMethod() { return completionMethod; } synchronized void setCompletionMethod(int i) { completionMethod = i; } protected void succeeded() { setCompletionMethod(SUCCEEDED); } protected void cancelled() { setCompletionMethod(CANCELLED); } protected void failed() { setCompletionMethod(FAILED); } protected void interrupted(InterruptedException e) { setCompletionMethod(INTERRUPTED); } private final long sleepTime; SleepTask(long sleepTime) { super(Application.getInstance(SimpleApplication.class)); this.sleepTime = sleepTime; } protected String doInBackground() throws InterruptedException { Thread.sleep(sleepTime); return "OK"; } } public static class SleepTaskListener extends TaskListener.Adapter { private int completionMethod = -1; private TaskEvent taskEvent = null; synchronized int getCompletionMethod() { return completionMethod; } synchronized void setCompletionMethod(int i) { completionMethod = i; } synchronized TaskEvent getTaskEvent() { return taskEvent; } synchronized void setTaskEvent(TaskEvent e) { taskEvent = e; } public void succeeded(TaskEvent e) { setCompletionMethod(SUCCEEDED); setTaskEvent(e); } public void cancelled(TaskEvent e) { setCompletionMethod(CANCELLED); setTaskEvent(e); } public void failed(TaskEvent e) { setCompletionMethod(FAILED); setTaskEvent(e); } public void interrupted(TaskEvent e) { completionMethod = INTERRUPTED; setTaskEvent(e); } } private void sleep(long n) { try { Thread.sleep(n); } catch (InterruptedException ignore) { } } /** * Verify that the Task.cancelled() method is called. */ @Test public void testCancelled() { // cancel before execution SleepTask task = new SleepTask(0L); SleepTaskListener stl = new SleepTaskListener(); task.addTaskListener(stl); task.cancel(false); assertTrue(task.isDone()); /* TBD: todo These checks can't be done until the Task has completed, i.e. until the done method and all of the listeners have run on the EDT. assertEquals(CANCELLED, task.getCompletionMethod()); assertTrue(task.isCancelled()); assertEquals(CANCELLED, stl.getCompletionMethod()); assertNotNull(stl.getTaskEvent()); assertEquals(stl.getTaskEvent().getSource(), task); */ // cancel after execution; interrupt Thread.sleep() task = new SleepTask(100000L); stl = new SleepTaskListener(); task.addTaskListener(stl); task.execute(); while (!task.isStarted()) { sleep(20L); } sleep(100L); // give task a chance to really start sleeping task.cancel(true); /* TBD: These checks can't be done until the Task has completed, i.e. until the done method and all of the listeners have run on the EDT. assertEquals("task.getCompletionMethod()", task.getCompletionMethod(), CANCELLED); assertTrue(task.isCancelled()); assertEquals(CANCELLED, stl.getCompletionMethod()); assertNotNull(stl.getTaskEvent()); assertEquals(task, stl.getTaskEvent().getSource()); assertNull(stl.getTaskEvent().getValue()); */ } } bsaf-1.9/src/test/java/org/jdesktop/application/WaitForStartupApplication.java000066400000000000000000000025731151640306200276740ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; /** * Support for launching an application from a non-EDT thread and * waiting until its startup method has finished running on the EDT. */ public class WaitForStartupApplication extends Application { private static Object lock = new Object(); // static: Application is a singleton private boolean started = false; /** * Unblock the launchAndWait() method. */ protected void startup() { synchronized(lock) { started = true; lock.notifyAll(); } } boolean isStarted() { return started; } /** * Launch the specified subclsas of WaitForStartupApplication and block * (wait) until it's startup() method has run. */ public static void launchAndWait(Class applicationClass) { synchronized(lock) { Application.launch(applicationClass, new String[]{}); while(true) { try { lock.wait(); } catch (InterruptedException e) { System.err.println("launchAndWait interrupted!"); break; } Application app = Application.getInstance(WaitForStartupApplication.class); if (app instanceof WaitForStartupApplication) { if (((WaitForStartupApplication)app).isStarted()) { break; } } } } } } bsaf-1.9/src/test/java/org/jdesktop/application/WaitForStartupSFA.java000066400000000000000000000032461151640306200260400ustar00rootroot00000000000000 /* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is * subject to license terms. */ package org.jdesktop.application; /** * Identical to WaitForStartupApplication. * * Support for launching a SingleFrameApplication from a non-EDT thread and * waiting until its startup method has finished running on the EDT. */ public class WaitForStartupSFA extends SingleFrameApplication { private static final Object lock = new Object(); // static: Application is a singleton @Override protected void startup() { // do nothing } /** * Unblock the launchAndWait() method * when the application is ready */ @Override protected void ready() { super.ready(); synchronized (lock) { lock.notifyAll(); } } /** * Launch the specified subclsas of WaitForStartupApplication and block * (wait) until it's startup() method has run. */ public static void launchAndWait(Class applicationClass) { synchronized (lock) { Application.launch(applicationClass, new String[]{}); while (true) { try { lock.wait(); } catch (InterruptedException e) { System.err.println("launchAndWait interrupted!"); break; } Application app = Application.getInstance(WaitForStartupSFA.class); if (app instanceof WaitForStartupSFA) { if (((WaitForStartupSFA) app).isReady()) { break; } } } } } } bsaf-1.9/src/test/java/org/jdesktop/application/utils/000077500000000000000000000000001151640306200230405ustar00rootroot00000000000000bsaf-1.9/src/test/java/org/jdesktop/application/utils/SwingHelperTest.java000066400000000000000000000035601151640306200267760ustar00rootroot00000000000000package org.jdesktop.application.utils; import static org.junit.Assert.*; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.RootPaneContainer; import org.junit.Test; public class SwingHelperTest { @Test public void testFindRootPaneContainerNull() { JComponent cmp = new JComponent() { }; RootPaneContainer rpc = SwingHelper.findRootPaneContainer(cmp); assertNull(rpc); rpc = SwingHelper.findRootPaneContainer(null); assertNull(rpc); } @Test public void testFindRootPaneContainerParent1() { JFrame f = new JFrame(); JButton b = new JButton(); f.getContentPane().add(b); RootPaneContainer rpc = SwingHelper.findRootPaneContainer(b); assertEquals(f, rpc); } @Test public void testFindRootPaneContainerParent2() { JFrame f = new JFrame(); JMenuBar mb = new JMenuBar(); JMenu m = new JMenu(); JMenuItem i = new JMenuItem(); m.add(i); mb.add(m); f.setJMenuBar(mb); RootPaneContainer rpc = SwingHelper.findRootPaneContainer(i); assertEquals(f, rpc); } @Test public void testFindRootPaneContainerInvoker1() { JFrame f = new JFrame(); JMenuItem i = new JMenuItem(); JLabel l = new JLabel(); JPopupMenu pm = new JPopupMenu(); pm.add(i); l.setComponentPopupMenu(pm); f.getContentPane().add(l);pm.setInvoker(l); RootPaneContainer rpc = SwingHelper.findRootPaneContainer(i); assertEquals(f, rpc); } } bsaf-1.9/src/test/resources/000077500000000000000000000000001151640306200160545ustar00rootroot00000000000000bsaf-1.9/src/test/resources/org/000077500000000000000000000000001151640306200166435ustar00rootroot00000000000000bsaf-1.9/src/test/resources/org/jdesktop/000077500000000000000000000000001151640306200204665ustar00rootroot00000000000000bsaf-1.9/src/test/resources/org/jdesktop/application/000077500000000000000000000000001151640306200227715ustar00rootroot00000000000000bsaf-1.9/src/test/resources/org/jdesktop/application/resources/000077500000000000000000000000001151640306200250035ustar00rootroot00000000000000bsaf-1.9/src/test/resources/org/jdesktop/application/resources/ApplicationDefaultLNF.properties000066400000000000000000000001471151640306200332330ustar00rootroot00000000000000 # This ResourceBundle is used by ApplicationDefaultLNFResourceTest Application.lookAndFeel = default bsaf-1.9/src/test/resources/org/jdesktop/application/resources/ApplicationMotifLNF.properties000066400000000000000000000002121151640306200327160ustar00rootroot00000000000000 # This ResourceBundle is used by ApplicationMotifLNFResourceTest Application.lookAndFeel = com.sun.java.swing.plaf.motif.MotifLookAndFeelbsaf-1.9/src/test/resources/org/jdesktop/application/resources/ApplicationNimbusLNF.properties000066400000000000000000000001441151640306200331010ustar00rootroot00000000000000 # This ResourceBundle is used by ApplicationSystemLNFResourceTest Application.lookAndFeel = nimbus bsaf-1.9/src/test/resources/org/jdesktop/application/resources/ApplicationSystemLNF.properties000066400000000000000000000001441151640306200331300ustar00rootroot00000000000000 # This ResourceBundle is used by ApplicationSystemLNFResourceTest Application.lookAndFeel = system bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Basic.properties000066400000000000000000000050121151640306200301400ustar00rootroot00000000000000 # This ResourceBundle is used by the ResourceMapTest class # The following resources are used by testBasics(), testParentResourceMap() aStringResource=aStringResource aHelloMessage=Hello %s integer123=123 integer0=0 integerNegative1=-1 short123=123 short0=0 shortNegative1=-1 long123=123 long0=0 longNegative1=-1 byte123=123 byte0=0 byteNegative1=-1 float123=123.0 double123=123.0 badlyFormattedInteger=warningExpected booleanTrue=True booleanTRUE=TRUE booleanYes=Yes booleanOn=on booleanFalse=False booleanFALSE=FALSE booleanNo=No booleanOff=off color567=5, 6, 7 color123=1,2,3 color345= 3,4,5 color5678=5, 6, 7, 8 color556677=#556677 color55667788=#55667788 fontArialPLAIN12=Arial-PLAIN-12 black1x1Icon=black1x1.png AbsoluteBlack1x1Icon=/org/jdesktop/application/resources/black1x1.png point12 = 1, 2 pointMinus12 = -1, -2 dimension56 = 5,6 dimension89 = 8.0, 9.0 rectangle1234 = 1, 2, 3, 4 rectangleMinus5309 = -5.0, -3.0, 0, 9 insets7890 = 7, 8, 9, 0 emptyBorder8675 = 8,6, 7, 5 emptyBorder0000 = 0, 0, 0, 0 sunURL = http://www.sun.com mailURI = mailto:users@appframework.dev.java.net badlyFormattedGiantInteger=This resource just exists to verify that ResourceConverter message strings will be truncated when the offending String is exceptionally long (> 128 characters) like this one is # The following resources are used by testAddResourceConverter testAddResourceConverter=testAddResourceConverter # The following resources are used by testResourceAnnotation TestResourceAnnotation.stringField=stringField TestResourceAnnotation.intField=123 TestResourceAnnotation.colorField=0, 1, 2 TestResourceAnnotation.booleanField=yes TestResourceAnnotation.staticField=staticField TestResourceAnnotation.objectField = objectField F1=stringF1Field TestResourceAnnotation.numbers[2] = 2 TestResourceAnnotation.numbers[3] = 3 TestResourceAnnotation.numbers[11] = 11 TestResourceAnnotation.icons[0] = ${black1x1Icon} TestResourceAnnotation.icons[1] = black1x1.png TestResourceAnnotation.shortcutX = shortcut X TestResourceAnnotation.shortcutShiftX = shortcut shift X # The following resource is used by testPlatformResource # See Basic_WindowsXP.properties, Basic_WindowsXP_zz.properties notPlatformSpecific = notPlatformSpecific platformSpecific = this should be overridden by Basic.properties_WindowsXP # The following resource is used by testLocaleResource # See Basic_zz.properties notLocalized = notLocalized zzLocalized = this should be overridden by Basic_zz.properties zzAndPlatformSpecifice = this should be overridden by Basic_WindowsXP_zz.properties bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Basic_WindowsXP.properties000066400000000000000000000002001151640306200321140ustar00rootroot00000000000000 # This ResourceBundle is used by the ResourceMapTest class, # testPlatformResource method platformSpecific = platformSpecific bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Basic_WindowsXP_zz.properties000066400000000000000000000002121151640306200326420ustar00rootroot00000000000000 # This ResourceBundle is used by the ResourceMapTest class, # testLocaleResource method zzAndPlatformSpecific = zzAndPlatformSpecific bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Basic_zz.properties000066400000000000000000000001661151640306200306700ustar00rootroot00000000000000 # This ResourceBundle is used by the ResourceMapTest class, # testLocaleResource method zzLocalized = zzLocalized bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Child.properties000066400000000000000000000002541151640306200301450ustar00rootroot00000000000000 # This ResourceBundle is used by the TestResourceMap class # The following resources are Used by testKeySetChild(), testKeySetParentChild() p1=p1 p2=p2 p3=p3 p4=p4 p5=p5 bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Controller.properties000066400000000000000000000021011151640306200312360ustar00rootroot00000000000000 # This ResourceBundle is used by the ApplicationActionMapTest class # Resources for testActionProperties() allActionProperties.Action.text = All allActionProperties.Action.shortDescription = short allActionProperties.Action.longDescription = long allActionProperties.Action.smallIcon = black1x1.png allActionProperties.Action.command = AllCommand allActionProperties.Action.mnemonic = A allActionProperties.Action.accelerator = control A # Resources for testActionMnemonicProperties() checkActionMnemonics0.Action.text = &File checkActionMnemonics1.Action.text = E&xit checkActionMnemonics5.Action.text = Save &As # Resources for testActionAnnotationKeyProperty() alternateActionName.Action.text = All alternateActionName.Action.shortDescription = short alternateActionName.Action.longDescription = long alternateActionName.Action.smallIcon = black1x1.png alternateActionName.Action.command = AllCommand alternateActionName.Action.mnemonic = A alternateActionName.Action.accelerator = control A actionParametersC.text = actionParametersC actionParametersZ.text = actionParametersZ bsaf-1.9/src/test/resources/org/jdesktop/application/resources/DefaultActionsApplication.properties000066400000000000000000000002651151640306200342150ustar00rootroot00000000000000 # This ResourceBundle is used by the ApplicationActionsTest class quit.Action.text = Q quit.Action.shortDescription = Q delete.Action.text = D delete.Action.shortDescription = D bsaf-1.9/src/test/resources/org/jdesktop/application/resources/DelayInputBlockDialogTask.properties000066400000000000000000000000401151640306200341070ustar00rootroot00000000000000BlockingDialogTimer.delay = 4500bsaf-1.9/src/test/resources/org/jdesktop/application/resources/DoNothingTask.properties000066400000000000000000000005561151640306200316430ustar00rootroot00000000000000 # This ResourceBundle is used by TaskTest.testResourceLoading() DoNothingTask.title = the title DoNothingTask.description = the description DoNothingTask.message = the initial message DoNothingTask.messageParam0 = message 0 DoNothingTask.messageParam1 = message 1 %s DoNothingTask.messageParam2 = message 1 %s 2 %s DoNothingTask.justAResource = just a resource bsaf-1.9/src/test/resources/org/jdesktop/application/resources/ExprEval.properties000066400000000000000000000006231151640306200306500ustar00rootroot00000000000000 # This ResourceBundle is used by the TestResourceMap class # Resources for testExpressionEvaluation hello = Hello world = World place = ${world} helloworld0 = Hello World helloworld1 = Hello ${world} helloworld2 = ${hello} ${world} helloworld3 = ${hello} ${place} escHelloWorld = \\${hello} \\${world} escOnly = \\${ noSuchVariableKey = hello ${borf} noClosingBrace = ${hello world justNull = ${null} bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Injection.properties000066400000000000000000000015321151640306200310440ustar00rootroot00000000000000 # This ResourceBundle is used by the TestResourceMap class # The following resources are Used by testInjectComponentProperties() testLabel.text=Hello World testLabel.font=Arial-PLAIN-12 testLabel.enabled=false testLabel.background=#556677 testLabel.alignmentX=0.5 testLabel.icon=black1x1.png testLabel.displayedMnemonicIndex = 3 labelNullText.text = ${null} # The follow resources are used by testInjectComponentHierarchy Properties() mainFrame.title = Frame title mainFrame.iconImage = black1x1.png Edit.menu.mnemonic = 68 item.mnemonic = 69 item.text = Item text parentPanel.background=#550000 parentPanel.foreground=#006600 childPanel.background=#000000 childPanel.foreground=#000077 textField1.text=textField1 textField2.text=textField2 textField2.background=0, 0, 0 mnemonicLabel1.text = &mnemonic mnemonicLabel2.text = Save &As button.text = E&xit bsaf-1.9/src/test/resources/org/jdesktop/application/resources/LocaleChangeTest_de.properties000066400000000000000000000002441151640306200327360ustar00rootroot00000000000000 # This ResourceBundle is used by the ResourceMapTest class # See testDefaultLocaleChange(), ./LocaleChangeTest_en.properties hello = Hallo welcome = Hallo Welt bsaf-1.9/src/test/resources/org/jdesktop/application/resources/LocaleChangeTest_en.properties000066400000000000000000000002451151640306200327510ustar00rootroot00000000000000 # This ResourceBundle is used by the ResourceMapTest class # See testDefaultLocaleChange(), ./LocaleChangeTest_de.properties hello = Hello welcome = Hello World bsaf-1.9/src/test/resources/org/jdesktop/application/resources/MultiBundle.properties000066400000000000000000000005471151640306200313530ustar00rootroot00000000000000 # This ResourceBundle is used by ResourceMapTest.testMultiBundle() # It's one of three: MultiBundle.properties, MuliBundle_AllLocales.properties, # MultiBundle_WindowsXP.properties noOverride = OK AllLocales_override = should be overriden in MultiBundle_AllLocales.properties WindowsXP_override = should be overriden in MultiBundle_WindowsXP.properties bsaf-1.9/src/test/resources/org/jdesktop/application/resources/MultiBundle_AllLocales.properties000066400000000000000000000003711151640306200334410ustar00rootroot00000000000000 # This ResourceBundle is used by ResourceMapTest.testMultiBundle() # It's one of three: MultiBundle.properties, MuliBundle_AllLocales.properties, # MultiBundle_WindowsXP.properties AllLocales_override = AllLocales OK AllLocales_only = AllLocales bsaf-1.9/src/test/resources/org/jdesktop/application/resources/MultiBundle_WindowsXP.properties000066400000000000000000000003571151640306200333340ustar00rootroot00000000000000 # This ResourceBundle is used by ResourceMapTest.testMultiBundle() # It's one of three: MultiBundle.properties, MuliBundle_AllLocales.properties, # MultiBundle_WindowsXP.properties WindowsXP_only = XP WindowsXP_override = WindowsXP OK bsaf-1.9/src/test/resources/org/jdesktop/application/resources/Parent.properties000066400000000000000000000002631151640306200303530ustar00rootroot00000000000000 # This ResourceBundle is used by the TestResourceMap class # The following resources are used by testKeySetParentChild() p2=Parentp2 p4=Parentp4 p6=p6 p7=p7 p8=p8 p9=p9 p10=p10 bsaf-1.9/src/test/resources/org/jdesktop/application/resources/ProxyTestActions.properties000066400000000000000000000003301151640306200324170ustar00rootroot00000000000000 # This ResourceBundle is used by the TestProxyAction class # Generates three Actions: testAction, testAction[Menu], testAction[Toolbar] testAction.Action.text = testAction testAction.Action.command = TestCommand bsaf-1.9/src/test/resources/org/jdesktop/application/resources/SimpleApplication.properties000066400000000000000000000000311151640306200325300ustar00rootroot00000000000000currentPlatform=Default bsaf-1.9/src/test/resources/org/jdesktop/application/resources/SimpleApplication_lin.properties000066400000000000000000000000271151640306200333770ustar00rootroot00000000000000currentPlatform=Linux bsaf-1.9/src/test/resources/org/jdesktop/application/resources/SimpleApplication_osx.properties000066400000000000000000000000321151640306200334220ustar00rootroot00000000000000currentPlatform=Mac OS X bsaf-1.9/src/test/resources/org/jdesktop/application/resources/SimpleApplication_sol.properties000066400000000000000000000000311151640306200334050ustar00rootroot00000000000000currentPlatform=Solaris bsaf-1.9/src/test/resources/org/jdesktop/application/resources/SimpleApplication_win.properties000066400000000000000000000000311151640306200334050ustar00rootroot00000000000000currentPlatform=Windows bsaf-1.9/src/test/resources/org/jdesktop/application/resources/black1x1.png000066400000000000000000000002161151640306200271160ustar00rootroot00000000000000PNG  IHDRwSsRGBgAMA a cHRMz&u0`:pQ< IDATWc```\iIENDB`