Creating An AbleBean

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:

  1. The bean class file
  2. The bean info file
  3. The bean customizer file

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.

A Bean Wrapper

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:

  1. AbleBeanWrapper.java
  2. AbleBeanWrapperBeanInfo.java
  3. AbleBeanWrapperCustomizer.java

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:

  1. When your algorithm is constructed; these methods should go in the AbleFortuneTeller default constructor.
  2. When your algorithm is initialized, and ready to begin processing; these methods should go in the AbleFortuneTeller init() method.
  3. What to call if you interrrupt processing and want to reset; this logic should appear in the AbleFortuneTeller.reset() method. Typically its good to define default properties in reset, and you may wish to call this method from the constructor as well. You may wish to set properties in reset without sending property change events if you call reset from the constructor.
  4. What to call when processing data, which will be called from the AbleFortuneTeller.process() method.
  5. If you want to support timer events, is there any additional logic to call beyond what you call when processing data from the AbleFortuneTeller.processTimerEvent method.
  6. Which of your member variables you want to display on the customizer screen, and whether you want to implement that screen as a separate class, inner class, or customizer member variable. This logic will be entered into setPropertyName and getPropertyName methods in the AbleFortuneTeller class. Definining these methods will allow the property to be viewed in an AbleInspector.

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.

A Bean With Data Buffer Connections

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.

Constructor 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();
  }

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
  }

reset()

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);
  }

process()

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
  }

setSimpleBeanColor

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.

getSimpleBeanColor()

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.

readObject()

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.

 

A Bean Using Timer Event Processing

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:

  1. Click on the AbleFileWatcher icon on the Samples tab to create an instance of the bean.
  2. Right click on the bean, and select Properties... from the context menu.
  3. Select the tab that says File Watcher on the customizer that appears.
  4. Use the Browse... button to pick any file. Now the Initialize button is enabled.
  5. Select a condition of Threshold and specify a size of 1 byte. Once initialized, the bean will act anytime the file you selected is greater than 1 byte.
  6. Select Show an alert dialog for the action that the bean is to perform.
  7. Press the Initialize button. Assuming the file you chose was greater than a byte in length, in a few seconds a dialog will appear because this button calls the init() method which starts timer processing.
  8. Press Continue until you tire of being told your file threshold has been reached. Then press Quit to end processing.

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.

Constructor methods

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();
}

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()    
       }
}

reset()

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);
}

processTimerEvent()

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(); }

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() ;
  }
}

Compiling your Classes

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