PI: Laboratory 4: Calculator - Dispatch and Procedures

Introduction

As per policy, you are encouraged to discuss this assignment in detail with
your classmates, but you are required to do the write-up on your own. You may
get comments from other students on all portions of your write-up before
turning it in, if you wish. Please include the names of anyone with whom you
collaborate, in any way, on this assignment, and indicate the nature of the
collaboration. [Failure to include this information is a violation of the
collaboration policy.]

This assignment emphasizes the following topics:

Finite state machines

Dispatch

Procedural encapsulation

You should read through this entire assignment and complete the Lab preparation section before coming
to lab. Some portions of the Post Lab write-up also require your thinking
about them before and during the laboratory portion of the assignment.

Pre-Lab

Although most of the work on the pre-lab assignment may be done in
cooperation with other students, we recommend that you design at least
part of one of the FSMs by yourself. In any case, make sure that each
of you fully understands the solutions that you come up with.

Lab Preparation

The laboratory project for this week is a simple four-function
calculator. We have constructed the calculator's GUI (graphical user
interface), and your mission is to write the logic controller (the
"brains") behind this graphical interface.

Specifically, you will implement an active object class which will
interact with a Calculator object to respond to the user. The
Calculator can do such things as figuring out which button
the user presses, and managing redrawing, displaying, and other
various things that happen on your computer screen. However, it does
not actually know how to interpret these button presses, or
what answer to display. That is your object's job.

For this lab, you will need to define an active (i.e., animate)
object class that will interact with the Calculator object.
A particular instance of a Calculator object will be passed
to your object's constructor when your object is created (i.e., when
you run the Calculator.Main program), so be sure to define an
appropriate constructor. Your object must then tell the
Calculator exactly what to do, such as recognizing and
appropriately responding to the user's button presses.

Reviewing the
Calculator Interface

You should begin by examining the Calculator interface (also javadoc) . It defines
everything that you'll need to know about a Calculator. In
particular, it contains three kinds of declarations:

ButtonIDs

The first things defined in Calculator.java are the "buttonID
constants": Calculator.OP_ADD, Calculator.OP_SUB,
Calculator.OP_MUL, Calculator.OP_DIV,
Calculator.EQUALS, Calculator.DOT, and
Calculator.CLEAR.

These are all static, i.e., accessible as parts of the
Calculator interface: e.g., Calculator.OP_ADD. They
are also final, i.e., they cannot be changed. Finally, they
are all of type int. (Hint: think switch/case.)

All of these constants are distinct from one another and from the
numbers 0 through 9; it is intended that the numbers 0 through 9 serve
as buttonIDs for their respective buttons.

An additional constant, Calculator.NO_OP, is not a
buttonID but is provided for convenience.

ButtonLabels

Calculator.ButtonLabels is a constant array of
Strings that provides names for each button. That is,
Calculator.ButtonLabels[buttonID] is a String
containing a suitable label for the button identified with the
int buttonID (which can be any of the defined buttonID
constants, or 0-9). All of the buttonIDs, as well as 0 through 9, may
be used as indices for ButtonLabels.

Methods: getText,
setText, getButton

Every Calculator object has its own getButton method:

public int getButton();

The int returned is a buttonID indicating which of the
buttons was pressed: one of 0 through 9, or one of the buttonIDs defined in the Calculator
interface. Each time that the getButton method is called, it
returns the next button pressed. So, if the calculator's user presses
3 + 4 =, your object's first call to your Calculator's
getButton() method will return 3. The next call to
getButton() will return Calculator.OP_ADD. The third
call will return 4, and the fourth call will return
Calculator.EQUALS. Then, if you call your Calculator's
getButton method again, it will wait for another
button to be pressed before returning.

Each Calculator object also has its own getText and
setText methods for interacting with the Calculator's
display. (Note that the display, and these methods, operate using
Strings, not a numeric type such as int.) Their signatures
are as follows:

public String getText();

public void setText( String s);

getText() returns the String currently displayed on that
Calculator's screen. Unlike getButton(), getText()
doesn't wait for something to happen (e.g., for the value to change) before
returning it; it just tells you what text is currently being displayed.

setTextreplaces any currently displayed text with its
single argument, a String.

Reviewing the provided
helper methods

Before you go to lab, you should also familiarize yourself with the following
method signatures.

We've provided you with a coercion routine which takes a String
and returns a floating point (double) decimal number:

double cs101.util.Coerce.StringTodouble( String s);

(Remember that in Java, double (the primitive data type) and
Double (the object) are two different kinds of things.)

You can also use Java's built-in routine to do the same thing. It
throws NumberFormatException if the string you pass it doesn't
represent a double. The signature looks like this:

Designing the FSM
controller(s)

In lab, you will be writing a class that will
interact with an instance of the Calculator object, and serve
as a controller for it. You should name your class
ButtonHandler, and it should be a self-animating active
object. At each point in time, your class should ask the Calculator
for the next button pressed, and then, if appropriate, perform any
desired computations and update the Calculator's display.
(Needless to say, it should also update its own internal state
appropriately at each step.)

As usual, you should follow an incremental design strategy
-- keeping the end goal (a working calculator) in mind, but starting
with simple implementations first. Before going to lab, think about
how you would implement the following:

Implementing Echo

A simple test that will help ensure that your infrastructure is
working is to simply echo each button pressed back to its
Calculator's screen. (Of course, you can also use
System.out.println, but echoing to the calculator is so much cooler!)
You can start by echoing back the buttonIDs (which are ints)
as they are handed to you, but you'll get prettier results if you use
the ButtonLabels array, which provides you with more
informative String names for the non-numeric
buttons. (Remember that ButtonLabels belongs to the
Calculator interface, not to an individual instance object.)

Implementing Reset

Once you've gotten echoing to work, you can start to build in some
logic. A useful feature to have is a reset feature -- that is,
a certain button or sequence of button presses that will bring your
calculator back into a known state.

Write code that will allow you to reset your calculator. For this
purpose, you will probably find it useful to define a
reset() method which you can call whenever you want to reset
the calculator. Later on, you can then fill the body of
reset() with the things you want to do when you reset (i.e.,
setting fields to their appropriate values). Remember that there is
nothing wrong with an object calling its own methods. In fact,
methods used in this way can be very useful for grouping commonly used
code together, so that instead of rewriting the same code whenever you
need it, you can just call the method.

Question: How would you make the calculator reset
(i.e., call the reset() method you have defined in your class) when
then the CLEAR button is pressed, but not at any other time?

Scaling Up

Remember, don't try to implement a complex calculator all at once.
Break it down into pieces that you can test independently, and test
them thoroughly before moving on to the next step.

At this point, you can now try to design a simple
ButtonHandler that accepts only digit op digit
=. Any other input should cause the ButtonHandler
to reset itself. Map out the logic of this controller. At each step,
be explicit about what appears on the Calculator's display.
Since the operator does not appear on the display, you may want a
place to store it. Be sure that you know where you'll keep each piece
of information at each point along the way.

The target exercise is to have a calculator that understands
digit op digit equals, along with a working clear button.

Questions: Draw an FSM state diagram that represents
this logic. How many states does it have? How do you move from one
state to another?

Extra Stuff

Once you have digit op digit =, you can build more
complexity in any of several ways. Take these one step at a
time. Some of them require limited additional storage, e.g., if you
wish to treat all digits or all numbers uniformly.

Reset the calculator when the clear button is pressed.

Add the ability to handle multi-digit integers.

Add the ability to handle decimal numbers.

Make the clear button clear the display instead of resetting the
calculator.

Add the ability to handle op or = in
other positions.

Make the clear button clear the last key pressed.

Make the clear button clear the last key pressed if pressed once, but
clear the entry or reset the calculator if pressed twice.

Add memory. This will require a revised interface and a revised GUI
class to implement, so you should only expect to actually build this if
you have time to kill. This requires external storage.

In each case, be sure that your logic can handle any button press at any
time. Think of examples that your code should work on, as well as examples
where it might not behave as you would like. Write these examples down and
experiment with them in lab.

Drafting your code

As always, you should draft your code before coming to lab.

Your code should define a class called ButtonHandler. Its
constructor method should take a Calculator as its only argument.
Your ButtonHandler should be an active object: it should have its
own (activated) animacy. At each point in time, it should gobble up a button
press (by calling the Calculator object's getButton()
method) and, if appropriate, update the Calculator's display. (It
should also update its own internal state at each step.)

You will almost certainly want to keep your central control loop small.
This is best accomplished by spinning off procedures to handle separate
cases. By passing parameters, you can make these cases reasonably general.
Remember that you don't want to be writing a lot of the same code many times.
When this happens, it is a good hint that you've got a common pattern, and
it's always a good idea to write down the common pattern, give it a name, and
make it into its own method. But make sure that your common pattern makes
sense, too. A good rule of thumb is: if you can't explain your code pretty
clearly and concisely to someone else, it isn't designed right.

Details of the ButtonHandler specification, as well as hints for
a stage-by-stage implementation, are given in the
Laboratory section, below. Roughly speaking, it follows these steps:

Implement ButtonHandler as an active object type, but with
minimal logic.

Build a simple FSM into your code.

Expand your code in pieces, one step at a time, working with and not
against your abstractions.

Laboratory

What to Bring to Lab

Bring a development plan, listing at least 3 stages of code:

minimal code that will compile

code that echoes the buttons clicked to the display

a third stage which is a reasonable next step (not too big)

You do not need to have the whole code, but an english description of
what the code accomplishes and how.

Finite State Machine which implements digit op digit equals.
It should include transitions for any button from any state.

You should also have read the entire problem set and be prepared to
discuss an implementation strategy before you arrive.

Getting Started

Retrieve the code from the usual place
(\\stufps01\stufac\pi\calculator). The Codewarrior project
will not compile off the top because if requires you to have written a
calculator.ButtonHandler class.

When you begin to code, even though you may have constructed a
sophisticated design for your ButtonHandler, you should always begin
your lab-work with a simple, independently testable implementation and build
on it as each stage works robustly.

For example, start with a very simple class with just a constructor that
takes a Calculator as its only argument, and make sure that it compiles.
Then, you can begin testing your class design, without any of the internal
logic. Although it makes a pretty lousy calculator, a simple test that will
help ensure that your infrastructure is working is to simply echo each button
pressed back to its Calculator's screen.

Compile and run your code to make sure that you've built things correctly.
If you have problems, now is a good time to work them out.

Scaling up

Once you can get a very simple class working, you can then move on to
implementing a real calculator. Start with the 1-digit calculator you were
asked to design in the pre-lab, and add functionality incrementally if you
have the time.

The target exercise is to have a calculator that understands
digit op digit equals, along with a working clear button.

Remember that we think it's better to produce basic but elegant,
well-tested, well-documented, and well-understood code than code with
additional features but poorly written/documented/tested. We
are more interested in how well you do what you do than in how much
you do."

Record your experiences, including tests that various stages of your
implementation succeed and fail. Make sure that you understand how your code
behaves, and how it will behave under inappropriate as well as appropriate
circumstances.

Before you leave

Before you leave lab, you will need to have your code checked off by a course
staff member.

Post-Lab, AKA What
To Turn In

Your completed assignment should include:

on the front page, how much out-of-class time (approximately)
you've spent on reading, on preparation of the homework, in lab, and
on other non-class-time course-related activities (and what).
These times should include work from last Wednesday 5pm to this
Wednesday at 5pm