Solution for
Programming Exercise 10.2


THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

Exercise 10.2: Write a program that will count the number of lines in each file that is specified on the command line. Assume that the files are text files. Note that multiple files can be specified, as in "java LineCounts file1.txt file2.txt file3.txt". Write each file name, along with the number of lines in that file, to standard output. If an error occurs while trying to read from one of the files, you should print an error message for that file, but you should still process all the remaining files.


Discussion

The main() routine for this program is a simple for loop that processes the command-line arguments. Each argument is supposed to be the name of a file. The loop just prints the file name and calls a subroutine that counts the number of lines in the file and outputs the result:

         for (int i = 0; i < args.length; i++) {
            System.out.print(args[i] + ": ");
            countLines(args[i]);
         }

The countLines() subroutine will catch any errors that occur when it tries to access the file. If an error occurs, it will output an error message instead of the number of lines. Since the error is handled in the subroutine, it won't crash the program or stop the main() routine from going on to process any remaining files.

The countLines() subroutine creates a TextReader stream to read from the file. It then calls the getln() routine from the TextReader class to read lines of text from the file, until the end of the file is encounters. It counts each line as it reads it. In the end, the value of the counter is the number of lines in the file. The subroutine writes this value to standard output. All this is done in try...catch statements so that an error can be handled if it occurs. It's pretty straightforward. The complete solution is given below. I do have a few comments, though.

The loop that reads and counts lines from the TextReader, in, is given by

            while (in.peek() != '\0') {
                   // Read the next line and count it.
               in.getln();
               lineCount++;
            }

The function in.peek() looks ahead at the next character in the stream. When the end-of-stream is reached, the value that is returned is '\0', the character with code number zero. I should note that this is not built into streams in general. It's just the way that I designed the TextReader class. TextReaders have another function, eof(), for testing for the end of the stream, and it might be tempting to replace the above loop with

            while (in.eof() == false) {
                   // Read the next line and count it.
               in.getln();
               lineCount++;
            }

Because of a technicality in the way that I designed TextReader class, this won't work properly. When the eof() function is called, it skips past spaces and blank lines before it tests for the end-of-stream. The idea is to test whether there is anything left in the stream besides whitespace. The problem is that the blank lines that are skipped by eof() are not counted by the above loop. In effect, the loop counts only the non-blank lines in the file. Again, you should understand that this is not a problem with Java streams, but only with the way that I designed the TextReader class. I could easily have written eof() in such a way that it would not skip blank lines, but then it would be less useful in certain other contexts. This example also shows the kinds of technicalities that can make text processing difficult

Another note, about the command line for calling this program. If you are using UNIX or Linux, you can take advantage of something called "wildcards" in the command-line arguments. For example, if you say, "java LineCounts *.txt", the "*" is a wildcard. The operating system will expand "*.txt" into a list of all the files in the current directory that end in ".txt". Similarly, "*xx*" expands into a list of all file names that contain "xx", and "fil*dat" expands into the list of file names that begin with "fil" and end with "dat". The "*" matches any number of characters in the file name, including zero characters. This expansion is done before your program sees the command-line arguments. Typing the command "java LineCounts *.txt" would be exactly equivalent to typing something like "java LineCounts file1.txt file2.txt file3.txt". This type of expansion happens for any command in UNIX, not just for the "java" command.


The Solution

   /* 
       This program reports the number of lines in each of the files
       that are specified as command line arguments.  The files are
       assumed to be text files.  If a file does not exist, or if
       some error occurs when the attempt is made to read the file,
       then an error message is printed (but the other files are
       still processed).  This program depends on the non-standard
       TextReader class.
   */
   
   import java.io.*;
   
   public class LineCounts {
   
   
      public static void main(String[] args) {
            // The main() routine simply gets the file names from the
            // command line and calls the countLines() routine to process
            // each name.  Since any errors are handled in the countLines()
            // routine, the main program will continue after an error occurs
            // while trying to process one of the files.
         
         if (args.length == 0) {
                // This program must have at least one command-line 
                // argument to work with.
             System.out.println("Usage:   java LineCounts <file-names>");
             return;
         }
         
         for (int i = 0; i < args.length; i++) {
            System.out.print(args[i] + ": ");
            countLines(args[i]);
         }
         
      }  // end main()
      
      
      static void countLines(String fileName) {
             // Count the number of lines in the specified file, and
             // print the number to standard output.  If an error occurs
             // while processing the file, print an error message instead.
             // Two try...catch statements are used so I can give a
             // different error message in each case.
             
         TextReader in;  // A stream for reading from the file.
         int lineCount;  // Number of lines in the file.
         
         try {
             in = new TextReader( new FileInputStream(fileName) );
         }
         catch (Exception e) {
             System.out.println("Error.  Can't open file.");
             return;
         }
         
         lineCount = 0;
         
         try {
            while (in.peek() != '\0') {
                   // Read the next line and count it.
               in.getln();
               lineCount++;
            }
         }
         catch (Exception e) {
            System.out.println("Error.   Problem with reading from file.");
            return;
         }
         
         System.out.println(lineCount);
      
      }  // end countLines()
      
   
   } // end class LineCounts


[ Exercises | Chapter Index | Main Index ]