We provide several examples for creating a custom AbleBean. Each extends the AbleObject base class. If your logic is already coded and your goal is to include this logic in an AbleBean, you could implement a wrapper for your class. We provide template methods to assist you in AbleBeanWrapper. If you are starting from scratch, you should use either the SimpleAbleBean, which uses Able's data buffer conections, or the AbleFileWatcher, which uses timed event processing. You may wish to use the wrapper design pattern in either case because it insulates your logic from the methods required by Able.
There are three java parts that must be created for any AbleBean:
If you have an existing class that implements your algorithm or you wish to insulate your logic from methods required by Able, you may elect to wrapper it. There are wrapper files that are heavily commented that you can use to start.
Here are the three skeleton wrapper files:
If for instance, your existing class is called FortuneTeller, open these wrapper files, globally change the string BeanWrapper to FortuneTeller, set the package name, and save the file using the same substitution. Before editing the wrappers further, you should consider which of your algorithm's existing methods should be called at these stages:
Continue editing your FortuneTeller class files. The file comments will instruct you as to what should be changed in the methods you are overriding from AbleObject. Comments bounded by lines of "=" characters indicate code that should call methods in your FortuneTeller class. Refer to the SimpleAbleBean and AbleFileWatcher samples to see how these beans override AbleObject methods.
Perhaps the simplest data communication scenario Able supports allows bean data to be updated by a bean's process method. This method is called when data is provided in the bean's input buffer, and the bean is responsible for performing some manipulation of that data and passing its results to its output buffer. The SimpleAbleBean provided simply retrieves a String from the input buffer, stores its value in a member variable, and sets the value in the output buffer.
To see how the SimpleAbleBean works, go to the Samples panel on the Agent Editor's icon palette. Move the mouse slowly over each icon and a tooltip text window will display the name of the bean. Click on the icon for the "SimpleBean". This will create an instance and place it on the canvas.
To view bean activity, right click to bring up a context (popup) menu. Select "Inspect" to open an Inspector window. This will initially show inputBuffer and outputBuffer as null. Move the Inspector window off to the side. Now bring up the context menu again and select "Properties...". This will open a customizer dialog for the simple bean. Enter a string value for the color property, say "Blue", and click on OK to close the dialog. We just set the simpleBeanColor property value. Now to move the value to the outputBuffer, either click on the Step button on the top left, or select "Process" from the context menu. This will cause the process() method to be called, and the contents of the simpleBeanColor property to be placed in the outputBuffer. You should see the string you entered in the Customizer dialog appear in the Inspector window display.
Each time you open the customizer and enter a new Color value and then do a process() on the bean, the string will be moved to the outputBuffer. To see some more interesting behavior of this bean, go to the SimpleAbleAgent example.
Bean class file (SimpleAbleBean.java)
The easiest way to create a JavaBean that implements the AbleBean interface is to extend the AbleObject base class, which is what our SimpleAbleBean does. AbleObject is used as the base implementation class for all of the core AbleBeans such as AbleImport as well as the function-specific beans such as AbleNeuralClassifier. In addition to providing concrete implementations for all AbleBean methods, AbleObject also implements the AbleEventListener methods, and is fully remotable using RMI (AbleObject extends UnicastRemoteObject).
We will inherit most of the behavior provided by the AbleObject base class. This includes getter/setter methods for most of the attributes, support for BufferConnections, PropertyConnections, and EventConnections. We will focus on only those things required to add specialized processing functions to a standard AbleBean.
Class Imports
Specify the following imports:
import java.rmi.*; import java.io.*; import com.ibm.able.*;
We need to import the java.io. package to pick up the Serializable interface, required so we can save and load the bean from the Able Editor using Import/Export bean under the File menu.
Class Methods
Any bean that extends AbleObject should provide its own implementation of the follow methods:
In addition, it is customary to provide a set and get method for each data member, in this case for the simpleBeanColor data.
First create a default zero-parameter constructor, which is a requirement for any JavaBean. Because we are extending AbleObject which is a remote object, we must add the "throws RemoteException" clause to the constructor. In this simple example, the constructor sets the bean name and then just calls the init() method. Because the Agent Editor constructs beans as it loads for introspection, keep the constructor simple. More complex beans probably should not call the init() method in the constructor.
public SimpleAbleBean() throws RemoteException { super("SimpleBean"); init(); }
This method is generally used to initialize any local data members and resources. It also specifies the behavior of the AbleEventQueue which is contained by the bean. While the event queue supports timed events and asynchronous event processing running on its own thread, in this example we choose not to use either of these capabilities and we do not create the event queue thread. We only support data connections with other beans.
Since we want to support data flow using BufferConnections, we must allocate the inputBuffer and outputBuffer arrays. Currently, Able supports either String[] buffers (for alpha or mixed alpha and numeric data) or double[] buffers (for all numeric data). In most cases the size of these buffers is dependent on user configuration data or parameters and so the buffers are dynamically allocated in the init() or reset() methods. Our bean will have only one data member, a String whose value denotes color.
public void init() throws RemoteException { // need to allocate the input and output buffers here (if used) inputBuffer = new String[1] ; outputBuffer = new String[1] ; simpleBeanColor = "magenta" ; setDataFlowEnabled(true) ; // No timer processing, and no asynch events setSleepTime(0); setTimerEventProcessingEnabled(false); setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled); // Note: we don't create an event queue thread }
This method will reset the bean to a known state. For example, in neural networks, this method would typically reset the network weights and training parameters. In order to keep our example simple, we just call the init method to reallocate the data buffer connections and reset the color to a default value.
public void reset() throws RemoteException { init() ; // re-allocate the input buffers }
This is the main processing method where the bean does its stuff. Since our bean supports buffer connections, a processConnections() call is done here to move the source data into the inputBuffer. Next the bean would process the input data and place the results in the outputBuffer. Our example passes only one element, the bean color String. If buffer connections are not supported or used, then the bean would get its input data through sensors or property connections, process it and pass on the results via effectors, property connections or events. A dataChanged() call at the end of process signals other beans and any open Inspectors that the bean has changed.
public void process() throws RemoteException { processBufferConnections() ; // move data into inputBuffer (if any) // perform the main processing here Object in = getInputBuffer(0) ; // see if there is data in the input buffer if (in != null) { simpleBeanColor = (String)getInputBuffer(0) ; // copy to the color parameter } // Note: we either process data in the input buffer // or use other data member values as inputs // assign output data to the outputBuffer here setOutputBuffer(0, simpleBeanColor) ; dataChanged(this) ; // tell any AbleEventListeners that we changed }
Here is the usual set method for the simpleBeanColor data; note that it sends a property change event when the method is called.
public void setSimpleBeanColor(String newBeanColor) throws RemoteException { String oldBeanColor = simpleBeanColor; simpleBeanColor = newBeanColor ; firePropertyChange("simpleBeanColor", oldBeanColor, newBeanColor); }
This method will be called from the bean's customizer or editor when the OK button is pressed.
Here is the customary get method for the simpleBeanColor data:
public String getSimpleBeanColor() { return simpleBeanColor; }
This method will be called from the bean's customizer or editor to populate the property panel used to display and change the value.
Other AbleBean methods implemented by AbleObject will be inherited and need only be provided if the default behavior must be modified.
To demonstrate timed event processing, we will use the AbleFileWatcher sample bean whose purpose is to check a file for changes at some periodic interval and to take an action when a change occurs.
To see how the bean works, open the Able Agent Editor, and follow these steps:
Experiment with other conditions and actions as you wish. Press the Initialize button each time you change the screen values. Check out the property tab labeled General, and watch the State value change depending on whether the bean is processing events. You can change the timer interval on this page as well.
Bean class file using timer events (AbleFileWatcher.java)
This class is similar to the SimpleAbleBean, and will be described in terms of the methods that it provides which are significantly different to further illustrate Able functionality. Its member variables store the file being watched, the conditions to be watched, and the action to take when the condition exists. The bean will check the condition at a timed interval using the timer event processing provided in the base AbleObject class.
Class Methods
Any bean that extends AbleObject and uses TimerEvents should provide its own implementation of the follow methods:
In addition, we provide without discussion a set and get method for each data member, as well as convenience methods to obtain information about the watched file.
In the constructor we call the reset method because that establishes the default values for the condition and action. The init method is not called because we have not specified the name of the file to watch. Several constructor formats are provided, including a construction in which one can specify the name of the file to watch when the file watcher is created.
public AbleFileWatcher(String theBeanName) throws RemoteException { super(theBeanName); reset(); }
In the init method, we set the bean's attributes to process only timer events and start timer event processing. The file to watch must be specified before we start processing.
public void init() throws RemoteException { // Enable timer processing, but not asynch events setTimerEventProcessingEnabled(true); setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled); // create/resume the event queue thread startEnabledEventProcessing(); }
The reset method sets defaults and could also be called from the bean customizer to reset values which the user is likely to change. It also calls init to ensure only timer events are enabled and begins processing anew.
public void reset() throws RemoteException { setSleepTime(5000); // call processTimerEvent() every 5 seconds // set defaults for member variables action = ACTION_ALERT; command = ""; condition = FILE_THRESHOLD; Dialog dialog = null; // dialog to display for alert if (file == null) lastModified = 0; else lastModified = file.lastModified(); // last file modification threshold = Long.MAX_VALUE; // file size threshold init() ; }
When the sleep time interval expires, the processTimerEvent method will be called. In this case we would like the same logic to be run when the timer expires as other events so we just call the process method here.
public void processTimerEvent() throws RemoteException { process(); }
The purpose of this bean is to check the file to see if the specified condition is true. If so, it is to perform the desired action. Private methods can be viewed in the AbleFileWatcher bean source if you wish to see the implementation.
public void process() throws RemoteException { if (checkCondition()) { performAction() ; } }