This example shows the easiest method for creating a custom AbleAgent by extending the AbleDefaultAgent base class.
There are three java parts that must be created for any AbleAgent or AbleBean:
To see how the SimpleAbleAgent works, go to the Samples panel on the 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 "SimpleAgent". This will create an instance and place it on the canvas.
To see what is going on in the bean, right click to bring up a context (popup) menu. Select "Inspect" to open an Inspector window. This will show inputBuffer and outputBuffer as null. Move the Inspector window off to the side. Its possible to provide your own inspector which can be plugged into this context menu also.
Now bring up the context menu again and select "Process". This will start the agent thread running. Every 5 seconds a new Color value will be placed in the simpleBeanColor property and then moved to the outputBuffer. You should see the updated color string appear in the Inspector window display. This shows the asynchronous behavior of the AbleAgent.
To see how buffer connections can be used to move data between AbleBeans, create an instance of the SimpleAbleBean. Now bring up the context menu on the SimpleAbleBean and select Connections->Buffer. A rubberband will appear starting at the right side of the SimpleAbleBean. Move the mouse over the SimpleAbleAgent and click on it. You should see a connection line appear between the SimpleBean and the SimpleAgent. This means that whenever the SimpleBean places a new value in the outputBuffer, it will appear at the inputBuffer of the SimpleAgent. Also, now that we have created a buffer connection, the process() method of the SimpleAgent will not automatically place new Color values in its outputBuffer. It will copy the value from the inputBuffer to the outputBuffer.
To see this happen, we first need to put a new value in the
SimpleBean. Open the SimpleBean customizer dialog and enter
"Magenta" in the Color field. Press OK to close
the dialog and change the property. Now do a process() on
the SimpleBean (from the context menu). You should
see the new color value in an Inspector on the SimpleBean. You
should also see the new value show up in the inputBuffer of the
SimpleAgent.
The easiest way to create a JavaAgent that implements the AbleBean and AbleAgent interface is to extend the AbleDefaultAgent base class. AbleDefaultAgent is used as the base implementation class for all of the function-specific AbleAgent beans. In addition to providing concrete implementations for all AbleBean and AbleAgent methods, it also implements the AbleEventListener methods, and is fully remotable using RMI (AbleDefaultAgent extends UnicastRemoteObject via AbleObject).
We will inherit most of the behavior provided by the AbleDefaultAgent 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 AbleAgent.
Specify the following imports:
import java.rmi.*; import java.io.* ; import com.ibm.able.*; import com.ibm.able.agents.*;
We need to import the java.io. package to pick up the Serializable interface, required so we can save and load the Agent from the Able Editor. AbleAgents are also AbleBeanContainers, meaning they can contain other AbleBeans. The Able Editor will place any AbleAgents in a cascade menu under the File->New menu item. We also specify that SimpleAbleAgent implements the Runnable interface (and so has a run() method).
Any agent that extends AbleDefaultAgent should provide its own implementation of the follow methods:
First create a default zero-parameter constructor (which is a requirement for any JavaBean). Because we are extending AbleDefaultAgent which is a remote object, we must add the "throws RemoteException" clause to the constructor.
public SimpleAbleAgent() throws RemoteException { super("SimpleAgent"); init(); // some agents require parameters to initialize }
Some agents may require additional data or parameters to be specified before they can be initialized. Those agents would not call the init() method in the constructor. For example, the AbleNeuralClassifier agent needs to know the file layout so that it can construct its import bean and back propagation network bean. Its customizer provides a Generate Beans button which is enabled after required parameters are provided; when pressed, the init() method is called.
This method will initialize any local data members and resources. It also sets up the AbleEventQueue to trigger timer events every 5 seconds. When the timer pops, the beans processTimerEvent() method is called. A separate thread is created in the event queue to manage the timer behavior. No asynchronous event processing is enabled.
If 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.
public void init() throws RemoteException { // allocate the input and output buffers (if used) inputBuffer = new String[1] ; outputBuffer = new String[1] ; colorIndex = 0 ; setDataFlowEnabled(true) ;// Timer processing, but no asynch events // call processTimerEvent() every 5 seconds setSleepTime(5000); setTimerEventProcessingEnabled(true); setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled);// create/resume the event queue thread startEnabledEventProcessing(); }
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.
public void reset() throws RemoteException { init() ; }
This method is the main processing method where the bean does its stuff. For beans that 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. 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 RemoteException {// move any data into the inputBuffer processBufferConnections() ; // perform the main processing here Object in = getInputBuffer(0) ; // see if there is data in the input buffer if (in != null) { // copy to the color parameter simpleBeanColor = (String)getInputBuffer(0) ; } else { // calculate the color parameter // just cycle through the colors colorIndex = colorIndex % colors.length ; simpleBeanColor = colors[colorIndex++] ; }// 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 we changed }
Most AbleAgents define their own processTimerEvent() method which handles asychronous behavior. The SimpleAbleAgent contains an AbleEventQueue object (in base class AbleDefaultAgent) which has a local thread for the timer. When the timer pops (time is specified in the init() method above), the processTimerEvent() method is invoked on a separate thread. In this example, we simply call the process() method.
public void processTimerEvent() throws RemoteException { process() ; }
The bean info file contains information about the properties and methods supported by the bean. There are a couple of bean attributes used by the Able Editor to position the beans on the icon palette ("able-category") and on the canvas ("able-slot"). The bean info file specifies the name of the bean class itself, the customizer class, and the icon (*.gif) files. The bean info file extends the java.beans.SimpleBeanInfo class, and overriding the getBeanDescriptor, getIcon, and getAdditionalBeanInfo methods.
This method provides the able-category, which is the name of the tab or palette to place the icon representing this object, and the able-slot, which is the horizontal column to use when adding a new bean to the canvas.
public BeanDescriptor getBeanDescriptor() {BeanDescriptor returnDescriptor = new BeanDescriptor(beanClass, customizerClass); returnDescriptor.setValue("hidden-state", Boolean.TRUE); // icon palette page returnDescriptor.setValue("able-category",Able.CAT_SAMPLES); // horizontal position on canvas returnDescriptor.setValue("able-slot", "2");return returnDescriptor;}
This method provides the image for the requested size and color content.
public java.awt.Image getIcon(int iconKind) {switch (iconKind) {case BeanInfo.ICON_COLOR_16x16: return iconColor16x16Filename != null ? loadImage(iconColor16x16Filename) : null; case BeanInfo.ICON_COLOR_32x32: return iconColor32x32Filename != null ? loadImage(iconColor32x32Filename) : null; case BeanInfo.ICON_MONO_16x16: return iconMono16x16Filename != null ? loadImage(iconMono16x16Filename) : null; case BeanInfo.ICON_MONO_32x32: return iconMono32x32Filename != null ? loadImage(iconMono32x32Filename) : null;} return null; }
This method finds the parent class, retrieves its BeanInfo object, and returns it.
public BeanInfo[] getAdditionalBeanInfo() {Class superclass = beanClass.getSuperclass(); try {BeanInfo superBeanInfo = Introspector.getBeanInfo(superclass); // AbleObject info return new BeanInfo[] { superBeanInfo };} catch (IntrospectionException ex) {ex.printStackTrace(); return null;}}
The easiest way to create a customizer is to extend the AbleDefaultAgentCustomizer base class. Since the SimpleAbleAgent extends AbleDefaultAgent, extending that customizer allows you to inherit panels and controls that can change properties of the AbleDefaultAgent. The SimpleAbleAgentCustomizer then has only to create and manage a panel for data specific to the SimpleAbleAgent. Generally a customizer must be a subclass of the awt Component class. In the Able environment which is based on the Swing GUI library, we support JPanel, JDialog, and JFrame. A customizer that is a subclass of JDialog or JFrame will be instantiated and then opened using the show() method. Simple beans will typically extend one of the provided customizers, while more complex beans will extend JPanel directly or may require complex dialogs or even full-blown editor windows with menu bars and icon palettes.
In the SimpleAbleAgent customizer, we extend the AbleDefaultAgentCustomizer, and show how you can support a text field name, a button to reset the agent, and a property change listener to detect and display when the color property changes. Note that the property change listener is registered with the base customizer which removes the listener when the window closes.