Comp 114, Fall 2005

Assignment 6: Shape Groups (Note: Two Early Submissions!)

Date Assigned: Tue Nov 15, 2005

Part 1 Completion Date for 5% Extra Credit: Tue Nov 22, 2005

Part 1-7 Completion Date for 5% Extra Credit: Tue Dec 6, 2005

Part 1-7 Completion Date: Fri Dec 9, 2005

Objectives

·  Observer Pattern

·  Assertions.

·  Visitor pattern.

·  Delegation

·  Factories

In this assignment you will you will augment the functionality of nested shape lists by allowing shapes in such lists to move together. To do so, you will use the observer pattern. This is the hardest part! Next, in an extra credit exercise, you will extend part 1 to allow shapes to move incrementally by implementing another observer pattern. Then you will add a simple assertion to the scanner. After that you will add a universal assertion, first without and then with the visitor pattern. Using the visitor pattern is also hard. You will also redo your scanner for nested shapes so that it uses delegation instead of inheritance. Finally, you will implement two factories used by ObjectEditor. This part is trivial, and is independent of the rest of the assignment and project – it serves only to illustrate the nature and power of factories.

In this assignment, you are not required to subtype previous code because of the limited time and the large number of interrelated classes involved. Thus, you can directly modify any of the previous classes (after copying them.)

To do this assignment, you will need to use the assertion library available at: http://www.cs.unc.edu/~dewan/comp114/f05/code. Feel free to modify the classes in the library if you wish to use Java assertions directly.

The project is due on the last day of classes in the recitation, but you will get 5%extra credit if you do it by Dec 6th. If you finish the first part by Nov 22, you will get an additional 5% extra credit. In the recitation of December 9th or TA hours before that, you will demonstrate this assignment to one of the TAs. Either bring a latptop or a floppy containing your code for the demonstration. As usual, you must also upload your files and submit screen shots. Parts other than 1 and 5 are relatively simple if you study the class material well.

Part 1: Shape Groups and Observer Pattern

In this part, you will implement the notion of a shape group whose elements move together. A shape group consists of all leaf level nodes (shapes) in the tree defined by a level 1 shape list. A level 1 shape list is a direct child of the whiteboard object. Figure 1 shows level 0, 1 and 2 lists. It also shows the shape group defined by one level 1 shape list (child 4 of the whiteboard). Lists at levels other than 1 do not define shape groups.

You must ensure that when you move a member of a shape to a new location, the positions of all other members of the shape group change by the same amount (Figure 2). In other words, when a setter method is called on a member of a shape group to change its X (Y) coordinate by deltaX (deltaY), you must call the setter methods of all other members of the group to change their X(Y) coordinate by deltaX(deltaY). This should be done using the observer pattern. Each member of a shape group should be made an observer of each of the other members of the shape group. When a shape group member changes, it notifies its observers of the change, which then make a corresponding change to their locations. You must carefully choose the arguments of the notification method to send the appropriate information.

To do this part, you will need to define additional interfaces and change existing classes and interfaces. In particular, you will have to write code to determine the leaf descendents of a shape list. It is OK if you compute observers of all observables each time a new shape is added to the tree. But make sure that an object does not get the same notification twice – that is, do not store the same observer twice in an observable. Use Vectors to store observers – otherwise you will not be able to do one of the assertion parts.

To get full credit, you must not use the instanceof operation – instead you should add appropriate methods to the common interface (ShapeNode) implemented by shapes and shape lists.

Figure 1 A Shape Group

.

(a) Dragging a shape group member (b) Other members of the group move by same amount

Figure 2 Moving a shape group

Part 2: Incrementally Dragging a Shape Group (Extra Credit)

As we see in Figure 2, changes to observer positions are made after the drag operation is completed and not during the drag operation. The reason is that ObjectEditor does not refresh the objects as they are dragged. In this part you will fix this limitation, that is, you will allow a whole shape to be dragged incrementally.

To do so, you must implement another example of the observer pattern. Specifically, you must use a standard interface for announcing property changes to the X and Y properties of a shape, which ObjectEditor uses and is described in the slides and class notes.

Your abstract shape class must import the following classes:

import java.beans.PropertyChangeListener;

import java.beans.PropertyChangeEvent;

The class must provide a method for registering property change listeners such as ObjectEditor:

public void addPropertyChangeListener(PropertyChangeListener l);

Finally, whenever a change occurs to the X or Y property, it must invoke on each property change listener, l, the following method:

l.propertyChange (propertyChangeEvent)

where propertyChangeEvent() can be created using the following constructor:

new PropertyChangeEvent(source, propertyName, oldValue, newValue);

source would be the shape changed, propertyName the property (“X” or “Y”) changed, and oldValue and newValue wrapper Integer objects storing the value of the property before and after the setter method is invoked. Once you have done this additional coding, you need to perform one more step to make ObjectEditor listen to these events. When running the program, execute the ViewàIncremental Refresh command (Figure 3).

Figure 3 Making ObjectEditor listen to property changes

Part 3: Simple assertion

Identify an invariant of the method in your scanner class that returns the next token. In your writeup, indicate if this is also a class invariant. Implement the invariant as precondition and postcondition assertions in your method. Introduce an error in your code (by commenting out or replacing code) that makes the invariant false. Create a screen shot showing the assertion fail when the error is introduced. In your write-up indicate what the error was.

Part 4: Assertion Without Assertion Library

Now implement the following invariants of the setter methods of the abstract shape class:

The relative offset between a shape and each of its observers remains constant

The relative offset between a shape, s, and its observer, o, is the pair (s.getX() – o.getX(), s,getY() – o.getY());

Introduce an error in one of the setter method to make the assertion fail, and demonstrate the failure in a screen shot (Figure 4).

Figure 4 Demonstration of failing assertion

Do this part without the assertion library – that will be easier than using the library. Use the class Vector to store offsets. Before you implement this part, in your write-up, indicate if it is a universal or existential assertion.

Part 5: Assertion with Visitor Pattern

Now do the previous part with the assertion library. The reason for making you use the library is to give you practice with the visitor pattern. This part is also hard.

Your visitor will be more complicated than the one presented in class in that it will have to take many more arguments in the constructor. These could include a shape, its observers, and the offsets of the observers. Think of using the Vector indexOf() method in the visit method.

Part 6: Delegation instead of inheritance

In assignment 5, you made your scanner for nested shape-lists a subclass of the scanner for flat shape-lists. Thus you used inheritance to reuse code from the previous scanner. Use delegation now to reuse code. Thus, rewrite the scanner for nested shape-lists so that the relationship between it and the scanner for flat shape-lists is HAS-A rather than IS-A.

Part 7: Factories

The user-interface ObjectEditor creates contains many panels – containers for other widgets. Some of these panels are created by graphics and other libraries used by ObjectEditor. The top (left and right) panels of the whiteboard (Figure 1) are examples of such panels. Other panels, such as the bottom panel of Figure 1, are created by it. However, it does not instantiate panel classes directly – instead it asks the registered panel factory to do so. The factory chooses the class to instantiate and initializes the panel if necessary. In this part, you will implement and register two such factories. The first factory will create a Swing panel, which is an instance of the class: javax.swing.JPanel. It will use the class java.awt.Cursor to set the cursor of the panel to the hand cursor:

panel.setCursor(new Cursor(Cursor.HAND_CURSOR));

The second factory is similar except that it instantiates the class java.awt.Panel and creates a Cursor.CROSSHAIR_CURSOR. Before returning the panel, should display its class (Figure 5). Verify that the correct cursor is created by mousing over the bottom panel.

Figure 5 Results of selecting different panel factories

Both factories should implement the interface bus.uigen.view.PanelFactory, which is given below

package bus.uigen.view;
import java.awt.Container;

public interface PanelFactory {

public Container createPanel ();

}

java.awt.Container is a super class of both javax.swing.JPanel and java.awt.Panel.

To register a factory, you must invoke the following method:

bus.uigen.view.PanelSelector. setPanelFactory (bus.uigen.view.PanelFactory panelFactory);

This method should be called in the main class. First call it to register the Swing panel factory and then call it to register the AWT panel factory. Changing between the two factories should require changing only the argument to this method.

Submission Instructions

Submit:

1.  A printout of all of the classes and interfaces you end up with after all parts.

2.  Screenshots to show all of the parts working.

3.  Write-ups for part 3 and 4.

4.  As always, upload the assignment directory to by midnight of the day the assignment is due and do not change the code after you submit it in class.

Also, as mentioned above, you must demonstrate in person the last assignment you want graded on the last day of classes or in TA hours before that.