This page documents the method overrides and APIs for writing a component in J-Sim/ACA. We assume that the reader has a basic understanding of ACA, the documents of which can be found in
A component in J-Sim (1) is passively triggered by arrived data, (2) is resettable, (3) is cloneable, (4) is diagnosable and (5) may actively act as a data source. To realize all these features, a set of methods has been defined in drcl.comp.Component. These methods should be overridden in a sub-class and are described in the section of Method Overrides.
drcl.comp.Component provides a rich set of APIs
to facilitate writing of a component subclass. These APIs are
divided into two parts and described in the sections of Basic
Component API and Advanced Component API
respectively.
The process() method is invoked to handle arrived data at a port:
protected void process(Object data_, Port inPort_)
{
...
} |
The reset() method is invoked to reset the component (and the child components) to the initial stage:
public void reset()
{
super.reset();
...
}
|
The duplicate() method is invoked by a component to duplicate the content of another component:
public void duplicate(Object source_)
{
super.duplicate(source_);
...
}
|
A subclass should override the info() method to provide useful runtime information of the component.
public String info()
{
...
}
|
A component may be actively started and act as a data source. Such a component must implement the drcl.comp.ActiveComponent interface and override at least the _start() method. An ActiveComponent may also override the _stop() and _resume() methods if it supports such functions:
public class FooComponent extends drcl.comp.Component implements ActiveComponent
{
...
protected void _start()
{
...
}
protected void _stop()
{
...
}
protected void _resume()
{
...
}
...
}
|
A component sends data at a port using one of the following methods of drcl.comp.Port:
public void doSending(Object data_); public Object sendReceive(Object data_); public void doLastSending(Object data_); |
The first method is sufficient in most cases. The second one is a "blocked" call,
i.e., the
call is blocked until the peer component returns a result. The same operation can be achieved by
invoking the doSending() method and then handling the result in
the process() method override. However, sendReceive()
preserves the entirety of the code context, and facilitates the debugging and
maintenance process..
The last method is provided to fine tune the performance. It is the same as doSending()
except that the method also notifies the runtime that the current thread is about
to complete its task (e.g., sending the last piece of data) so that the runtime can
re-use the thread for better performance. (See the section of finish() for details.)
A component may add a port using one of the following methods:
public Port addPort(String groupID_, String portID_, boolean removable_); public Port addPort(String groupID_, String portID_); public Port addPort(String portID_, boolean removable_); public Port addPort(String portID_); public Port addPort(Port port_, String groupID_, String portID_, boolean removable_); public Port addPort(Port port_, String groupID_, String portID_); public Port addPort(Port port_, String portID_, boolean removable_); public Port addPort(Port port_, String portID_); |
The 1st and 5th methods are of the most complete form, where the default port group is used
if no group ID is given.
The removable_ flag indicates whether or not the port is "pinned" (with the flag set to false)
so that a user cannot remove it using the removePort() method. This
is especially important when the port is used to
distinguish the incoming data. Consider the following component:
public class FooComponent extends drcl.comp.Component
{
Port port1 = addPort("port1");
Port port2 = addPort("port2");
...
protected void process(Object data_, Port inPort_)
{
if (inPort_ == port1) {
// handle data from port1
...
}
else if (inPort_ == port2) {
// handle data from port2
...
}
...
}
}
|
Since port1 is not pinned, if a careless user writes the following code to remove port1 and add another port (which is also named port 1), the (inPort == port1) clause above will never be executed:
FooComponent foo_ = new FooComponent("foo");
foo_.removePort("port1");
foo_.addPort("port1");
|
This is because the variable "port1" in "foo" now references to another port (being removed) than the port with ID "port1". To prevent this from happening, we should "pin down" the ports when they are created:
public class FooComponent extends drcl.comp.Component
{
Port port1 = addPort("port1", false);
Port port2 = addPort("port2", false);
...
protected void process(Object data_, Port inPort_)
{
if (inPort_ == port1) {
// handle data from port1
...
}
else if (inPort_ == port2) {
// handle data from port2
...
}
...
}
}
|
The methods listed below are special forms of addPort(). They facilitate adding
of ports
of the event, fork and server types respectively1:
1 A port can be of type "in", "out",
"in&out", "event", "fork", or "server". By default, a port is of type "in&out".
(See Appendix A for details.)
public Port addEventPort(String groupID_, String portID_); public Port addEventPort(String portID_); public Port addForkPort(String portID_); public Port addServerPort(String groupID_, String portID_); public Port addServerPort(String portID_); |
A component may get the system time by the getTime() method:
public double getTime(); |
The system time is maintained by the (associated) runtime environment. It is in
the unit of second and is initialized to zero
when the runtime commences. The system time is different from the wall time.
To get the wall time, one should use java.lang.System.getCurrentTimeMillis().
drcl.comp.Component defines a set of methods to
facilitate exporting of the debug, garbage, event
and error messages:
public void debug(Object message_); public void drop(Object data_); public void drop(Object data_, String description_); public void Port.exportEvent(String eventName_, Object eventObject_, String description_); public void Port.exportEvent(String eventName_, double value_, String description_); public void error(java.lang.Object data_, String method_, Port p_, Object error_); public void error(String method_, Object error_); |
Before exporting a message, one should check the corresponding flag to see if
exporting of such message has been enabled. For example, an isDebugEnabled()
check should precede exporting of a debug message.
if (isDebugEnabled())
debug("foo got something!");
|
To export an event message, one should use Port.isEventExportEnabled()
with Port.exportEvent():
...
Port anEventPort = addEventPort("anEvent");
...
if (anEventPort.isEventExportEnabled())
anEventPort.exportEvent("GOT_SOMETHING", data_, "foo is ready for next!");
|
A component may send a message to itself at a future time instant, which we term as setting up an asynchronous event or a fork event (since each message is processed in an individual thread context):
public ACATimer fork(Port port_, Object data_, double relativeTime_); public ACATimer forkAt(Port port_, Object data_, double absoluteTime_); |
drcl.comp.ACATimer object which provides the
getTime() method that the component can use to look up the time
when the message will arrive. Also, the component can use the
ACATimer object to cancel the fork event using the cancelFork()
method as follows:public void cancelFork(ACATimer timer_); |
A component may send a message at a future time instant using one of the following methods:
public ACATimer send(Port port_, Object data_, double relativeTime_); public ACATimer sendAt(Port port_, Object data_, double absoluteTime_); |
Same as the fork methods, both methods return an ACATimer object
for looking up the time, and the event can be cancelled with the
cancelFork() method.
A component may put the current thread into sleep with one of the following methods:
public void sleepFor(double relativeTime_); public void sleepUntil(double absoluteTime_); |
Performance note:
In most cases, sleep can also be achieved by invoking the asynchronous fork
method. The fork method yields better performance while the sleep
method
preserves the entirety of the code context. Examples demonstrating the use
of sleep and fork are given in 6.1 and
6.2.
To better control the active threads in the system, J-Sim provides a set of thread synchronization methods to replace the ones currently provided in Java(TM):
protected void wait(Object o_); protected void notify(Object o_); protected void notifyAll(Object o_); protected void lock(Object o_); protected void unlock(Object o_); |
The first three methods replace Object.wait(), Object.notify() and Object.notifyAll(). The last two replaces the synchronized clauses in the Java language.
The following (static) method in drcl.comp.Component can be used to bind a contract to a port:
protected static void setContract(Class class_, String id_, drcl.comp.Contract c_); |
Similar to doLastSending(), finishing() is a method designed to tune the performance. It notifies the (associated) runtime that the current thread is ready to accept the next task so that the runtime can assign a new task to the thread in a more efficient manner:
protected void finishing(); |
A component may choose to be notified when another component or a port is added to, or removed from, it. First, the component must turn on the corresponding flag using the following methods:
protected void setComponentNotificationEnabled(boolean enabled_); protected void setPortNotificationEnabled(boolean enabled_); |
Then the component implements one or more of the following callback methods:
protected void componentAdded(Component c_); protected void componentRemoved(Component c_); protected void portAdded(Port p_); protected void portRemoved(Port p_); |
In J-Sim, the asynchronous events are handled by
a ForkManager (drcl.comp.ForkManager), which is part of the associated runtime. By default, all the components
share the same ForkManager instance. In a large system that contains thousands,
or more, of components (which may originate asynchronous events), the default
ForkManager
becomes a performance
bottleneck. To address this potential scalability problem, J-Sim allows a hierarchical,
and thus
distributed, ForkManager structure.
The fork manager hierarchy works as follows. When a fork event is
handed to the associated fork manager by a component, the fork manager puts the
fork event in its own queue. If the timestamp of the event is earlier than the
first event in the queue before the event is enqueued, then the fork manager
creates a special fork event and hands it to the parent fork manager. The
process repeats until the root manager is reached. In the worse case, if
all the fork events go all the way up to the root
manager, then the root manager is still subject to becoming the performance
bottleneck. However, we expect that some of them would stop at some fork
managers at lower layers of the hierarchy. The load of handling fork events is
hence distributed among the fork managers.
One may use the
getForkManager() and setForkManager() methods in
drcl.comp.Util, along with the setParent() method in
ForkManager to create the hierarchy.
This class is the base class of all the components. A component subclass must extend either this class or another component subclass.
For a component to be active as a data source, the component must implement the drcl.comp.ActiveComponent interface and then override _start()/_stop()/_resume(). By default, an ActiveComponent cannot be started again once it is started, that is, _start() is not reentrant. To make a component _start() reentrant, the component must implement the drcl.comp.RestartableComponent interface. A RestartableComponent is, by default, an ActiveComponent.
By default, a port is an execution boundary. That is, when the data is sent across the boundary, the data will be handled in a different thread context. An Extension is a component whose ports do not act as an execution boundary. That is, when the data is sent to an Extension, the data is handled immediately in the same thread context as if this component is part of the sending component (and thus the name).
Implementation Note: An Extension is a component which listens to the event of adding a port. In the portAdded() callback method, an extension turns off the execution boundary flag of the added port. So if a component extends Extension and wishes to override the portAdded() method, it must call super.portAdded() to maintain the extension property.
A WrapperComponent is a component which embeds a non-component object in it. It provides the following method to set/get the embedded object:
public void setObject(Object o_); public Object getObject() |
The class provides skeleton codes to write a component. In particular, it contains process(), reset(), duplicate(), info(), and _start()/_stop()/_resume().
The component shown below sends the "Hello World!" message every period second at the "port" port:
import drcl.comp.*;
/** This component sends "Hello World!" at "port@" every period seconds. */
public class HelloWorld1 extends drcl.comp.Component implements drcl.comp.RestartableComponent
{
double period = 0.5; // second
Port port = addPort("port", false);
/** Constructor. */
public HelloWorld1()
{ super(); }
/** Constructor. */
public HelloWorld1(String id_)
{ super(id_); }
public void reset()
{
super.reset(); // Let super class reset its fields.
}
public void duplicate(Object source_)
{
super.duplicate(source_); // Let super class copy its fields.
period = ((HelloWorld1)source_).period;
}
public String info()
{
return "Period = " + period + "\n";
}
// Starts as an ActiveComponent.
protected void _start()
{
while (true) {
port.doSending("Hello World!\n");
sleepFor(period);
if (isStopped())
wait(this);
}
}
// Resumes this ActiveComponent.
protected void _resume()
{
notifyAll(this);
}
public void setPeriod(double period_)
{
period = period_;
}
public double getPeriod()
{
return period;
}
}
|
The following script is used to test the component:
mkdir HelloWorld1 hello mkdir drcl.comp.io.Stdout stdout connect -c hello/port@ -to stdout/in@ run hello |
The script connects the HelloWorld1 to a standard output component to print the message. The following is a screen shot of a testing session (KDE2/Linux):
import drcl.comp.*;
/** This component sends "Hello World!" at "port@" every period seconds. */
public class HelloWorld2 extends drcl.comp.Component implements drcl.comp.RestartableComponent
{
double period = 0.5; // second
Port port = addPort("port", false);
/** Constructor. */
public HelloWorld2()
{ super(); }
/** Constructor. */
public HelloWorld2(String id_)
{ super(id_); }
public void reset()
{
super.reset(); // Let super class reset its fields.
}
public void duplicate(Object source_)
{
super.duplicate(source_); // Let super class copy its fields.
period = ((HelloWorld2)source_).period;
}
public String info()
{
return "Period = " + period + "\n";
}
protected void process(Object data_, Port inPort_)
{
if (!isStopped()) {
port.doLastSending("Hello World!\n");
fork(port, "Hello World!", period);
}
}
// Starts as an ActiveComponent.
protected void _start()
{
port.doLastSending("Hello World!\n");
fork(port, "Hello World!", period);
}
// Resumes this ActiveComponent.
protected void _resume()
{
_start();
}
public void setPeriod(double period_)
{
period = period_;
}
public double getPeriod()
{
return period;
}
}
|
The same test script for HelloWorld1 can be used to test this component with a change of the class name.
The drcl.comp.lib.Bouncer is a component that always bounces back what it receives. One can delay the response by specifying the delay parameter:
package drcl.comp.lib;
import drcl.comp.*;
/** This component bounces back any arrived data at the port at which the data arrives.
It keeps a counter to count the number of data arrivals. */
public class Bouncer extends Component
{
long count = 0;
double delay = 0.0;
public Bouncer()
{ super(); }
public Bouncer(String id_)
{ super(id_); }
public String info()
{
return "count = " + count + "\ndelay = " + delay + "\n";
}
public void reset()
{
super.reset();
count = 0;
}
public void duplicate(Object source_)
{
super.duplicate(source_);
count = ((Bouncer)source_).count;
delay = ((Bouncer)source_).delay;
}
public synchronized void process(Object data_, drcl.comp.Port inPort_)
{
count++;
if (delay > 0.0)
send(inPort_, data_, delay);
else
inPort_.doLastSending(data_);
}
/** Sets the counter value. */
public void setCount(long v_)
{ count = v_; }
/** Returns the counter value. */
public long getCount()
{ return count; }
/** Sets the delay. */
public void setDelay(double v_)
{ delay = v_; }
/** Returns the delay. */
public double getDelay()
{ return delay; }
}
|
Note that the above component uses the asynchronous event call send() to delay the bounce operation.
The following script inserts a bouncer between the HelloWorld1 component and the standard output component in the HelloWorld testing script:
mkdir HelloWorld1 hello mkdir drcl.comp.io.Stdout stdout mkdir drcl.comp.lib.Bouncer bouncer connect -c hello/port@ -to bouncer/port@ connect -c bouncer/port@ -to stdout/in@ run hello |
Now the message is first sent to the bouncer and then bounced to the standard output.
A port can be of type "in", "out", "in&out", "event", "fork", or "server". By default, a port is of type "in&out". A component cannot send data at an "in" port, nor can it receive data at an "out" port.
An "event" or "fork" port is essentially an
"in&out" port.
Normally, an "event" port is used to send out component events (drcl.comp.contract.EventMsg),
and a "fork" port is to receive asynchronous
(fork) events. J-Sim does not prevent ports of these two types
from doing normal sending and receiving operations. Nor does it prevent normal ports from sending component
events and/or receiving asynchronous events. It is, however, good practice not to
mix up operations of different types on the same port.
A "server" port is a special port by which a component provides a common service to the other components in the system. Please refer to Server Port for a detailed account.
* JAVA and all JAVA-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries.
~ END ~