CSC 241- set #2 (starting September 15, 1997)


Assignment #1

Assignment #1 is designed to help in reviewing designing and using classes, but also gives you the opportunity to design your first applet. You begin by designing a simple Light class and testing it. You will then go on to a more complicated trafficLight class that extends Canvas. trafficLight will allow graphical representation of trafficLights, it creates and maintains three Light objects that represent the three color lights in a traffic light. Keep in mind that Lights are not graphical objects, and simply, each keep the state of a light, on/off, and what color in each case. You will notice that some of the code is already provided for you, this is to make sure that you don't spend your time on size of circles and rectangles involved in drawing a traffic light. Testing trafficLight should be easy as I am giving you an applet that will use it to display four traffic lights.

The last part of this assignment requires the development of an applet that makes a traffic light and a button. and each time the button is clicked, it changes the light.


Writing Pre/Post conditions

I don't except you to develop mathematically complete pre and post conditions for your methods. It is, however, important to learn about pre/post conditions. A pre-condition is a clear statement of a method's assumptions at the start. A post condition is a clear statement and promise about what a method will do by the time it reaches its end. Here is an example:

Suppose that we have a container class that keeps an array of integer values. Lets call this list l(l). We want a method that sorts l, either in ascending or descending order. So, the sort method has an integer parameter that is suppose to be either 1 or 0. If its 1, we sort in ascending order, otherwise, we sort the values in descending order. Assume that last_cell is the index for the last value in the list. Here is a reasonable pre/post condition for sort:

          void sort (int a_or_d)
		  /** Precondition: 
                  (a_or_d == 0) ||  (a_or_d == 1)

		      Postcondition: 
                  ((a_or_d == 1) && (l[0] <= l[1] <= ... <= l[last_cell])) ||
                  ((a_or_d == 0) && (l[0] >= l[1] >= ... >= l[last_cell]))
          **/

Tank example.

It is time to review class design and encapsulation in Java. A simple tank class is designed to help in discussing state and methods of objects. Tank objects allow us to represent tanks that contain some type of fluid in application programs. In the version that we are discussing here, they are only responsible for controlling access to tank content.

How is this content manipulated by the application? Tanks are simple objects, we like to add fluid to them, remove fluid from them. Occasionally, we need to know what the current content of a tank is, and if they are full or empty. This is where the methods come into play. We concern ourselves with determining functionality for objects and giving a tank the methods that are needed for it to be useful. It takes some time to learn to effectively design objects, it takes significantly longer to learn to design them so that they remain useful beyond one application! You will learn about reuse in this course and the natural framework that Java provides for it.

Lets discuss a few issues that may not be obvious. Why would we not permit an application program to just manipulate content of a tank with simple assignment statements? The integrity of a tank is an important consideration. Having tank objects control modification of their contents by only allowing it to be done via an add or remove method provide a safeguard that is important. Our experiences in programming large applications, before object orientation tells us that much of our time was spent on tracking down errors that manifested themselves at a point different from where they were caused. The phrase that best describe this approach is programming with side-effects. What does this mean? you write an application that requires one or more tanks and whenever you need, increment or decrement the contents. You don't want to end up with a negative content or a content that is over capacity and good programmers would always check for such anomalies, or do they! When the side-effect of calling a variety of subprograms are changes in content of a tank, and one or more of those subprograms do not guard against such things we have problems. The process that you need to go through to figure out where this violation occurred is typically a very time consuming task. In some context, these types of error are very hard to find.

This is not to say we don't have logic errors when our approach is Object Oriented, but we have a framework that allows us to isolate such problems more rapidly that is both natural and easy. In our tank example, a tank object becomes responsible for handling its own content, the application gets to only manipulate the content through the add and remove methods provided for tanks. Now, Java also gives us a really nice way of handling exceptions like overflow or underflow attempts by an application. We can design add and remove methods to throw exceptions when such attempts are made. The application must catch these exceptions any time they call upon a tank to manipulate its content. The end result is that you find the problem at its source and not at some later point when the program breaks. You also never have to count on the application programmer to be cautious, as they won't even get to compile the program without dealing with exceptions.


Details of Tank class

capacity

All objects of class Tank are of fixed capacity. Infact, we only have a "class variable" for capcity that is a constant. public static final int capacity=5000; declares capacity. public means that capacity can be accessed anywhere in Tank or any subclass of tank. It could also be accessed in any application that imports the Tank class by simply referring to it as Tank.capacity. Making variables public should be done with care. In this case, you can see that capacity is also final which declares it as a constant that can't change. static is what makes capacity a "class variable". Static variables don't get constructed for each object of a class, they sare hared by all objects of the particular class.

content_

protected int content_; declares content_. Each object of class Tank gets its own content_. Note that the word static does not appear in the declaration. content_ is protected, this makes it accessible in Tank or any subclass of Tank, but not in the applications that import it. Subclassing will be discussed later in this lecture.

Tank Construction

A tank can be constructed with 0 or more gallons of fluid. There are two constructors build into this class, one with no parameter that sets tank's content to 0, and one with a parameter that is used to set the initial content of tank. Lets consider the one with a parameter:

  // Construct a new Tank with the indicated initial content.  Guard against
  // overflow due to initial content or negative initial content.
  public Tank(int init_cont)  throws
    TankOverFlowException, IllegalArgumentException {
      if (init_cont < 0) 
        throw new IllegalArgumentException();
      int overflow = init_cont - Tank.capacity;
      if (overflow > 0)
        throw new TankOverFlowException(this, overflow);
    content_ = init_cont;
  }

What we want to do is to set content_ to init_cont when a tank object is created with this constructor. Note that the throw statements only occur when we detect exceptions. Exceptions are generally abnormal cases. In your documentation (pre/post conditions), you make it clear that you don't want to handle negative values for your parameter. If the user of this class gives you one anyway, you throw the exception. We will later see how we catch exceptions in our testing of class Tank. An exception doesn't have to terminate the user's program, although in all the test programs for the Tank class it does.

methods

The Tank class has three methods: add, remove, and content. The first two methods change a tank's content, the third, just returns the content of a tank. add, in normal cases, executes content_ +=amount; which simply adds amount to content_. remove, in normal cases, executes content_ -=amount; which simply subtracts amount from content_.

Exceptions

Three exceptions may be thrown in this class: TankOverFlowException, IllegalArgumentException, and TankUnderFlowException. A tank may overflow when constructed with an initial content that is larger than capacity or when its add method is invoked and the amount provided would send the content over capacity. Underflow simply means that the amount supplied for the remove method would cause tank to go below empty. IllegalArumentException is thrown when amount or init_cont parameters are negative.

Check out this statement carefully: throw new TankOverFlowException(this, overflow);. The word new means that we are constructing an object of some class. An object of TankOverFlowException is being created and thrown. The throw statement requires an object of an exception class.

TankOverFlowException Class

Here is the code for the class TankOverFlowException.

public class TankOverFlowException extends Exception {
  public Tank overflowTank;
  public int overflow;
  public TankOverFlowException(Tank t, int amount) {
    super(); overflowTank = t; overflow = amount;
  }
}

Exceptions provide us with a nice example of inheritance in Object Oriented design. In java there are existing exception classes that are very specific, such as, FileNotFoundException used in test_more_tank.java which needs to open a file and read from it, IllegalArgumentException is used in the Tank class itself for handling bad parameters. However, we don't have an existing exception class that would deal with our overflow problem. So, we built one. Notice that TankOverFlowException extends Exception, that means it inherits everything in Exception, and is going to add on some new things. For instance, for this subclass of the Exception class, the variable overflowtank which is an object of class Tank is added. Also, the constructor is specific for TankOverFlowException. super(); is an invocation of the constructor for the super class Exception.

Lets take a second and look at throw new TankOverFlowException(this, overflow); from the Tank constructor shown earlier. When we throw an exception, we create an object of the particular exception class. In this case, we have specified two arguments for construction of such an object. The first one, based on the definition of the constructor, must be an object of class Tank. So, what is this? this provides the reference for the object where the overflow occurred in. This way, exception object that is thrown here knows what tank overflowed. The second argument provides by what amount the did the tank overflow.

Catching TankOverFlowException

Here is the code from test_simple_tank.java that shows how we try something that may cause an exception and how we catch the exception if it was thrown.

    try {
      Tank t1 = new Tank(50);//create a tank with 50 as content
      ...
    }
    ...
	catch (TankOverFlowException e) {
		System.err.println("Tank overflow by "+e.overflow);
	}

Remember that Tank construction may cause an overflow exception. So, in this test program, we need the try/catch combination to try to construct a tank, amongst other things, and catch the exception, if it failed. When using a method that may throw an exception, you must do so in the context of a try/catch or the compiler will complain.

The order in which you catch exceptions is sometimes important. The following two catch clauses in test_more_tank.java had to go in the order given:

    catch (FileNotFoundException e) {
      System.err.println("File does not exist");
      return; 
    }
    catch (IOException e) {
      System.err.println("Unsuccessful read");
      return; 
    }

Why should FileNotFoundException go before IOException? because, the first is a subclass of the later. If a FileNotFoundException is thrown when we perform InputStream ist = new FileInputStream(fname);, we want it caught as such and not just as an IOException! If you change the order of the catch clauses, you'll notice that the compiler will, very nicely, let you know that your catch (FileNotFoundException e) {...} is not reachable! What we are assuming here in the way we have handled these two exceptions is that catching FileNotFoundException helps us recognize that we may have a bad file name and catching IOException would determine a problem when we actually read.

IO in Java

Lets discuss the test_more_tank.java in more detail. This program is representative of test programs that one would write to test a program. It takes input values from a data file, such as, test.dat which enables us to have more flexibility in what we test a class for as we can change the data without modifying the program to test multiple cases. Basically, it is designed to prompt for a file name, and read integer values from the file that are either added or removed from a tank object. If the value read from the data file is positive, it is added to the tank, otherwise, it is removed from the tank. After each add or remove, a message is output that indicates how much was added or removed and what the content of the tank was after the operation. When there are no more values in the file, the program stops. If exception occurs, such as, tank overflow, the program would output a message and stop.

Lets discuss the logic and the IO operation of this program. The exception handling aspects have already been covered earlier. We will consider the code piece by piece:

String text; declares a string variable that we will use for reading integer values from our data file.

Tank t = new Tank(500); creates a tank with a 500 as its content, named t.

DataInputStream inp = new DataInputStream(System.in); creates an object that we can read strings from. System.in is an object of InputStream. This object only allows for input that of single characters. Class DataInputStream enables you to read strings amongst other things.

System.out.println("Enter File Name:"); prompts the user for a file name using the standard output object.

String fname = inp.readLine(); is used to read the input file name from the keyboard (user). The following sequence of code, simply, opens the input file, and read the first line of it into text.

      InputStream ist = new FileInputStream(fname);
	  DataInputStream istream = new DataInputStream(ist);
      text = istream.readLine();

You should notice text = istream.readLine(); also appears before the end of the while loop. End of file is determined when istream.readLine() returns null, which is why the while loop is designed to terminate if text is null. You will get deprecation warnings beause of readLine, but ignore them for now.

The statement int am = Integer.parseInt(text); invokes a class method in class Integer that allows for parsing a string and returning the integer value found in the string. The next few lines simply check the value read, and if positive, add it to tank, otherwise, remove it from tank. Note that -amt is sent t.remove.

        if (am > 0) {
          t.add(am);
          System.out.println("tank content after adding "+ am + " = "+
                             t.content());
        }
        else {
          t.remove(-am);
          System.out.println("tank content after removing "+ -am + " = "+
                             t.content());
        }