jaxx.runtime.swing.session.SwingSession Maven / Gradle / Ivy
/*
* #%L
* Graphical Widget
*
* $Id: SwingSession.java 2650 2013-04-07 10:33:20Z tchemit $
* $HeadURL: http://svn.nuiton.org/svn/jaxx/tags/jaxx-2.5.19/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/SwingSession.java $
* %%
* Copyright (C) 2004 - 2010 CodeLutin
* %%
* This program 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 3 of the
* License, or (at your option) any later version.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package jaxx.runtime.swing.session;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.JXTable;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.*;
import java.io.*;
import java.util.*;
/**
* Use to store and restore position and size of application. Default supported widgets
* are:
* java.awt.Window (and subclasses)
* javax.swing.JTabbedPane (and subclasses)
* javax.swing.JSplitPane (and subclasses)
* org.jdesktop.swingx.JXTable (and subclasses)
* javax.swing.JTable (and subclasses)
*
*
* usage:
* create SwingSession object
* add component that you want save
* explicite call to save
*
* You can use same SwingSession for multiple window but in this case you must
* have setName for each window with different name, otherwize there are
* collision between window component and result is undetermisitic
*
* This code is partialy inspired from http://kenai.com/projects/bsaf/pages/Home
* project. This project is under LGPL v2.1 license. We can't reuse directly this
* library because to many fields and methods are private and we can't implements
* it and modify some behavior.
*
* @author poussin
* @author kmorin
* @version $Revision: 2650 $
* @since 2.5.16
*
* Last update: $Date: 2013-04-07 12:33:20 +0200 (Sun, 07 Apr 2013) $
* by : $Author: tchemit $
*/
public class SwingSession {
private static final Log log = LogFactory.getLog(SwingSession.class);
protected File file;
protected boolean autoSave;
protected Set registeredComponent = Sets.newIdentityHashSet();
/** State object registered to get and set State.
* key: class of component managed by the state; value: the state*/
protected Map stateManager = Maps.newHashMap();
/** state of all component added with add method.
* key: path of compoenent; value: State */
protected Map states;
public SwingSession(File file, boolean autoSave) {
this.file = file;
this.autoSave = autoSave;
stateManager.put(Window.class, new WindowState());
stateManager.put(JTable.class, new JTableState());
stateManager.put(JTabbedPane.class, new JTabbedPaneState());
stateManager.put(JSplitPane.class, new JSplitPaneState());
stateManager.put(JXTable.class, new JXTableSwingSessionState());
states = loadStates(file);
if (states == null) {
states = Maps.newHashMap();
}
}
@Override
protected void finalize() throws Throwable {
save();
super.finalize();
}
/* 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
*/
static private class AbortExceptionListener implements ExceptionListener {
public Exception exception = null;
@Override
public void exceptionThrown(Exception e) {
if (exception == null) {
exception = e;
}
}
}
/* 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);
}
}
public void save() {
updateState();
AbortExceptionListener el = new AbortExceptionListener();
ByteArrayOutputStream bst = new ByteArrayOutputStream();
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.
*/
try {
e = new XMLEncoder(bst);
e.setPersistenceDelegate(Rectangle.class, new RectanglePD());
e.setExceptionListener(el);
e.writeObject(states);
} finally {
if (e != null) {
e.close();
}
}
if (el.exception != null) {
log.warn("save failed \"" + file + "\"", el.exception);
} else {
OutputStream ost = null;
try {
ost = new FileOutputStream(file);
ost.write(bst.toByteArray());
} catch (IOException eee) {
log.warn("save failed \"" + file + "\"", eee);
} finally {
if (ost != null) {
try {
ost.close();
} catch (IOException eee) {
log.warn("can't close properly \"" + file + "\"", eee);
}
}
}
}
}
/**
* Loads the states from the file
*/
public Map loadStates(File file) {
Map result = null;
if (file.exists()) {
XMLDecoder d = null;
try {
InputStream ist = new FileInputStream(file);
d = new XMLDecoder(ist);
AbortExceptionListener eee = new AbortExceptionListener();
d.setExceptionListener(eee);
Object bean = d.readObject();
if (eee.exception != null) {
log.warn("load failed \"" + file + "\"", eee.exception);
} else {
result = (Map) bean;
}
} catch (IOException eee) {
log.warn("load failed \"" + file + "\"", eee);
} finally {
if (d != null) {
d.close();
}
}
}
return result;
}
public void updateState() {
walkThrowComponent("", registeredComponent,
new SaveStateAction());
}
public void add(Component c) {
if (registeredComponent.contains(c)) {
log.warn(String.format(
"Component already added %s(%s)", c.getClass(), c.getName()));
} else {
registeredComponent.add(c);
walkThrowComponent("", Collections.singleton(c),
new RestoreStateAction());
}
}
/**
* Remove component from component to save
* @param c
*/
public void remove(Component c) {
registeredComponent.remove(c);
}
protected String getComponentName(Component c) {
String name = c.getName();
if (name == null) {
int n = c.getParent().getComponentZOrder(c);
if (n >= 0) {
Class clazz = c.getClass();
name = clazz.getSimpleName();
if (name.length() == 0) {
name = "Anonymous" + clazz.getSuperclass().getSimpleName();
}
name = name + n;
} else {
// Implies that the component tree is changing
// while we're computing the path. Punt.
log.warn("Couldn't compute pathname for " + c);
}
}
return name;
}
public State getStateManager(Class clazz) {
State result = null;
while (result == null && clazz != null) {
result = stateManager.get(clazz);
clazz = clazz.getSuperclass();
}
return result;
}
public void addToStateManager(Class component, State state) {
stateManager.put(component, state);
}
public State getStates(String path) {
return states.get(path);
}
public void setStates(String path, State state) {
this.states.put(path, state);
}
protected void walkThrowComponent(
String path, Collection roots, Action action) {
for (Component root : roots) {
if (root != null) {
String pathname = path + "/" + getComponentName(root);
State state = getStateManager(root.getClass());
if (state != null) {
action.doAction(this, pathname, root);
}
if (root instanceof Container) {
Component[] children = ((Container) root).getComponents();
if ((children != null) && (children.length > 0)) {
walkThrowComponent(pathname, Arrays.asList(children), action);
}
}
if (root instanceof JFrame) {
Component[] children = ((JFrame) root).getContentPane().getComponents();
if ((children != null) && (children.length > 0)) {
walkThrowComponent(pathname, Arrays.asList(children), action);
}
}
}
}
}
public static interface Action {
public void doAction(SwingSession session, String path, Component c);
}
public static class SaveStateAction implements Action {
@Override
public void doAction(SwingSession session, String path, Component c) {
State manager = session.getStateManager(c.getClass());
State state = manager.getState(c);
session.setStates(path, state);
}
}
public static class RestoreStateAction implements Action {
@Override
public void doAction(SwingSession session, String path, Component c) {
State manager = session.getStateManager(c.getClass());
State state = session.getStates(path);
if (state != null) {
manager.setState(c, state);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy