CPSC 170 Lab 12
More I/O

IMPORTANT: This lab is to be done using pair programming, which means that ALL code is produced with two (or three) people sitting at one computer. Take turns at the keyboard; each person in the pair/trio must spend a significant amount of time there. Put the names in your pair/trio at the top of your programs and submit only once for each pair/trio. As usual, create a lab12 directory for today's lab, open this document in Mozilla, and start emacs.

Writing Objects as Text

Input and output of objects is a tricky business. If your program manipulates an integer or other primitive type and you want to store that value to a file, you can write it as text. When you want to retrieve it you can read it back as text and use parseInt (or similar) to get the numeric value. An arbitrary number of integers can be stored the same way by using a loop to store and then read each value. Of course, in this case you need to be able to tell when to stop reading.

Things get more complex if you are mixing data types (e.g. strings and integers or doubles and integers). This frequently occurs when you want to write an object to a file. You have to write each piece of information in the object in such a way that it is reasonably easy to retrieve it later, and then remember how you stored it so that you can read it back correctly. Thus a class might have read and write methods that take text streams and do exactly this. Note that a class's toString method is usually intended for display, and often contains extraneous information (e.g., labeling) that makes it inappropriate for general data storage.

File Student.java contains a class that represents a student. Save this file to your directory and open it in emacs. Note that it has a toString method that returns the student information, neatly formatted and labeled. Proceed as follows:

  1. Add a method public void write(Writer out) to the Student class that takes a Writer and writes the student information to that Writer. (You'll need to import java.io.*). Consult your text or the Java documentation to see the methods of the Writer class -- they aren't as helpful as you might like. As we discussed previously, a PrintWriter has more useful methods, so you may want to construct a PrintWriter from the Writer that is passed in. Make sure that you pay careful attention to exactly what is getting written to the file. It can be any arrangement you want, but keep in mind that you will eventually want another program to be able to read your student information in again.

  2. Add a static method public static Student read(Scanner in) that takes a scanner, reads information for a student from that scanner, and returns a Student object containing that information. Note that this is a static method (Why?) If the read method can't read an entire object (e.g., if EOF is encountered or it gets a NumberFormatException), it should return null -- it should not throw an exception. Note: Your method should be able to seamlessly handle the following names "Jane", "Jane Doe" and "Jane Doe 2 " (the last is generated by the default constructor). Think carefully about how to make your Scanner handle this (and maybe read about scanners in the Java documentation)

  3. Write a program TestWrite that does the following: Note that you only need one Student variable; just use a loop to create a student and then write it to each file -- no need to store it after it has been written. Don't forget to close the files when you're done writing.

    Run your program and inspect the files to see if they seem reasonable.

  4. Now write a program TestRead that does the following: Don't forget to close file2. Now run TestRead, passing in the first output file from TestWrite as file1, and a new file as file2.

  5. Visually compare file2 from TestWrite and file2 from TestRead; they should look the same. Then use the unix utility diff to see if they really are identical (which they should be): diff  file1  file2 will list any differences between the files. If any differences appear, figure out why and correct your programs as necessary.

  6. Now modify TestWrite so that it generates a random number between 1 and 10 (inclusive), and generates and writes that number of students. Modify TestRead so that it reads all of the objects in the file instead of reading a fixed number of objects. (Don't rely on the upper bound of 10; TestRead should work for any number.) Test your programs.

Writing Objects Directly

Java supports object I/O, that is, streams and methods that allow objects to be written and read directly. You will need to consult the Java documentation, but here's an overview:

To see how object I/O works, do the following:

  1. Make the Student class implement the Serializable interface.

  2. Copy TestWrite.java into TestWrite2.java. Modify it so that instead of creating a FileWriter from file1, it creates a FileOutputStream (used for binary files) and constructs an ObjectOutputStream from it. Then use the writeObject method of the ObjectOutputStream class to write the Student objects directly to file1. (Still write the toString to file2, which should still be a FileWriter.) Note that you will NOT use the write method of the Student class.

  3. Copy TestRead.java into TestRead2.java, and similarly modify it to use an ObjectInputStream (created from a FileInputStream) to read from file1. Then use the readObject method of that class to read the objects from the file until the end of file (EOF) is encountered -- again, you will NOT use the read method of the Student class. IMPORTANT: Attempting to read from an ObjectInputStream that is at EOF does not return null -- it throws an EOFException. You should incorporate this into the flow of your program.

  4. As before, compare the output files. They should be identical.

HAND IN:

Make sure that the names of all members of your group are on all submissions