This lab introduces how to handle files as both input and output. Weβre coming back to Tracery (which you implemented in Lab 1) with this assignment but instead of always reading the grammar from a file and writing the expanded grammar to the command line, we will also be able to read a grammar from the command line and write the expanded grammar to a file. To do this, youβll be learning about some of the IO (input/output) classes in Java, and how the Java concept of a βStreamβ provides an abstraction layer above files and console I/O.
Β
You will be doing the following:
- Import the correct IO packages from the Standard Library
- Process command line arguments that determine whether the program is reading/writing from the console (command line) or from a file
- Load a grammar either from the console or from a file, based on the arguments the program was given
- Write the expanded grammar to either the command line or a file, based on the arguments the program was given
Β
Open the file βTraceryRecursion.javaβ that came with the lab. This is where youβll make all your changes. Every place in the file where youβre supposed to make a change is marked with a comment labeled βTo Doβ.
Importing packages in Java
Try compiling the code weβve given you. If you havenβt made any changes itβll tell you it canβt find the following symbols:
Β
- classβ βHashtable
- classβ βPrintStream
- classβ βInputStream
Β
But in Lab 1 we were able to use a βHashtableβ, so whatβs different? The problem is that weβre trying to use something from the Java Standard Library without telling Java that we are. To tell Java that weβre using something from the standard library, you use the importβ statement. In Lab 1 we had already included the correct imports for you. For lab 2 the compiler is telling you that you need to import these three classes in order to use them.
Β
As an example of importing a class, to import Hashtable from the standard library so you can use it, you would put this statement at the beginning of your program:
Β
importβ java.util.Hashtable
Β
The βjava.utilβ part is the package (a collection of classes) that βHashtableβ belongs to. To figure out the package of a class you want to import from the standard library, you look at its documentation page. The documentation for Javaβs Hashtable is βhereβ. In addition to all the info about where it is in the type hierarchy and its accessible fields and methods, the documentation page says what package the class is part of at the top of the page right above the class name.
Β
To import βPrintStreamβ and βInputStreamβ you can look up the package they belong to at their respective documentation pages (βhere βand βhereβ). Another trick to know about, though you donβt really need to use it in this lab, is how to import a bunch of classes from the same package at once. To import all of the classes from a package, you can use statement:
Β
importβ <package name>.*
Command line arguments
This program can take the following command line arguments:
Β
-in <input file> -out <output file>
Β
The ββ-inββ and ββ-outββ part of the arguments allow us to specify the input and output file arguments in either order. Below are described the different valid combinations of arguments.
Β
$ java TraceryRecursion
This reads the grammar from the console and writes the expanded grammar to the console. Reading from the console is called βstandard inβ and writing to the console is called βstandard outβ. Standard in is represented by the βInputStreamβ object stored in System.inβ, while standard out is represented by the βPrintStreamβ object stored in
System.outβ. We will be reading from the console using pipes and the command βcatβ (in unix systems like Mac OS and Linux and gitbash for Windows). There is more about this below.
Β
| $ java TraceryRecursion -in grammar-story.txt | ||
| This reads a grammar from file | grammar-story.txt | Β and writes to standard out. This |
βΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β β
was the behavior you implemented in Lab 1.
Β
$ java TraceryRecursion -out grammar-out.txt
This reads grammar from standard in and writes the expanded grammar to the text file grammar-out.txtβ.
Β
| $ java TraceryRecursion -out grammar-out.txt -in grammar-story.txt | ||
| This reads a grammar from from file | grammar-story.txt | Β and writes the expanded |
βΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β β
grammar to file βgrammar-out.txtβ. Note that since our command line arguments specify the input and output file with β-inβ and β-outβ you can put the arguments in either order (that is β-outβ first like the example above, or β-in first).Your code doesnβt need toβ handle bad command line arguments. Since β-inβ and β-outβ are what tell the program to use files, junk arguments will default the program to reading from standard in and writing to standard out.
Processing command line arguments
We use the command line arguments to figure out where weβre reading the grammar from and where weβre writing the grammar to. This is done with two helper functions that you need to write:
Β
publicβ βstaticβ InputStream βgetInputStreamβ(String[] args)β; publicβ βstaticβ PrintStream βgetOutputStreamβ(String[] args)β;
Β
There are comments marked with βTo Doβ in the file to remind you to define these methods. Youβll be calling these methods in βmainβ, passing them the command line arguments, in order to determine the βInputStreamβ and βPrintStreamβ to use.
Β
The nice thing about these methods is that they abstract away whether youβre reading/writing from standard IO or a file. The rest of the code can just read from the returned βInputStreamβ and write to the returned βOutputStreamβ without having to know or care whether its a file or the console.
Β
To understand this, hereβs a bit more information about how Java abstracts input and output. Youβll notice that βgetInputStream()β βreturns an βInputStreamβ βwhich is an abstractβ class (an βabstractβ class, like an βinterfaceβ, canβt be directly instantiated as an object). This means you will actually be returning a βsubclassβ of βInputStreamβ, where which subclass depends on whether itβs the console or a file. If reading from a file, youβll be returning something called a βFileInputStreamβ. The diagram below shows where it sits in the class hierarchy for IO.
Β
Β
Β
A βFileInputStreamβ is the subclass of βInputStreamβ for reading from a βFileβ. A βFileβ is Javaβs representation of a file in the computerβs file system. The field βSystem.inβ stores an βInputStream for standard input.β
Β
To return the correct input location youβll:
- Look for a ββ-inββ in βargs[]
- If there is an ββ-inββ then youβll make a new βFileInputStreamβ βthat will take a new βFileβ βnamed whatever is in βargs[]β in the index after the location you found ββ-inββ. Return the βFileInputStreamβ.
- Otherwise return βinβ which is also an βInputStream
Β
You can assume that there will be 0, 2 or 4 command line arguments.
Β
Now since weβre dealing with user input and we donβt know what exactly is in the folder with our program (from the programβs point of view), we want to handle the possibility that the input file we were told to open doesnβt exist. Weβll want to use a βtryβ-βcatch block to catch a βFileNotFoundExceptionβ within βgetInputStream()β. If you catch the exception, print the error message ββInput grammar file does not exist.ββ and return βSystem.inβ (as if no β-inβ argument was specified).
Β
Our function βgetOutputStream()β βis very similar to βgetInputStream()β βbut instead of returning an βInputStreamβ βweβre returning a βPrintStreamβ β(System.outβ is a PrintStream).β
Β
To return the correct output location youβll:
Β
- Look for a ββ-outββ in βargs[]
- If there is a ββ-outββ then youβll make a new βPrintStreamβ βthat will take a
new βFileβ βnamed whatever is in args[]βΒ in the index after the location youβΒ Β Β Β Β Β Β Β found ββ-outββ. Return the βPrintStream
- Otherwise return βoutβ which is also a βPrintStream
Β
As above, youβll want to catch a βFileNotFoundExceptionβ, print the error message
βOutput file can not be created.ββ, and return βSystem.outβ (as if no β-out argument was specified).
Piping on the command line
Another part of this lab is giving you practice using pipes on the command line to use the standard output of one program as the standard input of another. For our purposes weβll be using pipes to pipe the contents of a file into βTraceryRecursionβ, but in general itβs possible to hook lots of programs together on the command line using pipes. The general form of using pipes looks like
Β
<program β1β> | <program β2β>
Β
where the β|β character is the pipe. The line above would takes the standard output of <program β1β>β (which would normally just print to the console) and feeds it into the standard input of β<program β2β>β (which would normally read interactively from the console).
Β
To feed a file into the standard input of βTraceryRecursionβ, weβll use the the unix command βcatβ (short for concatenate). This command simply reads a file and writes the content to standard out. For example, if we type:
Β
cat grammar-story.txt
Β
this will print out the file contents to the console:
Β
animal:cat,emu,okapi
Emotion:happy,sad,elated,curious,sleepy
Color:red,green,blue
Name:emily,luis,otavio,anna,charlie character:#name# the #adjective# #animal# place:school,the beach,the zoo,Burning Man adjective:#color#,#emotion#, origin:once #character# and #character# went to #place#
Β
To take this output and feed it into βTraceryRecursionβ we can pipe the output of βcat into βTraceryRecursionβ:
Β
cat grammar-story.txt | java TraceryRecursion
Β
More generally this looks like:
Β
cat <file name> | java TraceryRecursion <arguments>
Β
Try running TraceryRecursionβΒ Β Β Β Β Β Β β without a β-outβ argument using pipes to feed grammar files into βTraceryRecursionβ.
Loading the grammar
For this lab, the βloadGrammar()β βfunction will be very similar to what it was in Lab 1 with one major difference: βinstead of giving it a string as an argument weβre passing it the βInputStreamβ we returned from βgetInputStream().β
Β
Weβre going to use an instance of βBufferedReader tβo read from this InputStreamβΒ Β Β Β .β The main reason for using this class instead of the raw βInputStreamβ βis for the function readLine()β which allows us to read strings that correspond to a line of the file, automatically handling the fact that different operating systems using different characters to indicate the end of a line. You can read more about βBufferedReaders hereβ. To make a new βBufferedReaderβ you need to give it a new βInputStreamReader which requires an βInputStreamβ βas part of its constructor. You can see the details of an InputStreamReaderβ βhereβ. So youβll be creating a new βBufferedReaderβ from a new InputStreamReaderβ which is created from the βInputStreamβ.
Β
Note: βThe constructor for βBufferedReaderβ wants an instance of the abstract class Readerβ as the argument. An βInputStreamReaderβ is a subclass of βReaderβ, which is why this works.
Β
Weβll use the βreadLine()β method on βBufferedReaderβ to read each line of the grammar and add it to the βHashtableβ. The loop where youβre calling βreadLine()β will have to know when to stop. Fortunately, it returns βnullβ when it gets to the end of the stream, so we can use this to know when the loop should stop.
Specifying that loadGrammar() throws an exception
Youβve probably seen at least a few βRuntimeExceptionsβ being thrown in your experience as a Java programmer. Among the most common are the
ArrayOutOfBoundsExceptionβ (when youβre trying to access an index that isnβt in an array) and a βNullPointerExceptionβ (when you try to access a field or method on a reference variable that hasnβt been set equal to anything). These are unchecked exceptions, and programmers donβt usually try to catch them.
Β
The βBufferedReaderβ methods in βloadGrammar()β throw the βcheckedβ exception IOExceptionβ. Since itβs checked, the compiler flags it as an error, requiring the programmer to either catch the exception or declare that the method can throw the exception. For example, to declare that βmain()β can throw an IOException, youβd change the signature to:
Β
publicβ βstaticβ βvoidβ βmainβ(String[] args)β βthrowsβ IOException
Β
Youβll need to do this for βloadGrammar()β. When you declare that a method throws a checked exception, the compiler is happy, because now it can check that anyone who calls the method that can throw an exception either catches the exception or declares that they throw the exception in turn.
Changes in mβ ain()
There are two things youβll need to change in βmain()β:
- Calling the methods βgetInputStream()β and βgetPrintStream()β to get the βInputStreamβ and βPrintStreamβ based on the arguments
- Changing the βprintln()β in βmain()β to be called on the βPrintStreamβ that getOutputStream() returnedβ
Outputting the grammar
Unlike in Lab 1, the method βoutputGrammar()β has two arguments. Like the previous lab, the first one is the grammar weβre expanding. The second is the βPrintStream weβre going to print the grammar to. All we have to do here is to call βprintln()β on the PrintStream argument rather than on standard output.
Example arguments and output
$ java TraceryRecursion -in βgrammar-story.txtβ -out βoutput.txt
Β
Running TraceryRecursionβ¦
Set seed β1
Β
The contents of output.txt are:
Β
| GRAMMAR:
adjective:Β Β Β Β Β Β Β Β Β Β Β ββ#color#ββ,ββ#emotion#ββ, place:Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β ββschoolββ,ββthe beachββ,ββthe zooββ,ββBurning Manββ, emotion:Β Β Β Β Β Β Β Β Β Β Β Β Β ββhappyββ,ββsadββ,ββelatedββ,ββcuriousββ,ββsleepyββ, origin:Β Β Β Β Β Β Β Β Β Β Β Β Β Β ββonce #character# and #character# went to #place#ββ, color:Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β ββredββ,ββgreenββ,ββblueββ, name:Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β ββemilyββ,ββluisββ,ββotavioββ,ββannaββ,ββcharlieββ, character:Β Β Β Β Β Β Β Β Β Β Β ββ#name# the #adjective# #animal#ββ, |
animal:Β Β Β Β Β Β Β Β Β Β Β Β Β Β ββcatββ,ββemuββ,ββokapiββ, once otavio the blue emu and luis the curious emu went to school once otavio the elated cat and charlie the blue emu went to school once anna the elated okapi and charlie the sad okapi went to Burning Man once emily the happy okapi and otavio the red cat went to school once otavio the blue cat and emily the sleepy okapi went to the beach once luis the green cat and charlie the blue emu went to school once otavio the red emu and otavio the red okapi went to the zoo once charlie the green cat and luis the green okapi went to the zoo once luis the red emu and emily the red cat went to school once otavio the sad okapi and luis the happy emu went to school
Β
Β
Helpful tips:
Packages:
- Overview of using package members:
https://docs.oracle.com/javase/tutorial/java/package/usepkgs.html
Β
Try-Catch:
- Overview of catch blocks in Java:
https://docs.oracle.com/javase/tutorial/essential/exceptions/catch.html βΒ Β Β Β Β Throwables in Java:
https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html
Β
I/O Streams
- Relationship of the various Input and Output Streams: https://stackoverflow.com/questions/22563986/understanding-getinputstre am-and-getoutputstream







