In this chapter it is explained how the interaction of Jadex agents with other system components that are not necessarily agents can be done. For this purpose it is shown how agent internals can be accessed from other (non-agent) threads (cf. Section 15.1, “External Processes”) and additionally how agent listeners can be employed to get notified whenever changes within the agent happen (cf. Section 15.2, “Agent Listeners”).
A Jadex agent is synchronized in the sense, that only one plan step at a time is executed (or none, if the agent is busy performing internal reasoning processes). Sometimes one may want to access agent internals from external threads. A good example is when your agent provides a graphical user interface (GUI) to accept user input. When the user clicks a button your Java AWT/Swing event handler method is called, which is executed on the Java AWT-Thread (there is one AWT Thread for each Java virtual machine instance). To force that such external threads are properly synchronized with the internal agent execution, you are not allowed to call Jadex methods directly from those threads. If you try to do so, a runtime exception “Wrong thread calling plan interface” will be thrown. On the contrary, it is allowed to call the external access from any thread (including plan resp. agent threads).
The AbstractPlan class provides a method getExternalAccess()
which returns an accessor which automatically does the necessary thread synchronization.
This accessor implements the ICapability
interface, providing access
to all features of the capability (beliefbase, goalbase, etc.).
In addition, some convenience methods
are provided to wait for goals to be completed or messages to be received. These
methods should be used with caution, as they could easily lead to deadlocks.
To avoid at least one source of deadlocks, it is not possible to call blocking methods on
this accessor from the plan thread. Whenever you call the wrong object from
the wrong thread, a RuntimeException will immediately identify the problem.
The following code presents an example where a belief is changed when the user
presses a button.
public void body() { ... JButton button = new JButton("Click Me"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // This code is executed on the AWT thread (not on the plan thread!) IBeliefbase bb = getExternalAccess().getBeliefbase(); bb.getBelief("button_pressed").setFact(new Boolean(true)); } }); ... }
Figure 15.1. External process example
Agent listeners can be used to get informed whenever agent state changes happen. Normally, listeners will be employed in agent external components such as a GUI for getting information about declared elements. A GUI e.g. could use a listener to update its view with respect to belief changes in the agent. Generally, for all important agent attitudes such as belief, plans and goals as well as the agent itself different listener types exist (see Table 15.1, “Available listeners”).
Depending on the listener type different callback methods are provided
that are automatically invoked when relevant changes happen.
Whenever a callback method is invoked a so called AgentEvent
is passed
and contains relevant information about the change that happened. It
basically offers the two methods getSource()
and getValue()
. The source here is the originating element
of the change event. For belief and beliefset changes, the agent event additionally contains
the changed fact object, accessible by getValue()
.
The invocation of listener methods can happen either on the agent thread or on a
separate thread. If the notification is performed on the agent thread
it is not possible to use blocking calls such as
dispatchTopLevelGoalAndWait()
. This is
only allowed if asynchronous listeners are used. The decision to use
a synchronous or an asynchronous listener is made when the listener is
added.
The addition and removal of listeners can be done either on the instance
elements themselves (e.g. a goal) or on the bases (e.g. the goalbase).
In case the listener shall be added on an instance element it is only
necessary to pass the listener object itself and the asynchronous flag
as parameters of the call (e.g. addBeliefListener(
IBeliefListener listener, boolean async)
).
In case a type-based listener shall be used e.g. for getting informed about
new goal instances in addition to the parameters aforementioned also
the type needs to be declared (e.g. addGoalListener(
String type, IGoalListener listener, boolean async)
).
In the listener example below (see Figure 15.2, “Agent listener example”) it is shown how a belief listener can be directly added to a "name" belief via the external access interface. It is used to update the value of a textfeld whenever the belief value changes.
IExternalAccess agent = ... agent.getBeliefbase().getBelief("name").addBeliefListener(new IBeliefListener() { public void beliefChanged(AgentEvent ae) { textfield.setText("Name: ["+ae.getValue()+"]"); } }, false);
Figure 15.2. Agent listener example
Table 15.1. Available listeners
Listener | Element | Listener Methods |
---|---|---|
IAgentListener
|
ICapability
|
agentTerminating() [a]
|
IBeliefListener
|
IBelief
|
beliefChanged()
|
IBeliefSetListener
|
IBeliefSet
|
factAdded() ,
factRemoved() ,
beliefSetChanged() [b]
|
IConditionListener
|
ICondition , IExpressionbase
|
conditionTriggered()
|
IGoalListener
|
IGoal , IGoalbase
|
goalAdded() ,
goalFinished()
|
IInternalEventListener
|
IEventbase
|
internalEventOccurred()
|
IMessageEventListener
|
IMessageEvent ,
IEventbase
|
messageEventReceived() ,
messageEventSent()
|
IPlanListener
|
IPlan ,
IPlanbase
|
planAdded() ,
planFinished()
|
[a] The event source will always be the terminating agent itself (i.e., the root capability) even when registering the listener on a subcapability. [b] The value of the agent event will be null when all facts have changed, e.g., due to a dynamic facts expression. |