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. Here are some guidelines on coding too.
There are three java parts that must be created for any AbleBean:
Once files are created they naturally need to be compiled, either with the javac tool provided with your JDK or in your graphical development environment. Since Able is intended to create distributed agents, it uses Remote Method Invocation and this may require the use of the rmic tool provided with java or additional steps if you use a graphical development environment.
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 in the path reflecting your package name. 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. When bean are reserialized, they may require special logic to be implemented within a readObject method.
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, initialized fixed arrays for data flow, defines default values, 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.
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 more complex cases where the size of these buffers is dependent on user configuration data or parameters, allocate the buffers dynamically in the init() or even process() methods.
public SimpleAbleBean() throws AbleException { super("SimpleBean"); inputBuffer = new String[1] ; outputBuffer = new String[1] ; reset(); init(); }
In this simple example, the init method just sets the state of the bean to active. 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.
Our bean will have only one data member, a String whose value denotes color.
public void init() throws AbleException { // Configure any contained beans super.init(); // starts processing and sets state }
This method will reset the bean to default values for its properties as well as Able processing options. For example, in neural networks, this method would typically reset the network weights and training parameters. In this example, we set all the Able processing options off except for data flow, and set the default value for the bean color.
public void reset() throws AbleException { // No timer processing, and no asynch events, just dataflow setDataFlowEnabled(true) ; setSleepTime(0); setTimerEventProcessingEnabled(false); setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled); // Default bean properties: simpleBeanColor = DefaultBeanColor; // no property change event // setSimpleBeanColor(DefaultBeanColor); // send property change event // may want to fire a mass property changed event firePropertyChange(AbleProperty.UpdateProperties, null, null); }
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 could get its input data through events, property connections or a sensor named as a user-defined function. After processing it, the results would be passed via property connections, events, or an effector named as a user-defined function. A dataChanged() call at the end of process signals other beans and any open Inspectors that the bean has changed.
public void process() throws AbleException { 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 AbleException { 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.
In some cases your bean may need additional processing to occur when it is reserialized. For instance, some of your member variables may be file objects, and you could elect to preserve the file's name rather than the file object itself when your bean is serialized. You declare data members that are not to be serialized with the transient keyword. Here is an example of overriding the method called when your bean is reserialized. In this case it calls the default method, and then checks to see if the bean was saved in an initialized state. If so, it calls the init() method which is designed in this case to recreate any variables that are transient.
private void readObject(ObjectInputStream theObjectInputStream) throws ClassNotFoundException, IOException { theObjectInputStream.defaultReadObject(); if (this.getState() == AbleState.Active) init(); }
This method will be called when the bean is read from a serialized file. It need not concern itself with reconstructing data from any superclass.
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 called but if no file to watch is specified initialization will not be performed. When a serialized FileWatcher is constructed, the filename may exist. Several constructor formats are provided, including a convenience construction in which one can specify the name of the file to watch when the file watcher is created.
public AbleFileWatcher(String theBeanName) throws AbleException { super(theBeanName); reset(); init(); }
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 AbleException { if (file != null) { // no point in doing because we just start processing so it goes // straight to active/waiting super.init() } }
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 sets default Able processing options.
public void reset() throws AbleException { // 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 // Enable timer processing, but not asynch events or data flow setTimerEventProcessingEnabled(true); setSleepTime(5000); // call processTimerEvent() every 5 seconds setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled); setDataFlowEnabled(false); }
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 AbleException { 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 AbleException { if (checkCondition()) { performAction() ; } }
Able requires environmental changes typical to other Java packages for compiling class files. Review the environment discussion if necessary to see what changes to make to your CLASSPATH. Because Able is designed to create distributed agents, you may need to use the rmic java tool for compiling the RMI skel class files as follows:
rmic -d . packagename.directory.beanname