Object Oriented Design Course

Object Oriented Design - Exercise 3

Design, Code, Test and Refactor Using Behavioral Design Patterns

In this exercise you will:

bullet

Design the first version of a visual interactive XML pretty-printer

bullet

Produce a set of UML class and sequence diagrams to document your design

bullet

Experience design patterns in actual Java code

bullet

Use existing unit tests to test changes to existing code

bullet Experiment with refactoring, assertions, and simple package design.
bullet

Use Swing to create a simple graphical user interface

The deadline for this exercise is Thursday, April 14th, 2005.

Requirementss

In this exercise you need to write an interactive program - a window-based program that presents HTML conversions of an XML file, while waiting for the user to enter commands and responding to the ones it knows. The program will be based on the XML pretty-printer you wrote in exercise 1, and enable the creation of multiple HTML views of an input XML file, each displaying different parts of the document in different ways.

Write an executable program called 'xmli' (or a main class Xmli in Java), which takes exactly two command-line arguments - a file name of an XML file, and the number of HTML views to show. The program starts by displaying a visual form, with a frame for each view, a text box for entering commands, and a results label for command outputs. It then waits for commands from the user. The input file must be a valid XML document, under the requirements of the xmlp program you developed in exercise 1. Each command is a line of text; the program responds to each command and then returns to wait for the next one.

The program must recognize the following commands:

  1. edit [output-name]
    This command sets the current view to the given view name, meaning that all subsequent select commands will refer to that view, until another 'edit' command is entered. The names of the output views are the numbers from 1 to maxviews, where maxviews is xmli's second command line argument; the initial view is 1.
    This command prints to the results label the line: "Now editing view [view-number]". 

  2. select [tag-name]
    This command selects the tags in the input XML document that should appear in the current edited view. The match is case-sensitive. A selected tag will appear in the current view, in HTML format, together with any attributes it may have. Selecting a text tag selects that tag and its text; selecting a tag that contains other tags selects the contained tags as well, including all their information except comments.
    The select command selects all tags with the given name anywhere in the XML document's hierarchy - not just the top level. If a child tag is selected, then its parent (and its parent recursively, if it is a child itself) is selected as well.  The examples below demonstrate this.
    This command prints to the screen the line: "Selected [count] tags", where [count] is the total number of selected tags in this command, including parents of child nodes and children of composite tags that were selected. Each tag (and particularly the root) should be counted once.
    Note that since you can only select tags, and only selected tags appear in outputs, then XML comments are never printed to the output in this exercise. Comments should not be displayed to the output in any case.
     
  3. upcase [tag-name]
    This command changes all existing views to display all tags with the given name in upper case in the HTML output. The tag name itself should be displayed in uppercase in the output; if it is a text tag, then the text should be in uppercase as well, but if it's a composite tag, then child tags should not be modified. Attributes are not affected by this command.
    This command only affects the views in the current state: if a tag is selected in a view (using select) only after an upcase command for that tag, then that tag should not be displayed in uppercase. The upcase command only affects tags that are already displayed.
    This command prints to the results label the names of the views that were affected by it, in this format: "Modified views: view1, view2, ...", where view1, view2 and so forth are the names of the views that were modified by this command. It is required that the time required to create this line will be proportional to the number of modified views, and not to the number of all views. 

  4. locase
    This command is similar to upcase, but causes the output text to be displayed in lower-case instead of upper-case letters. All other behavior and rules are exactly the same.

  5. undo
    Results in "undoing" the last command - all the above four commands are undoable. Undo can be called repeatedly to undo a series of commands. If nothing can be undone when undo is called, then "Nothing to undo" is printed to the results label. Otherwise, undo prints a line depending on the reversed command:
    When undoing edit: "Now editing view [view-number]" (of the current view after the undo).
    When undoing select: "Deselected [count] tags", where [count] is the number of tags that the reversed command selected.
    When undoing upcase or locase: "Modified views: view1, view2, ..." where the view numbers are the ones affected by the undo. Note that locase does not undo upcase or vice versa, since the original text may be mixed-case.

Exiting the program is by the using the X button at the upper-right corner of the window. Errors, such as requesting an unknown command or giving illegal parameters, should result in an informative error message on the results label, and a safe return to waiting for the next command.

There are no commands for printing the output - all views must show the selected tags, in HTML format and in mixed, lower or upper-case as requested, all the time. The XML parsing and conversion to HTML must be done using the code from xmlp; the simplest way to display the HTML on the screen is by using Swing's JEditorKit. The multiple views can be created by placing a JTable on the screen with one row and a column for each view; each cell in the JTable can contain a JEditorKit that displays that view.

In addition, there are two requirements regarding the performance of the program. This is because of the fact that although xmli is now an interactive program, in future versions is may be used as a server-side component, to which other computers will send requests (as strings in the above formats) over the network. The program may then have to handle thousands of views concurrently, and propagate changes to large groups of them (through the upcase and locase commands). To support this, the following is required:

  1. Each view must consume the minimal necessary amount of memory. In particular, you are not allowed to copy the selected tags of each output into its own XML document object. That would be a waste of memory.

  2. When displaying the names of modified views after executing or undoing an upcase or locase command, the time required to compute this list of views must be proportional to the number of affected views , and not to the total number of views. Looping over all views is too slow.

Design

In this exercise, you do not start from scratch but instead rely on the existing code base of exercise 1. You are required to use that code for the XML parsing and HTML creation - future changes and features done by the xmlp developers must be easy to incorporate in the xmli program, so these two programs have to share common code. You may change the code of exercise 1 for this exercise if you have to, but under one condition: the unit tests of exercise 1 are maintained as well. You should submit a set of passing unit tests for xmlp together with this exercise. If you add functionality inside exercise 1 classes, you must add to its unit tests as well to test the additions.

As with the xmlp program, this is only the first version of xmli , and it is crucial to maintain an open mind with respect to possible future requirements. Your design must specifically prepare for these additions:

  1. It may be required to support more sophisticated ways of selecting which tags are displayed: selecting parts of composite tags, selecting comments, or selecting based on criteria other than the tag name such as the number of child tags, length of text tags, existence of an attribute, or a combination of such conditions.
  2. It may be required to support more modifications on selected tags than just turning text to uppercase or lowercase. For example, new commands can be added to translate tags to another language, sort the output based on tag names or text, hide attributes and so forth. Just like upcase and locase, these commands will be applied to a group of views, but should not affect views that will select these tags later.

  3. It may be required to dynamically change the output format of each view. Since xmlp can be extended to create different HTML outputs, text outputs and so forth, it should be easy to add a command to xmli that dynamically changes the style of the current view to any one of the styles that xmlp will support.

You must design your program so that it is easy to add code that implements the above requirements. For each of the above requirements write an explanation in your README file, not more than three sentences long, which explains how it should be coded. An in exercise 1, three sentences are enough if your refer to known design patterns, which are easily supported by your design.

Code & Unit Test

This exercise intends you to divide your time equally between actual coding and between design, writing UML diagrams, and answering the questions. Code in Java, and use the standard libraries to their full extent - including Swing, and excluding existing XML parsers as was in ex1.

It is also required that you submit unit tests to test your work. Organize your unit tests into classes by subject, and write a method for each small test. Each test should be self-validating - that is, know by itself whether it has passed or failed. Writing unit tests should be an integral part of coding, and is essential when code must be changed in newer versions. You are required to use JUnit, and to submit unit tests for both xmlp and xmli code.

An ant build file must be submitted with this exercise in order to build it. Its default task must perform a full build: compile all files of both xmlp and xmli, and run all unit tests for both applications. The code you submit must be built with no compiler warnings, and pass all unit tests.

Coding & unit testing in this exercise include experimentation with three issues that were not considered in ex1: refactoring, assertions and package design. To explain how you used these issues in your code, answer the following three questions in your README file (these three questions together with the three design questions replace the six design questions from ex1). Each answer should be up to one paragraph long. Here are the questions, with some guidelines about how to deal with each issue:

  1. Refactoring. Describe and name at least two refactorings that you made on your code (preferably xmlp code), to make it more suitable for this exercise. Explain why changing existing code was the best way to solve a problem.
    As a general guideline, it is perfectly okay to change existing classes if certain new code really belongs in them - do not use inheritance, adapters or proxies just to avoid changing classes, since this just creates more code that has no reason to exist. On the other hand, if you need to make too many changes in existing interfaces and classes to add new functionality, then this indicates a problem in your previous design.
  2. Assertions. Describe at least five assertions that you placed throughout your code. Explain in each case why an assertion was appropriate, in contrast to an if, throw or other possible choices. You are not required to use a full-scale Design by Contract tool (such as iContract) for this exercise - Java's assert keyword is enough.
    There are two useful guidelines for writing assertions. 
    First, focus on coding simple and useful assertions, and not every possible fact about the code - trivial assertions are usually not worth the time, and too complex assertions may require a long time to code and are likely to induce bugs themselves. Second, insert contracts from the very beginning, and not after the design or code are ready, so that you will enjoy their benefits during coding and testing of this exercise, and not just make preparations for future versions.
  3. Package Design. Submit a package diagram - this is technically a class diagram, which shows only the packages in your code. Arrows between packages indicates dependencies between them.
    A package should contain a set of related classes, which are reused together. For example, the set of classes that parses XML, manipulates the data structure and outputs HTML can be placed in a package, but the classes including the 'main' methods of both xmlp and xmli do not belong there. Divide your classes into packages such that only shared code is in shared packages, so that only the minimal dependencies between packages exist. Packages are also used to hide implementation classes - do not automatically make each class public. And as a last guideline, do not over-complicate this issue: the entire code of xmlp and xmli should include 3-4 packages at most.

Submission

Our course uses the department's regular submission system; submit a zip file as usual, with the following contents:

bullet

All program source code, ant build file and a small script to run the program. Include all the code of exercise 1 in addition to all new code.

bullet

All unit tests source code, for both xmlp and xmli.

UML class diagrams, describing all classes in the program, including exercise 1 classes. There can be one or several diagrams, as long as they are readable.

UML sequence diagrams of two non-trivial object interactions of your choice.

bullet

The README file, with the usual contents (IDs, logins and full names, descriptive list of files and features) and answers to the three design questions and three coding questions above. The README file should also describe parts of the design or design choices.

The submitted structure of this exercise is as before.

How to Start

bullet

Read these general guidelines for Object-Oriented design (especially 'Design Guidelines')

bullet

Re-read about the Command, Observer and Strategy design patterns; some of the structural and creational patterns may also be useful here.

bullet

Design the program; start with the data structures, then the major operations, and finish with the "main" program. Create UML class and sequence diagrams (see also this tutorial) as you go.

bullet

During the design, add pre- and post-conditions to important methods, and state an invariant for each class and interface.

bullet

Read about unit testing and the JUnit framework before starting to code.

bullet

Code the program in the same three steps, writing unit tests in parallel with code. That is, write a test class for building the data structures, then a test class for the main operations, then a test class for the main program.

bullet

If during the coding and testing process you decide to change the design, maintain the UML diagrams to reflect the changes.

bullet

In every step of the way - design, code and test - regard the exercise 1 code as part of this exercise's code, which can be refactored as required. Consult the documented refactorings and see if they match any "bad smells" in your existing code.

bullet

Register to the course and then submit the exercise according to the above instructions.

Good luck!