ABLE, Version 1.1b

com.ibm.able.agents
Class AbleDefaultAgent

java.lang.Object
  |
  +--java.rmi.server.RemoteObject
        |
        +--java.rmi.server.RemoteServer
              |
              +--java.rmi.server.UnicastRemoteObject
                    |
                    +--com.ibm.able.AbleObject
                          |
                          +--com.ibm.able.agents.AbleDefaultAgent
Direct Known Subclasses:
AbleGeneticSearchAgent, AbleNaiveBayesClassifierAgent, AbleNeuralClassifierAgent, AbleNeuralClusteringAgent, AbleNeuralPredictionAgent, AbleRuleBase, FipaAgentDefaultAgent, SimpleAbleAgent

public class AbleDefaultAgent
extends AbleObject
implements AbleAgent, java.beans.PropertyChangeListener, java.io.Serializable

This class provides an Able container agent which can be composed of other AbleObjects. It serves as the default agent in the Able Editor.

Version:
$Revision: 1.13 $, $Date: 2000/04/18 15:44:31 $
See Also:
Serialized Form

Field Summary
protected  boolean activeDataSource
          Indicates if agent contains an active data source This field is initialized to false.
protected  java.lang.String agentAddr
          The address of this agent.
protected  java.lang.String agentHost
          The name of the host where this agent is running.
protected  java.lang.String agentName
          The formal, remotely addressable name of the agent.
protected  java.util.Vector beans
          The list of AbleBean objects contained by this agent
protected  java.util.Hashtable effectors
          The list of effectors
protected  java.util.Vector eventConnections
          A list of AbleEventConnection objects (listeners) on this object
protected  long numEpochs
          The number of epochs processed in training mode This field is initialized to zero (0).
protected  java.util.Vector processList
          The list of objects to be processed (in order)
protected  boolean processListOK
          Indicates whether the processList needs to be computed This field is initialized to false.
protected  java.util.Hashtable sensors
          The list of sensors
 
Fields inherited from class com.ibm.able.AbleObject
changed, chgSupport, comment, dataFlowEnabled, destBufferConnections, eventQueue, fileName, inputBuffer, listeners, logger, name, outputBuffer, parent, propertyConnectionMgr, sourceBufferConnections, state, stateChgSupport
 
Fields inherited from class java.rmi.server.RemoteObject
ref
 
Constructor Summary
AbleDefaultAgent()
          Construct an AbleDefaultAgent instance
AbleDefaultAgent(java.lang.String theName)
          Construct an object with the specified name.
AbleDefaultAgent(java.lang.String theName, java.lang.String theComment)
          Construct an object with the specified name and comment.
 
Method Summary
 void addBean(AbleBean theAbleBean)
          Add another bean to this container/agent.
 void addEffector(AbleEffector theEffector)
          Add an effector to the list of effectors.
 void addEventConnection(AbleEventConnection theConnection)
          Add an event connection.
 void addSensor(AbleSensor theSensor)
          Add a sensor to the list of sensors.
protected  void buildProcessList()
          Examine the connection graph and build a sequential processing list.
protected  java.lang.String clsNm()
          Get the name of this class for easy inclusion in exceptions.
 boolean containsBean(AbleBean theAbleBean)
          Determine whether a specific bean/agent is contained in this agent.
 boolean containsBean(java.lang.String theName)
          Determine whether a bean with a specific name is contained in this agent.
static java.lang.String Copyright()
          Determine the copyright of this class.
protected  void debugTrace(java.lang.String theMessage)
          Log a trace message.
 java.lang.String getAgentAddr()
          Retrieve the address of this agent.
 java.lang.String getAgentHost()
          Retrieve the name of the host on which this agent is running.
 java.lang.String getAgentName()
          Retrieve the current formal agent name of this agent.
 AbleBean getBean(java.lang.String theName)
          Look for a bean with a specific name in this agent, and, if found, return a reference to that agent.
 java.util.Vector getBeans()
          Return a list of all beans contained within this agent.
 AbleDataSource getDataSource()
          Retrieve the current active DataSource (if any).
 java.util.Hashtable getEffectors()
          Get all effectors registered with this manager.
 long getNumEpochs()
          Get the number of training epochs.
 java.util.Vector getProcessList()
          Get the sequential processing list
 java.util.Hashtable getSensors()
          Get all sensors registered with this manager.
 void handleAbleEvent(AbleEvent theAbleEvent)
          Handle an Able event.
 void init()
          Initialize and configure the bean.
 void init(java.lang.Object theArg)
          Initialize and configure the bean, using the specified Object.
 java.lang.Object invokeEffector(java.lang.String theEffectorName, java.lang.Object[] theArgs)
          Call the method that the effector represents, passing in the array of argument objects.
 java.lang.Object invokeSensor(java.lang.String theSensorName, java.lang.Object[] theArgs)
          Call the method that the sensor represents, passing in the array of argument objects.
 boolean isActiveDataSource()
          Tests whether this agent has an active data source.
static void main(java.lang.String[] args)
           
 void process()
          Perform the main, synchronous, standard processing function performed by this bean.
 void propertyChange(java.beans.PropertyChangeEvent theEvent)
          Handle a propertyChange event.
 void quitAll()
          Stop the all of the bean's asynchronous threads of control.
 void quitEnabledEventProcessing()
          Stop the agent's asynchronous thread of control.
 void removeAllBeans()
          Remove all beans from this container/agent.
 void removeBean(AbleBean theAbleBean)
          Remove a bean from this container/agent.
 void removeEffector(java.lang.String theEffectorName)
          Remove an effector from the list of effectors.
 void removeEventConnection(AbleEventConnection theConnection)
          Remove an event connection.
 void removeSensor(java.lang.String theSensorName)
          Remove a sensor from the list of sensors.
 void reset()
          Reset the agent to its "initialized" state.
 void resumeAll()
          Resume all of the bean's suspended asynchronous threads of control.
 void rmiRebind()
          Make this agent a distributed agent, accessible through RMI, and able to participate in a distributed FIPA agent platform.
 void rmiUnbind()
          Unbind this agent from RMI so that it is no longer accessible in a distributed FIPA agent platform.
 void setActiveDataSource(boolean theState)
          Set the flag indicating if there is an active data source
 void setEffectors(java.util.Hashtable theEffectors)
          Set all effectors for this manager.
 void setProcessList(java.util.Vector theProcessList)
          Set the sequential processing list
 void setSensors(java.util.Hashtable theSensors)
          Set all sensors for this manager.
 void suspendAll()
          Temporarily suspend all of the bean's asynchronous threads of control.
 java.lang.String toString()
          Retrieve a string describing (the contents of) the object.
 
Methods inherited from class com.ibm.able.AbleObject
addAbleEventListener, addDestBufferConnection, addPropertyChangeListener, addPropertyConnection, addSourceBufferConnection, addStateChangeListener, dataChanged, firePropertyChange, flushAbleEventQueue, getAbleEventListeners, getAbleEventProcessingEnabled, getAbleEventQueueSize, getBuffer, getBufferContents, getComment, getDestBufferConnections, getFileName, getInputBuffer, getInputBuffer, getInputBufferAsStringArray, getInputBufferContents, getLogger, getName, getOutputBuffer, getOutputBuffer, getOutputBufferAsStringArray, getOutputBufferContents, getParent, getPropertyConnectionManager, getSleepTime, getSourceBufferConnections, getState, hasInputBuffer, hasOutputBuffer, isAbleEventPostingEnabled, isAbleEventProcessingEnabled, isChanged, isConnectable, isDataFlowEnabled, isTimerEventProcessingEnabled, notifyAbleEventListeners, processAbleEvent, processBufferConnections, processNoEventProcessingEnabledSituation, processTimerEvent, removeAbleEventListener, removeAllAbleEventListeners, removeAllBufferConnections, removeAllPropertyConnections, removeDestBufferConnection, removePropertyChangeListener, removePropertyConnection, removeSourceBufferConnection, removeStateChangeListener, restartEnabledEventProcessing, restoreFromFile, restoreFromFile, restoreFromSerializedFile, restoreFromStream, resumeEnabledEventProcessing, saveToFile, saveToFile, setAbleEventProcessingEnabled, setChanged, setComment, setDataFlowEnabled, setFileName, setInputBuffer, setInputBuffer, setLogger, setName, setOutputBuffer, setOutputBuffer, setParent, setSleepTime, setState, setTimerEventProcessingEnabled, sourceConnectionsOK, startEnabledEventProcessing, suspendEnabledEventProcessing
 
Methods inherited from class java.rmi.server.UnicastRemoteObject
clone, exportObject, exportObject, exportObject, unexportObject
 
Methods inherited from class java.rmi.server.RemoteServer
getClientHost, getLog, setLog
 
Methods inherited from class java.rmi.server.RemoteObject
equals, getRef, hashCode, toStub
 
Methods inherited from class java.lang.Object
finalize, getClass, notify, notifyAll, wait, wait, wait
 

Field Detail

agentName

protected java.lang.String agentName
The formal, remotely addressable name of the agent. This may be the same as the agent's simple name, an RMI-style lookup name, or a FIPA-style name, depending on the context in which the agent is created and used.

agentHost

protected java.lang.String agentHost
The name of the host where this agent is running. It is initialised by this object's constructor, and changed if the agent migrates to another computer.

agentAddr

protected java.lang.String agentAddr
The address of this agent. It is initialised by this object's constructor, and changed if the agent migrates to another computer.

beans

protected java.util.Vector beans
The list of AbleBean objects contained by this agent

activeDataSource

protected boolean activeDataSource
Indicates if agent contains an active data source

This field is initialized to false.


eventConnections

protected java.util.Vector eventConnections
A list of AbleEventConnection objects (listeners) on this object

processList

protected java.util.Vector processList
The list of objects to be processed (in order)

processListOK

protected boolean processListOK
Indicates whether the processList needs to be computed

This field is initialized to false.


numEpochs

protected long numEpochs
The number of epochs processed in training mode

This field is initialized to zero (0).


sensors

protected java.util.Hashtable sensors
The list of sensors

effectors

protected java.util.Hashtable effectors
The list of effectors
Constructor Detail

AbleDefaultAgent

public AbleDefaultAgent()
                 throws java.rmi.RemoteException
Construct an AbleDefaultAgent instance

AbleDefaultAgent

public AbleDefaultAgent(java.lang.String theName)
                 throws java.rmi.RemoteException
Construct an object with the specified name.
Parameters:
theName - The name of the object


AbleDefaultAgent

public AbleDefaultAgent(java.lang.String theName,
                        java.lang.String theComment)
                 throws java.rmi.RemoteException
Construct an object with the specified name and comment.
Parameters:
theName - The name of the object

theComment - A comment for the object

Method Detail

getAgentName

public java.lang.String getAgentName()
                              throws java.rmi.RemoteException
Retrieve the current formal agent name of this agent.
Specified by:
getAgentName in interface AbleAgent
Returns:
The "agent name" of this agent.

The agent name may be in the form of a simple name (the same as returned by getName() ), an RMI registry name, or an official FIPA name, depending on the environment in which the agent is used.

Throws:
java.rmi.RemoteException - On any error.

getAgentHost

public java.lang.String getAgentHost()
                              throws java.rmi.RemoteException
Retrieve the name of the host on which this agent is running.
Specified by:
getAgentHost in interface AbleAgent
Returns:
A String containing the name of the host on which this agent is running.
Throws:
java.rmi.RemoteException - On any error.

getAgentAddr

public java.lang.String getAgentAddr()
                              throws java.rmi.RemoteException
Retrieve the address of this agent.
Specified by:
getAgentAddr in interface AbleAgent
Returns:
A String containing the address of this agent.
Throws:
java.rmi.RemoteException - On any error.

setActiveDataSource

public void setActiveDataSource(boolean theState)
                         throws java.rmi.RemoteException
Set the flag indicating if there is an active data source
Specified by:
setActiveDataSource in interface AbleAgent
Parameters:
theState - Indicate whether there is an active data source in this agent


isActiveDataSource

public boolean isActiveDataSource()
                           throws java.rmi.RemoteException
Tests whether this agent has an active data source.
Specified by:
isActiveDataSource in interface AbleAgent
Returns:
true if agent contains an active data source; false otherwise.

getDataSource

public AbleDataSource getDataSource()
                             throws java.rmi.RemoteException
Retrieve the current active DataSource (if any).
Specified by:
getDataSource in interface AbleAgent
Returns:
The current active data source, or null if there is no active data source.

addEventConnection

public void addEventConnection(AbleEventConnection theConnection)
                        throws java.rmi.RemoteException
Add an event connection.
Specified by:
addEventConnection in interface AbleAgent
Parameters:
theConnection - An AbleEventConnection object


removeEventConnection

public void removeEventConnection(AbleEventConnection theConnection)
                           throws java.rmi.RemoteException
Remove an event connection.
Specified by:
removeEventConnection in interface AbleAgent
Parameters:
theConnection - An AbleEventConnection object


setProcessList

public void setProcessList(java.util.Vector theProcessList)
                    throws java.rmi.RemoteException
Set the sequential processing list
Specified by:
setProcessList in interface AbleAgent
Parameters:
theProcessList - The objects to be processed


getProcessList

public java.util.Vector getProcessList()
                                throws java.rmi.RemoteException
Get the sequential processing list
Specified by:
getProcessList in interface AbleAgent
Returns:
Vector of beans to process (in order)

getNumEpochs

public long getNumEpochs()
                  throws java.rmi.RemoteException
Get the number of training epochs.
Specified by:
getNumEpochs in interface AbleAgent
Returns:
the number of epochs. An agent should return zero if the concept of epochs is not supported.

init

public void init()
          throws java.rmi.RemoteException
Initialize and configure the bean. The bean's state changes from AbleState.Uninitiated to AbleState.Initiated.

Also, the agent's formal name and address is determined, based upon the agent's simple name and the host on which the agent is running. Therefore, it is important that the agent's simple name not be changed once init() has been called.

Overrides:
init in class AbleObject
See Also:
AbleObject.startEnabledEventProcessing()

init

public void init(java.lang.Object theArg)
          throws java.rmi.RemoteException
Initialize and configure the bean, using the specified Object. The bean's state changes from AbleState.Uninitiated to AbleState.Initiated.

Also, the agent's formal name and address is determined, based upon the agent's simple name and the host on which the agent is running. Therefore, it is important that the agent's simple name not be changed once init() has been called.

In this implementation, the argument parameter is ignored!

Parameters:
theArg - An Object used to initialize the bean.

Overrides:
init in class AbleObject

reset

public void reset()
           throws java.rmi.RemoteException
Reset the agent to its "initialized" state.

In this implementation, nothing special is done, except that the reset() call is propagated to all DataFlowEnabled AbleBeans contained within this agent.

Overrides:
reset in class AbleObject

process

public void process()
             throws java.rmi.RemoteException
Perform the main, synchronous, standard processing function performed by this bean. Typically, this function involves taking data from the input buffer, processing the data, creating an output buffer, and placing the processed data into it.

In this implementation, a single processing step is performed by walking through the process list and calling the process() method on each DataFlowEnabled AbleBean contained within this agent.

Overrides:
process in class AbleObject

quitEnabledEventProcessing

public void quitEnabledEventProcessing()
                                throws java.rmi.RemoteException
Stop the agent's asynchronous thread of control. The agents's state changes to AbleState.Unknown when the thread stops.

Timer event processing and ABLE event processing cease, but if event Posting is still enabled, events may still be placed on the internal event queue by other processes.

In this implementation, the quit request is propagated to all AbleBean objects contained within this agent, since it doesn't make since to quit this object without quitting all the contained objects, too.

Overrides:
quitEnabledEventProcessing in class AbleObject

quitAll

public void quitAll()
             throws java.rmi.RemoteException
Stop the all of the bean's asynchronous threads of control.

If the bean is a simple AbleBean, this method just calls its quitEnabledEventProcessing() method; if the bean is an AbleBeanContainer, the bean additionally calls the same method on each contained bean.

Overrides:
quitAll in class AbleObject
See Also:
quitEnabledEventProcessing()

suspendAll

public void suspendAll()
                throws java.rmi.RemoteException
Temporarily suspend all of the bean's asynchronous threads of control.

If the bean is a simple AbleBean, this method just calls its suspendEnabledEventProcessing() method; if the bean is an AbleBeanContainer, the bean additionally calls the same method on each contained bean.

Overrides:
suspendAll in class AbleObject
See Also:
AbleObject.suspendEnabledEventProcessing()

resumeAll

public void resumeAll()
               throws java.rmi.RemoteException
Resume all of the bean's suspended asynchronous threads of control.

If the bean is a simple AbleBean, this method just calls its resumeEnabledEventProcessing() method; if the bean is an AbleBeanContainer, the bean additionally calls the same method on each contained bean.

Overrides:
resumeAll in class AbleObject
See Also:
AbleObject.resumeEnabledEventProcessing()

handleAbleEvent

public void handleAbleEvent(AbleEvent theAbleEvent)
                     throws java.rmi.RemoteException
Handle an Able event.

In this implementation, special processing occurs for EOF on the active data source. All other events are passed up to the superclass (AbleObject) for processing.

Parameters:
theAbleEvent - The event to handle.

Overrides:
handleAbleEvent in class AbleObject

addBean

public void addBean(AbleBean theAbleBean)
             throws java.rmi.RemoteException
Add another bean to this container/agent.
Parameters:
theAbleBean - A bean that is to be added to this container. If the specified bean is already contained in this agent, this action is ignored.

Note that if the bean to be added has a current parent, that parentage will be lost. This is because an ABLE bean can have only one parent at a time and this method will set the specified bean's parent to be this agent!

If there is already a bean with the same name as theAbleBean a unique name will be generated by appending a colon and integer

Throws:
java.rmi.RemoteException - On any error.

See Also:
AbleBeanContainer.removeBean(com.ibm.able.AbleBean), AbleBeanContainer.containsBean(com.ibm.able.AbleBean), AbleBeanContainer.getBean(java.lang.String), AbleBeanContainer.getBeans()

removeBean

public void removeBean(AbleBean theAbleBean)
                throws java.rmi.RemoteException
Remove a bean from this container/agent.
Parameters:
theAbleBean - A bean that is to be removed from this agent. If the specified bean is not contained in this agent, the request is simply ignored.

Note that if the remove is successful, the bean's parent is set to null.

Throws:
java.rmi.RemoteException - On any error.


removeAllBeans

public void removeAllBeans()
                    throws java.rmi.RemoteException
Remove all beans from this container/agent.
Throws:
java.rmi.RemoteException - On any error.


containsBean

public boolean containsBean(AbleBean theAbleBean)
                     throws java.rmi.RemoteException
Determine whether a specific bean/agent is contained in this agent. The determination is done by using AbleBean equality.
Parameters:
theAbleBean - A bean that is to be looked for in this agent.

Returns:
true if the specific bean exists in this agent, false otherwise.
Throws:
java.rmi.RemoteException - On any error.


containsBean

public boolean containsBean(java.lang.String theName)
                     throws java.rmi.RemoteException
Determine whether a bean with a specific name is contained in this agent. The determination is done by comparing bean names.
Parameters:
theName - The specific name of a bean that is to be looked for in this agent.

Returns:
true if the named bean exists in this agent, false otherwise.
Throws:
java.rmi.RemoteException - On any error.


getBean

public AbleBean getBean(java.lang.String theName)
                 throws java.rmi.RemoteException
Look for a bean with a specific name in this agent, and, if found, return a reference to that agent.
Parameters:
theName - The specific name of a bean that is to be looked for in this agent and, if found, returned.

Returns:
A reference to the found bean, or null if the bean is not found within this agent.
Throws:
java.rmi.RemoteException - On any error.


getBeans

public java.util.Vector getBeans()
                          throws java.rmi.RemoteException
Return a list of all beans contained within this agent.
Returns:
A vector containing all other beans contained within this agent. Note that the vector may be empty. Note also that there is no implied order to the elements on the vector.
Throws:
java.rmi.RemoteException - On any error.


addSensor

public void addSensor(AbleSensor theSensor)
               throws java.rmi.RemoteException
Add a sensor to the list of sensors.
Parameters:
theSensor - The sensor to be added to the list of sensors.


removeSensor

public void removeSensor(java.lang.String theSensorName)
                  throws java.rmi.RemoteException
Remove a sensor from the list of sensors.
Parameters:
theSensorName - The name of the sensor to be removed.


invokeSensor

public java.lang.Object invokeSensor(java.lang.String theSensorName,
                                     java.lang.Object[] theArgs)
                              throws java.rmi.RemoteException,
                                     java.lang.NoSuchMethodException,
                                     java.lang.reflect.InvocationTargetException,
                                     java.lang.IllegalAccessException,
                                     java.lang.SecurityException
Call the method that the sensor represents, passing in the array of argument objects.
Parameters:
theSensorName - The name of the sensor containing an encapsulated method.

theArgs - An array of arguments that the method expects.

Returns:
The invoked method's return value.
Throws:
java.lang.reflect.InvocationTargetException - if an exception was thrown by the invoked method
java.lang.SecurityException - if the method is not public or accessible
java.lang.IllegalAccessException - if the method is not public or accessible
java.lang.NoSuchMethodException - the method or method signature if not defined in the source object

setSensors

public void setSensors(java.util.Hashtable theSensors)
                throws java.rmi.RemoteException
Set all sensors for this manager.
Parameters:
theSensors - A list of AbleSensor objects that replaces the current list. The input list is cloned.


getSensors

public java.util.Hashtable getSensors()
                               throws java.rmi.RemoteException
Get all sensors registered with this manager.
Returns:
A copy of the list of all registered AbleSensor objects.

addEffector

public void addEffector(AbleEffector theEffector)
                 throws java.rmi.RemoteException
Add an effector to the list of effectors.
Parameters:
theEffector - The effector to be added to the list of effectors.


removeEffector

public void removeEffector(java.lang.String theEffectorName)
                    throws java.rmi.RemoteException
Remove an effector from the list of effectors.
Parameters:
theEffectorName - The name of the effector to be removed.


invokeEffector

public java.lang.Object invokeEffector(java.lang.String theEffectorName,
                                       java.lang.Object[] theArgs)
                                throws java.rmi.RemoteException,
                                       java.lang.NoSuchMethodException,
                                       java.lang.reflect.InvocationTargetException,
                                       java.lang.IllegalAccessException,
                                       java.lang.SecurityException
Call the method that the effector represents, passing in the array of argument objects.
Parameters:
theEffectorName - The name of the effector containing an encapsulated method.

theArgs - An array of arguments that the method expects.

Returns:
The invoked method's return value or null (if void).
Throws:
java.lang.reflect.InvocationTargetException - if an exception was thrown by the invoked method
java.lang.SecurityException - if the method is not public or accessible
java.lang.IllegalAccessException - if the method is not public or accessible
java.lang.NoSuchMethodException - the method or method signature if not defined in the source object

setEffectors

public void setEffectors(java.util.Hashtable theEffectors)
                  throws java.rmi.RemoteException
Set all effectors for this manager.
Parameters:
theEffectors - A list of AbleEffector objects that replaces the current list. The input list is cloned.


getEffectors

public java.util.Hashtable getEffectors()
                                 throws java.rmi.RemoteException
Get all effectors registered with this manager.
Returns:
A copy of the list of all registered AbleEffector objects.

propertyChange

public void propertyChange(java.beans.PropertyChangeEvent theEvent)
Handle a propertyChange event.
Specified by:
propertyChange in interface java.beans.PropertyChangeListener
Parameters:
theEvent - The property change event.

Overrides:
propertyChange in class AbleObject

buildProcessList

protected void buildProcessList()
                         throws java.rmi.RemoteException
Examine the connection graph and build a sequential processing list. examine connection graph and build process list look through the AbleObjects and their connections and determine the sequence to process them list all Imports (objects with no Input connections first) then all connected objects in sequence - regardless of if they are on or off Note: if multiple input connections are specified then all input data must be avail before the object is added to the list

rmiRebind

public void rmiRebind()
               throws java.rmi.RemoteException
Make this agent a distributed agent, accessible through RMI, and able to participate in a distributed FIPA agent platform.
Throws:
java.rmi.RemoteException - On any error.

rmiUnbind

public void rmiUnbind()
               throws java.rmi.RemoteException
Unbind this agent from RMI so that it is no longer accessible in a distributed FIPA agent platform.
Throws:
java.rmi.RemoteException - On any error.

clsNm

protected java.lang.String clsNm()
Get the name of this class for easy inclusion in exceptions.

debugTrace

protected void debugTrace(java.lang.String theMessage)
Log a trace message.
Parameters:
theMessage - A String containing text to be traced.


main

public static void main(java.lang.String[] args)
                 throws java.rmi.RemoteException

toString

public java.lang.String toString()
Retrieve a string describing (the contents of) the object.
Returns:
A String containing the current contents of the object.

Overrides:
toString in class AbleObject

Copyright

public static java.lang.String Copyright()
Determine the copyright of this class.
Returns:
A String containing this class's copyright statement.

ABLE, Version 1.1b

ABLE: Produced by Joe, Don, and Jeff who say, 'Thanks for your support.'