CISC 3115
Modern Programming Techniques
Lecture 3
Exception-Handling


Overview

A Bit of Motivation

Assume a Color class whose constructor accepts r, g, b values in the range of 0 … 255. Any value outside of that range produces an invalid color and attempting any operation results in either some exception or a meaningless result. Further assume a read method, along the lines we have been working with, i.e., it returns a Color object if the read is successful, or a null if there is no more data in the file.

Write an app that reads Color objects from a file, prints them out (using the class' toString method, and invokes the isGrey, and lighter methods. OTOH, if the r, g, b value(s) were invalid, an appropriate error message should be printed, and none of the operations performed, and execution should proceed to the next set of values to be read in.

The Constructor

public Color(int r, int g, int b) {
	if (r < 0 || r  255 | …) ???????
	this.r = r;
	this.g = g;
	this.b = b;
}
Notes
  • Constructors do not have return values, so having a boolean return type and returning to signify invalid value is not possible.

The read Method

public static Color read(
	if (!scanner.hasNextInt()) return null;
	int r = scanner.nextInt();
	int g = scanner.nextInt();
	int b = scanner.nextInt();
	return new Color(r, g, b);
}
Notes
  • How do we indicate that the object is invalid; returning null is reserved for no more data

The App

Color c = Color.read(scanner);

while (???) {
	if (???) {
		System.out.println(r);
		System.out.println(r.isGrey());
		System.out.println(r.lighter());
	}
	c = Color.read(scanner);
}
Notes
  • How can we distinguish between no more data and an invalid object?

Handling Exceptional Situations

Handling Exceptional Situations in the Absence of Exception Handling

To begin with, exception-handling means dealing with exceptional situation. Exceptional does not necessarily mean erroneous, though errors are a large part of that category. Exceptional mean 'outside the course of normal events'.

The Issue

Exceptional situations often arise as the result of making a call to a method and something happening in that method that is outside the usual stream of logic for the code. There are two main issues to be addressed in such a situation.
Being Notified of the Exceptional Situation
Processing the Exceptional Situation
All of the above is compounded by the fact that there may be more than one sort of exceptional situation, each with its own notification requirement as well as flow of logic.

Handling the Issue at the Point of Occurrence or Elsewhere

And, as mentioned before, regardless of which entity becomes responsible for dealing with the error or exceptional situation, the resulting logic is a distraction and results in a break in the flow of normal processing.

Some Examples

We present several coding contexts in which an exception (often interpreted as an error) situation can occur, and show various ways of dealing with it in the absence of exception handling.

Calculating the Average of an Array

double average(int [] arr) {

	int total = 0;
	for (int i = 0; i < arr.length; i++)
		total += i;
	return (double)total / arr.length;
}

Using a Trailer Value

// Uses a (special) trailer value 

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		double d = average(arr);
		if (Double.isNaN(d))
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + d);
	}
				
	static double average(int [] arr) {
		if (arr.length == 0) return Double.NaN;
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total/arr.length;
	}

	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}

Notes
  • Unlike most primitives, double actually has Double.NaN — an acceptable trailer value for all situations, i.e., a value that represents the absence of a valid double.
    • Double.NaN is a static variable of type double from the Double (wrapper) class in java.lang.
  • The average method checks for a 0-length array and returns this trailer value in that situation
    • The average of the array is returned in the 'normal' case
    • Again, in both situations a value of type double is returned.
  • The other primitives do not have such a 'universal' trailer value, though they may have a suitable trailer depending on the context; e.g. -1 for the index of an array or a grade in the range of 0…100.
  • Notice the processing of both the normal and exceptional situation requires the introduction of a conditional after the potentially 'offending' code. This conditional logic (and the subsequent two streams of logic for normal and exceptional processing) is distracting and disruptive to the understanding of the main (i.e., normal) flow of logic.

Using a Result Object

// Uses a 'result' object containing status of operation and value

import java.io.*;
import java.util.*;

public class Averager {
	static class Result {
		Result(double average, boolean wasSuccessful) {
			this.average = average;
			this.wasSuccessful = wasSuccessful;
		}
		Result(boolean b) {this(0, false);}
		Result(double average) {this(average, true);}
		public double average;
		public boolean wasSuccessful;
	}

	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		Result result = average(arr);
		if (!result.wasSuccessful)
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + result.average);
	}

	static Result average(int [] arr) {
		if (arr.length == 0) return new Result(false);
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return new Result((double)total / arr.length);
	}
				
	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}
Notes
  • In this approach, we are effectively returning two pieces of data: the status of the operation, i.e., was the average able to be calculated, and, if so, the actual value of the average
  • The issue is that methods can only return one value; to get around this, we introduce a result or wrapper class, containing two instance variables: a boolean status and a double for the average.
    • The term wrapper refers to the fact that the two values being returned are wrapped in this temporary, utility/service class (temporary in the sense that it's only being introduced because of the 'single value' method return restriction; utility in the sense that it serves a technical purpose (again, getting around the single value restriction) related to the language.
    • Other languages allow values to be returned from a method through the parameters (e.g., C++). In such languages one would typically return one or both of these values via a parameter rather than using a result class.
    • Using a result class in this fashion can be quite useful, to the extent that a Pair class is often provided by the language's predefined class library.
      • Clearly, such a class can be extended to as many values as desired; alternatively, one could make one or both of the values in the Pair class itself a Pair, slowing for as many (nested) values as desired. We'll revisit this later.
      • When we get to inheritance we will see how such a class can be used with arbitrary typed values. In our case, we created a 'custom' result class consisting of our required boolean and double values.
  • A bit more on the Result class … as mentioned above, this class is temporary class in that it is briefly used for the purpose of passing multiple return values back from a called method.
    • This class is solely for the purpose of the Averager class, and thus, rather than cluttering our class space with another class name, we make it an nested class, i.e., a class defined within another class definition.
      • There are several types of nested classes; for the moment we restrict ourselves to one declared with static in the class header, and is called a static nested class
      • Such a class can be treated almost as if it were just another 'top-level' class (i.e., one not defined within any other)
        • The main reason for defining the class in this manner is to empasize its logical relation to the outer class
          • In our case, this relationship is that Result is a class used specificaly and only by Averager.
      • Assuming it is accessible from the outside (i.e., not declared private), the class is referred to as OuterClass.NestedClass, e.g., Averager.Result.
        • WIthin the class, the usual scoping rules apply, so the class is just referred to by its name, e.g. Result.
      • The instance variables of the Result class are declared public. Given the temporary and short-lived use of the object of this class (their use is basically limited to returning the values from the method), there is no real reason to 'make them bulletproof' or provide any real semantics via methods; all that is really needed is for the caller to be able to access the instance variables.
      • We do, however, provide constructors to make it easy to create the Result object. In the case of a result object, we typically have the usual workhorse constructor (that accepts all values and initializes them), and then one for the valid case and one for the invalid case.
    • Again, notice the processing of both the normal and exceptional situation requires the introduction of a conditional after the potentially 'offending' code, and the checking of the result's status variable.

Using a Guard Method

// Uses a 'guard' (pre-condition) method -- similar to the hasXXXX methods of Scanner

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		if (!hasAverage(arr)) 
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + average(arr));
	}

	static boolean hasAverage(int [] arr) {return arr.length != 0;}

	static double average(int [] arr) {
		// if !hasAverage(arr) ...
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total / arr.length;
	}
				
	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}
Notes
  • In this approach, a method is provided that allows one to check the validity/legality of the possibly offending code. Such a method is called a guard or pre-condition method.
    • We had a similar situation on our Color of the Labs; the isValid method was used to check the validity of the Color object's instance variables before executing other methods which could fail if those variables were out of range.
    • guard methods are usually pre-condition, i.e., they are used before calling some method or executing some code. On occasion however, they are sometime post-condition, i.e., called after the possibly offending code (C++'s I/O library has examples of this).
  • And, once again, notice the processing of both the normal and exceptional situation requires the introduction of a conditional after the potentially 'offending' code variable.

Using null

Using null is really just a special case of a sentinel value. As null is a legal value for all object types, returning null is a way to indicate that something went wrong (or at least is different, or not 'normal'). The novelty here is that we are dealing with a primitive return type — double (had it been a String for example — which is an object — it would be more straightforward. We wouldn't expect to be able to use null for double and that is the secondary purpose of this approach: first, to show that null can act as a sentinel value and secondly, that there is a way to bring the primitives into the world of objects, namely using the primitive type wrapper classes. We will have a lot more to say about them when we talk about inheritance, the class hierarchy and collections later on.

The main takeaway from this example should not be the Double/double sleight-of-hand; that will be presented later. Rather, it's the use of null as the trailer value of sorts, and that this is thus applicable to all situations using objects as the return value.

/// Uses null as the exceptional case indicator

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		Double d = average(arr);
		if (d == null)
			System.out.println("0-length array ... average couldn't be taken");
		else
			System.out.println("average: " + d);
	}
				
	static Double average(int [] arr) {
		if (arr.length == 0) return null;
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total/arr.length;
	}

	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}

Notes
  • For each primitive in Java, the is a corresponding class in java.lang: Integer, Double, Boolean, Character, etc.
    • We will go into these classes in more detail in later lectures, for the moment, suffice it to say that these classes exist for the purpose of:
      • bridging the gap between primitive types and classes/objects
      • providing a repository for methods applicable to the primitive type, but unable to be defined as such (since primitives cannot be receivers).
  • Suffice it to say, for the moment, that a primitive type and its corresponding wrapper class are interchangeable
    • This is not 100% true, but it will do for the moment.
  • To use null (which only works for objects), we use Double instead of double
    • Anywhere we need the double the Double object is converted; again, this will be explained later.
  • And, once again, notice the processing of both the normal and exceptional situation requires the introduction of a conditional after the potentially 'offending' code variable.

Opening a File

Scanner keyboard = new Scanner(System.in);

System.out.print("File name? ");
String fname = keyboard.next();

Scanner scanner = new Scanner(new File(fname));

Using a Guard Method … Local Code

import java.io.*;
import java.util.*;

public class FileOpener {
	public static void main(String [] args) throws Exception {
		Scanner keyboard = new Scanner(System.in);
		Scanner datafile = null;
		String filename;
		while (true) {
			System.out.print("filename? ");
			filename = keyboard.next();
			File file = new File(filename);
			if (file.exists()) {
				datafile = new Scanner(file);
				break;
			}
			else
				System.out.println("'" + filename + "' not found, try again!");
		}

		// ... and here we go
		System.out.println("=== "  + filename);
		System.out.println("-----------------");
		while (datafile.hasNextLine()) {
			String line = datafile.nextLine();
			System.out.println(line);
		}
	}
}
Notes
  • We employ the exists method of the File class to check that we will be able to open the file
  • Problem:
    • we have to know of the existence of this guard method
    • We still have the distraction of normal/exceptional logic at the point of the offending code
  • Notice the infinite loop and use of break; once we break out of the loop, wer're good too go

Using a Guard Method … Separate Method

import java.io.*;
import java.util.*;

public class FileOpener {
	public static void main(String [] args) throws Exception {
		Scanner keyboard = new Scanner(System.in);
		while (true) {
			System.out.print("filename? ");
			String filename = keyboard.next();
			if (filePrint(filename)) break;
			System.out.println("'" + filename + "' not found, try again!");
		}
	}

	static boolean filePrint(String filename) throws Exception {
		File file = new File(filename);
		if (!file.exists()) return false;
		Scanner scanner = new Scanner(file);

		while (scanner.hasNextLine()) {
			String line = scanner.nextLine();
			System.out.println(line);
		}
		return true;
	}
}

Notes
  • Basically the same except here, the logic is a bit more involved as it requires communication back from the file printing method to the caller (which is responsible for getting the filename from the keyboard).
As a general rule, handling exceptional situations within the same method as the situation arises is not an issue, and not really under consideration.

Processing Possibly Corrupt Input Data

Suppose we had an application that read in student data which was subject to the following constraints:
class DataIntegrityChecker {
	public static void main(String [] args) {
		Scanner fileScanner = new Scanner(new File("data.text"));

		while (fileScanner.hasNext()) {
			String lastName = scanner.next();
			String lastName = scanner.next();

			int numAssignments = scanner.nextInt();
			for (int i = 1; i <= numAssignments; i++) {
				int assignment = scanner.nextInt();
				assignmentTotal += assignment;
			}

			String project = scanner.next();

			int midterm = scanner.nextInt();

			int finalExam = scanner.nextInt();
	}
}

A Smorgesbord of Approaches

class DataValidation {

	// Uses null
	public static String validateName(String name) {
		if (name.length() == 0) return null;
		if (!Character.isUpperCase(name.charAt(0))) return null;
		for (int i = 1; i < name.length(); i++)
			if (!Character.isLowerCase(name.charAt(i))) return null;
		return name;
	}

	// Uses trailer value (-1)
	public static int validateAssignment(int grade) {
		return grade >= 0 && grade <= 10 ? grade : -1;
	}

	// Uses result object	
	static class Result {
		Result(boolean isValid, String grade) {
			this.isValid = isValid;
			this.grade = grade;
		}
		Result(String grade) {this(true, grade);}
		Result(boolean isValid) {this(isValid, "");}

		public boolean isValid;
		public String grade;
	}

	public static Result validateProject(String grade) {
		return "ABCDF".indexOf(grade) >= 0 ? new Result(grade) : new Result(false);
	}

	// Uses guard method
	public static boolean validateExam(int grade) {
		return grade >= 0 && grade <= 100 ? true : false;
	}
}

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

class DataIntegrityChecker {
	public static void main(String [] args) throws FileNotFoundException {
		Scanner fileScanner = new Scanner(new File("../data.text"));

		int dataRecord = 0;

		while (fileScanner.hasNextLine()) {
			String line = fileScanner.nextLine().trim();
			if (line.length() == 0) continue;

			dataRecord++;

			Scanner lineScanner = new Scanner(line);

			String lastName = DataValidation.validateName(lineScanner.next());
			if (lastName == null) {
				error(dataRecord, "Invalid last name");
				continue;
			}

			String firstName = DataValidation.validateName(lineScanner.next());
			if (firstName == null) {
				error(dataRecord, "Invalid first name");
				continue;
			}

			int numAssignments = lineScanner.nextInt();
			boolean isInvalid = false;
			for (int i = 1; i <= numAssignments; i++) {
				int assignment = DataValidation.validateAssignment(lineScanner.nextInt());
				if (assignment == -1) {
					error(dataRecord, "Invalid assignment grade");
					isInvalid = true;
				}
			}
			if (isInvalid) continue;

			String project = lineScanner.next();
			DataValidation.Result result = DataValidation.validateProject(project);
			if (!result.isValid) {
				error(dataRecord, "Invalid project grade");
				continue;
			}

			int midterm = lineScanner.nextInt();
			if (!DataValidation.validateExam(midterm)) {
				error(dataRecord, "Invalid midterm grade");
				continue;
			}

			int finalExam = lineScanner.nextInt();
			if (!DataValidation.validateExam(finalExam)) {
				error(dataRecord, "Invalid final grade");
				continue;
			}

			System.out.println("#" + dataRecord + ": " + lastName + " " + firstName + " validated");
		}
	}

	private static void error(int dataRecord, String message) {
		System.out.println("*** In data record #" + dataRecord + ": " + message);
	}
}

Notes

Guards Methods Throughout

Guard methods are always applicable (pass the value to be checked, return true/false based on the validity); and thus using them makes the validation interface uniform.
class DataValidation {

	// Uses guard methods throughout

	public static boolean validateName(String name) {
		if (name.length() == 0) return false;
		if (!Character.isUpperCase(name.charAt(0))) return false;
		for (int i = 1; i < name.length(); i++)
			if (!Character.isLowerCase(name.charAt(i))) return false;
		return true;
	}

	public static boolean validateAssignment(int grade) {
		return grade >= 0 && grade <= 10 ? true : false;
	}

	public static boolean validateProject(String grade) {
		return "ABCDF".indexOf(grade) >= 0 ? true : false;
	}

	// Uses guard method
	public static boolean validateExam(int grade) {
		return grade >= 0 && grade <= 100 ? true : false;
	}
}

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

class DataIntegrityChecker {
	public static void main(String [] args) throws FileNotFoundException {
		Scanner fileScanner = new Scanner(new File("../data.text"));

		int dataRecord = 0;

		recordReadLoop:
		while (fileScanner.hasNextLine()) {
			String line = fileScanner.nextLine().trim();
			if (line.length() == 0) continue;

			dataRecord++;

			Scanner lineScanner = new Scanner(line);

			String lastName = lineScanner.next();
			if (!DataValidation.validateName(lastName)) {
				error(dataRecord, "Invalid last name");
				continue;
			}

			String firstName = lineScanner.next();
			if (!DataValidation.validateName(firstName)) {
				error(dataRecord, "Invalid first name");
				continue;
			}

			int numAssignments = lineScanner.nextInt();
			boolean isInvalid = false;
			for (int i = 1; i <= numAssignments; i++) {
				int assignment = lineScanner.nextInt();
				if (!DataValidation.validateAssignment(assignment)) {
					error(dataRecord, "Invalid assignment grade");
					isInvalid = true;
				}
			}
			if (isInvalid) continue;

			String project = lineScanner.next();
			if (!DataValidation.validateProject(project)) {
				error(dataRecord, "Invalid midterm grade");
				continue;
			}

			int midterm = lineScanner.nextInt();
			if (!DataValidation.validateExam(midterm)) {
				error(dataRecord, "Invalid midterm grade");
				continue;
			}

			int finalExam = lineScanner.nextInt();
			if (!DataValidation.validateExam(finalExam)) {
				error(dataRecord, "Invalid final grade");
				continue;
			}

			System.out.println("#" + dataRecord + ": " + lastName + " " + firstName + " validated");
		}
	}

	private static void error(int dataRecord, String message) {
		System.out.println("*** In data record #" + dataRecord + ": " + message);
	}
}

Notes
  • While the guard methods make everything uniform, we still have the distraction of the conditional that invoke them

Handling Errors and Exceptional Situations

There are several issues to concern ourselves with regard to handling exceptions, i.e., exceptional situations:

Detection: Who Detects the Exception?

Processing: Who Should Handle the Exception?

Notification: The Detector Notifying the Handler

Exception-Handling in Java

Detection and Notification

The throw Statement

Handling the Exception — the try/catch Block

Exception classes

The throws Clause

Miscellaneous

The Array Averager

// Uses exception handling

import java.io.*;
import java.util.*;

public class Averager {
	public static void main(String [] args) throws Exception {
		int [] 
			arr1 = {1, 2, 3, 4},
			arr2 = {};

		doIt(arr1);
		System.out.println();
		doIt(arr2);
	}

	static void doIt(int [] arr) {
		System.out.println("The array: " + toString(arr));

		try {
			System.out.println("average: " + average(arr));
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

	static double average(int [] arr) throws Exception {
		if (arr.length == 0) throw new Exception("0-length array ... average couldn't be taken");
		double total = 0;
		for (int i = 0; i < arr.length; i++) 
			total += arr[i];

		return (double)total / arr.length;
	}
				
	static String toString(int [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}
}

The File Opener

// uses exception-handling
import java.io.*;
import java.util.*;

public class FileOpener {
	public static void main(String [] args) {
		Scanner keyboard = new Scanner(System.in);
		while (true) {
			try {
				System.out.print("filename? ");
				String filename = keyboard.next();
				filePrint(filename);
				break;
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
		}
	}

	static void filePrint(String filename) throws Exception {
		File file = new File(filename);	
		if (!file.exists()) throw new Exception("'" + filename + "' not found, try again!");
		Scanner scanner = new Scanner(new File(filename));

		while (scanner.hasNextLine()) {
			String line = scanner.nextLine();
			System.out.println(line);
		}
	}
}

The Bad Data Handler

class DataValidation {
	public static String validateName(String name) throws Exception {
		if (name.length() == 0) throw new Exception("0-length name");
		if (!Character.isUpperCase(name.charAt(0))) throw new Exception("Non-uppercase first letter");
		for (int i = 1; i < name.length(); i++)
			if (!Character.isLowerCase(name.charAt(i))) throw new Exception("Non-lowercase internal letter");
		return name;
	}

	public static int validateAssignment(int grade) throws Exception {
		if (grade < 0 || grade > 10) throw new Exception("Invalid assignment grade");
		return grade;
	}

	public static String validateProject(String grade) throws Exception {
		if ("ABCDF".indexOf(grade) < 0) throw new Exception("Invalid project grade");
		return grade;
	}

	public static Integer validateExam(int grade) throws Exception {
		if (grade < 0 || grade > 100) throw new Exception("Invalid exam grade");
		return grade;
	}
}

		

import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;

class DataIntegrityChecker {
	public static void main(String [] args) throws FileNotFoundException {
		Scanner fileScanner = new Scanner(new File("../data.text"));

		int dataRecord = 0;

		while (fileScanner.hasNext()) {
			String line = fileScanner.nextLine().trim();
			if (line.length() == 0) continue;

			dataRecord++;

			Scanner lineScanner = new Scanner(line);

			try {
				String lastName = DataValidation.validateName(lineScanner.next());
				String firstName = DataValidation.validateName(lineScanner.next());

				int numAssignments = lineScanner.nextInt();
				for (int i = 1; i <= numAssignments; i++) {
					int assignment = DataValidation.validateAssignment(lineScanner.nextInt());
				}

				String project = DataValidation.validateProject(lineScanner.next());

				int midterm = DataValidation.validateExam(lineScanner.nextInt());
				int finalExam = DataValidation.validateExam(lineScanner.nextInt());

				System.out.println("#" + dataRecord + ": " + lastName + " " + firstName + " validated");
			} catch (Exception e) {
				System.out.println("*** Exception ... In data record #" + dataRecord + ": " + e.getMessage());
			}
		}
	}
}

Files used in this Lecture