5. Exceptions Lesson

Example 1: PhoneNumberParser

Example 1: PhoneNumberParser

Note

To show the solution for this activity, you will need to toggle “Show Solutions” above.

Note

This multipart activity walks students through the concepts of exception identification, avoidance, handling, stack traces, scope and throwing custom exceptions by using the classroom example of PhoneNumberParser.

Prerequisite Information

This practice activity should be done on Odin, so make to complete all of the tutorials in the Unix chapter before attempting it and that you followed the steps in that chapter to activate the CSCI 1302 shell profile on your Odin account. This practice activity also assumes that readers have worked through the material in the Exceptions chapter prior to starting the activity.

Part 1: Identifying a Potential Exception
Download Starter Code (Instructors)

Execute the command below to download and extract the files:

sh -c "$(curl -fsSL https://cs1302book.com/_bundle/cs1302-phone.sh)"
- downloading cs1302-phone bundle...
- verifying integrity of downloaded files using sha256sum...
- extracting downloaded archive...
- removing intermediate files...
subdirectory cs1302-phone successfully created
Download Starter Code (Students)
  1. Use the phone1302 command on Odin to download the starter code for this lesson:

    Listing 14 The command to download the starter code for this lesson on Odin.
    phone1302
    
    - downloading cs1302-phone bundle...
    - verifying integrity of downloaded files using sha256sum...
    - extracting downloaded archive...
    - removing intermediate files...
    subdirectory cs1302-phone successfully created
    
  2. Use cd to change to the cs1302-phone directory that was created and look at the files that were included in the starter code using tree. You should see output similar to the following:

    Listing 15 The files and directories in the starter code.
    .
    └── src
        └── cs1302
            └── phone
                ├── Driver.java
                └── PhoneNumberParser.java
    
Identify Dependencies (and Practice Emacs)
  1. Open PhoneNumberParser.java and Driver.java for editing at the same time in Emacs using the emacs command:

    Listing 16 Open PhoneNumberParser.java and Driver.java in Emacs.
    emacs src/cs1302/phone/PhoneNumberParser.java \
        src/cs1302/phone/Driver.java
    

    Note: Switching Buffers

    Each file will be opened in their own Emacs buffer. Use C-x o to switch between the buffers, as needed.

    Note: Saving Files in Emacs Buffers

    C-x C-s only saves the current buffer. If you want to save all buffers, use C-x s ! instead. To save a specific buffer, switch to that buffer, then use C-x C-s.

    Note: Minimizing Emacs

    If you minimize Emacs using C-z, then remember to bring it back using the fg command instead of rerunning the emacs command. Otherwise, you will open mutliple instances of Emacs, which may lead to issues with multiple versions of the file.

    Note: Exiting Emacs

    When you are done, you can exit Emacs using C-x C-c.

  2. On your exit ticket, write the order in which both .java files should be compiled. Explain the order you chose.

    Sample Solution

    PhoneNumberParser.java needs to be compiled first because Driver.java depends on (calls) the parse method in PhoneNumberParser. Therefore, when we compile Driver.java, it will need access to the compiled code of PhoneNumberParser.

Compile and Run
  1. From cs1302-phone, compile each .java file under src into bin separately using javac. Make sure that you include the appropriate command-line options to adjust the compiler’s destination directory and, if needed, its classpath. Also make sure that you compile the files in the appropriate order. Write down both the commands, in order, on your exit ticket.

    Sample Solution

    You would execute the following commands from within cs1302-phone:

    javac -d bin src/cs1302/phone/PhoneNumberParser.java
    
    javac -d bin -cp bin src/cs1302/phone/Driver.java
    

    If you entered the commands correctly, then the output of running tree should now look like this:

    Listing 17 The directory structure after compilation
    .
    ├── bin
    │   └── cs1302
    │       └── phone
    │           ├── Driver.class
    │           └── PhoneNumberParser.class
    └── src
        └── cs1302
            └── phone
                ├── Driver.java
                └── PhoneNumberParser.java
    
  2. Now, run the Driver class from this location using the java command. Make sure that you include the appropriate command-line options to adjust the class path so that the compiler knows where to find the Driver class. Write down the command on your exit ticket and underline the main class’s FQN.

    Sample Solution
    java -cp bin cs1302.phone.Driver
    

    The FQN for the main class is cs1302.phone.Driver.

  3. Enter a valid phone number at the prompt to make sure the code works as expected. Write down “Example:” followed by the number you tried on your exit ticket.

Predict the Output

Open PhoneNumberParser.java with Emacs and inspect the code.

If the user enters 706-12a-4567, the program will crash. Without running the code, write the following on your exit ticket:

  • Where do you think the exception will originate (file and line number)?

  • What type of exception will be thrown? How can we know what that is without executing the program and letting it crash?

Sample Solution

The program will crash when we call parseInt on the prefix string. This happens in the “prefix” portion of the parse method on this line:

int prefix = Integer.parseInt(prefixString);

Explanation: The input has a non-numeric character a in the prefix 12a. So, when the method tries to convert 12a to an integer, it will throw a NumberFormatException. This can be found in the Java API Documentation of the Integer class.

Interpreting the Stack Trace
  1. Run the program using the same input (706-12a-4567). The output should look similar to the following:

    Pro Tip

    If you ran the program recently (and haven’t changed the code), just use C-p from the command line to go back to a previously used command (or use C-r to search for it).

    Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
       at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
     at java.base/java.lang.Integer.parseInt(Integer.java:668)
     at java.base/java.lang.Integer.parseInt(Integer.java:786)
     at cs1302.phone.PhoneNumberParser.parse(PhoneNumberParser.java:24)
     at cs1302.phone.Driver.promptAndParse(Driver.java:32)
     at cs1302.phone.Driver.main(Driver.java:16)
    
  2. On your exit ticket, explain the output in as much detail as you can. For example:

    • Which method was called first?

    • What is the name of the second method that is called and where is it called (file and line number)?

    • What is the last method call in our code that appears in the stack trace?

    Sample Solution
    • The program starts in main.

    • main calls promptAndParse from line 16 in Driver.java.

    • parse which is a static method in the PhoneNumberParser class.

Part 2: Recovering from Exceptions
Exception Recovery

In your groups, modify the parse method in PhoneNumberParser to gracefully recover NumberFormatException exception. After your modification, the program should print the message “Non-numeric character detected in the phone number” followed by the message printed at the end of the main method (“end of main”) when a NumberFormatException occurs.

Before writing any code: Carefully consider which lines should be placed in the try block and why. Discuss this with your groups and summarize your thoughts on your exit ticket.

Listing 18 Example output when gracefully recovering from NumberFormatException.
java -cp bin cs1302.phone.Driver
start of main
Please enter your phone number in ###-###-#### format: 706-12a-4567
Non-numeric character detected in the phone number
end of main
Sample Solution
Listing 19 In PhoneNumberParser.java
 1public static void parse(String phoneNumber) {
 2
 3    try {
 4        // attempt to parse the "area code" portion
 5        String areaCodeString = phoneNumber.substring(0, 3);
 6        int areaCode = Integer.parseInt(areaCodeString);
 7
 8        // attempt to parse the "prefix" portion
 9        String prefixString = phoneNumber.substring(4, 7);
10        int prefix = Integer.parseInt(prefixString);
11
12        // attempt to parse the "line number" portion
13        String lineNumberString = phoneNumber.substring(8, 12);
14        int lineNumber = Integer.parseInt(lineNumberString);
15
16        // display the constituent parts or portions
17        System.out.printf("Area code: %d\n", areaCode);
18        System.out.printf("Prefix: %d\n", prefix);
19        System.out.printf("Line Number: %d\n", lineNumber);
20    } catch (NumberFormatException nfe) {
21        System.out.println("Non-numeric character detected in the phone number");
22    } // try
23
24} // parse
Semantics and Using the Exception Object
nfe Refers to a NumberFormatException Object
Listing 20 In PhoneNumberParser.java: Adjusted Catch Block
} catch (NumberFormatException nfe) {
    String message = nfe.getMessage();
    System.out.printf("Non-numeric character detected (%s)\n", message);
} // try
Example: 706-12a-4567
Listing 21 In PhoneNumberParser.java: Adjusted Catch Block
} catch (NumberFormatException nfe) {
    String message = nfe.getMessage();
    System.out.printf("Non-numeric character detected (%s)\n", message);
} // try
  1. Modify the catch block in your parse method to look like the code snippet shown above. If you don’t currently have a catch block, add this one (and make sure to add the corresponding try).

  2. Save and compile all of your code.

  3. Run the Driver class with input: 706-12a-4567.

Listing 22 Partial / Incomplete Output fot Input: 706-12a-4567
java -cp bin cs1302.phone.Driver
1start of main
2Please enter your phone number in ###-###-#### format: 706-12a-4567
3...

On your exit tickets, write down line 3 in the actual output that you observed when you ran the program with input 706-12a-4567.

Sample Solution (with memory map)
java -cp bin cs1302.phone.Driver
start of main
Please enter your phone number in ###-###-#### format: 706-12a-4567
Non-numeric character detected (For input "12a")
end of main
Example: 70x-12a-4567
Listing 23 Partial / Incomplete Output fot Input: 70x-12a-4567
java -cp bin cs1302.phone.Driver
1start of main
2Please enter your phone number in ###-###-#### format: 70x-12a-4567
3...
  1. In your group, guess what you think line 3 in the output will look like (without running the program) when the input is 70x-12a-4567, then write down your guess on your exit ticket. Write “(guess)” next to it so your instructor knows what it is.

  2. Next, run the program with input 70x-12a-4567, then write down the actual output that you observe for line 3. Write “(actual)” next to it so your instructor knows what it is.

  3. If your guess wasn’t correct, then discuss with your group and write down what you think your misunderstanding was. If you got it correct, then explain why the output is what it is.

Sample Solution (with memory map)
java -cp bin cs1302.phone.Driver
start of main
Please enter your phone number in ###-###-#### format: 70x-12a-4567
Non-numeric character detected (For input "70x")
end of main
Discussion

How many times does the catch block execute for input 70x-12a-4567?

Other Invalid Inputs
  1. As a group, come up with at least three other invalid inputs that do NOT cause a NumberFormatException, then write them down on your exit ticket.

  2. For each example on your exit ticket, note whether an exception will be thrown. If an exception will be thrown , note the type of exception that would be thrown and the location (file and line number) where it will originate.

Sample Solution
701-123-456     // StringIndexOutOfBounds
7062221348      // StringIndexOutOfBounds
abcdefg         // StringIndexOutOfBounds or NumberFormatException - depends on code.

706-233-128397  // No exception. Is it a problem? More soon!
706X222X1348    // No exception. Is it a problem? More soon!
Multiple Catch (Instructor Demo)

Adjust parse to catch both NumberFormatException and StringIndexOutOfBoundsException.

Sample Solution
Listing 24 In PhoneNumberParser.java
public static void parse(String phoneNumber) {

    try {
        // attempt to parse the "area code" portion
        String areaCodeString = phoneNumber.substring(0, 3);
        int areaCode = Integer.parseInt(areaCodeString);

        // attempt to parse the "prefix" portion
        String prefixString = phoneNumber.substring(4, 7);
        int prefix = Integer.parseInt(prefixString);

        // attempt to parse the "line number" portion
        String lineNumberString = phoneNumber.substring(8, 12);
        int lineNumber = Integer.parseInt(lineNumberString);

        // display the constituent parts or portions
        System.out.printf("Area code: %d\n", areaCode);
        System.out.printf("Prefix: %d\n", prefix);
        System.out.printf("Line Number: %d\n", lineNumber);
    } catch (NumberFormatException nfe) {
        String message = nfe.getMessage();
        System.out.printf("Non-numeric character detected (%s)\n");
    } catch (StringIndexOutOfBoundsException sioobe) {
        String message = sioobe.getMessage();
        System.out.println("The provided phone number is too short (%s)\n", message);
    } // try

} // parse
Discussion

Why might handling the exceptions within parse be a bad idea?

Hint: Think about what happens if we were to package up our parse method and sell it to another developer (without giving them access to the source code). They would be able to call the parse method but unable to modify the code.

Sample Solution
  • It keeps the method focused on its task (parsing), not error handling.

  • What if this code was used in a web API or mobile app? In those cases, you wouldn’t want the error message printed to the terminal! In other words, the caller should be able to decide how to handle the exception.

  • What if the code was used in an application for non-English speakers? We wouldn’t want the parse method printing messages in English.

  • In other words, the caller (like main or promptAndParse) should be able to decide how to respond i.e. log, show a message, retry, etc.

  • If we handle the exception within parse, the calling method is forced to handle the error in that way (assuming the caller does not have access to the code in parse).

Part 3: Propagating Exceptions
Discussion

Answer on your exit tickets:

Look through the existing code. If we removed the try/catch from the parse method, where is the next opportunity to catch them?

Sample Solution

The next opportunity is in promptAndParse in the driver.

Propagation

Adjust the code to allow the exceptions to propagate out of parse by removing the try/catch we added in the previous section. Then, update your code to catch both exceptions in the promptAndParse method of the Driver class.

Be sure to adjust the Javadoc comments above the parse method.

Sample Solution
Listing 25 In PhoneNumberParser.java
/**
 * Parse the supplied phone number ({@code phoneNumber}) into its constituent parts. The phone
 * number is eepected to be of the form {@code ###-###-####} where each {@code #} is a
 * single digit (i.e., a nonnegative integer between {@code 0} and {@code 9}).
 *
 * @param phoneNumber The phone number to parse.
 * @throws NumberFormatException if {@code phoneNumber} contains non-numeric characters.
 * @throws StringIndexOutOfBoundsException if {@code PhoneNumber} is too short.
 */
public static void parse(String phoneNumber) {

    // attempt to parse the "area code" portion
    String areaCodeString = phoneNumber.substring(0, 3);
    int areaCode = Integer.parseInt(areaCodeString);

    // attempt to parse the "prefix" portion
    String prefixString = phoneNumber.substring(4, 7);
    int prefix = Integer.parseInt(prefixString);

    // attempt to parse the "line number" portion
    String lineNumberString = phoneNumber.substring(8, 12);
    int lineNumber = Integer.parseInt(lineNumberString);

    // display the constituent parts or portions
    System.out.printf("Area code: %d\n", areaCode);
    System.out.printf("Prefix: %d\n", prefix);
    System.out.printf("Line Number: %d\n", lineNumber);

} // parse
Listing 26 In Driver.java
/**
 * Prompt the user to enter a phone number, then parse it using the {@link
 * cs1302.phone.PhoneNumberParser#parse(String)} method. If an exception is thrown when parsing
 * the phone number, then communicate the issue to the user with an appropriate error message.
 */
private static void promptAndParse() {
    Scanner input = new Scanner(System.in);

    System.out.print("Please enter your phone number in ###-###-#### format: ");
    String number = input.nextLine();

    try {
        // parse the phone number
        PhoneNumberParser.parse(number);

    } catch (NumberFormatException nfe) {
        System.out.println("Non-numeric character detected in the phone number");
    } catch (StringIndexOutOfBoundsException sioobe) {
        System.out.println("The provided phone number is too short");
    } // try

} // promptAndParse
Part 4: Allow Multiple Attempts
Practice Writing the Loop

In promptAndParse, add a loop to allow the user multiple attempts to enter a valid phone number using the following plan:

  1. Add a int parameter to promptAndParse called maxAttempts and update the Javadoc comments to reflect the new parameter.

  2. Update the code within the method to:

    1. assume valid input has not yet been entered
    2. loop while valid input has not yet been entered and max attempts not exceeded:
       try:
           1. prompt the user to enter a phone number
           2. get a line from user input
           3. parse the line from user input
           4. assume valid input has now been entered
       catch: (add more catch blocks as needed)
           1. display appropriate error message
    3. if valid input has not yet been entered:
           1. display an error -- maxAttempts has been exceeded
    
  3. Update your call to promptAndParse to provide an actual parameter.

Sample Solution

One sample solution is provided in the next dropdown in this lesson. Please remember that there are many valid solutions to this exercise.

Understanding a Solution

Take a few minutes to understand the code below.

Listing 27 In Driver.java
 1/**
 2 * Prompt the user to enter a phone number, then parse it using the {@link
 3 * cs1302.phone.PhoneNumberParser#parse(String)} method. If an exception
 4 * is thrown when parsing the phone number, then communicate the issue to
 5 * the user with an appropriate error message.
 6 *
 7 * @param maxAttempts the maximum number of allowed attempts.
 8 */
 9private static void promptAndParse(int maxAttempts) {
10
11    int attempts = 0;
12    boolean valid = false;
13
14    Scanner input = new Scanner(System.in); // ---- (1)
15
16    while (!valid && (attempts < maxAttempts)) {
17        try {
18            attempts += 1; // --------------------- (2)
19            System.out.print("Please enter your phone number in ###-###-#### format: ");
20            String number = input.nextLine();
21            PhoneNumberParser.parse(number);
22            valid = true; // ---------------------- (3)
23        } catch (NumberFormatException nfe) {
24            System.err.print("Non-numeric character detected: ");
25            System.err.println(nfe.getMessage());
26        } catch (StringIndexOutOfBoundsException sioobe) {
27            System.err.print("Phone number too short: ");
28            System.err.println(sioobe.getMessage());
29        } // try
30    } // while
31
32    if (!valid) {
33        System.err.print("maxAttempts has been exceeded: ");
34        System.err.println(maxAttempts);
35    } // if
36
37} // promptAndParse

Answer the following questions on your exit ticket:

  1. Why is the line marked (1) placed outside of the loop?

  2. If you move the line marked (2) to the end of the while loop (outside of the second catch block), will the code still work as intended?

  3. What can you conclude about the phone number that was entered if the line marked (3) executes?

Code Tracing

Assume maxAttempts is set to 2. Write the complete output for the following inputs on your exit tickets:

  1. 70a-12x-aaaa
    70a-123-aa
    
  2. 707-123-4567890
    
 1private static void promptAndParse(int maxAttempts) {
 2
 3    int attempts = 0;
 4    boolean valid = false;
 5
 6    Scanner input = new Scanner(System.in); // ---- (1)
 7
 8    while (!valid && (attempts < maxAttempts)) {
 9        try {
10            attempts += 1; // --------------------- (2)
11            System.out.print("Please enter your phone number in ###-###-#### format: ");
12            String number = input.nextLine();
13            PhoneNumberParser.parse(number);
14            valid = true; // ---------------------- (3)
15        } catch (NumberFormatException nfe) {
16            System.err.print("Non-numeric character detected: ");
17            System.err.println(nfe.getMessage());
18        } catch (StringIndexOutOfBoundsException sioobe) {
19            System.err.print("Phone number too short: ");
20            System.err.println(sioobe.getMessage());
21        } // try
22    } // while
23
24    if (!valid) {
25        System.err.print("maxAttempts has been exceeded: ");
26        System.err.println(maxAttempts);
27    } // if
28
29} // promptPhoneNumber
public static void parse(String phoneNumber) {

    String areaCodeString = phoneNumber.substring(0, 3);
    int areaCode = Integer.parseInt(areaCodeString);

    String prefixString = phoneNumber.substring(4, 7);
    int prefix = Integer.parseInt(prefixString);

    String lineNumberString = phoneNumber.substring(8, 12);
    int lineNumber = Integer.parseInt(lineNumberString);

    System.out.printf("Area code: %d\n", areaCode);
    System.out.printf("Prefix: %d\n", prefix);
    System.out.printf("Line Number: %d\n", lineNumber);

} // parse
Sample Solution
  1. 70a-12x-aaaa 70a-123-aa

    Listing 28 output
    Non-numeric character detected
    Non-numeric character detected
    
  2. 707-123-4567890

    Listing 29 output
    707
    123
    4567
    
Using Throw

707-123-4567890 is clearly too long, but our program will not currently generate an exception as any additional numbers are simply ignored.

  1. Modify parse in PhoneNumberParser.java so that it throws an IllegalArgumentException when the phone number is too long. Don’t forget to update the Javadoc comment!

    Sample Solution
    Listing 30 In PhoneNumberParser.java
    /**
     * Parse the supplied phone number ({@code phoneNumber}) into its constituent parts. The phone
     * number is eepected to be of the form {@code ###-###-####} where each {@code #} is a
     * single digit (i.e., a nonnegative integer between {@code 0} and {@code 9}).
     *
     * @param phoneNumber The phone number to parse.
     *
     * @throws IllegalArgumentException if the provided number is too long.
     */
    public static void parse(String phoneNumber) {
    
        if (phoneNumber.length() > 12) {
            throw new IllegalArgumentException("Phone Number too long");
        } // if
    
        // attempt to parse the "area code" portion
        String areaCodeString = phoneNumber.substring(0, 3);
        int areaCode = Integer.parseInt(areaCodeString);
    
        // attempt to parse the "prefix" portion
        String prefixString = phoneNumber.substring(4, 7);
        int prefix = Integer.parseInt(prefixString);
    
        // attempt to parse the "line number" portion
        String lineNumberString = phoneNumber.substring(8, 12);
        int lineNumber = Integer.parseInt(lineNumberString);
    
        // display the constituent parts or portions
        System.out.printf("Area code: %d\n", areaCode);
        System.out.printf("Prefix: %d\n", prefix);
        System.out.printf("Line Number: %d\n", lineNumber);
    
    } // parse
    
  2. Catch the IllegalArgumentException in promptAndParse and print the message from the exception object.

    Sample Solution
    Listing 31 In Driver.java
    private static void promptAndParse(int maxAttempts) {
    
        int attempts = 0;
        boolean valid = false;
    
        Scanner input = new Scanner(System.in); // ---- (1)
    
        while (!valid && (attempts < maxAttempts)) {
            try {
                attempts += 1; // --------------------- (2)
                System.out.print("Please enter your phone number in ###-###-#### format: ");
                String number = input.nextLine();
                PhoneNumberParser.parse(number);
                valid = true; // ---------------------- (3)
            } catch (NumberFormatException nfe) {
                System.err.print("Non-numeric character detected: ");
                System.err.println(nfe.getMessage());
            } catch (StringIndexOutOfBoundsException sioobe) {
                System.err.print("Phone number too short: ");
                System.err.println(sioobe.getMessage());
            } catch (IllegalArgumentException iae) {
                System.err.println(iae.getMessage());
            } // try
    
        } // while
    
        if (!valid) {
            System.err.print("maxAttempts has been exceeded: ");
            System.err.println(maxAttempts);
        } // if
    
    } // promptAndParse
    

Example 2: MyCat (Checked Exceptions)

Example 2: MyCat (Checked Exceptions)

Note

To show the solution for this activity, you will need to toggle “Show Solutions” above.

Prerequisite Information

This practice activity should be done on Odin, so make to complete all of the tutorials in the Unix chapter before attempting it and that you followed the steps in that chapter to activate the CSCI 1302 shell profile on your Odin account. This practice activity also assumes that readers have worked through the material in the Exceptions chapter prior to starting the activity.

Note

This activity is intended to be presented to and/or in collaboration with students during class. It serves as the foundation for a collection of follow-up activities that each begin with code that is effectively a sample solution for this activity.

Step 0: Quick introduction to the cat command

The Unix cat command reads files sequentially, writing their contents to the terminal (standard output). The file operands are processed in command-line order.

After completing this activity, you will have your own version of cat called MyCat that supports reading a single file. Adding support for multiple files will be done in a follow-up activity.

Step 1: Download Starter Code for MyCat (Instructor)

Execute the command below to download and extract the files:

sh -c "$(curl -fsSL https://cs1302book.com/_bundle/cs1302-cat.sh)"
- downloading cs1302-cat bundle...
- verifying integrity of downloaded files using sha256sum...
- extracting downloaded archive...
- removing intermediate files...
subdirectory cs1302-cat successfully created
Step 1: Download Starter Code for MyCat (Students)
cat1302
- downloading cs1302-cat bundle...
- verifying integrity of downloaded files using sha256sum...
- extracting downloaded archive...
- removing intermediate files...
subdirectory cs1302-cat successfully created

Change to the cs1302-cat directory that was created using cd, then look at the files that were bundled as part of the starter code using tree. You should see output similar to below:

.
├── etc
│   ├── ABC.txt
│   └── hello.txt
└── src
    └── cs1302
    └── cat
        ├── MyCat.java
        └── Printer.java
Step 2: Identify Dependencies
  1. Open MyCat.java and Printer.java for editing at the same time using the emacs command:

    Listing 32 Open both files at once
    emacs src/cs1302/cat/MyCat.java src/cs1302/cat/Printer.java
    

    Each file will be opened in their own Emacs buffer.

  2. Use C-x o to switch between the buffers, as needed.

    Note: Saving one buffer does NOT automatically save other buffers. To save your edits, type C-x C-s in each buffer.

  3. If you minimize Emacs using C-z, then remember to bring it back using the fg command instead of rerunning the emacs command. Otherwise, you will open mutliple instances of Emacs, which may lead to issues with multiple versions of the file.

  4. When you are done, you can exit Emacs by typing C-x C-c.

  5. On your exit ticket, write the order in which they should be compiled. Explain the order you chose.

    Sample Solution

    Printer.java needs to be compiled first because MyCat.java depends on (calls) the printFileLines method in Printer.

Step 3: Attempt to compile the starter code

Here is a quick summary of the classes in the starter code:

Fully-Qualified Name

Dependencies

Description

cs1302.cat.Printer

Utility class for printing files.

cs1302.cat.MyCat

cs1302.cat.Printer

Main class for the MyCat program.

  1. Attempt to compile the starter code. Based on this information, use javac to compile each .java file into a directory named bin, adjusting the class path as needed to take into consideration each file’s dependencies. Even if your commands are correct, one of the files will not compile due to an error – that is expected for now.

    Sample Solution
    javac -d bin src/cs1302/cat/Printer.java
    
    javac -d bin -cp bin src/cs1302/cat/MyCat.java
    
    src/cs1302/cat/MyCat.java:27: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
    Printer.printFileLines(file);
                          ^
    1 error
    
  2. Understand the compile-time error.

    src/cs1302/cat/MyCat.java:27: error: unreported exception FileNotFoundException;
        must be caught or declared to be thrown
    Printer.printFileLines(file);
    
    What it Means

    The error message emitted by the compiler mentions FileNotFoundException; however, this compile-time error message looks different from the runtime error messages we are used to seeing about exceptions (i.e., the ones that include a stack trace). It states that the exception “must be caught or declared to be thrown,” which means that FileNotFoundException is a checked exception and that the compiler found that we did not properly handle it in our code.

  3. Decide how to fix the compile-time error. Since FileNotFoundException is a checked exception that is not currently being handling in our code, we must determine which of the two fixes mentioned by the compiler is most appropriate in this particular situation:

    • “must be caught”

      To perform this fix, the code surrounding line 27 in MyCat.java must be adjusted so that the statement that is currently on line 27 is placed in a try block that includes a corresponding catch block for FileNotFoundException.

    • “must be […] declared to be thrown”

      To perform this fix, the code surrounding line 27 is left as is, however the signature of the method containing line 27 is adjusted to declare that it throws FileNotFoundException.

    Sample Solution

    “must be caught” is the better choice in this situation. Line 27 is in the program’s main method, and letting exceptions propagate outside of main is always discouraged since that will crash the program at runtime. If the method was not the main method, then using throws to explicitly propagate the exception can be acceptable so long as the programmer is okay with calling methods handling the exception themselves.

Step 4: Resolve the compile-time error
  1. Based on what we now know about the situation, use emacs to edit the code in MyCat.java to fix/resolve the compile-time error.

    Sample Solution
    19public static void main(String[] args) {
    20
    21    String filename = args[0];
    22
    23    try {
    24        File file = new File(filename);
    25        Printer.printFileLines(file);
    26    } catch (FileNotFoundException fileNotFound) {
    27        System.err.println("Unable to print: " + filename);
    28        System.err.println(fileNotFound.getMessage());
    29    } // try
    30
    31} // main
    

    The sample solution does not address an issue that can occur when no command-line arguments are supplied. This is intentional.

    Students might be tempted to handle that other issue by catching the ArrayIndexOutOfBoundsException that can be thrown when args.length == 0 and args[0] is evaluated; however, the program is supposed to default to reading from standard input in such cases, so a better solution is to use an if statement similar to the following near the top of the main method:

    if (args.length == 0) {
        args = new String[] { "-" };
    } // if
    
  2. Now that you have resolved the compile-time error, attempt to compile the starter code again (under the same assumptions mentioned earlier).

    Sample Solution
    javac -d bin src/cs1302/cat/Printer.java
    
    javac -d bin -cp bin src/cs1302/cat/MyCat.java
    
Step 5: Run MyCat and compare it to cat
  1. Use java to run cs1302.cat.MyCat twice, once for each of the two files in the etc. After that, use the cat command that comes with Unix to display the same files (and standard input).

    Important

    Your MyCat program should produce the same output as cat in both scenarios.

    Sample Solution
    java -cp bin cs1302.cat.MyCat etc/ABC.txt
    
        _    ____   ____
       / \  | __ ) / ___|
      / _ \ |  _ \| |
     / ___ \| |_) | |___
    /_/   \_\____/ \____|
    
    java -cp bin cs1302.cat.MyCat etc/hello.txt
    
    hhhhhhh                                lllllll lllllll                   !!!
    h:::::h                                l:::::l l:::::l                  !!:!!
    h:::::h                                l:::::l l:::::l                  !:::!
    h:::::h                                l:::::l l:::::l                  !:::!
     h::::h hhhhh           eeeeeeeeeeee    l::::l  l::::l    ooooooooooo   !:::!
     h::::hh:::::hhh      ee::::::::::::ee  l::::l  l::::l  oo:::::::::::oo !:::!
     h::::::::::::::hh   e::::::eeeee:::::eel::::l  l::::l o:::::::::::::::o!:::!
     h:::::::hhh::::::h e::::::e     e:::::el::::l  l::::l o:::::ooooo:::::o!:::!
     h::::::h   h::::::he:::::::eeeee::::::el::::l  l::::l o::::o     o::::o!:::!
     h:::::h     h:::::he:::::::::::::::::e l::::l  l::::l o::::o     o::::o!:::!
     h:::::h     h:::::he::::::eeeeeeeeeee  l::::l  l::::l o::::o     o::::o!!:!!
     h:::::h     h:::::he:::::::e           l::::l  l::::l o::::o     o::::o !!!
     h:::::h     h:::::he::::::::e         l::::::ll::::::lo:::::ooooo:::::o
     h:::::h     h:::::h e::::::::eeeeeeee l::::::ll::::::lo:::::::::::::::o !!!
     h:::::h     h:::::h  ee:::::::::::::e l::::::ll::::::l oo:::::::::::oo !!:!!
     hhhhhhh     hhhhhhh    eeeeeeeeeeeeee llllllllllllllll   ooooooooooo    !!!
    
    cat etc/ABC.txt
    
        _    ____   ____
       / \  | __ ) / ___|
      / _ \ |  _ \| |
     / ___ \| |_) | |___
    /_/   \_\____/ \____|
    
    cat etc/hello.txt
    
    hhhhhhh                                lllllll lllllll                   !!!
    h:::::h                                l:::::l l:::::l                  !!:!!
    h:::::h                                l:::::l l:::::l                  !:::!
    h:::::h                                l:::::l l:::::l                  !:::!
     h::::h hhhhh           eeeeeeeeeeee    l::::l  l::::l    ooooooooooo   !:::!
     h::::hh:::::hhh      ee::::::::::::ee  l::::l  l::::l  oo:::::::::::oo !:::!
     h::::::::::::::hh   e::::::eeeee:::::eel::::l  l::::l o:::::::::::::::o!:::!
     h:::::::hhh::::::h e::::::e     e:::::el::::l  l::::l o:::::ooooo:::::o!:::!
     h::::::h   h::::::he:::::::eeeee::::::el::::l  l::::l o::::o     o::::o!:::!
     h:::::h     h:::::he:::::::::::::::::e l::::l  l::::l o::::o     o::::o!:::!
     h:::::h     h:::::he::::::eeeeeeeeeee  l::::l  l::::l o::::o     o::::o!!:!!
     h:::::h     h:::::he:::::::e           l::::l  l::::l o::::o     o::::o !!!
     h:::::h     h:::::he::::::::e         l::::::ll::::::lo:::::ooooo:::::o
     h:::::h     h:::::h e::::::::eeeeeeee l::::::ll::::::lo:::::::::::::::o !!!
     h:::::h     h:::::h  ee:::::::::::::e l::::::ll::::::l oo:::::::::::oo !!:!!
     hhhhhhh     hhhhhhh    eeeeeeeeeeeeee llllllllllllllll   ooooooooooo    !!!
    

Choose Your Own Adventure

(After Steps 0-5 Above)

Adventure 1: Enhance MyCat
  1. During this adventure, you are tasked with implementing and documenting a new feature to enhance MyCat. The version of MyCat that is included in the starter code only accepts one command-line argument; however, the version of cat that comes with Unix supports zero or more command-line arguments.

    Example Outputs
    java -cp bin cs1302.cat.MyCat
    
    #### hello
    hello
    #### world
    world
    #### C-d
    
    java -cp bin cs1302.cat.MyCat etc/ABC.txt
    
        _    ____   ____
       / \  | __ ) / ___|
      / _ \ |  _ \| |
     / ___ \| |_) | |___
    /_/   \_\____/ \____|
    
    java -cp bin cs1302.cat.MyCat etc/ABC.txt etc/ABC.txt
    
        _    ____   ____
       / \  | __ ) / ___|
      / _ \ |  _ \| |
     / ___ \| |_) | |___
    /_/   \_\____/ \____|
    
        _    ____   ____
       / \  | __ ) / ___|
      / _ \ |  _ \| |
     / ___ \| |_) | |___
    /_/   \_\____/ \____|
    
    java -cp bin cs1302.cat.MyCat etc/hello.txt etc/ABC.txt
    
    hhhhhhh                                lllllll lllllll                   !!!
    h:::::h                                l:::::l l:::::l                  !!:!!
    h:::::h                                l:::::l l:::::l                  !:::!
    h:::::h                                l:::::l l:::::l                  !:::!
     h::::h hhhhh           eeeeeeeeeeee    l::::l  l::::l    ooooooooooo   !:::!
     h::::hh:::::hhh      ee::::::::::::ee  l::::l  l::::l  oo:::::::::::oo !:::!
     h::::::::::::::hh   e::::::eeeee:::::eel::::l  l::::l o:::::::::::::::o!:::!
     h:::::::hhh::::::h e::::::e     e:::::el::::l  l::::l o:::::ooooo:::::o!:::!
     h::::::h   h::::::he:::::::eeeee::::::el::::l  l::::l o::::o     o::::o!:::!
     h:::::h     h:::::he:::::::::::::::::e l::::l  l::::l o::::o     o::::o!:::!
     h:::::h     h:::::he::::::eeeeeeeeeee  l::::l  l::::l o::::o     o::::o!!:!!
     h:::::h     h:::::he:::::::e           l::::l  l::::l o::::o     o::::o !!!
     h:::::h     h:::::he::::::::e         l::::::ll::::::lo:::::ooooo:::::o
     h:::::h     h:::::h e::::::::eeeeeeee l::::::ll::::::lo:::::::::::::::o !!!
     h:::::h     h:::::h  ee:::::::::::::e l::::::ll::::::l oo:::::::::::oo !!:!!
     hhhhhhh     hhhhhhh    eeeeeeeeeeeeee llllllllllllllll   ooooooooooo    !!!
    
        _    ____   ____
       / \  | __ ) / ___|
      / _ \ |  _ \| |
     / ___ \| |_) | |___
    /_/   \_\____/ \____|
    
  2. To progress with this adventure, modify the main method in MyCat.java so that it works as expected when multiple command-line arguments provided by the user. To test your program, compare what it to what cat does when run with the same command-line arguments. Remember to also update the Javadoc comments in your program so that they correctly communicate any new behavior (e.g., the Javadoc comment for the main method).

    Sample Solution
    /**
     * Entry point for the application. Multiple filenames
     * can be passed in as command-line arguments. The
     * program should print the contents of all given files
     * to standard output.
     *
     * @param args  the command-line arguments
     */
     public static void main(String[] args) {
    
         String filename = args[0];
    
         File file = new File(filename);
         Printer.printFileLines(file);
    
         for (int i = 0; i < args.length; i++) {
    
             String filename = args[i];
    
             try {
                 File file = new File(filename);
                 Printer.printFileLines(file);
             } catch (FileNotFoundException fileNotFound) {
                 System.err.printf("Unable to print file %d: %s\n", i, filename);
                 System.err.println(fileNotFound.getMessage());
             } // try
          } // for
    
      } // main
    
  3. To complete this adventure, make sure that you can compile each .java file individually using javac and that you can run cs1302.cat.MyCat using java.

Adventure 2: Package Practice

Here is a quick summary of the classes in the starter code:

Fully-Qualified Name

Dependencies

Description

cs1302.cat.Printer

Utility class for printing files.

cs1302.cat.MyCat

cs1302.cat.Printer

Main class for the MyCat program.

  1. During this adventure, you are tasked with adjusting the file and code to realize the FQN change(s) described below:

    Before

    After

    cs1302.cat.Printer

    cs1302.io.Printer

    cs1302.cat.MyCat

    cs1302.cat.MyCat

  2. To complete this adventure, make sure that you can compile each .java file individually under the new FQN assumptions and that you can run MyCat using java to view a couple providing multiple files (and test it with standard input). We recommend that you delete your bin directory so that you do not accidentally use old versions when compiling and running.

Adventure 3: Code Style Practice
  1. During this adventure, you are tasked with making sure the code passes all of the course’s code style guidelines.

  2. To complete this adventure, make sure that you can compile each .java file individually, then use check1302 to discover code style issues and emacs to resolve those issues without changing the program’s behavior. After you edit the code, be sure to double check that the code still compiles and runs as expected Even minor edits can introduce errors, so it’s always best to do this double checking.

Adventure 4: API Documentation Practice
  1. During this adventure, you are tasked with generating and hosting the API documentation website for MyCat so that users connected to the VPN can access it by accessing your Webwork URL with their web browser (at /~myid/cs1302-cat-api where myid is your Odin username).

  2. To complete this adventure, generate the files for the API documentation website using the javadoc command, then use the :command`ln` command to create a symbolic link to those files under your ~/public_html directory so that you can access them using your web browser.

    If you do not remember how to generate and/or host a Javadoc-based API documentation website, then please refer to following sections in the Javadoc and API Documentation chapter before continuing:

Adventure 5: Interpreter Script Practice

Create an interpreter script to compile both files and run the program with multiple command line arguments.

Sample Solution
#!/bin/bash

javac -d bin src/cs1302/cat/Printer.java
javac -d bin -cp bin src/cs1302/cat/MyCat.java

java -cp bin cs1302.cat.MyCat etc/ABC.txt
java -cp bin cs1302.cat.MyCat etc/hello.txt