//{HEADER
/**
* This class is part of jnex 'Nexirius Application Framework for Java'
*
* Copyright (C) Nexirius GmbH, CH-4450 Sissach, Switzerland (www.nexirius.ch)
*
* <p>This library is free software; you can redistribute it and/or<br>
* modify it under the terms of the GNU Lesser General Public<br>
* License as published by the Free Software Foundation; either<br>
* version 2.1 of the License, or (at your option) any later version.</p>
*
* <p>This library is distributed in the hope that it will be useful,<br>
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
* Lesser General Public License for more details.</p>
*
* <p>You should have received a copy of the GNU Lesser General Public<br>
* License along with this library; if not, write to the Free Software<br>
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA</p>
* </blockquote>
*
* <p>
* Nexirius GmbH, hereby disclaims all copyright interest in<br>
* the library jnex' 'Nexirius Application Framework for Java' written<br>
* by Marcel Baumann.</p>
*/
//}HEADER
package com.nexirius.framework.command;
import com.nexirius.framework.FWLog;
import com.nexirius.framework.application.DialogManager;
import com.nexirius.util.ClassUtil;
import com.nexirius.util.EventMulticaster;
import com.nexirius.util.assertion.Assert;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.Hashtable;
/**
* This class provides a basic implementation of the Command interface.
* In some ways it is analagous to the javax.swing.AbstractAction class
* which implements the javax.swing.Action interface.
* An important difference is that Command/AbstractCommand is not coupled
* to any particular GUI API.
*
* @author Marcel Baumann
* Date Author Changes/Enhancements
* 1998.11.16 MB Created
* 1999.02.27 MB Bugfix: Precondition in setTimeout corrected
* 2003.05.30 MB introduce EventMulticaster with soft references (PropertyChangeListener instances) to avoid memory leaks
*/
public abstract class AbstractCommand
implements Command, Serializable {
// A command can maintain an arbitrary list of properties
private Hashtable keyTable = new Hashtable(2);
// If any PropertyChangeListeners have been registered, the
// propertyChangeListenerList field describes them.
Object propertyChangeListenerList = null;
// This is the name of the command. The name remains fixed throughout the
// lifetime of the command
private String commandName;
// This determines how much time in milliseconds should be allowed
// for a command to execute.
private long timeoutValue = 0;
// Signal to command processor that execution should not continue
// after timeout has occurred
private boolean abortAfterTimeout = false;
// Signal to command processor that execution should be synchronous
// otherwise asynchronous
private boolean synchronous = false;
// This flag indicates whether or not the command is enabled
private boolean enabled = true;
private boolean visible = true;
private boolean defaultButton = false;
private String resourceKey = null;
/**
* Constructor for AbstractCommand. The class's name will be used as a
* default command name.
*/
public AbstractCommand() {
super();
this.commandName = ClassUtil.getShortClassName(this);
init();
}
/**
* Constructor for AbstractCommand
*
* @param commandName the name of the command
*/
public AbstractCommand(String commandName) {
super();
Assert.pre(commandName != null, "Parameter commandName is not null");
this.commandName = commandName;
init();
}
private void init() {
setTimeout(timeoutValue);
setAbortAfterTimeout(abortAfterTimeout);
setSynchronous(synchronous);
setEnabled(enabled);
setVisible(visible);
setDefaultButton(defaultButton);
setResourceKey(commandName);
}
public Hashtable getProperties() {
return this.keyTable;
}
/**
* Add a PropertyChangeListener to the listener list.
* The listener is registered for all properties.
* <p/>
* A PropertyChangeEvent will get fired in response to setting
* a bound property, e.g. setEnabled
* <p/>
* The listener is attached as a SoftReference (make sure that the garbage collector does not
* remove your listener by attaching the listener reference to another instance preferably the GUI component)
*
* @param listener The PropertyChangeListener to be added
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeListenerList = EventMulticaster.addEventListener(propertyChangeListenerList, new SoftReference(listener));
}
/**
* Action to be performed in case an exception was thrown during command
* execution
*
* @param e Throwable
*/
public void exceptionAction(Throwable e) {
Assert.pre(e != null, "Throwable e is not null");
FWLog.log("AbstractCommand: An exception in command '" +
getCommandName() + "' during command execution");
e.printStackTrace();
DialogManager.error(e);
}
/**
* This method is called before doAction. Override this method if something
* should be performed in initialiseAction.
* <p/>
* The object passed as a parameter may used to pass initialising info
* to the command. If the command was invoked from a button or menu status
* then the object may correspond to the action event (the application
* programmer has to know).
*/
public void initialiseAction(Object parameter) {
}
/**
* This method is called after doAction. Override this method
* if something should be performed in finaliseAction
*/
public void finaliseAction() {
}
/**
* This action is performed if a command times out. Override this
* if some specific action is required.
* <p/>
* The default behaviour is to write a message to the logger
*/
public void timeoutAction() {
FWLog.log("AbstractCommand: Command '" + getCommandName() + "' timed out");
}
/**
* A flag to a command processor that the command should be aborted
* following a timeout. The value of abortAfterTimeout is typically
* set by calling setAbortAfterTimeout() from within the timeoutAction()
* method.
*/
public boolean getAbortAfterTimeout() {
return this.abortAfterTimeout;
}
/**
* Return the command name
*/
public String getCommandName() {
return this.commandName;
}
public void setCommandName(String commandName) {
this.commandName = commandName;
}
/**
* Return the timeout period i.e. the amount of time (in milliseconds)
* permitted for the execution of this command. A return value of zero
* indicates that no timeout period has been specified.
*
* @return long
*/
public long getTimeout() {
return this.timeoutValue;
}
/**
* Gets the Object associated with key (standard Properties stuff)
*/
public Object getValue(String key) {
return keyTable.get(key);
}
/**
* This method was created for jnex
*/
public boolean isEnabled() {
return enabled;
}
public boolean isVisible() {
return visible;
}
public boolean isDefaultButton() {
return defaultButton;
}
/**
* This method was created in VisualAge
*/
public boolean isSynchronous() {
return this.synchronous;
}
/**
* Sets the Value associated with key. Standard Properties stuff.
*
* @param key the key of the property
* @param newValue the value to be associated with the property
*/
public synchronized void putValue(String key, Object newValue) {
if (key.equals(Command.ABORT_AFTER_TIMEOUT)) {
abortAfterTimeout = ((Boolean) newValue).booleanValue();
} else if (key.equals(Command.ENABLED)) {
enabled = ((Boolean) newValue).booleanValue();
} else if (key.equals(Command.VISIBLE)) {
visible = ((Boolean) newValue).booleanValue();
} else if (key.equals(Command.DEFAULT_BUTTON)) {
defaultButton = ((Boolean) newValue).booleanValue();
} else if (key.equals(Command.RESOURCE_KEY)) {
resourceKey = (String) newValue;
} else if (key.equals(Command.SYNCHRONOUS)) {
synchronous = ((Boolean) newValue).booleanValue();
} else if (key.equals(Command.TIMEOUT)) {
timeoutValue = ((Long) newValue).longValue();
}
Object oldValue = null;
if (keyTable.containsKey(key)) {
oldValue = keyTable.get(key);
}
if (newValue != null) {
keyTable.put(key, newValue);
} else {
keyTable.remove(key);
}
firePropertyChange(key, oldValue, newValue);
}
/**
* Remove a PropertyChangeListener from the listener list.
* This removes a PropertyChangeListener that was registered
* for all properties.
*
* @param listener The PropertyChangeListener to be removed
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeListenerList = EventMulticaster.removeEventListener(propertyChangeListenerList, listener);
}
/**
* Modify the bound property 'abortAfterTimeout'
*
* @param abortAfterTimeout boolean
*/
public void setAbortAfterTimeout(boolean abortAfterTimeout) {
putValue(Command.ABORT_AFTER_TIMEOUT, new Boolean(abortAfterTimeout));
}
/**
* Modify bound propery 'enabled'.
* <p/>
* GUI widgets listening for property changes typically modify their
* 'enabled' status to correspond to that of the command
*
* @param enabled the value if 'enabled'
*/
public synchronized void setEnabled(boolean enabled) {
putValue(Command.ENABLED, new Boolean(enabled));
}
public synchronized void setVisible(boolean on) {
putValue(Command.VISIBLE, new Boolean(on));
}
public synchronized void setDefaultButton(boolean on) {
putValue(Command.DEFAULT_BUTTON, new Boolean(on));
}
/**
* Modify bound property 'synchronous'.
*
* @param synchronous true if the command should be executed synchronously
* @see com.nexirius.framework.command.Command
*/
public void setSynchronous(boolean synchronous) {
putValue(Command.SYNCHRONOUS, new Boolean(synchronous));
}
/**
* Modify the bound property 'timeout'.
* This sets the amount of time before the command times out.
* By setting a value of 0, the command will not timeout.
*
* @param timeoutValue long
*/
public void setTimeout(long timeoutValue) {
putValue(Command.TIMEOUT, new Long(timeoutValue));
}
public void setResourceKey(String resourceKey) {
putValue(Command.RESOURCE_KEY, resourceKey);
}
public String getResourceKey() {
return (resourceKey == null ? getCommandName() : resourceKey);
}
/**
* Support for reporting bound property changes. This method can be called
* when a bound property has changed and it will send the appropriate
* PropertyChangeEvent to any registered PropertyChangeListeners.
*/
private void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if (propertyChangeListenerList == null) {
return;
}
firePropertyChange(new PropertyChangeEvent(this, propertyName, oldValue, newValue));
}
public void firePropertyChange(PropertyChangeEvent event) {
Object listeners[] = EventMulticaster.getArray(propertyChangeListenerList);
boolean needGarbageCollect = false;
for (int i = 0; listeners != null && i < listeners.length; ++i) {
PropertyChangeListener listener = (PropertyChangeListener) listeners[i];
if (listener == null) {
needGarbageCollect = true;
} else {
listener.propertyChange(event);
}
}
if (needGarbageCollect) {
propertyChangeListenerList = EventMulticaster.garbageCollect(propertyChangeListenerList);
}
}
}