Chapter 15. External Interactions

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”).

15.1. External Processes

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

15.2. Agent Listeners

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

ListenerElementListener 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.