Agent Roadmap

February 2, 2012

1      Introduction

You will be working with a lot of code in this class. Not just code that implement algorithms, such as a sort or search, but system code. System code solves large problems and requires organization, documentation, test procedures, and maintenance.

NOTE: The examples in the roadmap may not match exactly the code you are working on; the actual code is an evolving thing. You can let me know about any discrepancies that you find.

1.1    What is a Roadmap?

Systems have many interacting components. It is not enough just to document each one. We need a description of what the system does, how it works, what its components are, what each component is responsible for, and how they interact. We need this roadmap to help us navigate through the system.

Besides a functional explanation, the roadmap also tells us if there is any design philosophy and where to look for things. A roadmap is a guide to aid your understanding of the system in question. Without a roadmap you have only the code to look at; you’ll soon see how difficult that is.

1.2    What are Agents?

Agents are autonomous reasoners that receive input, think about the state of their world, and then do things. You are completely familiar with agents because you are one. Think about it. You are part of a world, you receive input through your five senses, and you act on the input.

·         Sometimes you act immediately—pulling your hand away reflexively from a fire.

·         Sometimes you simply store the input away for later use—studying for a test.

·         Sometimes you react to the input by doing something—seeing a green light and proceeding forward.

·         Sometimes you queue up things to do later—putting a dinner date together for next week after getting a telephone call.

·         Sometimes you do nothing—your teacher gives you advice and you ignore it.

·         Sometimes you delegate—you get work and give it to others to do.

So, people are agents, but agents have a software expression as well. And that’s what this document is all about.

1.3    Autonomy, Processes, and Threads of Control

Simply stated, autonomy asserts independence. In particular:

·         A private memory, and

·         The ability to do what it likes when it want to.

As mentioned before people behave autonomously. Our brain is private and we can do what we want (although we often get advice on how to behave). A computer process is close to being an autonomous entity. Its address space—its code plus data—is generally private, and while the CPU is executing it, its code does what it wants.

Usually a process has a single thread of control. Without going into a lot of detail, that means the CPU has one program counter, a pointer to a current instruction within the process. Multi-threaded processes have several program counters, all executing in the same address space. A single-threaded process is doing one thing at a time; a multi-threaded process is doing several things concurrently.

When people say they can multi-task, e.g., drive and talk on their cell phone at the same time, they mean that they are multi-threaded. We can argue about the reality, but it is more than that. As agents, we can receive input, think, and do things simultaneously. In other words agents have at least three threads of control: one for receiving input, one for thinking about what to do, and one for doing.

1.4    What is an Agent Application?

An agent application is just that, a collection of agents that solves some problem. Again, you know such systems well because you are part of them in real life. Consider a restaurant as a system of agents working together to achieve some goals. There are waiters, busboys, cooks, and cashiers all trying to feed customers. Each type of agent responds to a variety of inputs and has its own actions. Cooks receive inputs from waiters about food orders, cook the food, and tell waiters when the food is ready. Cooks also prepare menus, shop for food, and so forth. Waiters are different. They talk to customers, take their orders, deliver food to them, and give them the check. Each type of agent has its own responsibilities.

An agent application is a collection of loosely coupled agents. That is, agents make requests of one another and give each other information. Then, the individual agent thinks about those input, thinks of actions to do, and does them when it thinks appropriate. The decoupling of the agent’s actions from the requests and data it receives is what makes them loosely coupled. This kind of behavior defines autonomous. [Autonomous also implies its own thread of control. Much more about that later.]

A tightly coupled application is the opposite. The subroutine or method invocation protocol you are familiar with from your programming classes produces tightly coupled applications. That is:

1.      The caller invokes a method, [for the rest of this document, I will use the term “method invocation” or “subroutine call” interchangeably.]

2.      The caller waits while the method executes,

3.      The caller continues once it gets the return value from the method invocation.

Notice that the caller waits for the method to complete, while the method has no life of its own until it is called. This kind of protocol is often called synchronous. Of course, a software agent uses method calls internally, but its external interface—the interface used by other agents—is logically asynchronous. Agents do not respond immediately through a return value. [Agents use messaging to communicate. Much more about that later as well.]

Your first project will use a restaurant application because it is so familiar. We have built many agent-based software applications. One of the first was an operating system for a time-sharing computer. Another was an agent-based controller for computer numerically controlled machines (CNC). And we built many for use in factories. Our factory floor applications had robot agents, conveyor agents, and camera agents, among others.

As gaming techniques becomes more important in serious gaming applications, you will see that agent programming techniques will drive most of them. Sophisticated games often have many independent players, objects, and other components with separate behaviors. Managing all that without agents is hard. We maintain that agents can be used for many, if not all, applications.

1.5    What is an Agent Framework?

A framework is an environment that forces some decisions on you. By accepting those decisions some capabilities are specialized, emphasized, and simplified. For example, Java is a programming framework that imposes an object-oriented methodology. If you choose Java, you are forced to organize your code into classes within inheritance hierarchies, use polymorphism, etc. Java is convenient to use for O-O programming. If you don’t like or want O-O, then you should choose some other programming framework.

An agent framework is an environment that supports the design and programming of agent applications. It’s that simple. We have built agent applications using assembly code, C, Java and manufacturing languages. A framework does not imply a programming language.

Frameworks can be elaborate (with an IDE, extensive libraries, GUI generators, and so forth) or fairly simple (a few base classes and a roadmap, like this framework). However it takes form, the goal of an agent framework is to make agent programming easier.

2      General Requirements for Agents

There is no universal set of requirements for agents. Most definitions seem to mention autonomy and loosely coupled. That’s about it. Each agent designer has his own conception of how to build an agent. Here is mine.

2.1    How Agents Communicate

Agents communicate by sending messages to one another. They share no data. Sending real mail is an appropriate metaphor for agent messaging. Here is what happens with real mail:

1.      The sender composes a message on paper. The sender and recipient decide upon the format of the message. Obviously, the sender expects the recipient to understand the message.

2.      The sender puts the message in an envelope, addresses it to the recipient, and gives it to the Postal service.

3.      The Postal service delivers it to the mailbox of the recipient.

4.      The recipient processes mail at his convenience: opening one message at a time, reading it, and doing what he feels like in response.

There are several important things to note about this scenario:

·         While the message is being composed, the sender has full control of its contents.

·         Once the recipient has the message, he has full control of the contents.

·         The data in the message is NOT SHARED data. The receiver can only assume what is stated in the message, a subtle but obvious point. For example, if the message says, “I have $20,” the receiver can only assume that at the time of the writing, the sender had $20. He cannot assume that the sender has $20 at the time of the reading.

·         The receiver cannot assume that the sender is waiting for a reply. Wouldn’t that be ridiculous in the real world? Once the sender mails the message, he goes off to do other things. That is typical of agents; they are not synchronized with one another; hence the term loosely-coupled.

·         The receiver does not have to act immediately on the message. For one thing, the message might be a status update, such as “I have processed 20 orders today.” That may be important to the receiver, but no action is required. If the message was a request, such as “Please check on the availability of product X,” the recipient might do it when he has time. After all, he may be busy, he may delegate it another agent, or he might ignore the request altogether.

Computer Science has formalized message-sending protocols between processes. Computer message systems are very much like real mail systems:

·         The mailbox of the recipient is a persistent queue. That means messages are stored permanently until removed by the recipient.

·         The recipient process removes messages from the queue when it wants to.

·         The recipient parses the message according to whatever format was decided upon. Typically, the recipient will have some kind of case or switch statement that dispatches control to a message handler once the message is parsed. What the message handler does is dependent on the contents and the application.

·         This protocol is asynchronous. The message-sending method call may return a value, but the value only indicates the success or failure of the mailing event.

Here’s some pseudo-code to show how messaging works. [We will discuss this pseudo-code in detail in class.]

//Sender pseudo-code for constructing and send a message

from = me;

to = theCook;

Message m = new Message(from, to);

m.insertCommand(“Prepare”);

m.insertParameter(2); //table number

m.insertParameter(“Hamburger”);

m.send();

... //continue on with other work.

 

 

//Recipient pseudo-code for receiving and parsing a message

While (queue is not empty) do {

Message m = queue.getNextMessage();

from = m.getRecipient();

command = m.getCommand();

switch command {

“Prepare”: {//Prepare command has two parameters

table = m.getNextParameter();

item  = m.getNextParameter();

Prepare(from,table,item);

/*Prepare() is the message handler

that we dispatch to after parsing.

We will have a lot to say about how

Prepare works in both Agent and

non-agent systems.*/

break; //done with Prepare

   }

someOtherCommand”: {

            ...

}

...

   }

}

Real messaging is complicated. Do we really have to do all that?  What do we do if the queue is empty? Good questions. We’ll talk about these issues later in the implementation.

2.2    How Agents Behave

An agent has three parts, message reception, a scheduler, and a collection of actions. Each part could logically have its own thread of control because, since each can act concurrently. My reality has shown that the scheduler and its actions can be in the same thread, i.e., synchronous. So, the schedule picks and action and executes. When the action is done, the scheduler can pick another action. Threads are NOT part of the agent design; I have built agent applications in single-threaded environments.

Message Reception

Message Reception identifies the messages this agent can receive from other agents--the external interface. For each message type, there will be a dispatch method that corresponds to it. The method name identifies the message type and the parameters of the method are the parameters of the message. Only state setting happens in the message reception methods.

Message reception is an ongoing, concurrent activity within an agent. In other words, the agent will be receiving messages while it is scheduling and performing actions. As we’ll see, this requirement will cause synchronization issues because the three parts of an agent DO share data.

Scheduler

The scheduler looks at its internal state to decide what action to run--it is the brains of the agent. It uses whatever intelligence it needs to select an appropriate action. The internal state of an agent is completely application dependent. It is up to you, the agent designer, to decide what variables and objects you need in order to maintain the state.

For example, a host agent probably has a list of customers waiting to be seated. When the host's scheduler realizes that a table is available, it should schedule the SeatCustomer(customer, table) action.

If the scheduler determines it has nothing to do, it needs to check again only when its internal state changes. Note that this is not really a requirement, just logically obvious. The internal state can only change by way of an incoming message or while carrying out an action. There are several other scheduling considerations that we’ll discuss when looking at a concrete scheduler in section 4.2.2.2.

Actions

An action is the agent code that does something. For example, the SeatCustomer(customer, table) action will (1) send a message to a waiter saying, “Seat this customer at this table.” and (2) update its state to remove the customer from the waiting list and (3) mark the table as occupied. The waiter will then take "ownership" of the customer and do what a waiter should do.

Actions are scheduled by the scheduler. After the agent has finished an action, the scheduler tries to pick another, perhaps the same, action. This goes on as long as the agent is active.

Of course, while that is going on, the agent is receiving message concurrently. We’ll see what being “active” means later.

2.3    What’s the Output? What do Agents do in Actions?

In a factory application a Robot agent may decide to open its gripper in preparation to picking up a part. The action code for the robot agent would actually energize an input on the physical robot that tells it to open its gripper. Our agent code interfaces to the devices through these DoXYZ() primitives.

We don’t have a physical robot in our class. We have two options:

·         We can simulate the physical robot. We can build an animation of the application setting. Then when we want to open the gripper, we'll call a DoOpenGripper() method which will instruct the animation to open the gripper on the animated robot. You will do this later during the factory project.

·         If there is no animation, we will use a DoXYZ() method that takes as a string the primitives that the agent can actually carry out. The DoXYZ() method in our application will only print what the agent would be doing if it were real. So, when we energize the gripper input, we simply call DoOpenGripper("Open gripper”), which would print the result in the trace. Since all the print statements go to the same place, we will see all the agents print statements interleaved as they are happening. This will give us a script of what all the agents are doing concurrently.

Only Actions can call DoXYZ() methods. You will be tempted to call them in message reception, but doing so is a serious mistake that we will discuss in class.

2.4    Managing an Application Made up of Agents

Thus far this section has discussed agent requirements, i.e., how an agent works and how one agent communicates with another agent. We also need to answer some configuration questions about an application of agents.

·         What agents are in an application? Someone needs to select and deploy the agents in the application.

·         How do agents find one another? Agents cooperate with one another in an application. How does an agent know the identity of the other agents in the application? How do agents enter and leave an application? Agent researchers worry about these and other tough questions. We’ll do it more simply as we’ll see.

·         How do agents get specific configuration information? An individual agent may need information in order to carry out its work. The information could be hard coded in the agent. More likely, it must come from an outside source. We have two ways to do configuration:

1.      A Main Routine – In our v1.0  prototype all the agents can be created and managed from a single Main() routine in a program called Main.java.

2.      A Control Panel – v1.0 also contains a GUI control panel that will allow the user to interactively manage the application. v3.0 has a control panel and full restaurant animation.

2.5    Deploying a System of Agents

Systems of agents can be deployed in different ways to suit various non-functional requirements. Since a theoretical agent is a separate process, it can run on its own computer. You just have to find a message protocol (or an accommodation) to enable the communications. Theory aside, it is natural that there is quite a bit of flexibility in agent deployment strategies. Here are two:

·         All the agents run in one process, each agent is a separate thread. Messaging is simplified to be simple method calls. This is the strategy for our code.

·         Each agent is a separate process, perhaps running on separate machines. Now, messaging must be more formal, perhaps (1) using real messaging with mailboxes or (2) through a distributed method invocation technology like Corba or RMI.

The flexibility of multiple processes was critical to our manufacturing real-time systems. Some agents had to run in “hard” real-time (never miss a deadline), some were real-time (could miss an occasional deadline), and GUI agents were soft real-time (no real deadlines). We had special hardware for the first two cases, used Windows for the user interface, and a specialized real-time operating system for carrying the messages among other things. Agents could be moved from machine to machine depending on the non-functional real-time requirements. New gaming strategies will surely use hybrid techniques: bundling agents in processes, distributing agents on different CPU's within the same machine, and so on.

3      The Restaurant Application

It is now time to specify a restaurant application. That means describing all the potential agents who play a role in the application. Of course, a complete specification for a real restaurant would be a lot of hard work and beyond the scope of this class. Instead, this section gives only the most basic general requirements. Each deliverable will contain those requirements to be implemented. Remember that requirements specify what is to be done; the design describes how to do it.

Don’t lose focus just because this application is about restaurants. We are using restaurants because you are familiar with them. Designing agents is the same no matter what application you are building. Don’t worry about having to have lots of agents for an application. A large restaurant has dozens of employees. One of our factory applications had over 20 agents in it.

3.1    The Agents in a Restaurant

3.1.1    The Host

The Host greets customers among other things. Here are some of the host requirements:

·         The host assigns customers to working waiters when a table is available.

·         The host reassures waiting customers every n minutes.

·         The host handles complaints from customers.                                                     

·         The host manages waiters taking breaks, getting sick, or leaving.

·         The host closes the restaurant, does final bookkeeping, supervises cleanup, etc.

3.1.2    The Customer

The customer is the client of the restaurant. The restaurant’s job is to feed the customer and see to his every need. Customers have requirements as well. In fact, after reading the requirements below, perhaps the Customer should have been named the Person. We'll talk about this in class.

·         When he gets hungry, he goes to restaurant, prepares his own food, or visits his mother.

·         When at a restaurant he presents himself to the host. Once seated he will order from the menu, and eat the food once it is delivered. Ask for a check, pay and leave.

·         If the restaurant is crowded he might leave.

·         Perhaps he will make a comment to the host about the quality of the food.

·         Perhaps trying to impress his date, he will bribe the host in order to get a good table.

3.1.3    The Waiter

The waiter has the most complicated role in the restaurant. Since he is taking care of potentially many customers, he has to make decisions about who to service and when. Here is what he does:

·         He seats customers at tables. He takes their order, sends the order to the chef, serves the food when it is ready, and gives the customer his check.

·         If the customer doesn’t like his order, the waiter takes it back and gets customer something else.

·         When the customer leaves, he clears the table or perhaps he tell a busboy to do it, if there is a busboy.

3.1.4    The Cook

The cook cooks food. In normal circumstances:

·         When he becomes aware of an order, he cooks it and tells the waiter when the order is ready.

·         An item, though it is on the menu, might be unavailable.

·         When he runs out of food that he needs, he buys it.

3.1.5    Other Potential Agents

There are many other agents that could be in the restaurant application:

·         Busboy – This agent supports the waiter in servicing clients. The busboy often does the serving, handles simple customer requests, clears the table, etc.

·         Cashier – This agent takes the check and payment and enters it into the restaurants payment system, usually a cash register.

·         Manager – This agent may oversee the restaurant, assign employees, etc. Obviously, if a manager and host are both present, they have to divide responsibilities.

·         Head Chef – This agent creates the menu, is responsible for the shopping, oversees the kitchen, etc.

·         Sous Chef – This agent supports the head chef, does most of the actual cooking, etc.

·         Hat Check – This agent handles coats, hats, etc. Probably is only in the fanciest restaurants.

In real life, some people handle multiple roles. For example, there may be no cashier; the waiter handles those responsibilities. That’s no problem for us. You just design your agents and assign them the responsibilities you want and see if you have a “working” restaurant.

3.2    The First Restaurant Prototype - v1.0 [Optional]

[Please note: Everything to do with v1.0 is optional. In the past it was the starting point for students. Now the starting point is v3.0. Still I suggest that you look at the simple diagram below and spend a few minutes downloading, running, and inspecting v1; it's on the lecture/lab page. v1 has no animation to complicate things. If you want, you can skip to section 4.]

This first deliverable is typical of prototypes: a simple specification that “gets the application going.” No one worries too much about whether all the requirements are fulfilled, or whether it is efficient or beautiful.

3.2.1    Requirements for the Prototype

MaitreD Requirements:

Requirement 1: Greets Customer

Normal Scenario: When he becomes aware of customer entry into restaurant, he keeps track of them. If a table is available, a waiting customer is seated. MaitreD will be informed of leaving customers.

                                                                                                                                                    

Customer Requirements:

Requirement 2: Getting Hungry:

Normal Scenario: When he gets hungry, he goes to the restaurant.

Requirement 3: Eating at a restaurant:

Normal Simplified Scenario: Present yourself to the greeter. When you are seated, simulate getting food and eating it. When done, leave and inform the MaitreD.

 

General Requirements:

Requirement 4: Many Customers can come into the restaurant “simultaneously.”

Requirement 5: Restaurant has n tables. [Implies concurrent seating and eating.]

 

This is a good time to introduce you to UML interaction diagrams. These diagrams show how the objects of your system interact. In our case they will show how the agents interact, since the agents are our primary objects. At this early stage of requirements we only draw some simple pictures that show the messaging behavior for the most important scenarios. This simple prototype only has one behavior, and that is shown in Figure 1, just below.

Figure 1: Interaction Diagram for First Prototype

3.2.2    v1 MaitreD Agent Design

From a design a programmer is supposed to be able to implement the code. How much detail goes into the design? That depends on you and your programmers. Often a talented programmer does both; in that case the design will be sketchy since he is going to implement it.

A design can be programming language independent and should be. It is often like pseudo-code or logic—just enough to specify how you want the system to work. At a minimum, it should lay out the classes (or data structures in a non object-oriented design), the methods, the objects, constraints, etc. Remember, the programmer is not supposed to be making any “decisions” during implementation. He simply “codes” the design. Mostly, that never happens. Designs aren’t usually that good.

An agent design is very specific: describe the messages, actions, and scheduler. Let's start.

3.2.2.1  Input Messages

IWantFood(CustomerAgent customer)
            On receipt: Put customer on waiting customer list.
                        or to be more specific
            On receipt: waitingCustomers.add(customer);

           

 

msgLeavingTable(CustomerAgent customer)
            On receipt: Remove customer from the table he was sitting at.
                        or to be more specific
            On receipt: if ($ table in tables ' table.getOccupant() == customer) 

                         then table.setUnoccupied();

[In case those special logic symbols don't print in your browser, here is the same expression with the symbols replaced by English:]

if (there exists table in tables such that table.getOccupant() == customer) 

                        then table.setUnoccupied();

My general feeling is this:

·         On first pass of the design I tend to write the more informal statement.

·         I ALWAYS make a second pass tightening it up with real variables and logic.

The point is to get the ideas down first, and the details later. No sense bothering too much about variables and types while you are "discovering" the design.

3.2.2.2  Actions

seatCustomer(CustomerAgent customer, Table table) {

Do ("Seating " + customer + " at " + table); //Tracing

table.setOccupant(customer); //Record the customer at the table you sit him at
waitingCustomers.remove(customer); // Remove customer from waiting list.

customer.PleaseSitAtTable( ); // Send the customer a message that he is being seated.

}

3.2.2.3  Scheduling rules

Rule1: If there exists a table and customer, so that table is unoccupied and customer is waiting, then seat him at the table.

In First Order Logic, we would write:
if ($ table in tables ' ~table.isOccupied( ) and $ customer in waitingCustomers)

                        then  perform seatCustomer(customer, table);

 

[In case those special logic symbols don't print in your browser, here is the same expression with the symbols replaced by English:]

if (there exists table in tables such that ~table.isOccupied( ) and there exists a customer in waitingCustomers)

            then  perform seatCustomer(customer, table);

 

3.2.2.4  Data Structures:

List waitingCustomers; // holds customers as they arrive

Collection tables; // each member is of type Table

private class Table {

CustomerAgent occupiedBy;

int tableNumber;

 

void setOccupant(CustomerAgent cust) {}

void setUnoccupied() {}

CustomerAgent getOccupant() {}

boolean isOccupied() {}

}

3.2.3    v1 Customer Agent Design

The customer agent has a very simple, but flawed design in this prototype. It works, but I don't like it. Why not? The customer agent is an example of a finite state machine (fsm). The implementation doesn't reflect the fsm structure well. Part of the old assignment was to rewrite it. Don't worry about that now. We'll look at the proper written CustomerAgent in the v3.0 delivery.

3.2.4    A few more details

-          Keeping the agents running. Each agent has to receive messages and in parallel, must keep executing the scheduler, which invokes actions. We don't have that control structure designed yet. Since every agent will use the same structure, we'll put the control structure in the base agent, which will be described later.

-          Timer Utilities. This release includes some timer utilities found in java.util.Timer. We need the ability to set a timer for some length of time and then execute a routine when it expires. Of course, the timer runs in its own thread (make sure you understand this). We’ll explain this mechanism in class. We’ll also how to implement an anonymous inner class in Java.

-          How does the agent get hungry? The customer agent represents a very simplified person that gets hungry and goes to a restaurant to get fed. How does the agent get hungry? The agent has a setHungry() method that does this. Think of it as a message. Perhaps it might look like the following:

public void setHungry() {

event = gotHungry;

print(“I’m hungry”);

fsm(); //invoke the finite state machine, which is the schedule

}          

Here’s how we use setHungry():

In the Main.java main routine, described in the next section, the  setHungry()  method is called directly. That gets the customer going. Once he’s hungry, he goes to the restaurant, etc.

After the customer leaves the restaurant, how does he get hungry again? There are three approaches: (1) The main routine in Main.java could somehow do it, but that wouldn’t be too elegant; 2) We could wait for some time to expire and automatically get hungry again. That’s what happens in the prototype. We call the timer routine, which will then call setHungry() again. 3) We could have a user interface with a button that let’s us set the agent to be hungry. You’ll see that in the GUI.

·         How long does agent eat?  We use the same timer method to simulate eating. In other words, once the agent is seated, we simulate the eating period by setting a timer. When the timer expires, the eating is done. You will find many applications for using timers.

3.2.5  Running the v1.0 Prototype via  Main.java

The main routine in Main.java creates the agents in the prototype application, sets the maitre’d for the customers, and then sets the customers hungry. Take a look.

public class Main {

public static void main(String argv[]) {

/*

The following four lines create our four agents.

Each agent instance has a thread in which it runs.

The thread is created and started by the constructor.

*/

MaitreDAgent maitreD = new MaitreDAgent();

CustomerAgent c1 = new CustomerAgent(“Fred”);

CustomerAgent c2 = new CustomerAgent(“Ethel”);

CustomerAgent c3 = new CustomerAgent(“Ginger”);

/*

The next three lines tell the customer instances who

the maitreD is. The maitrD learns of the customers by

the first message the customer sends to the maitreD.

*/

c1.setMaitreD(maitreD);

c2.setMaitreD(maitreD);

c3.setMaitreD(maitreD);

 

maitreD.startAgentThread();

c1.startThread();

c2.startThread();

c3.startThread();

  /*

At this point the agents are started, but are not

doing anything, because nothing has happened to make

them do things. So, we (artificially) make each

customer hungry. c2 is made especially hungry (that

will make him eat longer.)

  */

c1.setHungry();

c2.setHungerLevel(7);

c2.setHungry();

c3.setHungry();

  /*

This main thread ends leaving the agents to “fend for

themselves.” You will see that after a customer is fed

and not hungry, it will set a timer to set it hungry

again later. That’s why this prototype runs forever.

  */

  }

}

To run this code, connect to the release directory and type:  ant run

You should see output like this:

     [java] Fred: I'm hungry

     [java] Ethel: I'm hungry

     [java] Ginger: I'm hungry

     [java] Ethel: Going to restaurant

     [java] Fred: Going to restaurant

     [java] Ginger: Going to restaurant

     [java] MaitreD: Seating customer Ethel at table 1

     [java] Ethel: Received msgSitAtTable

     [java] MaitreD: Seating customer Fred at table 2

     [java] Fred: Received msgSitAtTable

     [java] Fred: Being seated.

     [java] Fred: Eating for 5000 milliseconds.

     [java] Ethel: Being seated.

     [java] Ethel: Eating for 7000 milliseconds.

     [java] Fred: Done eating, cookie=1

     [java] Fred: Leaving.

     [java] MaitreD: customer Fred leaving table 2

     [java] MaitreD: Seating customer Ginger at table 2

     [java] Ginger: Received msgSitAtTable

     [java] Ginger: Being seated.

     [java] Ginger: Eating for 5000 milliseconds.

     [java] Ethel: Done eating, cookie=1

     [java] Ethel: Leaving.

3.2.6    Running the v1.0 Prototype with the GUI

The restaurant application as run in the first deliverable is obviously simplified. It was just a quick stop along the development of our restaurant. This is the right time to REPLACE the functionality of the old main() routine of Main.java with a GUI.

As a replacement for the old main() routine, we can think of the GUI as a configurator for the restaurant. That is, it has “buttons” to control the activities and objects in the restaurant. It can also show us status and object information for the application. For this release we want it to be able to do everything that Main.java  could do, except with GUI flexibility. The requirements will give the details.

Here are the minimum requirements for the gui.

·         GUI can create the MaitreD agent.

·         Customers can be added.

·         Customers can be set to hungry. (The timing hack in the customer agent that makes him hungry after some amount of time should be removed.)

To run the gui, connect to the release directory and type:  ant run.gui

This gui was designed by a student in one of the previous classes.

3.3    v3.0 - A specification of a restaurant

v3.0 is an implementation of a restaurant. Of all the requirements specified in the section above, some have been selected for design. This is typical during early system specification. First a system is fully specified, then for each implementation (or prototype) some subset of the requirements is selected for design and implementation. Succeeding iterations might add (or change) requirements.

3.3.1    Customer Requirements

Requirement 1: Getting Hungry:

Normal Scenario: When he gets hungry, he goes to restaurant and tells the Host he wants to eat.

Requirement 2: Eating at a restaurant:

Normal Scenario: Present himself to the host. When he is seated by his waiter, he will be presented with a menu. Tell the waiter when you are ready to order, and then tell him your choice. When you get your food, eat it. Tell the waiter when you are done and leaving the restaurant.

There can be many customers.

3.3.2    Host Requirements

Requirement 1: Greets Customer

Normal Scenario: When he becomes aware of customer entry into restaurant, he puts customer on waiting list. If table is available, then he tells a working waiter to seat the customer at the free table.

 

Requirement 2: In charge of tables.

Knows about the tables in the restaurant. Assigns customers to tables. When customer leaves, the waiter will inform the Host that the table is free.          

There will be only one host.

3.3.3    Waiter Requirements

Requirement 1: Services Customer

Normal Scenario: Host tells waiter to seat the customer at a table. Once seated the waiter gives the customer a menu. When the customer is ready to order, the waiter takes the customer’s choice. He gives the order to the cook. When the order is ready, the waiter serves the customer. When the customer is done, he tells the waiter that he is leaving. [No check or money collection in v3.0]

There can be many waiters. See section 11.3 about menus.

3.3.4    Cook Requirements

Requirement 1: Cooks orders       

Normal Scenario: Receives orders from the waiter. Cooks them. When an order is ready, the appropriate waiter is notified.

There will be only one cook.

3.3.5    Interaction Diagram

An interaction diagram shows the EXTERNAL behavior of object oriented systems. In agent systems the interaction diagram shows the messaging behavior over an instance of the running system. In general, there is one interaction diagram for each scenario. A system is normally described by many such diagrams.

 

Each diagram should be simple. There is no branching or alternatives. You can make comments on the diagram. See section 3.1 for an example of an interaction diagram. We will do the v3.0 diagram in class.

3.3.6    GUI and Animation

The v3.0 application comes with a GUI and animation. From the GUI:

  • Customers and Waiters can be created.
  • Customers can be set to Hungry.
  • Waiters can be set to go on and off breaks [you will implement it later.]

I will do the cook and customer agents in class. You will reverse engineer the host and waiter for homework.

3.3.7    The v3.0 Implementation deliverable

The starting point system is on the lecture lab page. There is a java and C++ version. Details of downloading and using them will be discussed in class and be the focus of your first lab.

4      Agent Design and Implementation

Now that we have the high-level requirements for our agent application, we can consider how the Agent is designed and implemented. Let's review some of agent basics:

·         Agents are autonomous.

·         Agents communicate by messaging.

·         Agents have three logical parts, which can act concurrently.

·         Agents will not share data with one another.

·         Agents have actions that are independently schedulable for as long as the agent is active.

Let's review a few software-engineering ideas:

There are other possible requirements that would influence our design. For example, we might want to be able to run each agent on a separate computer. Should we anticipate this requirement and consider it in our design? It certainly seems natural. The answer is not yet; we won’t let this requirement influence our design.

In early releases of the software, we may decide to 1) implement a simplified version that does not fulfill all of its requirements, or 2) compromises some of the requirements slightly. That’s acceptable as long as the client agrees. The client is usually the customer stakeholder of the software, the person paying for it. In our class, I am the client. Any deviation from the requirement must be explained to me.

Early releases are often called prototypes. A prototype can help us learn about the system, uncover new requirements while giving the customer insight into what he has specified. We have learned that early quick releases can be more important than waiting for a delivery that exactly fulfills all the requirements. The early v1.0 system described above is such a prototype.

4.1    The Design of v3.0

An agent design must specify four things:

·         The agent data;

·         The agent messages;

·         The scheduling rules;

·         The agent actions

4.2    A Concrete Agent

The abstract base class, Agent.java, will be described later. For now, all that we have to know is that the above four things must appear in a concrete (derived) agent. For example, the waiter agent might have the following signature:

public class WaiterAgent extends Agent {...}

Let’s see what goes inside this concrete agent.

4.2.1    The Agent (Messaging) Interface

In section 2.1 formal messaging was described. I have made a design decision in the prototype that we do not need formal messaging. Our agents will simulate formal messaging as follows:

·         An agent’s external interface will be all the message-handling calls. That way no parsing is required; we'll go over this in a future class.

·         Message-handling calls return VOID. Its execution must act on the message as it sees fit: store information into its state; put requests on an internal queue; whatever is appropriate. It must not return a value; that is not in the spirit of messaging. And it must return quickly; handlers generally do not do “semantic” processing; they just add information to the state of the agent. The scheduler will call actions to do the actual work.

Here’s pseudo-code to show how this works:

//Sender Code found in an action of the waiter

Prepare(me,2,“Hamburger”); //Call message-handler directly.

... //continue on with other work.   

 

 

//Recipient Message-Handler for Prepare (in the cook Interface)

Void Prepare(sender, table, item){

/*This code does EXACTLY the same thing as it did

in the dispatch code in the messaging example of section 2.1*/

...

}

All we are doing is having the sender call the message-handler method directly. No marshalling of the message, no external message queues are used, no persistence of messages. That simplifies some things, but complicates others.

Notice also that execution of the handler code is happening within the sender’s thread of control (make sure you understand this). Of course, that’s not real messaging, but it is a logical simplification. Doing messaging this way depends on three key points:

·         The method call must return VOID or we aren’t doing messaging.

·         There must be no shared data between code in the sender and code in the recipient. Even though they may in the same address space, they MUST NOT share data. Any structures passed as parameter need to be copied by the recipient since Java passes pointers (make sure you understand this). Otherwise a fundamental agent principle is being violated.

·         The message-handling call must return quickly. It should set some state or queue something up for the scheduler to look at. If it tries to do a significant amount of processing, then it isn’t behaving like an agent. Remember that the message-handling method is acting in the sender’s thread of control. In the case of Prepare(sender, table, item) the handler would put this on the cook’s queue of things to do and when its scheduler picked this item, a Cooking action would actually prepare the food and send a message to sender when the item was finished. [Can you figure out why the table number is part of the message?]

So, with this simulation of messaging we have another design decision that violates two requirements.

Requirement: An agent has three parts, each with its own thread of control.
Violation: Here the message reception part DOES NOT have its own thread—it is in the sender’s thread.
Accommodation: Since the sender and receiver’s scheduler have different threads of control, the effect we are looking for, i.e., scheduler and message reception happen concurrently, is still satisfied.

 

Requirement: Agents must not share data.
Violation: By having the sender call the recipient’s dispatch routine directly, the recipient has direct access to the sender’s structures.
Accommodation: If the recipient or the sender COPIES the structure, then no data is shared and we have the same effect as in real messaging. Which do you think should do the copying?

Is this acceptable? Yes, if we understand what we are doing, if we document it carefully, and if the client agrees. And since I am the client, anything that satisfies me is all right.

·         Java does have a full messaging package. If your application requires it, use it. We just don’t need it for our current purposes.

·         If we want to implement the agents as separate processes, not just separate threads, Java has a package called RMI (Remote Method Invocation). Uusing RMI one process can invoke another even if it is executing on a different machine. RMI copies all data structures that are used in remote method calls.

4.2.2    The Agent Scheduler

The base agent, as we will see later, is mostly an infinite loop that invokes the scheduler. When you build a concrete agent, you must implement a scheduler. The scheduler holds the logic that decides what an agent can do. Here’s how it should look:

/** Scheduler.  Determine what action is called for, and do it. */

boolean pickAndExecuteAnAction () {

     //Rule 1:

     if (condition1) {action1(...); return true;}

 

     //Rule 2:

     if (condition2) {action2(...); return true;}

 

     return false; //we have tried all our rules and found nothing to do.

                    //So return false to main loop of abstract agent and wait.

}

 

Here is a scheduling rule that might exist for the cook:

if ($ o in orders ' o.isPending()) then  (prepareOrder(o); return true);
The above is read as follows: if there exists an o in orders such that o.isPending() then call prepareOrder(o) and return true.

There are many things to note here:

·         The conditions can be arbitrarily complex. The above rule is typical: look for the existence of an object that satisfies some predicate, then pass it to some action.

·         The rules are prioritized by the order in which they appear in the code. If the scheduler  picks an action, it returns true to the base agent, whose loop invokes the scheduler again, trying the rules from the top. This is a very simple scheme that has worked for me in all my agent implementations.

·         This scheduler invokes one action at a time. Could an agent have two actions active at the same time? In my experience, whenever I seem to have that need, I look closely and find that I need another agent. Having agents only executing one action at a time simplifies the agent design. This one-action-at-a-time is almost a requirement for me, but I let individual agent designers decide.

·         This scheduler is not concurrent with actions. That is, this schedule makes a synchronous call to an action and waits for it to complete before returning and trying to schedule another action. That’s a requirement violation. But since we have just decided to only schedule one action at a time, there seems to be no problem. Or is there? Take a moment to think of a problem that might arise with this design. Warning: an answer is coming…Suppose you are performing some action when a very high priority message comes in, say:
            PleaseSeat(“Prof. Wilczynski”);
Don’t you want to drop everything and give Prof. Wilczynski the best service you can? Yes, you do. Your scheduler needs to be able to suspend the active action, and schedule a new action involving the professor. The scheduler cannot do this if it is synchronously waiting for actions to complete. It must be in its own thread monitoring the messages and actions (make sure you understand this). However, suspending actions and scheduling new ones increases the complexity of the agent design too much—we may handle this case in a later version. So, for now, we will recognize the following violation:

*        Requirement: An agent has three parts, each with its own thread of control.
Violation: Here the scheduler and actions work in the same thread.
Accommodation: Only one action can be carried out at a time and it always runs to conclusion, i.e., it cannot be interrupted, canceled, or restarted.

4.2.3    The Agent Actions

Finally it’s time to describe the actions the agent actually performs. Each action is encoded in a single method. For example, here is the host code that assigns a customer to a waiter:

private void tellWaiterToSitCustomerAtTable(

                             MyWaiter waiter, CustomerAgent customer, int tableNum){

      print("Telling " + waiter.wtr + " to sit " + customer +" at table "+(tableNum+1));

      waiter.wtr.msgSitCustomerAtTable(customer, tableNum);

      tables[tableNum].occupied = true;

      waitList.remove(customer);

      nextWaiter = (nextWaiter+1)%waiters.size(); //get next waiter ready

    }

As usual there are several things to note:

·         This is real Java code, not pseudo-code. Eventually, the implementation has to reach this level. But still, my pseudo-code is very much like Java. That's why I use Java.

·         tellWaiterToSitCustomerAtTable() returns void. All actions do.

·         tellWaiterToSitCustomerAtTable() is private. Only the scheduler can invoke an agent’s actions. The parameters are Java objects instantiated from Java classes.

·         The print() is for tracing only.

·         Then we send the waiter a message to seat the customer.

·         The next three lines update the agent’s data structures: 1) marking the table as being occupied; 2) removing the customer from the waiting list; 3) setting up the next waiter to be used.

4.3    The CustomerAgent - An example of a finite state machine

The general agent as described above has its intelligence encoded in its rule-based scheduler. Most agents will be designed this way. However, you may have an agent whose control structure can be more simply modeled as a finite state machine (fsm). Recall that a finite state machine is defined by a transition function that maps (state, event) ---> (state, output). That is, an fsm is a table of the form:

 

 

e1

...

em

s1

nextState, someOutput

...

nextState, someOutput

s2

nextState, someOutput

 

nextState, someOutput

...

...

...

...

sn

nextState, someOutput

 

nextState, someOutput

 

where the nextState is one of the s1, s2, ...sn, and the someOutput is an action of our agent. In our fsm agent, message reception sets the event (and other variables as needed), while the scheduler embodies the table. Here is the states and events from the v3 CustomerAgent.

 

    public enum AgentState

                {DoingNothing, WaitingInRestaurant, SeatedWithMenu, WaiterCalled, WaitingForFood, Eating};

    private AgentState state = AgentState.DoingNothing; //The start state

 

    public enum AgentEvent

                {gotHungry, beingSeated, decidedChoice, waiterToTakeOrder, foodDelivered, doneEating};

    private AgentEvent event; //Messages set the event

    List<AgentEvent> events = new ArrayList<AgentEvent>(); //events are added to list via messages

 

Here is the first message the customer receives:

    /** Sent from GUI to set the customer as hungry */

    public void setHungry() {

            events.add(AgentEvent.gotHungry);

            isHungry = true; // a hack

            print("I'm hungry");

            stateChanged();

    }

 

 

Here is the beginning of the fsm scheduler:

    protected boolean pickAndExecuteAnAction() {

            if (events.isEmpty()) return false;

            AgentEvent event = events.remove(0); //pop first element

 

            //Simple finite state machine

            if (state == AgentState.DoingNothing){

                if (event == AgentEvent.gotHungry)         {

                        goingToRestaurant(); //the output action

                        state = AgentState.WaitingInRestaurant;

                        return true;

                }

                // elseif (event == xxx) {} //if there were other events for this state

            }

            if (state == AgentState.WaitingInRestaurant) {

                if (event == AgentEvent.beingSeated)       {

                        makeMenuChoice();

                        state = AgentState.SeatedWithMenu;

                        return true;

                }

               // elseif (event == xxx) {} //if there were other events for this state

            }

            if (state ...

           ...

  }

 

You can see the actions in the code. We will talk more about this in class.

5      Agent Base Class Design

At this point of the software engineering cycle, an O-O design will follow—classes, methods, etc. At the base class level, we only know about the run cycle.

class Agent {

     run();//invokes the scheduler repeatedly

}

Since the base class agent has no application semantics, we can only specify the run cycle (to find and execute an action).

Remember, the idea of design is to describe HOW a system will work as abstractly as possible so as to make sense, be implementable, understandable. The programming details are filled in during the implementation phase. DO NOT CLUTTER UP A DESIGN WITH PROGRAMMING DETAILS. In the design we don't even have to specify the constructor, which is really an implementation detail.

In (very) pseudo-code here is one conception of the natural run-cycle of an agent:

run(){

 while agent is active {

pick an action;

if action is picked

  then perform the action;

  else sleep until state changes;

}

}

Some would want this tightened up a bit. Perhaps:

run(){

 while agent.IsActive() {

action = pickAnAction();

if action

  then perform(action);

  else sleep() until stateChanged;

}

}

Is this better? Now we have to add  AgentIsActive(), pickAnAction(), perform(), sleep()and stateChanged  to our base class specification. Notice also that the notion of an agent being "active" has been introduced. Perhaps we should add a routine to deactivate an agent, say deactivateAgent(). Let's do it without thinking too much about this decision. So, our Agent base class design now looks like:

class Agent {

     //variables

     Action action;

     bool stateChanged;

//methods

     run();//invokes the scheduler repeatedly

     deactivateAgent();

bool agentIsActive();

Action pickAnAction();

perform(Action);

}

This tightening up has added types to the method declarations. For years I have created designs without strong typing and survived. However, the trend is clearly toward typing in designs. Let's just do it. That being said, notice that the design is getting harder to read as more programming lingo is introduced.

In any case does the above specification really work? Don't actions take parameters? Yes, they do. This level of pseudo-code is missing them and there is no obvious way of putting them in. [Why?] Perhaps we should abstract the action out of this loop, something like:

run(){

 while agentIsActive() {

if ~ pickAndExecuteAnAction()

  then sleep() until stateChanged;

}

}

I like this. Now we would have the following design for the base class agent:

class Agent {

//variables

     bool stateChanged;

//methods

     run();//invokes the scheduler repeatedly

     deactivateAgent();

bool agentIsActive();

bool pickAndExecuteAnAction();

}

What about pseudo-code for the new methods? There isn't any, because at this level of abstraction there is nothing to specify; the intent of the routines is clear. That's all that is needed for design.

Notice how we made several passes to a satisfactory design for the run() method. That was no surprise.

5.1    Agent Base Class Implementation

5.1.1    The Abstract Base Class Agent

The abstract Agent class was designed to make coding of a concrete agent easy. The abstract class has the code for:

·         Managing the agent thread that runs the infinite loop that invokes the concrete scheduler.

·         Various utilities.

You should be looking at Agent.java while reading the rest of this section.

5.1.1.1  Agent Threading Model

The abstract Agent class contains a private class whose signature is:

private class AgentThread extends Thread {...}

And the Agent class has an instance variable to hold the newly created thread:

private AgentThread agentThread;

The agentThread variable will be initialized by the agentThread.start()  method:

agentThread = new AgentThread(getName());

agentThread.start() is the method that actually gets the thread running. Java will create the thread and give control to its run() method. agentThread.start() will be called by the code that constructs the agent.

What is going on? For one thing, an agent designed this way is not really autonomous. It’s going to be a thread within some other process’ address space. Our prototype has the entire application as one process with each agent in its own thread. So, here we have a typical software engineering occurrence, an implementation decision that violates a requirement.

Requirement: An agent is autonomous.
Violation: Here the agent is running as a thread in an address space with other agents. The possibility of sharing data is possible.
Accommodation: As we will see, we will be careful NOT to share data. Then, if each agent gets its own thread, then the concurrency is there by way of the operating system scheduler (illusion of concurrency).

Also, why doesn’t the Agent class extend Thread directly? What’s the purpose of making the thread an instance variable of the agent? Mostly, it is a design decision to make unit-testing easier. Code that wants to test a class usually does things like the following:

Instantiate the class, e.g.  ClassA a = new ClassA();

Then execute the methods of the class and see if they do the right thing, e.g.
if (a.sum(2,3) != 5) assertFailure();

Doing this kind of unit testing is difficult if the object is actually running in a separate thread (you will try this in your lab). So instead, we make the Agent a regular object and create an instance variable to hold the thread. It’s not as complicated as it looks and we will go over this in great detail in class.

5.1.1.2  The run Method (Main Loop)

In the Java threading model, a thread is started by invoking the start() method. That tells the operating system to create another thread of control in this address space, and start its execution at its run() method. In other words, run() performs the same function for a thread as main() does for a process.

Our run() routine is tricky. Its intention is to invoke the scheduler for as long as there is something to do. If the scheduler—implemented in pickAndExecuteAnAction()—has nothing to do, it returns false and the agent sleeps until its internal state changes (make sure you understand why this makes sense). The agent uses a classic synchronization mechanism called a binary semaphore to implement this logic. One of the private variables is:

Semaphore stateChange = new Semaphore(1, true); //start with one permit

Semaphore is a type in Java 1.5's new java.util.concurrent package. We will discuss semaphores in a lecture when we discuss concurrency.

Here is the run() method:

public void run() {

            goOn = true;

 

            while (goOn) {

                  try {

                     // The agent sleeps here until someone calls stateChanged(),

                     // which causes a call to stateChange.release(), which wakes up agent.

                     stateChange.acquire();

                     //The next while clause is the key to the control flow.

                     //When the agent wakes up it will call respondToStateChange()

                     //repeatedly until it returns FALSE.

                     //You will see that pickAndExecuteAnAction() is the agent scheduler.

                     while (pickAndExecuteAnAction());

                  } catch (InterruptedException e) {

                        // no action - expected when stopping or when deadline changed

                  } catch (Exception e) {

                        print("Unexpected exception caught in Agent thread:", e);

                  }

            }

}

goOn is a simple private variable that controls the main loop. As you can see goOn is set to true; a method called stopAgent() turns it off. For as long as goOn is true, the loop continues. The intention is for the agent to run forever until stopAgent() is called or the execution of the whole process is killed.

The loop executes two statements within a try-catch. The first invokes the method acquire() on the semaphore variable stateChange.  The call puts the thread to sleep until someone calls the semaphore method release(). The agent waits here until someone calls stateChanged(), which results in a call to stateChange.release(), which wakes up the agent. Who calls stateChanged()? There are two events that change the state of the agent:

·         message reception code;

·         action code.

We will see examples soon.

Back to the try clause within the while loop. The clause:
                    while (pickAndExecuteAnAction());
is the key to the control flow. When the agent wakes up it will call the scheduler via pickAndExecuteAnAction () repeatedly until it returns FALSE. You will see that pickAndExecuteAnAction () is the agent scheduler.

Notice that pickAndExecuteAnAction () is an abstract method in Agent. That means that any concrete class that extends Agent must implement pickAndExecuteAnAction (). The scheduler code must be abstract because details of the scheduler are application dependent.

5.1.1.3  Utilities in Abstract Agent Class

There are four utility routines:

Do(String msg);

print(String msg);

print(String msg, Throwable e);

getName();

 

Do() is for simulating the agent primitives. It will call print().

The print() routines are for our simulated output (or any output we want).

getName() returns the agent name.

These routines are simple. See the code for details.

6      More on Design and Pseudo-Code

There are a few design themes that have been uncovered even with this small amount of code we've specified.

·         Design is iterative; new layers being added as details are uncovered in the pseudo-code.

·         A good designer can "peek" forward to implementation to make sure his design is implementable. This is the reason that great designers are (or were) great implementers.

·         Designs must compile! Messages, variables, and actions must match

·         Designs must work! "Execute" the design and see if interaction diagrams are satisfied.

 

A key part of those themes is being able to write great pseudo-code. Great pseudo-code has depth without compromising understanding, simplicity yet still complete. You learn this skill by practice and reading designs and code written by professionals. All designers have a "style" and I'm no exception. So most of what I'm going to show is the result of my 40 years of programming expertise.

6.1    Abstract Collections

My designs make frequent use of abstract Collections, Sets, Lists, Queues, and Stacks. I never use arrays (unless the object has array semantics, like mathematical objects frequently have). Arrays are mostly implementation things.

A good thing for you to get used to is Java's collection classes. We will be using them often. The Java 1.5 api documentation is at:
http://docs.oracle.com/javase/1.5.0/docs/api/
Put that link in your bookmarks and go there often for questions about java classes.

Also, here is a link to information about Java collections, including a tutorial:
http://docs.oracle.com/javase/1.5.0/docs/guide/collections/

What follows is a brief summary about some of the Collection classes I like to use in design.

6.1.1    Collection

A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The main operations are:

boolean add(E o)

Ensures that this collection contains the specified element (optional operation). Returns true if this collection changed as a result of the call. (Returns false if this collection does not permit duplicates and already contains the specified element.)

boolean remove(Object o)

Removes a single instance of the specified element from this collection, if it is present (optional operation). More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements. Returns true if this collection contained the specified element (or equivalently, if this collection changed as a result of the call).

boolean contains(Object o)

Returns true if this collection contains the specified element. More formally, returns true if and only if this collection contains at least one element e such that (o==null ? e==null : o.equals(e)).

boolean isEmpty()

Returns true if this collection contains no elements.

int size()

Returns the number of elements in this collection. If this collection contains more than Integer.MAX_VALUE elements, returns Integer.MAX_VALUE.

6.1.2    Set

A Collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.

A SortedSet further guarantees that its iterator will traverse the set in ascending element order, sorted according to the natural ordering of its elements (see Comparable), or by a Comparator provided at sorted set creation time. Several additional operations are provided to take advantage of the ordering.

6.1.3    List

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list. Lists are 0-based. Unlike sets, lists typically allow duplicate elements. The additional operators are:

void add(int index, E element)

Inserts the specified element at the specified position in this list (optional operation). Shifts the element currently at that position (if any) and any subsequent elements to the right (adds one to their indices).

int lastIndexOf(Object o)

Returns the index in this list of the last occurrence of the specified element, or -1 if this list does not contain this element. More formally, returns the highest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.

int indexOf(Object o)

Returns the index in this list of the first occurrence of the specified element, or -1 if this list does not contain this element. More formally, returns the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.

E get(int index)

Returns the element at the specified position in this list.

6.1.4    Queue (and Stack)

A Collection designed for holding elements prior to processing. Besides basic Collection operations, queues provide additional insertion, extraction, and inspection operations.

Queues typically, but do not necessarily, order elements in a FIFO (first-in-first-out) manner. Among the exceptions are priority queues, which order elements according to a supplied comparator, or the elements' natural ordering, and LIFO queues (or stacks) which order the elements LIFO (last-in-first-out). Whatever the ordering used, the head of the queue is that element which would be removed by a call to remove() or poll(). In a FIFO queue, all new elements are inserted at the tail of the queue. Other kinds of queues may use different placement rules. Every Queue implementation must specify its ordering properties.

As noted above, a stack is just a LIFO (Last In, First Out) queue.

6.2    Using Collections

6.2.1    Search without Iteration

One of the reasons to use abstract Collections and Lists is that it makes it easy to write nice design expressions for finding objects. We've already seen this in the waiter scheduler:

Rule1: If there exists a table and customer, so that table is unoccupied and customer is waiting, then seat him at the table.

In First Order Logic, we would write:
if ($ table in tables and $ customer in waitingCustomers ' ~table.isOccupied())

                               then  perform seatCustomer(customer, table);

 

[If those special logic symbols don't print in your browser, here is the same expression with the symbols replaced by English:]

if (there exists table in tables and there exists customer in waitingCustomers

such that ~table.isOccupied())

               then  perform seatCustomer(customer, table);

Both "there exists" clauses specify the existence of a member of a collection. Then you can use those members for filtering (the "such that" clause", and acting on them (the "perform" clause). This is so easy to implement in Java 1.5 because the new iteration looks very much like the design code.

6.2.2    Specifying conditions on all members of a collection

Sometimes you want to make a statement about every element in a collection. Such statement can be constraints or just conditions on which you want to do something. Here is a constraint that might appear in a unit test:

·         All members in the collection C must be initialized.
or more formally:
Assert ("c in C, ~null(c))
or if your browser doesn't print that "for-every" symbol:
Assert (for every c in C, ~null(c))

You might use the for-every construct in a scheduling rule. Here is one a waiter might try:

·         if every customer I am responsible for is eating, then I can take short break.
or more formally:
if (" cust in Customers ' isEating(cust)) then perform takeABreak("short");
or if your browser doesn't print that "for-every" symbol:
if (for every cust in Customers such that isEating(cust)) then perform takeABreak("short");

The "for-each" and "for-every" logic constructs are very useful is design.

6.2.3    Don't Clobber Variables—Use collections instead.

In concurrent systems, like ours, you must be aware that variables are used in different threads and may be subject to race condition (these are timing problems that we will discuss in depth later). Students often use simple variables in their messaging and it is simply inadequate: they get clobbered when new messages come before you have "consumed" the original. For example, suppose you designed your Maitre'D like this:

CustomerAgent  newCustomer=null; //variable to hold the newly arrived customer.

//messages
IWantFood(CustomerAgent customer)
         On receipt: Set  newCustomer = customer.

Then, later in scheduler in actions, you write:

//rule 1
if (there exists table in tables and newCustomer!=null such that ~table.isOccupied())

                               then  perform seatCustomer(newCustomer, table);

THIS DOESN'T WORK. Your usage of newCustomer might be too late. It might be clobbered by a new message before you get to use it; you would lose track of the first customer. That's why you use collections. YOU MUST UNDERSTAND THIS.

6.2.4    Don't Have Explicit cases—Use collections instead

Suppose you are keeping track of food in your restaurant. You currently serve three kinds of food: chicken, steak, and fish. Let's say your design has a message about a delivery of a certain amount of food of one of those types. Here's what I often see in the design.

//variables:
int chicken_count;
int steak_count;
int fish_count;

//messages
Deliver (deliveryType, deliveryCount) {
               if deliveryType == "chicken" chicken_count+=deliveryCount;
               else if deliveryType == "steak" steak_count += deliveryCount;
               else if deliveryType == "fish" fish_count+= deliveryCount;
}

What if you have dozens of cases? Infinitely better is:

//variables
Collection<Food> foods;
class Food {
               type,
               count; //type is one of chicken, steak, fish, …
}
or more succinctly in design in case you don't use the internal Food class:
Collection<{type,count}> foods;

Deliver (deliveryType, deliveryCount) {
               for f in foods where f.type= deliveryType do f.count+=deliveryCount;
}

Notice that my preferred design has only one variable instead of three. Fewer variables is the theme of the next section.         

6.3    Lists and Booleans

Underappreciated is a careful usage of variables. What I mean is:

·         When should you introduce one?

·         What should its name be?

·         What state information does it hold?

·         What are your alternatives?

Students and other inexperienced programmers add variables to designs without thinking about these questions. And then their designs get more complicated. Before I add a variable to a design, I think hard about whether I REALLY need it. I have learned that every single variable takes a lot of maintenance, and unnecessary variables are wasteful. Adding a variable to increase understandability is always valid.

Here's a pattern I have seen often. Suppose you have a Waiter Agent. He is assigned customers by the Host Agent. His responsibilities include seating assigned customers and then waiting on them (taking their order, giving them food, etc.) Here's what I sometimes see:

list<MyCustomer> assignedCustomers;

list <MyCustomer> seatedCustomers;

class MyCustomer {

               Customer c;

               int table;

}

bool sitCustomer=F;

bool serveCustomer=F;

bool getFoodForCustomer=F;

//Message

SitCustomerAtTable(Customer c, int table){

               assignedCustomers.add(new MyCustomer(c,table));

               sitCustomer=T;//means there is at least one customer in assignedCustomers list

}

//Action

SeatCustomer(MyCustomer c){

               Do ("Seating customer …");

               assignedCustomers.remove(c); //did I forget anything?

               seatedCustomers.add(c); //remove from one list and add to another

               send msgPleaseFollowMe to c;

}                            

//Scheduler rules:

if  sitCustomer then SeatCustomer(assignedCustomers.first());

if serveCustomer then …;

if getFoodForCustomer then …;

What's wrong with this?

·         There are two lists holding the same kind of information, i.e., customers, but members of the lists are in different states, assigned and waiting to be seated and seated (and presumable waiting to be served). How many of these lists will you need?

·         The design uses a different boolean for each action. I suspect this is done to make writing the scheduler easier. Do you think it does? How many of those Booleans will you have?

·         The "did I forget anything comment" refers to a common programming error. In this action the designer forget to test whether sitCustomer should be turned off. Very easy to forget since it's not close by.

I understand the above design, but I much, much prefer the following:

list<MyCustomer> customers;

class MyCustomer {

               Customer c;

               CustomerState s; //one of assigned, seated, ordering, waitingForFood, etc.

                                              //More about state variables in next section.

               int table;

}

//Message

SitCustomerAtTable(Customer c, int table){

               customers.add(new MyCustomer(c,assigned,table));

}

//Action

SeatCustomer(MyCustomer c){

               Do ("Seating customer …");

               c.s=seated; //no remove from one list and add to another

               send msgPleaseFollowMe to c;

}

//Scheduler rules:

//Rule 1

if  assignedCustomers.hasWaitingCustomers() then SeatCustomer(assignedCustomers.getFirstWaiting());

One instance variable instead of five. The variable is more complicated by having the customer state encoded in it, but I claim this is vastly easier to manage and understand. Look how much less code there is.

Are you nervous that hasWaitingCustomers() has to search the list, that assignedCustomers.getFirstWaiting() has to search the list again? Are you worried about efficiency? DO NOT WORRY ABOUT SUCH THINGS DURING DESIGN.  Let your programmer worry this. Design is supposed to be elegant. I have a million stories about this.

6.4    State Variables

In the above section I introduced the notion of a state variable in the MyCustomer class that the waiter is maintaining. That seems so natural to me that I do such things routinely.

What state variables might be in a Customer agent? Perhaps:

  • stateOfNourishment: unknown, normal, hungry, ravenous, full, bursting,…
  • stateOfLocation: unknown, home, restaurant, …
  • stateOfPosition: unknown, standing, sitting, …

Each state can only have one value. That's the key. The states are orthogonal, i.e., they are different dimensions of the overall state of the customer. Thus, the "full" customer state with respect to this type of customer is a triple, (stateOfNourishment, stateOfLocation, stateOfPosition). I say "full" because the complete state depends on the application.

The state of the customer from the Host point of view is completely different:

  • stateOfCustomer: waiting, seated, atBar, …

Notice the states are different from the customer triple. The states depend completely on what the Host “cares” about.

AWaiter might have the following state variables:

  • stateOfMyself: working, onBreak, …
  • stateOfCustomer: seated, hasMenu, hasOrdered, servedFood, hasCheck, …

Again the states are application dependent. Each agent keeps track of what it needs to keep track of.

Java 1.5 has enumerations. State variables are now easy to declare and easy to use. Here's a snippet:

public enum stateOfCustomer { seated, hasMenu, hasOrdered, servedFood, hasCheck };
stateOfCustomer soc; //declaring a variable of this type.
soc = hasMenu; //assigning to the variable

6.5    Naming Variables

Do it carefully especially if the code has a lifetime beyond its first writing (or if someone else, like a professor or grader, has to look at it).  Remember the following points:

  • Naming is an intellectual exercise. Great designers create great names.
  • These variables are not just for you. Even if they were, you'll be surprised at how much good names help you design and program. If done properly, it makes your design and code more legible.
  • Good names tend to be longer. So what? The price is well worth paying. Cut and paste names. I never type a name twice.
  • Good names are a sure sign of a professional.

7      Tools

The previous sections described our design and implementation. Now we will look at the environment in which our code will run. Real projects have these and many more needs:

  • Compile and run
  • Create jars
  • Run test cases
  • Create releases
  • Clean up directories
  • Take care of cross-platform issues
  • Create Javadocs

These are tasks you do over and over. Typically, we’ve used scripting languages to create Makefiles to mechanize these tasks. Java gives us tools to do a lot of what Makefiles do—not everything, but a lot. Fortunately, there is now even more help:

  • The Apache Group has developed a build tool called Ant,
  • The Extreme Programming people have built a testing tool called JUnit.

Both are part of the repertoire of all professional Java programmers.

An aside: Here it is, the first week of class and you must learn about Agents, Ant, and JUnit. This is how life is when working on a system. But, remember, you don’t have to understand everything in depth (even if you had the time) in order to work with and modify code and scripts. A reading understanding of other people’s work is enough. With time and need you will develop the required depth.

You will be asked to read several papers about these systems. Don’t study these papers. Don’t try to understand everything in them. You can’t. Neither can I. Their purpose is to give you a quick profile of what the systems can do and how professionals use them. You will learn some basics by doing the Agent problem.

You mostly need to understand what’s in front of your nose—what your application is doing and the parts of the tools it uses. And even then, if things are working correctly, you may not need to know every detail in depth. Certainly, if you change or add something new, then you need to do some research. You develop depth with these tools by doing, not reading.

7.1    Ant

7.1.1    Installation Instructions for your PC

 

1) Go to http://ant.apache.org/ and follow the link to the download.

2) Put the zip file in a directory, e.g.,  D:\Program Files\Apache Group

3)      Unzip the file. It will create a subdirectory called apache-ant-1.6.1

4)      You should see a directory structure like the following:

 

D:\Program Files\Apache Group\apache-ant-1.6.1>dir

 Volume in drive D is MAIN

 Volume Serial Number is 007C-CDD0

 

 Directory of D:\Program Files\Apache Group\apache-ant-1.6.1

 

07/12/2004  09:53a      <DIR>          .

07/12/2004  09:53a      <DIR>          ..

07/12/2004  09:53a      <DIR>          bin

07/12/2004  09:53a      <DIR>          docs

07/12/2004  09:53a      <DIR>          etc

07/12/2004  09:53a      <DIR>          lib

02/12/2004  02:28p              17,191 KEYS

02/12/2004  02:28p              11,766 LICENSE

02/12/2004  02:28p               3,356 LICENSE.dom

02/12/2004  02:29p                 677 LICENSE.sax

02/12/2004  02:29p               2,698 LICENSE.xerces

02/12/2004  02:29p                 981 NOTICE

02/12/2004  02:28p               2,657 README

02/12/2004  02:28p             105,439 WHATSNEW

02/12/2004  02:28p              18,478 welcome.html

               9 File(s)        163,243 bytes

               6 Dir(s)   3,928,408,064 bytes free

You should read the following papers. All are in the ant distribution:

  • This paper seems to have good lecture material:
    apache-ant-1.6.1\docs\ant_in_anger.html
  • For building first build files:
    apache-ant-1.6.1\docs\manual\usinglist.html
  • For running Ant:
    apache-ant-1.6.1\docs\manual\running.html

In v1 of the Agent release, to see all the Ant targets, type:

  >ant

 

You should examine the build.xml file and “understand” it. Try some of the targets.

To run the v3 agent release, type:

  >ant restaurant

7.1.2    Configuration and Directory Structure

The Java developers of Ant have determined a directory organization for applications. They have thought long and hard about it and we will simply adopt it (but don’t worry, we’ll have you make some changes for later releases). Let’s call the top-level directory for our application agent0-1 (the first number refers to the version of the functionality, the second to the number of the release of that functionality).

Ant uses a file called build.xml for its instructions. It will be located in the top-level directory that houses this application. The build.xml file assumes the following file organization (it may be slightly different than this):

agentV1 Holds build.xml, application jars, readme files.

   bin                      Holds batch files for running the application.

   build                   Holds the classfiles resulting from compilation.

   javadoc              Holds the javadoc documentation files.

   src                       Holds the source java files.

  

The subdirectories build, javadoc, and src will follow your package model. We will have two packages:

·          package agent  for the base class and utilities.

·          package restaurant for the agent application

When you run Ant, you specify a target. The following targets are set up in your build.xml:

compile   Compile the source code

test                         Compiles and tests the source code by running junit tests

run                         Run the simple command-line agents program Main.java, if there is one

run.gui                   Run the agents program using the gui, if there is one

restaurant               Run the full application with animation

javadoc                  Create javadoc of the source code

dist                         Create a full distribution of the project including all the binary jars, source code, and ant files

clean                      Delete all the compiled files

 

To do a compile, you type at the prompt:    >ant compile
To just see the targets, type at the prompt:  >ant

Study the build.xml file and get familiar with it and the Ant program itself.

7.2    JUnit

JUnit is a package that makes a first-class citizen of testing for the Java software development process. Learning to use JUnit is not hard; acquiring the JUnit-style testing discipline is hard.

7.2.1    Installation Instructions

 

1) Go to http://www.junit.org/index.htm and follow the link to the download.

2) Put the zip file in some directory, e.g.,  D:\

3)      Unzip the file. It will create a subdirectory called junit3.8.1

4)      You should see a directory structure like the following:

 

D:\junit3.8.1>dir

 Volume in drive D is MAIN

 Volume Serial Number is 007C-CDD0

 

 Directory of D:junit3.8.1

 

07/12/2004  10:10a      <DIR>          .

07/12/2004  10:10a      <DIR>          ..

07/12/2004  10:10a      <DIR>          doc

07/12/2004  10:10a      <DIR>          javadoc

07/12/2004  10:10a      <DIR>          junit

09/04/2002  11:29p              15,172 cpl-v10.html

09/04/2002  11:29p             121,070 junit.jar

09/04/2002  11:29p              21,614 README.html

09/04/2002  11:29p              57,635 src.jar

               4 File(s)        215,491 bytes

               5 Dir(s)   3,928,489,984 bytes free

 

5)      Look at README.html, but don’t put junit.jar on your classpath.

6)      To test the installation, try the following (Be very careful about the directory you are connected to; also notice my explicit classpath):

 

D:\junit3.8.1>java -classpath .;junit.jar junit.textui.TestRunner junit.samples.money.MoneyTest

......................

Time: 0.04

 

OK (22 tests)

 

You should read these papers:

  • Junit Introduction (located in your JUnit release at: junit3.8.1\doc\testinfected\testing.htm)
    or you can view it at: Junit Introduction
    I will discuss this introduction paper in class.
  • Here is another paper of interest: Ant and Junit

7.3    Javadocs

Notice that the Ant program has a target for javadoc. Java programs can be commented in special ways in order to create the special kind of Java documentation that we are all familiar with. I expect you to enhance all the code with excellent Javadoc comments.

You generally place a javadoc comments above the class definition and before every non-private method. In addition to the text and HTML that is allowed, javadoc supports some special tags that have special meaning to the javadoc output.

@author

Specifies the author of the document

Must use javadoc -author ... to generate in output

@version

Version number of the document

Must use javadoc -version ... to generate in output

@param

Documents a method's arguments

@return

Documents the return type of a method

@throws

Documents the exceptions a method throws

These tags must occur at the beginning of a line and include everything until another tag or the end of the comment.

Here’s an example of a javadoc comment that might appear above a class:

  /** Description of Agent Base class

   *

   *    Lots of good documentation here ...

   * 

   *  @author <A HREF="mailto:dwilczyn@usc.edu">

   *                      David Wilczynski</A>

   */

Here’s an example from a Semaphore class for the acquire routine (this routine is not in our current code):

  /** Wait until semaphore is full or the timer expires.

   * If the semaphore is already full, return immediately.

   * Always leaves the semaphore empty.

   * @param timer time to wait.  If null, returns immediately.

   * @return true if <code>take()</code> was successful

   *      (semaphore was full or became full),

   *              else false (timer expired).

   * @throws InterruptedException if interrupted while waiting

   */

 

synchronized public boolean acquire (Deadline timer){

//The code follows here.

...

}

 

8      Unit Testing

Unit testing is important for many reasons. The most important one for real projects arises from the following development scenario:

  • You are part of a team. You have given an agent (or other component) to implement.
  • Some time in the future your agent (component) will be integrated with the work of your colleagues.
  • You don’t want the integration to fail because of your component.

 

You have to find a way to test your agent (component) without benefit of a full system so that you are confident that the integration will go smoothly.

                                 

As we know unit testing entails:

  • Setting up an environment (the so-called “fixture”);
  • Making calls to the unit being tested;
  • Check to see if the results match your expectations.

 

For the example we did in class from the Junit paper, the above was easy:

  • We bound some Money variables to values (created the “fixture”)
  • We called the add and equals methods (performed the “tests”)
  • We called Junit methods, such as Assert.assertTrue, to see if the results met our expectation.
  • Then we created suites of tests that could run from the command line, or could run from a GUI.

 

What’s the analog for agents? How do we unit test an agent?

8.1    Unit Testing An Agent

Agents are complex objects that run in a complex environment with other agents. At first glance we should send the agent some messages and see if it “behaves” correctly. There’s a lot to what “behaves” means, but now that we are agent experts, we need to know:

·         Is the state of the agent correct prior to receiving a message?

·         Are the state variables set correctly on message reception?

·         Does the scheduler pick the right actions, if any, to execute?

·         Do the actions do the right things? In other words do they update their state correctly and send the right messages.

Let’s investigate each of these in turn and in the context of the Host agent of v3.0 when it is just starting up.

8.2    Checking Message Reception

When an agent receives a message, it sets some state and calls stateChanged(). Testing whether it does this correctly is much like the unit testing in the Junit paper. So, for example if we are testing the “I’m hungry(customer)” message: we’d first check to make sure the agent is in a state that we expect. So, we should do some pre-condition testing prior to sending the message. For example:

  • make sure waitingCustomers is empty prior to receiving the message.
  • "send" the message.

Now, test to see whether the message was received correctly. We want to check that:

  • waitingCustomers is not null.
  • customer is in the list.
  • customer is at the end of the list (if you expect a FIFO discipline).
  • Make sure the stateChanged semaphore is full (i.e. has positive permits).

 

These tests are straightforward, but doing them in a fully running agent is not. After the agent receives the message, the schedule might run before our tests are complete. Right? The agent may start carrying out some actions that change the variables, before we can test them. In other words our test code is in a race condition with the agent. That is definitely not good for testing. How do we fix that?

 

Simple. Just don't call the startThread() method in your testing code. That will give you full control of each of the agent's components.

 

8.3    Checking the Scheduler

Now your test code looks something like this pseudo-code:

 

Customer c = new Customer(“John”);

Waiter w = new Waiter(“Bill”)

Host h = new Host();

Assert.assertTrue(waitingCustomers EQ NIL));//Should be no customers on startup

h.msgI’mHungry(c);

//now comes code to test the variables

Assert.assertTrue(waitingCustomers NEQ NIL));   

//other tests    

pickAndExecuteAnAction(); //calls the scheduler

 

The question now is does the right action fire? This is somewhat complicated. If there are tables empty, you want the waiter assignment action to fire. If the tables are full you want to make sure that nothing happens. So clearly, you have to set up several experiments to check out ALL the possibilities. Let’s look at the case where tables are free and we expect the waiter assignment action to fire.

8.4    Checking the Actions

The first line of code in the action of our agents is a Print or a Do() (which does a print). That simply puts the output in the standard place for VIEWING by you. In the Junit discussion, they said whenever you have a print statement in your code, you should consider writing a test. Here is exactly that opportunity.

 

That Print statement describes which action is executing and what its parameters are. Instead of just printing it, you should also put it into some public data structure, such as an array of strings, which can be tested (you will be supplied with a Log class for exactly this purpose). Let's assume your action outputs the string “Assigning waiter Bill to sit customer John at table 1.”  Then, you can write a test:

Assert.assertTrue(output[0].equals(“Assigning waiter Bill to sit customer John at table 1”));

 

So, testing that the right action has been called is easy. If the agent changes any state after the action is called, testing will involve techniques we’ve already explored.  What about testing whether the agent sends the right messages to other agents and is prepared to receive replies from them? That’s not so easy. Agents send messages to and receive messages from other agents. Testing this seems to mean using the other agents in your test suite. But we already explained how unit testing means we must test without them. And anyhow, using full running agents is hard to control in the testing environment because of automatic mode. We need another technique in our repertoire called Mock Objects.

8.5    Checking the Messaging using Mock Objects

The Host action is going to assign responsibility of the customer to the chosen waiter. The action will do something like:

1.      Output a string describing the intent of the action.

2.      Update its waiter data structure to indicate a new customer has been assigned.

3.      Update its table data structure

4.      Update its customer data structure

5.      Send a message to the waiter with the customer and table assignment.

 

All but 5 are easy to test. Easy in the sense that at least we’ve done that kind of testing before. Testing the message-send means that we have to make sure the message that this Host action composes is compliant with the Waiter’s messaging interface. Doesn’t this imply that we have to have the Waiter in our test suite in order to make sure it is received correctly? It seems to, but Mock Objects come to the rescue.

 

Mock objects are fake implementations of objects that have the same interface as the real objects. Their purpose is to SIMULATE the real objects strictly for testing. Of course, to use Mock Objects correctly the interfaces must obviously have been determined. Otherwise, how could anyone unit test anything?

 

Here’s how the Mock Object message handler works:

  • It outputs a string that describes the message and its parameters. That string is going to be what you test against. you will use the same log mechanism you used with testing that the correct action was called.
  • It then does WHATEVER you need it to do in order to advance your testing. Remember it is pure simulation. It doesn’t have to be a real agent; it just has to act like one. It might simply send a message back to your agent. It might send an error message back. It might start a timer and send a message back later. You design it.

The point is that the Mock Object is just a hack, a well-thought out hack to be sure, that’s just there for simulation.

 

Let’s see what the mock Waiter agent needs to do in our current scenario.

  1. It needs to output what’s in the message. Something like “Waiter Bill receives message to seat customer John at table 1.” You will verify this output in your test code using the log mechanism.
  2. Now, normally the waiter would set some variables and eventually seat the customer, take his order, etc. When the customer is done eating, the waiter sends a message back to the Host saying the table is now empty. Well, we don’t care about all that except for the message back to Host. We only care about the Host. So, we have two alternatives:
    1. The mock Waiter can send the message; or
    2. Your testing code can send it (after testing that it received the right message). 

In either case you have to test that your Host agent does the right thing on receiving the next message. Your testing code MUST KNOW what is coming back. If two or three responses are possible from the Waiter, it may be simpler to use method b.

 

This is probably your first introduction to writing simulation code. It’s actually more difficult than real code because understanding all the cases is important, but how to “manipulate” your testing environment to carry them all out can be challenging (and fun).

 

The agent you are building should not care whether it is running in the real or test environment. Therefore:

  • The mock agents must have the same class names as the real agents.
  • You will have to set up your Ant environment to be able to store mock objects “near” the real agent and the unit test suites.
  • Don’t forget, if the interface of an agent changes, the mock agents, and the tests need to be fixed.

 

The TA’s will help you set all this up in lab next week.

9      Other Design Ideas

9.1    Multi-step actions

Little has been said about what goes into an action. It is simply a chunk of “schedulable” code. So far in our restaurant application, the “look” of all the actions has been the same:

  • A “print” statement that describes/simulates the activity;
  • State setting and a call to stateChanged()
  • Perhaps a message to another agent.

 

Sometimes it makes sense for an action to wait for some event to occur, and continue when it occurs. For example, imagine trying to design how a waiter might take the order from a table with several customers. Let’s assume for a moment that a customer can give the waiter a full order in one string. This action might look like the following:

 

Print “waiter:” waiter “getting order from customer:” table.customer() “at” table.number();

c.msgWhatWouldYouLike();

//Let’s wait, but how??

storeOrder(order); //where did order come from???

Print “waiter:” waiter.name() “received order” order

“from customer:” table.customer();    

 

The main difference is that this action does in one piece of code what we would have to do in two actions in our current agent design: one action to “ask” for the order and another to “store” it. We expressively avoided any mechanism like this before because since our scheduler was synchronized with its actions, it might wait forever for the response and be blocked from doing anything else. If we could solve that problem (and we will in the next section), we will have the convenience of more “natural” multi-step actions like the one above.

 

So, as indicated by the comments above, there are a few problems:

1.      How do we actually wait?

2.      How does the message from the customer unblock us?

3.      Where did the order data come from?

                                          

The answer to 1 and 2 is to use a semaphore. And why not? Semaphores were designed to synchronize activities. Here, the waiter wants to wait until the customer places the order. It’s exactly the classic situation in which semaphores are first introduced. Remember?

 

First the agent must create a semaphore in the same scope as stateChange, something like:

 

Semaphore waitForOrder = new Semaphore(0,true);

 

Then the line of code above that waits will be:
                
waitForOrder.acquire()

 

It will wait until the semaphore is filled by the message reception. Something like:

 

void msgHereIsMyOrder(String order){

  orderResponse.order = order;

  stateChanged();

  waitForOrder.release();

}

 

The orderResponse data structure is another agent global variable used specifically for storing the data that comes from this message as shown above. Now the code for the action can be completed:

 

Print “waiter:” waiter “getting order from customer:” table.customer() “at” table.number();

c.msgWhatWouldYouLike();

waitForOrder.acquire();

c.storeOrder(orderResponse.order);

Print “waiter:” waiter.name() “received order”
      orderResponse.order “from customer:” table.customer();

 

There are two serious problems here that we will ignore when you implement this for your agents. Can you see them?

·         You assumed that the response came from the customer you sent the message to. Will that always be the case? Maybe you should add a Customer parameter to the msgHereIsMyOrder() message handler and store that in orderResponse  along with the order. Then your action code could test if it is from the right customer. What if it isn’t?

·         Could you possibly get a message from a Customer you were not expecting? If you did, what would happen? [The single global data structure would be clobbered.] How would you solve this? [Have a queue of order-Customer pairs? Need a loop around the take which tests that the message came from the right customer, etc.]

 

As you can see this gets complicated in a hurry. Let’s not worry about those details for now.

 

Now that we’ve solved the multi-step action, more complicated actions are available to us. Here’s action code for taking orders from a table with many customers.

 

Print “waiter:” waiter.name() “getting orders from table:” table.name();

for {Customer c : table.customers()) { //java 1.5 iteration

  Print “waiter:” waiter.name() “asking customer:” c.name() “for order”;

c.msgWhatWouldYouLike();

  waitForOrder.acquire();

c.storeOrder(orderResponse.order);

Print “waiter:” waiter.name() “received order”
   orderResponse.order “from customer:” c.name();

 

}

 

So, we iterate through each customer at the table, asking and waiting. We could handle the iteration in the scheduler; we scheduler an action that does an  “ask and store” for a single customer.  Is that better? Why? [More easily interrupted?] We’ll see

 

Can you think of a different, realistic design that might involve multiple customer order messages arriving in arbitrary order? [Say the waiter “broadcasts” a message to the table: msgWhatWillYouBeOrdering(); Then the responses will come in any order. What will your action look like now?]  How would you simulate a broadcast? [Loop through and send each the message. Then wait for replies.] In real life this puts the shared data burden on the customers to order themselves. What is the shared data? [The ear channel of the waiter who can only “hear” one message at a time. If the customers wrote down their order on paper or email, he could take them all “simultaneously.”

 

All that was relatively easy. Now we have to solve the problem that could arise from a blocked multi-step action.

9.2    Shutting down the restaurant

You could send each agent a stopThread() message. That would work from a “computer science” point of view. But, that is surely inadequate from a “restaurant application” point of view. Here is the most important shutdown requirement:

·         Once you close, accept no more customers.

Notice this says nothing about customers currently in the restaurant; you must finish serving them. Obviously, there would be many others about cleanup, closing out the cash register, shutting the restaurant, etc.

9.3    Menus

In the waiter requirements he hands a menu to the customers. Where do the menus come from? In a real restaurant that issue must be faced. In our implementation, the waiter "holds" the menus.

10  Possible Homework Problems/Questions

·         How should the initial call to the agent scheduler happen?

·         What has to be done if the agent constructor doesn’t call startAgent()?

·         Why is pickAndExecuteAnAction() abstract?

·         Why are there no message-handling calls in abstract Agent class? Could there be? [Yes, if you wanted all to respond to, say, a Status() message, then you could have an abstract Status() method in the base class.]

·         Come up with a design for the requirement, “once you close, accept no more customers.”

·         Can you figure out why the table number is part of the message in section 4.2.2.1?

·         A single waiter may be servicing many clients. In some scenario, he may seat one customer, and then deliver a food order to another, and so forth. He will service the newly seated customer when the time comes. This, of course, brings up the topic of priorities. Can you come with a priority scheme?

·         If no waiters are working, customers queue up with the host. Should the host do anything if the restaurant starts to get busy?

·         Can you test the interleaving discussed in section 8.8 using JUnit?

·         What is the reason that the customer’s choice is turned into an order in the interaction diagram of section 8.6?

11  Change Reporting

3/8/2005 Added Chapter 9 about multi-step actions and concurrent actions.

3/31/2005 Adding Chapter 10 about RMI agents.

Summer 2006: Redid the roadmap to emphasize design skills.

Summer 2011 Removed v1, removed concurrent actions, starting from v3, added fsm customer agent.

Winter Break 2012  General updating.

The University of Southern California does not screen or control the content on this website and thus does not guarantee the accuracy, integrity, or quality of such content. All content on this website is provided by and is the sole responsibility of the person from which such content originated, and such content does not necessarily reflect the opinions of the University administration or the Board of Trustees