You will write a simulation for a restaurant. The simulation has four parameters: the number of eaters (diners) that wish to enter the restaurant; the number of tables in the restaurant (there can only be as many eaters as there are tables at a time; other eaters must wait for a free table before placing their order); the number of cooks in the kitchen that fill orders; and the capacity of machines in the kitchen used for producing food.
The simulation will take place as follows. Eaters will attempt to enter the restaurant, but will only be allowed in if there are free tables; otherwise they must wait until a table is free. Once an eater has entered he will place an order, which is a list of food items. Each placed order will be handled by an available cook, who will prepare all the food in the order, using various machines. Each machine has a capacity, and can produce food items in parallel (perhaps for different orders), up to its capacity. There will be one machine for each kind of food item. A cook handles an order by submitting each item to a relevant machine (there is a different machine for each kind of food) that is not at full capacity; the cook may have to wait for machines that are full. Items can be cooked in parallel. Once all of the items have been cooked, the order is complete, and delivered to the eater, who then leaves the restaurant. Then the cook can retrieve the next order. Once all of the eaters who want to enter the restaurant and eat have done so, the simulation ends.
Our particular restaurant will only have three food items (and thus three machines): a burger, made by machine Grill, which takes 500 ms to make; fries, made by machine Frier, which take 250ms to make; and a coke, made by machine Soda Fountain, which takes 100 ms to make. All three machines have the same capacity, as specified by the capacity parameter.
Much of the design of the simulation will be up to you. However, we do have some requirements.
First, you must not use the monitor pattern for synchronizing any part of your simulation. To be starkly precise: the keyword synchronized and calls to wait, notify, and notifyAll must not appear anywhere in your code. Any synchronization you use must be higher-level abstractions provided by the concurrency libraries, such as CountDownLatches, BlockingQueues, etc.
Second, you must implement a method to validate the output of the simulation. This will be run immediately after your simulation completes, to validate its output. (In point of fact, when testing your project, we will use our own validation method. The one you implement will help you with getting the simulation right. We will also grade that you have implemented it correctly.)
Third, you must implement (or use) the following classes, in particular the listed methods. All of these are available in the skeleton code. You may implement any other methods you need for these classes, as you wish. You can also implement additional classes as needed:
* The starred classes are provided for you, and should not be changed; the remaining classes can be changed within the bounds given below.
main(String args[]) : This class is the entrypoint of the simulation, which is initiated by the main method. The four simulation parameters are read from the command-line in the following order: number of eaters, number of cooks, number of tables, machine capacity. All must be greater than zero (else an exception is thrown, likewise if insufficient arguments). It will call runSimulation with these parameters.
runSimulation(numEaters, numCooks, numTables, machineCapacity) : This method executes the simulation, returning the list of SimulationEvents generated during the simulation run. Each event should be printed immediately, via its toString() method, as well as logged to be returned for validation by the Validate.validateSimulation method.
When the simulation starts, it will generate a SimulationEvent via a call to SimulationEvent.startSimulation(). The simulation consists of the given number of eaters, cooks, and tables. It also involves exactly three machines, each with the given capacity. One machine makes burgers, one makes fries, and one makes cokes. The machine name is "Grill" for the burger machine, "Frier" for the machine that makes fries, and "Soda Fountain" for the machine that makes cokes. Each Eater places the same order: one burger, one fries, one coke. Once all Eaters have completed, the simulation terminates, shutting down the machines, and calling interrupt on each of the cooks (telling them they can go home). The last thing the simulation will do is generate the event SimulationEvent.endSimulation().
Food
This class is provided for you. It represents a
food item. You will create only three food items for your simulation
---hamburger, fries, and coke, as described above--- but your classes
should treat food items generically; i.e., you should be able to
easily change just the Simulation class if you want the
simulation to have Eaters order different food items or
different amounts, without changing any other class.
Eater implements Runnable (should run as its own
thread)
constructor Eater(String name, List<Food> order) : takes the name of the eater and the food list it wishes to order; the format of the name is described below. You may extend this constructor with other parameters if you would find it useful. Each eater's order must be given a unique order number, which is used in generating relevant simulation events; it is efficacious to generate this number in the constructor. All eater names should be of the form "Eater "+num where num is between 0 and the number of eaters minus one.
run() : attempts to enter the restaurant, places order, waits for order, eats order, leaves. Eaters will generate the following events.
Before entering the restaurant: SimulationEvent.eaterStarting()
After entering the restaurant: SimulationEvent.eaterEnteredRestaurant()
After placing order (but before it has been filled): SimulationEvent.eaterPlacedOrder()
After receiving order: SimulationEvent.eaterReceivedOrder()
Just before about to leave the
restaurant: SimulationEvent.eaterLeavingRestaurant()
Cook implements Runnable (should run as its own thread)
constructor Cook(String name) : the name is the name of the cook (described below). You may extend this constructor with other parameters if you wish. All cook names should be of the form "Cook "+num where num is between 0 and the number of cooks minus one.
run() : waits for orders from the restaurant, processes the order by submitting each food item to an appropriate machine. Once all machines have produced the desired food, the order is complete, and the Eater is notified. The cook can then go to process the next order. If during its execution the cook is interrupted (i.e., some other thread calls the interrupt() method on it, which could raise InterruptedException if the cook is blocking), then it terminates. Cooks will print the following messages:
At startup: SimulationEvent.cookStarting()
Upon starting an order: SimulationEvent.cookReceivedOrder()
Upon submitted request to food machine: SimulationEvent.cookStartedFood()
Upon receiving a completed food item: SimulationEvent.cookFinishedFood()
Upon completing an order: SimulationEvent.cookCompletedOrder()
Just before terminating: SimulationEvent.cookEnding()
constructor Machine(String name, Food food, int capacity) : the name of the machine, the Food it makes, and the capacity (how many Food items may be in process at once). The names of the machines are specified in the description of the Simulation class, above.
makeFood() : This method is called by a Cook in order to make the Machine's food item. You can extend this method however you like, e.g., you can have it take extra parameters or return something other than void. It should block if the machine is currently at full capacity. If not, the method should return, so the Cook making the call can proceed. You will need to implement some means to notify the calling Cook when the food item is finished. While a machine is making food items, it will generate the following events:
At startup: SimulationEvent.machineStarting()
When beginning to make a food item: SimulationEvent.machineCookingFood()
When done making a food item: SimulationEvent.machineDoneFood()
When shut down, at the end of the simulation: SimulationEvent.machineEnding()
Hint: you will need some way for your Machine to cook food items in parallel, up to the capacity, but ensure that each item takes the required time. You might do this by having a Machine use threads internally to perform the "work" of cooking the Food (in this case, the only thing that will actually be done is waiting the proper amount of time). This approach will require some way of communicating a request by a Cook to make Food to an internal thread, and a way to communicate back to that Cook that the Food is done.
Begin by downloading the project zip file here: Skeleton Eclipse project (or just the code). The directory structure of the zipped folder should allow the existing code to be directly imported into the Eclipse IDE. Note that all code is part of the cmsc433.p2 package, and you should preserve this package structure even if not using an IDE. After downloading the source, familiarize yourself with the above requirements while looking at the skeleton code, and the javadocs found here (and also in the zip file): API Specification. You will need to implement addiitonal methods and/or classes for your simulation; this is just the starting point.
We have allowed a great deal of freedom in the design of your classes for this project. Because of this freedom, constructing generic unit-level or system-level tests poses a problem. In particular, we've allowed you to modify the constructors of many objects (such as Machine and Eater), and unit tests would not be repeatable on modified objects. Therefore, you are responsible for your own tests for this project. We will test your code with our own validator, which will check many additional simulation characteristics.
We have provided a class SamplePublicTests as a starting point for testing your simulation, which assumes your Validate class is correct. If not, all bets are off. As such, you will also want to write tests to test your validator (e.g., by generating bogus simulations and making sure it flags them). As a suggestion: you should running your own simulation tests involving different parameters for the simulation. The public tests only try 1x1x1x1, 2x1x1x1 etc. As you become convinced your validation method is correct, make sure it works with different variations of these parameters. Or, to help debug your validation, try these other parameters to see if they help reveal whether the issue is.
Every file you submit should have your name and UID. To enforce academic integrity, Code WILL BE CHECKED for similarity to other submissions. Each student should make his or her own individual submission.
Submit your code on the Department's Submit Server. Contact the TA if you have any troubles submitting.
You are also required to submit a short "design document" describing the design choices you made in your simulation. Argue how your design satisfies the validation criteria, particularly in its use of synchronization. Feel free to draw pictures to better make your points. Due to the non-deterministic nature of multi-threaded code, a significant portion of your project grade will come from manual inspection of your code, AS GUIDED BY THIS ACCOMPANYING DOCUMENT. Therefore, it is in your best interest to provide a sufficient amount of detail in order to reduce the chance for misinterpretation. Include this document as a PDF file in the top directory of what you submit.
For this project, due to the freedom we've given you with design, your design document should also include a comprehensive test plan. The test plan should describe how you tested each piece of functionality in your code. You may also include any tests that you've written and instructions on how to run these tests.