HW3: Interfaces and Inheritance

Due Thursday, September 25th at 11:59 PM
Homework developed by Paul Hilfinger, with operational tweaks from Josh Hug

Table of Contents

Introduction

This homework is intended to reinforce various OOP concepts. You won't have to write much code, and it's unlikely you'll have to do much debugging. However, the problems will be pretty tricky. It's probably very easy to stall out on this assignment; make efficient use of your time if you're stuck by working with other students or seeking help.

Part 1: TrReader

Throughout this problem, you'll want to refer to the official documentation for the standard abstract class java.io.Reader.

A reader is an interface to "types of object that have a 'read' operation defined on them." The idea is that each time you read from a Reader, it gives you the next character (or characters) from some source; just what source depends on what subtype of Reader you have. As we saw in lab 3 with List, a program defined to take a Reader as a parameter doesn't have to know what specific subtype of Reader its getting; it just reads from it.

Create a class that extends Reader, and provides a new kind of Reader, a TrReader, that translates the characters from another Reader. That is, a TrReader's source of characters is some other Reader, which was given to the TrReader's constructor. The TrReader's read routine simply passes on this other Reader's characters, after first translating them.

public class TrReader extends Reader {
    /** A new TrReader that produces the stream of characters produced
     *  by STR, converting all characters that occur in FROM to the
     *  corresponding characters in TO.  That is, change occurrences of
     *  FROM.charAt(0) to TO.charAt(0), etc., leaving other characters
     *  unchanged.  FROM and TO must have the same length. */
    public TrReader(Reader str, String from, String to) { 
       // FILL IN
    }

    // FILL IN
}

For example, the following short program would read characters would read characters from System.in and print them back out as they come in.

  public static void main(String[] args) throws IOException {
    Reader in = new InputStreamReader (System.in);
    while (true) {
       int c = in.read();
       if (c == -1) {
          break;
       }
       System.out.print ((char) c);
    }
  }

By contrast, in the follow program, your class TrReader will act as an intermediary, converting all lower case a, b, c, and d into upper case A, B, C, and D, respectively.

  public static void main(String[] args) throws IOException {
    Reader in = new InputStreamReader (System.in);
    TrReader translation = new TrReader(in, "abcd", "ABCD");
    while (true) {
       int c = translation.read();
       if (c == -1) {
          break;
       } 
       System.out.print((char) c);
    }
  }

What is System.in, you ask? This refers to the Unix standard input. The idea of standard input is an important Unix concept that you'll probably want to become familiar with at some point if you are not already. For our purposes in this assignment, standard input will read from what you type into the terminal when you run your program, although using the Unix redirection > and piping | operators, you can also take input from files.

Sept 22, 2014 (new): The program above will work even if you only implement the int read(char[], int, int) method of your reader. This is because the default implementation of int read() uses your read(char[], int, int)tt> method as you can see in the source code for the Reader class, similar to what we discussed in class.

As another example, in this third program (below), the TrReader has no work to do, and so the ultimate effect will be the same as if we had simply read from in directly (as we did in the first program):

Reader in = new InputStreamReader (System.in);
TrReader translation = new TrReader(in, "", "");
while (true) {
   int c = translation.read();
   if (c == -1) {
      break;
   } 
   System.out.print((char) c);
}

Hints and Clarifications

The read() method (which you are not required to implement) returns an int, but we're supposedly working with items of type char. The reason for this is that read() returns a -1 if there's nothing left to read, and -1 is not a valid char.

You should not create a new char[] array for the read(char[], int, int) method. Use the one that is given to you. Also keep in mind that all Readers (including the built in ones) must implement this particular version of read.

If you get an error that contains unreported exception IOException; when you're trying to make, what you're missing is throws IOException in one of your method declarations. We haven't learned what this means yet, so don't worry. Just pattern match.

Sept 25, 2014 (new) If you want to test your program, try running the TrReaderTest class, shown below.

import java.io.FileReader;
import java.io.Reader;
import java.io.IOException;
import java.util.Arrays;

/** Tests the TrReader class
 *  @author Josh Hug
 */

public class TrReaderTest {
    /** Tests the TrReader class
     */

    public static void main(String[] args) throws IOException {
        Reader r = new FileReader("TrReaderTest.java");
        System.out.print("This tests reads in some of the source code for ");
        System.out.print("this test. Then feeds it into TrReader. If it ");
        System.out.print("works, you should see the source for this test, ");
        System.out.println("but scrambled.");

        TrReader trR = new TrReader(r, "import jav.", "josh hug___");
        char[] cbuf = new char[250];

        /* Reads 150 characters from the buffer */
        trR.read(cbuf);
        System.out.println(new String(cbuf));
    }
}

Part 2: Translate

Using the TrReader class from problem #1, fill in the following function. You may use any number of new operations, one other (non-recursive) method call, and that's all. In addition to String, you are free to use any library classes whose names contain the word Reader (hint hint), but no others. See the template file Translate.java. Feel free to include unit tests of your translate method.

Again, you should refer to the Reader interface documentation.

/** The String S, but with all characters that occur in FROM changed
 *  to the corresponding characters in TO. FROM and TO must have the 
 *  same length. */
static String translate(String S, String from, String to)
{
    // NOTE: This try {...} catch is a technicality to keep Java happy.
    try { 
    // FILL IN
    } catch (IOException e) { return null; }
}

Part 3: OOP Gymnastics

Fill in the Java classes below to agree with the comments. However, do not use any if, switch, while, for, do, or try statements, and do not use the `?:' operator in ANY code that you write for this problem. All fields (a.k.a. instance variables) in the WeirdList class must be private. The methods in WeirdListClient should not use recursion. DO NOT FIGHT THE PROBLEM STATEMENT!

All of these seemingly arbitrary restrictions have been imposed in an effort to direct you into a solution that illustrates object-oriented features. You are going to have to think, but the answers are quite short. I expect these to be extremely challenging. There are some pretty intense aha! moments with this problem.

Part 3a: WeirdList Basics

In part 3a, you'll implement a WeirdList. A WeirdList is sort of like an IntList, except that it has to be implemented without using if, switch, while, for, do, try, or the ?: operator. You may not add any additional methods to WeirdList.

It also supports one particularly strange operation called map, which takes an interface (topic covered 9/19) as an argument. If you look at the interface definition, you'll see that a class that implements this interface needs supply only a single method int apply(int x). The map

public class WeirdList {
    /** The empty sequence of integers. */
    public static final WeirdList EMPTY =
        null;  // REPLACE THIS LINE WITH THE RIGHT ANSWER.

    /** A new WeirdList whose head is HEAD and tail is TAIL. */
    public WeirdList(int head, WeirdList tail) { /* FILL IN */ }

    /** Returns the number of elements in the sequence that
     *  starts with THIS. */
    public int length() {
        return 0;  // REPLACE THIS LINE WITH THE RIGHT ANSWER.
    }

    /** Print the contents of THIS WeirdList on the standard output
     *  (on one line, each followed by a blank).  Does not print
     *  an end-of-line. */
    public void print() { /* FILL IN */ }

    /** Part 3b: Apply FUNC.apply to every element of THIS WeirdList in
     *  sequence, and return a WeirdList of the resulting values. */
    public WeirdList map(IntUnaryFunction func) {
        return null;  // REPLACE THIS LINE WITH THE RIGHT ANSWER.
    }
}

Part 3b: Fancier WeirdList Tricks

Implement the map function for WeirdList and the add function for WeirdListClient (described below and in the skeleton).

/** Functions to increment and sum the elements of a WeirdList. */
class WeirdListClient {
    /** Part 3b. Returns the result of adding N to each element of L. */
    static WeirdList add(WeirdList L, int n) {
        return null; // REPLACE THIS LINE WITH THE RIGHT ANSWER.
    }

    /** Part 3c. Optional problem: Returns the sum of the elements in L */
    static int sum(WeirdList L) { 
        return 0; // REPLACE THIS LINE WITH THE RIGHT ANSWER.
    }
}

Part 3c: One Last Leap (optional)

Complete the implementation of the sum method of WeirdListClient. This is optional, but it's cool. Try to figure it out.