Homework 3: OOP, Interfaces and Inheritance

A. 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. Make efficient use of your time if you're stuck by working with other students or seeking help.

As usual, you can obtain the skeleton with

$ git fetch shared
$ git merge shared/hw3
$ git push

B. TrReader

The standard abstract class java.io.Reader is described in the on-line documentation. It is a general 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. A program defined to take a Reader as a parameter doesn't have to know what subtype 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, we can define

Reader in = new InputStreamReader(System.in);

which causes in to point to a Reader whose source of characters is the standard input (i.e., by default, what you type on your terminal, although you can make it come from a file if desired). This means that

while (true) {
   int c = in.read();
   if (c == -1) {
   System.out.print ((char) c);

would simply copy the standard input to the standard output.

However, if we write

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

then we will copy the standard input to the standard output after first capitalizing all occurrences of the letters a--d.

If we have defined

/** A TrReader that does no translation. */
TrReader noTrans = new TrReader(someReader, "", "");

then a call such as noTrans.read() simply has the same effect as someReader.read(), and

while (true) {
   int c = notrans.read();
   if (c == -1) {
   System.out.print((char) c);

just copies the contents of someReader to the standard output, just as if we substituted someReader.read() for notrans.read().

By the way, 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) method, as you can see in the source code for the Reader class.


The TrReaderTest contains a smidgen of testing for your convenience--nothing thorough, however.

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 (just before the opening {). We haven't learned what this means yet, so don't worry (or read Chapter 11 of HFJ) and just do it.

C. Applying TrReader

Using the TrReader class from part B, 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 (check the on-line documentation), but no others. See the template file Translate.java. Feel free to include unit tests of your translate method.

/** 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; }

D. WeirdList

Fill in the Java classes on the next page to agree with the comments. However, do not use any if, switch, while, for, do, or try statements, and do not use the ?: operator. The WeirdList class may contain only private fields. The methods in WeirdListClient should not use recursion.

DO NOT FIGHT THE PROBLEM STATEMENT! I really meant to impose all the restrictions I did 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. See the skeleton templates in WeirdList.java, IntUnaryFunction.java, and WeirdListClient.java. The skeleton also provides some cursory tests.

/** An IntUnaryFunction represents a function from
 *  integers to integers. */
public interface IntUnaryFunction {
    /** The result of applying this function to X. */
    int apply (int x);

/** A WeirdList holds a sequence of integers. */
public class WeirdList {
    /** The empty sequence of integers. */
    public static WeirdList EMPTY = // FILL IN;

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

    /** The number of elements in the sequence that
     *  starts with THIS. */
    public int length() { /* FILL IN */ }

    /** Apply func.apply to every element of THIS WeirdList in
     *  sequence, and return a WeirdList of the resulting values. */
    public WeirdList map(IntUnaryFunction x) { /* FILL IN */ }

    /** Return a string containing my contents as a sequence of numerals
     *  each preceded by a blank.  Thus, if my list contains
     *  5, 4, and 2, this returns " 5 4 2". */
    public String toString() { /* FILL IN */ }

    /** 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 */ }

    // You should NOT need any more methods here.


class WeirdListClient {
    /** Return the result of adding N to each element of L. */
    static WeirdList add(WeirdList L, int n) { /* FILL IN */ }

    /** Return the sum of the elements in L. */
    static int sum(WeirdList L) { /* FILL IN */ }