Project 5 — A Polymorphic Document Storage System (PDSS)

Assigned Saturday, November 29th
Program Due Tuesday, December 9th by 11:59pm
Weight 11%
Updates
  • None yet.

Objectives

To gain experience:

  1. Using polymorphism in C++.
  2. Adapting a hierarchy of classes to a new design.
  3. Using exceptions.

Project Policy

This project is considered an CLOSED project. Please review the open vs. closed project policy before beginning your project.

Being the last project, there will not be any extensions with penalties for turning this project in late. It absolutely must be submitted by the due date/time, or you will receive a 0.

Overview: the Polymorphic Document Storage System

In Project 4, you designed and implemented support for a collection of document types as a C++ class hierarchy, to take advantage of the reusability that inheritance affords. In this project, you are going to redesign and augment the work from Project 4, to implement four improvements. First, you will be restructure Project 4's code to make it simpler and clearer, as well as more flexible, by taking advantage of the even more powerful mechanisms of late binding and polymorphism. Second, you will add a new command to the system: "respond", that lets you reply to emails, send a follow-up memo, etc.. Third, to see how well the existing document hierarchy supports extensibility, you will add a new class to support an additional document type: (TechReport). Last, you will integrate exceptions into your classes to better control error-handling.

Description

For Project 4, we provided a description of the features of the various document classes you would need, and left the design of the class hierarchy up to you. (In this document, "document classes" refers to the base class Document, and all the subclasses derived from it, like Email and Report.) For this project, we have selected a specific hierarchical structure for you to use as the basis for your own implementation. In the provided hierarchy, we still have Document as the base class, with Report being directly derived from Document. However, Email and Memo are now derived from a new intermediate class called Correspondence, which is derived from Document. This allows us to abstract out those commonalities between Email and Memo that are not shared with Report. The Correspondence is never meant to be instantiated.

The new hierarchy looks like this:

                                 Document
                                    /\
                                   /  \
                                  /    \
                                 /      \
                         Correspondence  \
			       /\         \
                              /  \         \
                             /    \         \
                            /      \         \
                         Email    Memo      Report
                                              |
                                              |
                                              |
                                          TechReport
We have provided class definitions that implement the above basic structure. The specific files are described below.

Also note the relationship of the new document type TechReport, which is directly derived from Report. This will be described in more detail later.

The Provided DocumentStore Class

For this project, as for the previous one, we will be providing the driver program, as well as an implementation of the DocumentStore container class. The new DocumentStore class is much improved from the version we provided in Project 4, in the following ways:

  1. It contains complete versions of the methods that we had left up to you to write in Project 4. So regardless of how well you did on the previous project, everyone will start on an even footing for this project.
  2. It uses polymorphism (or rather, assumes the document classes it works with will implement polymorphism) to greatly reduce the size and complexity of its code, making it much more easily understandable and maintainable. For example, it no longer depends on a type field in the game instance to decide which of several member functions to call, but instead lets it be managed automatically by the late binding mechanism. (Incidentally, that means we no longer need the GetType() method, nor any of the DOC_TYPE_XXXX constants. However, the supporting fields, methods, and constants are still defined in the various document class definitions--it is your responsibility to remove them.
  3. It now accepts a new, fourth type of document: the technical report, defined as the class TechReport, which you are responsible for implementing. A command has been added for creating this new document type (command #8).
  4. A new operation has been added: respond/reply. You will be writing the support for this in the various document classes. This is command #9.
  5. The system now uses exceptions. You will have to finish the implementation of a new exception class, and throw appropriate instances of this exception inside your document classes.
  6. Lastly, the handling of the distribution list for memos has changed: DocumentStore used to pass to the Memo constructor as an argument one long string of all the names concatenated together. It now passes in a vector instead, with each name in the distribution list as a separate element, and the provided Memo class keeps it as a vector. This update was added to make implementing respond/reply easier.

The new polymorphism-aware DocumentStore methods are significantly shorter and simpler than the versions for Project 4. For example, the DocumentStore::Display() method has been reduced from 32 to 8 lines! You should carefully study the new DocumentStore.cpp methods to see what is expected of your new Document hierarchy of classes.

Implementation

You should do the implementation of this project in four stages:

Stage 1: Adaptation of the Correspondence, Email, Memo, and Report classes

You will be adapting the code for the Email, Memo, and Report classes from the non-polymorphic versions of Project 4. To level the playing field among people who had varying degrees of success in finishing Project 4, we are providing complete implementations of those classes that the instructor wrote as his own solution for Project 4. The provided implementation has working versions of all of the following methods:

GetType()
GetID()
GetAuthor()
DisplayBody()
Search()
DisplayHeader()
GetSubject():          // only needed in Email and Memo
GetRecipientName():    // only needed in Email and Memo
GerRecipientEmail():   // specific to Email
GetDistributionList(): // specific to Memo
GetTitle():            //specific to Report

The provided document classes' implementations were designed to use simple inheritance and member function overriding, with no virtual functions, and therefore, no polymorphism. Because of this, we originally had to use lots of if/then/else-statments and complex pointer-casting (e.g., casting a Document* to a Memo*) in the methods of DocumentStore to get the compiler to invoke the correct document-type-specific method implementation.

Your job is to update the provided classes to make them fully polymorphic. Since the provided Project 5 DocumentStore class now assumes it can depend on polymorphism, it is currently incompatible with the provided Project 4 document class implementations. You will have to modify the provided document classes to use virtual functions and dynamic binding to make them work again for Project 5. This should be relatively straightforward if you understand the mechanics of how to write virtual functions. As provided, the document class implementations have most of the methods you will need for a working system already implemented, distributed amoung the various classes in the hierarchy. Your job is to possibly move some of the method definitions either higher up or lower down in the class hierarchy as appropriate, and combined with judicious use of virtual, or even possibly pure virtual, methods, maximize efficiency and code reuse among the classes. Two examples will set the tone:

When you're restructuring, don't forget that a child class that overrides a parent class's method can still internally call on the parent's implemetation to do part of the work. (Hint: possibly useful for Email and Memo to share code in the parent Correspondence class for printing out headers, since they share most of their header information.)

You really should not have to write much new code for this part. Once you have finished this adaptation, you should be able to compile the full program to see if the new polymorphic logic works properly. (Ignore the compiler warnings about "function returning non-void"--you'll fix those later in the next stage.) As long as you don't try to respond to a document, or to create a technical report document type (we will fill these in in the next few stages), the program should be otherwise completely functional.

One last note: if you do not modify the logic of these functions to any significant degree from what we provided, you do not have to add documentation for the functions we are providing. However, you do have to fully document any functions you modify or add. You also must fully document any new classes you create, in particular, the TechReport class, described later.

Stage 2: Addition of the CreateResponse() method

[This stage, and the next stage--creating the TechReport class--are actually closely related, so you should read through both sections before thinking about the design for either.]

This stage is where you will do the bulk of your new coding. We want you to extend the various document classes to support the new functionality of being able to respond to existing documents. This method is called CreateResponse(), and we have written in a non-virtual "stub" (an incomplete version of the method that doesn't actually do anything) into each of the provided document classes. Your job is to flesh out these stubs with real code, according to the specifications below. This method needs to be supported by every concrete (i.e., non-virtual) document class, even if that support merely consists of always throwing an exception. In most normal cases, this method will create a new dynamic object of the same class as the document being responded to, and return a pointer to it.

Since the CreateResponse() method is called polymorphically from DocumentStore::Respond() on a pointer of type "Document *", you must make this work by also adding the correct type of declaration for CreateResponse() at the appropriate place(s) in your document class hierarchy. Think about whether a virtual or pure-virtual method would be best. Your job is to add a type-specific implementation to each of the actual real document classes: Email, Memo, Report, and later, TechReport. The logic is differently for each of these classes, as outlined here:

In the case of a successful response, this method will usually return a dynamically-allocated document object, of the same type as the document being responded to, but some document types will implement "response" by modifying the existing document in-place (specifically, TechReports--see below) . If a new document is created by CreateResponse(), it should return a pointer to that response document; otherwise, it should return NULL. The calling function, DocumentStore::Respond(), passes in the new unique document ID that should be assigned if a new document is in fact created, and watches the return value to determine if the ID was actually used for a new document, this being signalled by a non-NULL return value. In that case, it will add the newly-created response document to the active list, and increment the next-assigned ID.

For Email or Memo, in the case where the prospective author was rejected, you must throw an exception--this is covered in more detail in the next stage--with the message "non-recipients cannot respond." For the case where the user is trying to respond to a Report (which is never allowed), the same exception type should be thrown, but with the message "cannot respond to a report". Note that due to the way our classes are structured, since DocumentStore::Respond() collects the response body, but the document class's CreateResponse() does the validity-checking, the program will not get to complain about bad authorship until after the text is already entered; this is a small design flaw in our system--ah, well...

Note that the command-line interface has already been augmented to handle the additional document type and new commands, so there is nothing to do there.

Stage 3: Addition of Exceptions

As mentioned above in the section on adding the CreateResponse() methods, the various versions of that method should all throw an exception (specifically, an instance of the class BadResponseException) in the case where the request was rejected for one reason or another (detailed in Stage 2 above). Like the exception class examples we covered in lecture, BadResponseException should have a data member that can store an error message string, and also provide an accessor method called GetMessage() to get that string out, as well as a 1-argument constructor that takes the message string as a parameter.

The implementation of DocumentStore::Respond() already assumes the document object might throw this exception, and has wrapped the call to CreateResponse() with a try/catch block--examine that code to see what your BadResponseException needs to implement.

Stage 4: Implementation of the TechReport class

The last task will be to write the TechReport class. The versions of the files TechReport.h and TechReport.cpp that we provide are skeletal, actually mostly just documentation, plus just enough structure to let you compile and test your Email, Memo, and Report classes first. You must write the full class definition and method definitions. However, since the TechReport class inherits all of Report's data members and methods, only a few things need to be added or overridden. There are several things that are different about a TechReport from a plain Report, and in fact, from all the other document types:

  1. Responding to a TechReport is very different. We still do it via the same method interface as for the other document classes: we override the CreateResponse() method. However, the behavior is quite different. In TechReport, CreateResponse() does not create a new responce document, as it does in the other document types. Instead, it simply adds a new comment onto the existing document. To do this, the document's textBody is not modified. Instead, a new instance of a Comment class is pushed onto a vector of comments stored in the TechReport. (We've defined the Comment class for you, at the end of the TechReport.h file.) Since no new document is created, TechReport's CreateResponse() will return a NULL, instead of the usual pointer-to-new-response-document. This lets the caller (DocumentStore::Respond()) know that a new response document was not created. The nextID parameter is ignored.
  2. Displaying a TechReport is different. While the header is displayed in exactly the same way as a Report, TechReport::DisplayBody() does a little more. After first printing out the body text, it prints out all of the comments, labelling each with the comment number and author, in the format: "Comment <#> by <author>:"; for example: "Comment 7 by Jane Doe:". The comments are separated from each other and from the body by blank lines. If there are no comments yet, output "--No comments yet." Refer to the sample output.
  3. The Search() method must scan both the textBody and all of the comments for the requested string. Note that the comment labels (e.g., "Comment 7 by Jane Doe:") are not part of the comments, so a search for the string "Comment" or an author name (e.g. "Doe") should not turn up anything, unless that string was actually embedded in the body of the original report or one of the responses.
Since this is a new class that you are creating, all of the class coding standards apply: you must put in a file header, a function header for every function, and inline comments to describe your variables and code.

Requirements

  1. You may not change any of the code in Proj5.cpp, Proj5Aux.{h,cpp}, or DocumentStore.{h,cpp}. You should not even submit these files. You are free to change any of the rest of the files, and create any additional files you may need (don't forget to add them to the Makefile!).
  2. If you use any dynamic objects as members in your classes, you must have appropriate destructors for the class that deletes these dynamic objects. Don't forget to make all of your destructors virtual!
  3. The output must match the specifications outlined here, and the provided sample output, as exactly as possible.

Provided Code

First, there are the provided working driver and DSS source files; these files must not be modified:

Next, there are the provided non-polymorphic implementations of the document classes. You must significantly upgrade these to make them work polymorphically; however, most of your changes will simply involve moving already-implemented functions around from one class to another, and make various methods virtual.

Lastly, we are providing, for the first time, a Makefile. Study it to see how it works. If you add any new files, modifying the Makefile is easy: just add it (as a .o filename) to the line that begins "OBJS=". Also, make sure you submit the Makefile with your project, regardless of whether you modified it or not.

If you've ssh’ed into the GL servers, you can copy all of the files over directly: issue the following command at the command prompt while you’re inside your project directory:

cp /afs/umbc.edu/users/p/a/park/pub/cmsc202/fall14/proj5/* .
(Note the '.' at the end of the command--that is very important.)

Example Session

A sample run is available here. Please try to stick as closely as possible to the exact output format given in the sample.

Grading

See the course website for a description of how your project will be graded.

We will be automating much of the grading for this project. It is absolutely essential that you do not modify any of the files that we provided to you that we instruct you not to alter. You do not have to submit these, but if you inadvertantly do, we will just replace them with our own versions before attempting to compile and run your program.

Another requirement is that your executable is called Proj5.out; be careful to preserve this if you modify your Makefile.

Project Submission

Before submitting your project, be sure to compile and test your code on the GL system. See the Project Compilation section of the course Projects page for details on how to compile and execute your project on GL.

Assuming you’ve used the specified file names, submit your project by typing the command

submit cs202 proj5 [all of your various .h and .cpp files] Makefile

See the Project Submission page for detailed instructions. Note that we require you to submit the Makefile, even if you did not modify it, because it is one of the files we allowed you to modify, so we will always use your version.

You may resubmit your files as often as you like, but only the last submittal will be graded and will be used to determine if your project is late. Also note that if you rename or remove certain files, the old versions that you submitted earlier will stay in the submit directory unless you use "submitrm" to clean them out, which you should. At the end, a "submitls" should show exactly those files that are part of your project: no more, no less. For more information, see the Projects page on the course web site.

Remember — if you make any change to your program, no matter how insignificant it may seem, you should recompile and retest your program before submitting it. Even the smallest typo can cause errors and a reduction in your grade.