Lab 10: Record-based File I/O

Outline

Motivation and Introduction

This lab will introduce you to file-based I/O, and to storing complex objects in files. While Java provides some features that would make this very easy, you will instead be working with byte streams to gain familiarity with the underlying systems.

The code you create in this lab will be useful in Project 4 (which starts after break.)

Overall Deliverable

This lab will be done in three parts. We expect all students to at least complete part 1, most to complete part 2, but would not be surprised if many have a hard time with part 3. The farther you get, the less work for Project 4.

File format

The recipe file will consist of multiple recipe records. Each recipe record begins with the line @recipe. Next is the name of the recipe, ended by a line @ingredients. Following this will be a list of ingredients, one per line. This will consist of a quantity (floating point number), measure (cup, tablespoon, teaspoon, pounds, etc.), and then the ingredient itself, terminated by the end of line. You can assume that the measure will be a single word with no spaces. The list of ingredients is ended by a line @directions, with the following lines (up to @end constituting the directions. There may then be another @recipe, or the end of the file.

A very simple sample recipe is found in file water.txt. The file recipes.txt contains a more complex example.

Part 1: Reading strings from a file

The first task will be to simply read in a recipe from a file, and write it to the screen. Your program should take the name of the file as a command-line argument:

java ReadRecipeFile water.txt

It will then simply write what is in the file to System.out, except that instead of the lines @reciple, @ingredients, and @directions, you will write a blank line. There should be two blank lines between recipes. For example:

b146-19 51 $ java ReadRecipeFile water.txt

Drink of water

8.0 ounces water

Put water in a glass and consume.

Note that the format of the number need not match the input (you will find later parts easier if you convert the number to a float or double.) This will be easiest if you use the Scanner class in the java.util package. Note that you should keep showing recipes until you get to the end of file.

Part 2: Writing strings to a file

This is simply Part 1 in reverse, however it will only write a single recipe.

b146-19 51 $ java WriteRecipeFile newwater.txt
Drink of water

8. ounces water

Put water in a glass and consume.
<cntrl-d>

At the end of this, the file newwater.txt should be identical to the file water.txt (except that the format of a number may be different.)

Note the <cntrl-d> at the end: This is the standard end-of-file character, and is typically used to mark the end of input.

Note that for full credit, you should append to an existing file rather than overwriting it. This will require make use of the FileOutputStream class as well as the PrintWriter class (both in the java.io package), as if you just create a PrintWriter using a file name it will erase what is in the file before you begin.

Part 3: Writing structured objects

The above should be fairly straightforward, but to use the data, you really want to represent it appropriately. For this part, you should define Recipe and Ingredient classes. Each class should have a method to read from a stream, and to write to a stream. There are various ways to do this - you could have a constructor that takes an InputStream as an argument, and populates the object from that stream; or a read method that replaces the contents of the object (you'll have done the bulk of the work for this in part 1.) How you do this is a design decision you have to make. Likewise, you should have a write method that takes an OutputStream as an argument and writes itself to that stream (again, you'll have pretty much done this in part 2.)

Note that a recipe has multiple ingredients - how you represent this is up to you.

Implementation Details

You will implement this from scratch - we give you nothing to work with. You will want to use the Scanner and PrintWriter classes, see the Java API documentation.
Warning:Scanner and PrintWriter do what is called buffered I/O: A Scanner will read ahead, and a PrintWriter may not write until flushed. It is not a good idea to use multiple Scanners on the same InputStream. You can use multiple PrintWriters on the same output stream, but you must remember to flush() each before the next uses it.

Note on command line arguments: We have not discussed this in class, but if you remember, the main always has an array of Strings as an argument. Each entry in this array corresponds to a (space-separated) argument on the command line, which gives you a way to find the file name that was entered on the command line.

Note that you should use exceptions for error handling, particularly in your Recipe and Ingredient classes. For example, what if you try to read a recipe, but the file is not in the right format? In such a case, you should throw an exception, so the higher-level program using your class can decide what to do.

Test Cases

You should at least be able to do parts 1 and 2 for the file:
water.txt .
Eventually, you will need to be able to support a more complicated example, with multiple ingredients and multiple recipes: recipes.txt .

Lab Submission

Your project must be turned in electronically before the end time of your lab. Please turn in all the .java files you have created.

Use the following command from the directory of your project to turn in your Lab
    turnin -v -c cs180secXXX -p lab10 *.java
where XXX is the code for your lab based on its day and starting hour (e.g., cs180sect1). Note that the -v option will show the files you turned in - if it doesn't match what you expect, then make sure you are in the right directory and try again.

If you are having trouble submitting your files, talk to a TA for help.