Homework 3: OOP, Interfaces and Inheritance

A. Introduction

As usual, you can obtain the skeleton with:

$ git fetch shared
$ git merge shared/hw3 -m "Start hw3"
$ git push

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.

B. TrReader

The online documentation for the abstract class java.io.Reader can be found here. It is used as a general template to define types of objects that have a read operation. 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 that takes in a Reader as a parameter doesn't need to know the subtype of the Reader it is reading from; it simply reads from it.

Create a new TrReader class that extends the abstract Reader class and translates the characters from another Reader. That is, a TrReader's source of characters is some other Reader, which is passed into the TrReader's constructor. The translation of the characters given by the Reader object will be translated in the read routine of the TrReader class.

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 ANY MISSING ABSTRACT METHODS HERE
}

For example, we can define Reader r = new InputStreamReader(System.in); which causes r 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 the following code snippet will simply copy the standard input to the standard output (i.e., by default, text that shows up on your terminal):

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

Note that -1 is used to denote the end of the stream because it not a valid character. The notes at the bottom of the spec explain more, if you're curious. Printing to standard output is just printing as you normally do. After you implement your translating reader, the following snippet of code will copy the standard input to standard output, but capitalize all occurrences of the letters a--d. For example, if the input was "bad data structure", the correctly translated output should be "BAD DAtA struCture".

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

If we define a TrReader that does not do any perform any translation at all, then noTranslate.read() has the same effect as someReader.read(). As a result, the contents of someReader are copied to standard output with no translation:

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

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

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 the int read(char[], int, int) method (as can be seen in the source code for the Reader class).

Notes

C. Applying TrReader

Implement the translate function in Translate.java using the TrReader class that you defined in part B. You may use any number of new operations, one other (non-recursive) method call, and nothing else. In addition to StringReader, you are free to use any library classes whose names end with Reader (check the online documentation), but no others. Feel free to create unit tests for your translate method to ensure correctness.

D. WeirdList

Conceptual introduction here

Complete WeirdList.java and WeirdListClient.java. Do not use any if, switch, while, for, do, or try statements, and do not use the ?: conditional operator. The WeirdList class may contain only private fields (except for the public static final WeirdList EMPTY that is already provided in the skeleton code). The methods in WeirdListClient should not use recursion.

DO NOT FIGHT THE PROBLEM STATEMENT! We really do mean to impose all these restrictions in an effort to direct you towards a solution that illustrates object-oriented features. You are going to have to take some time to think, but the solutions themselves are quite short. The skeleton provides some cursory tests. You may refer to IntUnaryFunction.java, but do not modify this file.

HINT: Feel free to create additional classes to use. See IntUnaryFunction.java.

E. Submission

The files you will be turning are:

DO NOT FORGET to add and commit all the files you created as a part of your solution for section D.

Don't forget to push both your commits and tags for your final submission. As a reminder, you can push your tags by running:

$ git push --tags