Project 5 — A Polymorphic Document Storage System (PDSS)
Assigned | Saturday, November 29th |
---|---|
Program Due | Tuesday, December 9th by 11:59pm |
Weight | 11% |
Updates |
|
Objectives
To gain experience:
- Using polymorphism in C++.
- Adapting a hierarchy of classes to a new design.
- 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 | | | TechReportWe 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:
- 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.
- 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. - 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). - 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.
- 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.
- 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:
-
The provided classes each have
DisplayBody()
implemented independently, but pretty much identically, in each of the document subclasses--this sounds like it might be a good candidate for moving the multiple implementations up to a single version in a class higher up in the hierarchy, and letting inheritance do the hard work. (Note that we purposefully provided a not-so-well-organized implementation, to give you opportunity for some interesting restructuring to do in this stage.) -
The
DisplayHeader()
method implementation needs to be very different from one document type to another, and yet, the new DocumentStore::ListDocuments() now invokes DisplayHeader() on a generic Document pointer. So, you must have the correct placement of virtual function declarations among the various parent and child document classes to make this work, e.g., get the system to run the Memo-specific DisplayHeader() even though we are invoking it on a Document pointer.
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:
- Email: The "author" parameter must be checked: only the recipient of the original email may respond (matching on the name, not the email address). A new Email object is dynamically allocated. In the new response email, the author field is set to the "author" parameter. The subject is set to "Re: <original subject>" (where you would replace "<original subject>" with the actual subject of the original email). The recipient should be set to the author of the original memo. The recipient's email should be set to the new recipient's name, with "@yoyodyne.com", literally, appended. The body of the response memo is set to the "bodyText" parameter. The ID of the new document is set to nextID. A pointer to the new document is returned.
-
Memo:
The "author" parameter must be checked: only the recipient or one of the
people on the distribution list of the original memo may respond.
A new Memo object is dynamically allocated.
In the new response memo, the author field is set to the "author" parameter.
The subject is set to "Re: <original subject>" (where you would replace
"<original subject>" with the actual subject of the original memo).
The recipient should be set to the author of the original memo.
If the responding author was from the original distribution list,
the new distribution list should have the responder's name replaced with the
recipient of the original memo. (Think about why: if you did not do this,
the original recipient would never see the response!)
This substitution is much easier because the distribution
list is now implemented as a vector of strings, as mentioned earlier.
The body of the response memo is set to the "bodyText" parameter.
The ID of the new document is set to nextID.
A pointer to the new document is returned.
(An example: if the original Memo was from Anne to Jim, with Bob and Mary on the distribution list, only Jim, Bob, or Mary would be allowed to respond. If Jim responded, the response memo would have {author=Jim, recipient=Anne, distribution=Bob,Mary}; but, if instead, Mary were to respond, the new memo would have {author=Mary, recipient=Anne, distribution=Bob,Jim}) - Report: it is illegal to respond to a report.
- TechReport: responses are handled very different for this document type; this is detailed later in the section on "Implementation of the TechReport class".
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 classBadResponseException
)
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:
-
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 aComment
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. -
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. -
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.
Requirements
- 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!).
- 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!
- 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:
-
Proj5.cpp: contains a simple
main()
- Proj5Aux.h, Proj5Aux.cpp: These two files implement the main part of the user interface driver logic
- DocumentStore.h, DocumentStore.cpp: the class definition and implementation of the DSS container class; already upgraded to use polymorphism, and to support the new commands as well as the new TechReport document type
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.
- Document.h, Document.cpp: base class to entire document hierarchy
- Correspondence.h, Correspondence.cpp: child of Document; parent class for Email and Memo
- Email.h, Email.cpp
- Memo.h, Memo.cpp
- Report.h, Report.cpp
- TechReport.h, TechReport.cpp: these are just skeletal files, which you will be writing most of.
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.