Exception Handling


I. Basic Concepts

   A. Exception: 

	1. An unusual event, either erroneous or not
	2. Detectable by either hardware or software
	3. May require special processing

   B. Exception handler: A code segment that processes an exception

   C. Examples of exceptions
	1. End of file
	2. Division by 0
	3. Bad input
 	4. Subscript out of range
	5. Pointer out of range (seg fault in C)

    D. How exception handling typically occurs in C

	1. Function returns a status flag
	2. Program unit that calls the function must check the status
	  	flag and take appropriate action

II. Exception Handling in Java

    A. Exception Handlers

	1. Specified using a try-catch clause

	    try {
	 	// Code that is expected to raise the exception
	    }
	    catch(type variable_name) {
		// A handler body
	    }
	    ...
	    catch(type variable_name) {
		// A handler body
	    }
	    finally {
	        // An optional block of code to be executed regardless of
		// whether the try block fails or succeeds
            }

	2. catch clauses can have only a single formal parameter

	   a. the type must be a class whose ultimate superclass is Throwable (more on this in a
	      moment)
	   b. user-defined classes provide a way of passing multiple
		parameters.
	   c. the formal parameter may be ignored but the variable
		name must be provided
	   d. the formal parameter's type allows Java to identify the type
	   	of exception which has occurred

        3. any variable declared within the try block is de-allocated before
	    the catch statement is executed. Hence if you want access to
	    a variable used or assigned a value within the try block, make
	    sure you declare that variable outside the try block.

    B. Types of Exceptions

	1. All Java exceptions are descendants of the Throwable class. 
		The Throwable class provides three very helpful methods:

	    a. printStackTrace(): prints the standard error message for
		this class plus a record of the method calls leading
		up to this exception. This method should not be redefined by
		subclasses.

	    b. getMessage(), toString(): both methods return a string object
		that contains the standard error message for this class.
		Typically each subclass redefines this method.

	2. Java pre-defines two exception classes that are subclasses of
		Throwable

	    a. Error: This class and its descendants are related to errors
		that are thrown by the Java virtual machine, such as running 
		out of heap memory.

		i. These exceptions are never thrown by user programs and 
			should never be handled by user programs

		ii. Your program will be terminated when one of these errors
			occurs

	    b. Exception: Has two descendents

		i. IOException: Thrown when an input or output operation
			has an error

		ii. RuntimeException: All other run-time errors

		   1. Java provides a number of pre-defined exception classes 
			such as ArrayIndexOutOfBoundsException and
			NullPointerException

		   2. User-defined exceptions should subclass RunTimeException

    C. Raising Exceptions

	1. exceptions are raised using the throw command:
		throw [expression];

	        Example: throw new BadOperatorException(operatorToken,
	                   "bad operator token");

	2. A throw without an operand can only appear in a handler

	    a. Such a throw reraises the exception and propagates it to
		an outside program unit. If there is another, more general
		exception handler that could handle the exception in the 
		same catch list as the handler that reraises this throw,
		the handler will not be called.

	    b. Throws are matched to exception handlers whose formal parameter
		matches the type of the thrown expression

		i. A handler with a formal parameter that is a class type T
			matches any expression with class type T or whose
			type is a derived class of T.

			Example:  class Exception extends RunTimeException {...}
				  class StackException extends Exception {...}

			          try {
					throw StackException();
				  }

				All of the following catch statements will
				catch the above exception:

				a. catch (StackException e) {...}
				b. catch (Exception e) {...}
				c. catch (RunTimeException e) {...}

	    c. The throw is matched by examining the catch statements that
		follow the try statement in sequential order. That means
		that exception handlers with more restrictive types should
		be placed before exception handlers with more general types.

	    	i. If the throw cannot be matched locally then it is
			dynamically propagated up the call chain.

	    d. If you want your catch clause to catch any possible exception that occurs
	       within the try block then have it catch an object of type Throwable. Since
	       Throwable is the top-level exception class, every exception object will
	       be a Throwable object and hence will be caught. Also, since a catch clause
	       with a Throwable object will catch everything, it should be the final catch
	       clause in your list.

	       i. When catching a generic Throwable object, you can get the specific 
	       name of the class that was thrown by first using Java's getClass() 
	       method to get the class object and then the getName() method to get 
	       the class's name:

		catch (Throwable genericObject) {
		    String name = genericObject.getClass().getName();
		    ...
		}

    D. Continuation

	1. After a handler completes execution, control flows to the finally
	   statement, if there is one, and then to the first
           statement following the try construct (the statement
           immediately after the last catch statement).

	2) The finally clause: In Java, the finally clause provides a 
		way to execute a block of code regardless of whether the
		try exits normally or abnormally. You might need to do this
		to clean up system state, such as closing a file.

	    a) The finally statement is placed after the last catch statement

	    b) The code in a finally statement is executed even if a return
		statement is executed within the try or one of the catch
		clauses

	    c) The code in a finally statement is executed even if no catch
		clause catches the exception

		Example:
		public class temp {
		    public temp() {}
		    public void execute() throws MyException {
			try {
			    throw new MyException();
			}
			finally {
			    System.out.println("Leaving execute via finally");
			}
		    }
		    public static void main(String [] args) {
			temp foo = new temp();
			try {
			    foo.execute();
			} catch (MyException e) {
			    System.out.println("caught MyException in main");
			}
			System.out.println("Leaving main");
		    }
		}

		class MyException extends Throwable {}

		Executing this code generates the following output:

		Leaving execute via finally
		caught MyException in main
		Leaving main

    E. It is okay for a method to not handle an exception that it throws. 
       If a method throws an exception that it does not handle, then it 
       should declare that it does not do so by using the throws 
       statement at the beginning of the method: 

	    a) Syntax: returnType methodName(params) throws exception-list 
		       { ... }

		where exception-list is a comma separated list of exception
		names

		Example: char readToken() throws IOException {
			    return (char) System.in.read();
			 }

	        Notice that you must worry about exceptions thrown by
		methods that you call. Hence the throws statement is
		required both for exceptions that a method explicitly throws
		and for exceptions that are generated but not handled by
		methods it calls

	    b) Exception to the rule (pardon the pun :)): If the exception
		is a subclass of Error or RuntimeException, then the method
		does not need to specify the exception in the throws list

	    c) If you fail to specify an unhandled exception in the throw
		list, and it is not subject to the above exception, the Java
		compiler will generate an error message

		i) In C++ it is not required for a method to declare that it 
			does not handle	an exception (although it is optional 
			and can be done using the throws statement).

    F. Java Example: Here's the Java code that implements an array and
         does automatic range-checking. Java does range-checking for you
	 automatically but the example is a nice one.

         class SafeArray {
	    int data [];
	    int size = 10;
	    
	    // We do not have to declare that SafeArray throws an 
	    // OutOfMemoryException since an OutOfMemoryException
            // is a subclass of RuntimeException

	    public SafeArray(int s) throws ArraySizeError, ArrayRangeError {
		size = s;
		if (size <= 0) throw new ArraySizeError(size);
		data = new int[size];
		if (data == null)
		    throw new OutOfMemoryException();
		for (int i = 0; i < size; i++)
		    data[i] = 0;
	    }

	    public void set(int index, int value) throws ArrayRangeError {
		rangeCheck(index);
		data[index] = value;
	    }
	    public int get(int index) throws ArrayRangeError {
		rangeCheck(index);
		return data[index];
	    }
	    
	    void rangeCheck(int index) throws ArrayRangeError {
		try {
		    if ((index < 0) || (index >= size))
			throw new ArrayRangeError(index, size);
		}
		catch (ArrayRangeError e) { 
		    System.err.println("caught it"); 
		    throw e;
		}
	    }

	     public static void main(String args[]) {
		 SafeArray myArray = null;

		 try {
		     myArray = new SafeArray(100);
		     myArray.set(3, 10);
		     myArray.get(-1);
		 }
		 catch (OutOfMemoryException e) {
		     System.err.println("Out of memory error occurred while trying to allocate memory for myArray\n");
		 }
		 catch (ArrayRangeError e) {
		     System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size);
		 }
		 catch (ArrayError e) { // catches the ArraySizeError exception
		                        // thrown by SafeArray
		     e.response();
		     System.exit(1);
		 }

		 for (int i = -2; i <= 100; i++) {
		     try {
			 myArray.set(i, i);
			 if (i == 50)
			     new SafeArray(-10);
		     }
		     catch (ArrayRangeError e) {
			 System.err.println("index " + e.index + " is out of range. Must be between 0 and " + e.size);
		     }
		     catch (ArrayError e) {
			 e.response();
		     }
		 }
	     }
	 }

       abstract class ArrayError extends Throwable {
	    abstract public void response();
	}

	class OutOfMemoryException extends RuntimeException {}
        class ArraySizeError extends ArrayError {
	  public int size;
	    public ArraySizeError(int s) { size = s; }
	    public void response() { 
		System.err.println("Error: The array size of " + size + " is negative");
	  }
	}

	class ArrayRangeError extends ArrayError {
	    public int index;
	    public int size;
	    public ArrayRangeError(int i, int s) {
		index = i;
		size = s;
	    }
	    public void response() {}
	}
)