Problem Set 4
Subtyping and Inheritance
Due: Monday, 11 October
(drop off at my office, Olsson 236A, before 5pm)

Collaboration Policy. For this problem set, you may either work alone and turn in a problem set with just your name on it, or work with one other student of your choice. If you work with a partner, you and your partner should turn in one assignment with both of your names on it.

Regardless of whether you work alone or with a partner, you are encouraged to discuss this assignment with other students in the class and ask and provide help in useful ways. You may consult any outside resources you wish including books, papers, web sites and people, with the important exception of materials from previous cs2220/cs205 courses. If you use resources other than the class materials, indicate what you used along with your answer.

Reading: Chapter 7 and Bertrand Meyer's Static Typing and Other Mysteries of Life.
Purpose In the first part of this assignment (questions 1-4), you will do some exercises that develop your understanding of type hierarchies and behavioral subtyping. In the second part (questions 5-11), you will enhance an image processing application using subtyping and inheritance.

Subtyping

Strangely enough, some workers in the field have been advocating a contravariant policy. Here it would mean that if we go for example to class RANKED_GIRL, where the result of roommate is naturally redefined to be of type RANKED_GIRL, we may for the argument of routine share use type GIRL, or rather scaringly, SKIER of the most general kind. One type that is never permitted in this case is RANKED_GIRL! Here is what, under various mathematical excuses, some professors have been promoting. No wonder teenage pregnancies are on the rise.

Bertrand Meyer's justification for violating the substitution principle from Static Typing and Other Mysteries of Life

Liskov's Chapter 7 and Meyer's Static Typing and Other Mysteries of Life describe two very different rules for subtypes.

Liskov's substitution principle requires that the subtype specification supports reasoning based on the supertype specification. When we reasoned about a call to a supertype method, we reasoned that if a callsite satisfies the preconditions in the requires clause, it can assume the state after the call satisfies the postconditions in the effects clause. This means the subtype replacement for the method cannot make the precondition stronger since then our reasoning about the callsite may no longer hold (that is, the callsite may not satisfy the stronger precondition). Hence, the type of the return value of the subtype method must be a subtype of the type of the return value for the supertype method; the types of the parameters of the subtype method must be supertypes of the types of the parameters of the supertype method. This is known as contravariant typing.

Bertrand Meyer prefers covariant typing: the subtype replacement method parameter types must be subtypes of the types of the parameters of the supertype method. We will generalize his rules to apply to preconditions and postconditions also: the subtype method preconditions must be stronger than the supertype method precondition (presub => presuper) and the subtype postconditions must be stronger than the supertype postconditions (postsub => postsuper). The => notation indicates logical implication: a => b means that knowing a is true is enough to guarantee that b must be true. Note that unlike the corresponding Liskov substitution rule, (presuper && postsub) => postsuper, there is no need for presuper in the covariant rule since postsub => postsuper.

Consider the minimal Tree class and its BinaryTree subtype, both specified below:

public class Tree
   // OVERVIEW: A Tree is a mutable tree where the nodes are int values.
   //   A typical Tree is < value, [ children ] > 
   //      where value is the int value of the root of the tree
   //       and children is a sequence of zero or more Tree objects
   //         that are the children of this tree node.
   //   A Tree may not contain cycles, and may not contain the same
   //   Tree object as a sub-tree in more than one place.

   public Tree (int val) 
	// EFFECTS: Creates a tree with value val and no children: < value, [] >

   public void addChild (Tree t) 
	// REQUIRES: t is not contained in this.
	// MODIFIES: this
	// EFFECTS: Adds t to the children of this, as the rightmost child:
	//   this_post = < this_pre.value, children > 
	//    where children = [ this_pre.children[0], this_pre.children[1], ..., 
	//                  this_pre.children[this_pre.children.length - 1], t]
	//   NOTE: the rep is exposed!

   public Tree getChild (int n)
	// REQUIRES: 0 <= n < children.length
	// EFFECTS: Returns the Tree that is the nth leftmost child 
	//   of this.
	//   NOTE: the rep is exposed!


public class BinaryTree extends Tree
   // OVERVIEW: A BinaryTree is a mutable tree where the nodes are int values
   //   and each node has zero, one or two children.
   //
   //   A typical BinaryTree is < value, [ children ] > 
   //      where value is the int value of the root of the tree
   //       and children is a sequence of zero, one or two BinaryTree objects
   //         that are the children of this tree node.
   //   A BinaryTree may not contain cycles, and may not contain the same
   //   BinaryTree object as a sub-tree in more than one place.

   public BinaryTree (int val)
	// EFFECTS: Creates a tree with value val and no children: 
	//             < value, null, null >

   @Override
   public void addChild (BinaryTree t)
	// REQUIRES: t is not contained in this and this has zero or one children.
	// MODIFIES: this
	// EFFECTS (same as supertype):
	//   Adds t to the children of this, as the rightmost child:
	//   this_post = < this_pre.value, children > 
	//     where children = [this_pre.children[0], this_pre.children[1], ..., 
	//                  this_pre.children[this_pre.children.length - 1], t]

   @Override
   public BinaryTree getChild (int n) 
	// REQUIRES: 0 <= n < 2 
	// EFFECTS: If this has at least n children, returns a copy of the BinaryTree 
	//   that is the nth leftmost child of this.  Otherwise, returns null.

The questions below concern the Liskov substitution principle and Eiffel subtyping rule. Although we are asking them in the context of a Java-like class definition, note that the actual subtyping rules for Java are not the same as either of these. Java does has a signature rule (described below), but doesn't place any constraints on the behavior of methods.

The Java signature rule follows the covariant rule for return types. (This is a relatively recent change to Java — before Java 1.5, the Java signature rule was novariant: the subtype methods return type must match exactly).

For parameters, the actual Java rule is more complex because of overloading. The Java compiler allows a subclass to define a method that has the same name but different parameter types (including subtype) from a method in the superclass, but instead of overriding the superclass method it will overload the inherited method. Thus, the way addChild is declared in the BinaryTree subtype would be allowed by Java, but would not actually override the Tree.addChild(Tree) method. Instead, it would overload that method. This can be quite dangerous since the overloaded methods are resolved based on apparent types, not actual types. For example, try this program:

static public void main (String args[]) {
  Tree t = new BinaryTree (3);
  BinaryTree bt = new BinaryTree (4);

  t.addChild (bt);                   // Calls the addChild(Tree) method
  bt.addChild (new BinaryTree (5));  // Calls the addChild (BinaryTree) method
  bt.addChild (new Tree (12));       // Calls the addChild (Tree) method
}
Note that the first call uses the inherited addChild(Tree) because the apparent type of t is Tree, even though its actual type is BinaryTree.

For these questions, you should assume a language that does not have overloading, but instead interprets the addChild method as overriding the supertype's method. (We use the @Override annotation to indicate this in the specification. The Java compiler would issue a warning for this, since the method does not actually override a superclass method. This is a good example of the value of using these annotations!)

1. Does the getChild method in BinaryTree satisfy the Liskov substitution principle? Explain why or why not.

2. Does the addChild method in BinaryTree satisfy the Liskov substitution principle? Explain why or why not.

3. Does the getChild method in BinaryTree satisfy the Eiffel subtyping rules? Explain why or why not.

4. Does the addChild method in BinaryTree satisfy the Eiffel subtyping rules? Explain why or why not.

ImageChop

For the second part of this assignment, you will extend an image processing application (somewhat similar to PhotoShop and Picasa, but a tad less sophisticated). ImageChop does have one feature not provided by Picasa, which is to support filters that can involve multiple images (PhotoShop does provide mechanisms for extending it with new image filters).

Download: ps4.zip — this file contains all the source code for ImageChop.
Create a new project ps4 in Eclipse, containing the extracted files from ps4.zip (you can use File | Import to add these files to your project). Create a Run configuration with the Main class as ps4.GUI.

To avoid running out of memory, we need to increase the maximum size of the Java VM heap. Do this by selecting the Arguments pane, and adding a VM argument (the bottom window in the pane):

-Xmx512m -ea
The first argument sets the maximum size of the VM heap to 512 megabytes. The second argument turns on assertion checking.

To turn on compiler warnings about missing @Overload annotations, select Project | Properties, then select Java Compiler and Errors/Warnings under it. Uncheck the Enable project specific settings box, and then select Configure Workspace Settings (at the top right). This pops up a dialog you can use to control the warnings given by the Java compiler. Select the Annotations (at the bottom) and for "Missing '@Override' annotation' select Warning.

ImageChop provides a graphical user interface (GUI) for manipulating a set of images by applying filters to generate new images. Every filter must be a subtype of the Filter datatype. The filters provided are shown in the class hierarchy below:

java.lang.Object
 subtypeps4.Filter
     subtypeps4.PointFilter
         subtypeps4.BrightenFilter
         subtypeps4.GreyscaleFilter
     subtypeps4.BlurFilter
     subtypeps4.FlipFilter
     subtypeps4.MultiFilter
         subtypeps4.AddFilter
         subtypeps4.TileFilter
The Filter class is a subtype of java.lang.Object, the ultimate supertype of all Java object types. The Filter class provides methods for examining and manipulating an image including the filter abstract method. Since filter is an abstract method, the Filter abstract class provides no implementation of filter. To make a useful Filter object, we need a subclass of Filter that provides an implementation of the filter method.

We provide two abstract subtypes of Filter:

For details on the provided subtypes, see the specifications in the provided code.
5. Develop a new filter that replaces each pixel in the image with its negative (that is, the red value of the new image is 255 - the red value of the old image, and similarly for blue and green). To work with the provided GUI code, you should add the name of your filter to the effects array (initialized at the top of the GUI class). The GUI will load the class named ps4.effectFilter for the name selected from the menu, so if you name your class NegativeFilter in the ps4 package, you should add "Negative" to the effects array.

For the next two questions, you are to implement new filters. The behavior of these filters is not specified precisely; you can determine in your implemention a good way to provide the effect described.

6. Develop a new filter that tints an image so the pixels near the top of the image (low row numbers) become gradually more blue.
7. Develop a new filter that produces an image that is the "average" of two or more images.

Behavioral Subtyping

8. How well does the provided Filter type hierarchy follow the behavioral subtyping rules? Your answer should consider the PointFilter and MultiFilter abstract classes and their important methods, as well as the other filter subclasses, explaining whether or not they satisfy the substitution principle.

Adding Parameters

Many of the provided filters would benefit from allowing the user to set additional options. For example, the BlurFilter has an integer field repeats that controls the number of repetitions of the fliter loop, and the TileFilter has an integer field tileSize that controls the size of the tile. It would be more useful if filter parameters could be set by the user. Your goal is to modify the design of the ImageChop implementation to support filters that take a parameter in the cleanest, simplest way possible.
9. Describe at least two substantially different approaches to supporting parameterized filters. You may assume that the filter parameter value is a single integer (although, it is, of course, better to support more general parameters including multiple parameters and parameters of different types). For each of your proposed approaches, draw the modified class hierarchy. Discuss the tradeoffs between the different designs — which design involves the most changes to the code? which design is easiest to implement?

10. Modify the application to support the parameterized filters. In addition to modifying the filter classes, you will need to modify the GUI to allow a user to enter the parameter for a parameterized filter. This will involve modifying the GUIHandler.actionPerformed method defined in GUI.java. Hint: look at how the MultiFilter is handled.
For question 10, you will need to create a new dialog box to obtain user input for the parameter value. The Swing tutorial, How to Make Dialogs provides some useful documentation and examples for this.

11. Develop a new filter of your choice, and use it (as well as the provided filter) to create an interesting image. Be creative!

Turn-in Checklist: You should turn in your answers to questions 1-11 on paper either in class on Thursday, 7 October, or at my office before 5pm on Monday, 11 October. Also, submit the image you produced for question 11 as a JPG and a zip file containing all of your code by email to evans@cs.virginia.edu. You should also post your image (more than one is fine) on the course blog to share with the class. There may be token prizes for the best images created.