Lab 10: Record-based File I/O
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.)
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.
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.
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
.
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.
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.
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.
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 .
Your project must be turned in electronically before the end time of your lab. Please turn in all the .java files you have created.
turnin -v -c cs180secXXX -p lab10 *.javawhere XXX is the code for your lab based on its day and starting hour (e.g., cs180sect1). Note that the
-voption 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.