All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.application.SessionStorage Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version

/*
 * 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()); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy