Lab 2: Test Questions

In order to be able to complete this lab in the classroom, you'll have to do some work ahead of time. The lab makes use of a couple concepts we'll cover in more detail later in the course, including abstract classes and getting input from a text file (rather than the keyboard). Let's start with the input file stuff.

Pre-Lab: (to complete before the day of the lab)

How to Get Input from a File

In this assignment you'll be writing a program that takes its input from a file, rather than from the keyboard. First, recall how we usually get input from the keyboard--we use a Scanner bound to the System.in input stream. Here's a simple example of how to get an integer from the user:

  Scanner scan = new Scanner (System.in);
  System.out.print ("Enter your age: ");
  int age = scan.nextInt();

That first line creates a new Scanner object and binds it to the keyboard input stream, System.in.

To read from a file, you need to associate a FileInputStream object with the name of the file. Then you must associate a Scanner object with that FileInputStream object. From then on, you can access the Scanner object just as you would with a Scanner that you had bound to System.in (i.e., as you would do to get input from the keyboard.) As an example, suppose in our main() we want to read an integer from a text file named "bob.txt" that contains the string "123 456":

// somewhere at the top of the file...
import java.util.*;
import java.io.*;
...
public class GetFile {
 public static void main() throws FileNotFoundException { //Apologies
   Scanner myFile = new Scanner(new FileInputStream("bob.txt"));
   int firstInt = myFile.nextInt();
   System.out.println("From file we read "+firstInt);
 }
}  

hot!For this to work in Eclipse, you must place the file bob.txt in the your project's main directory (folder). Let's assume we've created a new Java project named "Test Questions" for this assignment. Depending on how you've set up the project (i.e., whether you selected "create separate folders for sources and class files" as you created the project), your project folder will either contain just your .java and .class files, or will contain directories called "bin" and "src" (which, in turn, contain your .class and .java files). To guarantee you get the input file in the correct directory you can drag-and-drop the file you want (bob.txt) into Eclipse. Just drag-and-drop the file into the project folder that you created for this assignment. I.e., drag and drop the input file onto the icon that the red arrow points to in the image below (here I'm assuming the name of the input file is "bob.txt"):

the red arrow points to which icon you want to drop the data file onto

After you've added the data file, Eclipse should look something like this:

How Eclipse should appear after you've added the text input file

Note that the function (main, here) that creates the FileInputStream object must have the strange phrase "throws FileNotFoundException" tacked onto the function declaration. You'd also need to do the same for any function that called main(). So, for example, if you had a method named "secondary" which, in turn, invoked main, you'd have to append the "throws FileNotFoundException" onto the declaration of secondary, as well. We'll discuss what "throws" means when we cover "exception handling" later in the course. For now, just think of it as necessary voodoo! :-)

We'll do more with File Input/Output later in the course (see chapter 10, of the textbook.) In particular, we will learn how to deal with input that is incorrectly formatted (e.g., in the example above, supposing the first text in bob.txt wasn't an integer?)

A Word on Using Scanners

When you use nextInt(), you often want to use nextLine() in conjunction with it. Here's an example: suppose you want to read the contents of a text file named "file.txt" containing

52
hi there
43 

You create a Scanner object attached to the file as we discussed above:

Scanner scan = new Scanner(new FileInputStream("file.txt")); 

Then you use the Scanner to get the integer (ie., 52):

int x = scan.nextInt(); 

Voila! The value of x is now 52. Now comes the tricky part. Think of each input stream as a kind of pointer to a single character in the file. When you first created the Scanner, it was--effectively--pointing to the first chararcter in the file, the '5'. The nextInt() method reads characters one after another for as long as they are digits, and stops at the first non-digit character. So, in this example, it read the '5', then the '2', then stops at the next character, which is a newline (i.e., '\n'). Note that the Scanner is left still pointing at that newline character.

So, now we want to grab the text in the next line of the input file. The nextLine() method reads all the characters in the file up to and including the next newline character. If the next instruction you provide (incorrectly!) is

String text = scan.nextLine();   // Option 1 (buggy) 

then text will contain only a single newline character, because that is what the Scanner was pointing at when we called nextLine. So, what you really need to do is something like:

// Option 2:
   String text = scan.nextLine();   // "consume" the newline that followed the integer I just read
   text = scan.nextLine();  // Works now, as the file pointer starts pointing at the first character in this line

Now text will contain "hi there", and the Scanner will be "pointing" at the '4' character, ready for the next time you want to read input from the file.

To Submit before Start of Lab:

Write a program consisting of a class named "GetFile". Running the class should read the contents of the file testbank.dat and print it out. Each line of the input file should print on a separate line, with that line preceded by the "number" of the input line. So your output should start like this:

1) 5
2) e
3) 5
4) Why does the constructor of a derived class have to call the constructor of its parent class?
5) m
6) 4
7) Which of the following is not a legal identifier in Java?

...

Before the start of the lab, submit your GetFile.java to the drop box. This must be done during the first half hour of the lab to receive full credit.

============This marks the end of the "pre-lab" portion of the lab===========

Lab:

An abstract class is declared via the abstract keyword, and must contain at least one abstract method. Here is an example:

public abstract class Mammal { 
  ...
  public abstract int MyFirstAbstractMethod(...);
  ...
}
 

Note that the declaration of the method MyFirstAbstractMethod is immediately followed by a semicolon, not curly-brackets. That's because the method is "abstract", so we don't provide an implementation of the method. That will be the job of the subclasses of Mammal. The abstract declaration is a promise to Java that any subclass of Mammal must either, itself, also be abstract, or must provide an implementation of this method. Here's an example:

public  class Giraffe extends Mammal { 
  ...
  public  int MyFirstAbstractMethod(...) {
    // an implementation of the method goes here
  }
  ...
}
 

In this exercise you will use inheritance and polymorphism to read, store, and print questions for a test.

Step 1: Write an abstract class TestQuestion that contains the following:

Step 2: Define Essay as a subclass of TestQuestion. Essay will need an instance variable to store the number of blank lines needed after the question. If this we were using this program, this would provide blank space on an exam for students to write their answers in.

Write a readQuestion(Scanner s) method for the Essay class that reads the definition of one question from a Scanner. You may assume that the format of the input in the Scanner will be an integer, by itself on a line, indicating the number of blank lines to be printed after the question, followed by a single line of text--the question itself. The file testbank_essay.txt contains the definition of a single essay question.

Write a toString method for the Essay class that returns a String representing the essay question. When this string is printed, it should yield the question, followed by the correct number of blank lines. You will want to use '\n' characters in creating the returned string. Remember that you don't want print statements in toString.

Add a main() method to Essay that binds a Scanner to the file testbank_essay.txt and uses it to see that readQuestion and toString work correctly. If x is an Essay variable holding the question defined in the file, then System.out.println(x) should result in (note the blank lines following the question):

How do interfaces provide polymorphism in Java?




Call a TA or the instructor to validate your output.

Step 3: Define MultChoice as a subclass of TestQuestion. It will need an array of Strings, each to hold one of the possible answer choices for the question.

Write a readQuestion(Scanner s) method for the MultChoice class that reads the definition of one multiple choice question from a Scanner. The format of the input for a multiple choice question will be several lines (the exact number will vary, depending on the value of the first line):

The file testbank_multChoice.txt provides an example of a single question definition.

After you've read the first of those lines, you'll be able to create the array of Strings that you'll need to store the third line and those that follow it. You'll also know how many times you'll have to loop to read those lines from the Scanner (which will have already been bound to the input file).

Write a toString method that returns a String representing the multiple choice question. When this string is printed, it should yield the question, along with its possible choices, indented, and labeled a), b), etc. You may want to use '\t' and '\n' characters in creating the returned string. The '\t' characters are "tabs", that you can use to create indentation. (Remember that you don't want print statements in a toString.)

Add a main() method to MultChoice that binds a Scanner to the file testbank_multChoice.txt and uses it to see that readQuestion and toString work correctly. If x is a MultChoice variable holding the question defined in the file, then System.out.println(x) should result in:

2) Which of the following is not a legal identifier in Java?
	a) guess2
	b) 2ndGuess
	c) _guess2_
	d) Guess

Step 4: Now to tie things together by creating a class that will read and print an entire "test", consisting of a mixed series of essay and multiple choice questions.

Assume that the input file, testbank.dat, is formatted thus:

The very first item of input, before any questions, is an integer indicating how many questions will be entered. So the following input represents three questions: an essay question requiring 5 blank lines, a multiple choice question with 4 choices, and another essay question requiring 10 blank lines:

3
e
5
Why does the constructor of a derived class have to call the constructor of its parent class?
m
4
Which of the following is not a legal identifier in Java?
guess2
2ndGuess
_guess2_
Guess
e
10
How do interfaces provide polymorphism in Java?

Define a class WriteTest that creates an array of TestQuestion objects, and then prints those questions. It should read the defintions (in the format described above) of the questions from the file testbank.dat. To begin, then, it will need to define a Scanner object associated with a FileInputStream for that file (see above). Then it can process the data from that file as follows: first reading an integer that indicates how many questions comprise the test (i.e, how many are in the file). It should then create a MultChoice object for each multiple choice question and an Essay object for each essay question, and store each object in the array. (Because it's an array of TestQuestion and both Essay and MultChoice are subclasses of TestQuestion, objects of both types can be stored in the array.) As each such object is created, WriteTest should invoke the readQuestion method on that object so that it can define itself from the input file. In other words, you'll want code something like:

TestQuestion questions[] = new .... // Make an array big enough to hold all the questions 
           // Use first number in the data file to see how big
// Define each question from data file
for (int questionNumber = 0; ??stillMoreToRead?? ; questionNumber++) {
    // Read next line from the data file; it should define the question type
    if ??question is an essay?? 
       questions[questionNumber] = new Essay();
    else // question is a multiple-choice
       questions[questionNumber] = new MultChoice();
    questions[questionNumber].readQuestion(?yourScanner?); //polymorphic

}...
// Print the test!
The last line uses polymorphism to invoke the appropriate readQuestion method.
  • When all of the data has been read, it should use a loop to print the questions, numbered, in order. You'll probably want the loop to use something like
    System.out.println(questions[i])
    which uses polymorphism to take advantage of the toString methods you've defined for the Essay and MultChoice classes.
  • Submit your project

    To submit the program, zip the project folder and submit it to the dropbox. You should submit only your .java files.

    Sample Output

    Below is an example as to how your program might print the sample input file, testbank.dat. You are free to alter the formatting some. For example, you might want the numbering style to be "1." instead of "1)", or you might fiddle with the indentation pattern.

    1) Why does the constructor of a derived class have to call the constructor of its parent class?
    
    
    
    
    
    
    2) Which of the following is not a legal identifier in Java?
    	a) guess2
    	b) 2ndGuess
    	c) _guess2_
    	d) Guess
    
    3) How do interfaces provide polymorphism in Java?
    
    
    
    
    
    
    
    
    
    
    
    4) Java does not support multiple inheritance.  This means that a class cannot do what?
    
    
    
    
    5) A JPanel has an addMouseListener method because JPanel is a subclass of
    	a) JComponent
    	b) JApplet
    	c) Object