org.jdesktop.application.SessionStorage Maven / Gradle / Ivy
Show all versions of swixml Show documentation
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.application;
import java.applet.Applet;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
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.JComponent;
import javax.swing.JFrame;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
/**
* 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 SessionState.Property} object has been defined.
* 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 getSesssionStorage} 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
* Property Property} objects are registered by default:
*
*
*
* Base Component Type
* sessionState Property
* sessionState Property Value
*
*
* Window
* WindowProperty
* WindowState
*
*
* JTabbedPane
* TabbedPaneProperty
* TabbedPaneState
*
*
* JSplitPane
* SplitPaneProperty
* SplitPaneState
*
*
* JTable
* TableProperty
* TableState
*
*
*
* 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();
*
*
* FIXME - @param javadoc
* @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());
}
// FIXME - documentation
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();
}
/* Return 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 - Property.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) {
Property 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 Property}
* {@link #getProperty(Component) exists} for that component,
* and the component has a {@link java.awt.Component#getName name}, then
* its {@link Property#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 Property.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.
* @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 - Property.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) {
Property 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) Property} exists for that component,
* and the component has a {@link java.awt.Component#getName name}, then
* its state is {@link Property#setSessionState restored}.
*
* @param root the root of the Component hierarchy to be restored.
* @param fileName the {@code LocalStorage} filename.
* @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);
}
}
/**
* 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 #putProperty
* @see #getProperty(Class)
* @see #getProperty(Component)
*/
public interface Property {
/**
* 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);
}
/**
* 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 #save
* @see #restore
*/
public static class WindowState {
private final Rectangle bounds;
private Rectangle gcBounds = null;
private int screenCount;
private int frameState = Frame.NORMAL;
public WindowState() {
bounds = new Rectangle();
}
public WindowState(Rectangle bounds, Rectangle gcBounds, int screenCount, int frameState) {
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);
}
}
/**
* 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 #save
* @see #restore
* @see WindowState
*/
public static class WindowProperty implements Property {
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
*/
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))) {
String clientPropertyKey = "WindowState.normalBounds";
Object r = ((JFrame)c).getRootPane().getClientProperty(clientPropertyKey);
if (r instanceof Rectangle) {
frameBounds = (Rectangle)r;
}
}
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
*/
public void setSessionState(Component c, Object state) {
checkComponent(c);
if ((state != null) && !(state instanceof WindowState)) {
throw new IllegalArgumentException("invalid state");
}
Window w = (Window)c;
if (!w.isLocationByPlatform() && (state != null)) {
WindowState windowState = (WindowState)state;
Rectangle gcBounds0 = windowState.getGraphicsConfigurationBounds();
int sc0 = windowState.getScreenCount();
GraphicsConfiguration gc = c.getGraphicsConfiguration();
Rectangle gcBounds1 = (gc == null) ? null : gc.getBounds();
int sc1 = getScreenCount();
if ((gcBounds0 != null) && (gcBounds0.equals(gcBounds1)) && (sc0 == sc1)) {
boolean resizable = true;
if (w instanceof Frame) {
resizable = ((Frame)w).isResizable();
}
else if (w instanceof Dialog) {
resizable = ((Dialog)w).isResizable();
}
if (resizable) {
w.setBounds(windowState.getBounds());
}
}
if (w instanceof Frame) {
((Frame)w).setExtendedState(windowState.getFrameState());
}
}
}
}
/**
* 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 #save
* @see #restore
*/
public static class TabbedPaneState {
private int selectedIndex;
private int tabCount;
public TabbedPaneState() {
selectedIndex = -1;
tabCount = 0;
}
public TabbedPaneState(int selectedIndex, int tabCount) {
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;
}
}
/**
* 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 #putProperty putProperty}
* method.
*
* @see TabbedPaneState
* @see #save
* @see #restore
*/
public static class TabbedPaneProperty implements Property {
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
*/
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
*/
public void setSessionState(Component c, Object state) {
checkComponent(c);
if ((state != null) && !(state instanceof TabbedPaneState)) {
throw new IllegalArgumentException("invalid state");
}
JTabbedPane p = (JTabbedPane)c;
TabbedPaneState tps = (TabbedPaneState)state;
if (p.getTabCount() == tps.getTabCount()) {
p.setSelectedIndex(tps.getSelectedIndex());
}
}
}
/**
* 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 #save
* @see #restore
*/
public static 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() { }
public SplitPaneState(int dividerLocation, int orientation) {
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;
}
}
/**
* 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 #putProperty putProperty}
* method.
*
* @see SplitPaneState
* @see #save
* @see #restore
*/
public static class SplitPaneProperty implements Property {
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
*/
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
*/
public void setSessionState(Component c, Object state) {
checkComponent(c);
if ((state != null) && !(state instanceof SplitPaneState)) {
throw new IllegalArgumentException("invalid state");
}
JSplitPane p = (JSplitPane)c;
SplitPaneState sps = (SplitPaneState)state;
if (p.getOrientation() == sps.getOrientation()) {
p.setDividerLocation(sps.getDividerLocation());
}
}
}
/**
* 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 #save
* @see #restore
*/
public static 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() { }
public TableState(int[] columnWidths) {
this.columnWidths = copyColumnWidths(columnWidths);
}
public int[] getColumnWidths() { return copyColumnWidths(columnWidths); }
public void setColumnWidths(int[] columnWidths) {
this.columnWidths = copyColumnWidths(columnWidths);
}
}
/**
* 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 #putProperty putProperty}
* method.
*
* @see TableState
* @see #save
* @see #restore
*/
public static class TableProperty implements Property {
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
*/
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
*/
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]);
}
}
}
}
}
}
private void checkClassArg(Class cls) {
if (cls == null) {
throw new IllegalArgumentException("null class");
}
}
/**
* Returns the {@code Property} object that was
* {@link #putProperty registered} for the specified class
* or a superclass. If no Property has been registered,
* return null. To lookup the session state {@code Property}
* for a {@code Component} use {@link #getProperty(Component)}.
*
* Throws an {@code IllegalArgumentException} if {@code cls} is null.
*
* @param cls the class to which the returned {@code Property} applies
* @return the {@code Property} 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 Property getProperty(Class cls) {
checkClassArg(cls);
while(cls != null) {
Property p = propertyMap.get(cls);
if (p != null) { return p; }
cls = cls.getSuperclass();
}
return null;
}
/**
* Register a {@code Property} for the specified class. One can clear
* the {@code Property} for a class by setting the entry to null:
*
* sessionStorage.putProperty(myClass.class, null);
*
*
* Throws an {@code IllegalArgumentException} if {@code cls} is null.
*
* @param cls the class to which {@code property} applies.
* @param property the {@code Property} object to register or null.
* @see #getProperty(Component)
* @see #getProperty(Class)
* @see #save
* @see #restore
*/
public void putProperty (Class cls, Property property) {
checkClassArg(cls);
propertyMap.put(cls, property);
}
/**
* If a {@code sessionState Property} 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 Property} object for each component
* to whose session state is to be saved or restored.
*
* The {@code putProperty} method registers a Property object for
* a class. One can specify a Property object for a single Swing
* component by setting the component's client property, like this:
*
* myJComponent.putClientProperty(SessionState.Property.class, myProperty);
*
* One can also create components that implement the
* {@code SessionState.Property} interface directly.
*
* @return if {@code Component c} implements {@code Session.Property}, then
* {@code c}, if {@code c} is a {@code JComponent} with a
* {@code Property} valued
* {@link javax.swing.JComponent#getClientProperty client property} under
* (client property key) {@code SessionState.Property.class}, then
* return that, otherwise return the value of
* {@code getProperty(c.getClass())}.
*
* Throws an {@code IllegalArgumentException} if {@code Component c} is null.
*
* @see javax.swing.JComponent#putClientProperty
* @see #getProperty(Class)
* @see #putProperty
* @see #save
* @see #restore
*/
public final Property getProperty(Component c) {
if (c == null) {
throw new IllegalArgumentException("null component");
}
if (c instanceof Property) {
return (Property)c;
}
else {
Property p = null;
if (c instanceof JComponent) {
Object v = ((JComponent)c).getClientProperty(Property.class);
p = (v instanceof Property) ? (Property)v : null;
}
return (p != null) ? p : getProperty(c.getClass());
}
}
}