Programming 3
University of Alicante, 2023–2024
Fifth Programming Assignment
Relative weight of this assignment in the practice grade: 15%. |
Interface inheritance
Interface inheritance allows us to model systems by thinking about the responsibilities that objects must fulfil rather than how they are implemented. This allows us to build complex, scalable and loosely coupled models in a natural way.
In this assignment we will work on a word processing system that we have designed exhaustively using interface inheritance, only using implementation inheritance in situations where it is really convenient.
The library we have created allows us to represent documents that can then be exported to different formats. The current implementation allows exporting to HTML and markdown. There are hundreds of sources with information on these formats. This page and this other page (in Spanish) contain simple and comprehensive information on both formats. A a side note, all pages on the Programming 3 website are written in markdown and exported to HTML.
We provide the code for this library in the attached base project.
NOTE: Only associations rather than compositions have been considered in the design to simplify the code of this assignment and avoid having to make defensive copies.
Structure of a document
HTML and markdown documents are structured as block elements (block) or inline elements (inline). Block elements occupy the entire width of the page even if the content occupies less. When we put two block-type contents, they are stacked vertically on top of each other. Inline elements fit within the content, and usually appear next to each other on the same line. Typically these elements are embedded in a paragraph. In this web page we have surrounded the boxes of the block elements with green dashed lines, and the inline elements with red dotted lines.
What do I have to do?
Your job in this practical assignment is to add new functionalities to this library. Reading carefully the following section (Description of the design) and understanding how this library works is key to successfully implement what we ask you to do in the New functionalities section.
Description of the design
The different interfaces and classes of the library are distributed in different packages. In UML diagrams, a package is represented by a box with a top tab containing the package name. Look at each diagram to see where to find or place each interface/class.
Blocks and lines
During the assignment we will use several structures based on implementation inheritance and interface inheritance that will help us save code while allowing us to conveniently extend the application.
Based on what we have described above, we can say that a document is composed of elements of type block and elements of type inline, as shown in the following diagram. As in the end a document will be an aggregation of many elements of both types, inline (IParagraphContent
) and block (IBlock
), we make them both inherit from the IDocumentElement
interface.
In the following sections we describe the model step by step. It is interesting to see how we can isolate parts of it without showing the whole model because with interfaces we abstract responsibilities and behaviours. This is one of the advantages of object-oriented programming.
Nested composite structures
In the system we have designed, we find ourselves in several occasions with elements of a given type that contain other elements that in turn can be of the same type. This happens with blocks. A block can contain elements of type inline but also other elements of type block, so that nested structures can be created. In the example of blocks and lines you can see how there are block elements (the ones surrounded in green), inside other blocks.
Later we will see how we have other similar composite elements such as bullet lists. The document itself is an aggregation of block and inline elements.
To avoid having to program this structure multiple times, we have created an abstract class AbstractComposite
whose responsibility is to contain lists of elements IDocumentElement
. In order to create nested structures we make AbstractComposite
implement the IDocumentElement
interface.
In this way, we can put one AbstractComposite
inside another, and make structures like this:
AbstractComposite
- Block
- Inline
- Inline
- Block
AbstractComposite
- Block
- Inline
- Block
- Inline
AbstractComposite
- Block
- Inline
- Block
- Block
We can see that the addItem
method is protected because it will be used only by the classes that inherit it, so we can restrict the type of elements we add.
(For more information on methods not discussed in this assignment description, refer to the source code of the library provided in the base project. You will understand the export…() methods when you read the Export section).
Document
A document (Document
), by containing a composite, possibly nested, structure of blocks and lines, has been made to inherit from AbstractComposite
. In a document, all elements must be inside at least one block, so we only have one add(IBlock)
method. This method, in its implementation, will invoke the addItem
of the base class AbstractComposite
(we can do this because IBlock
inherits from IDocumentElement
).
See how with this design we could create embedded documents inside other documents because IDocument
indirectly implements the IDocumentElement
interface.
Paragraphs
Paragraphs (Paragraph
) are blocks that contain other blocks or inline elements. This functionality is achieved by simply making Paragraph
implement IBlock
and inherit from AbstractComposite
.
We have not put any method to add content in Paragraph
. The paragraph content will be initialised with the parameters it receives in the constructor. We have two overloaded constructors, one with a list of IParagraphContent
, and another, polyadic, to make it easy to instantiate simple paragraphs. Both constructors add these items to the paragraph using the addItem
method inherited from AbstractComposite
.
See how a paragraph can be added to a document simply by assuming that a paragraph behaves like a block, or in other words, that Paragraph
implements IBlock
.
Bullet lists
Bullet lists are structures like this:
- This is an unordered bullet list element.
- This is another element.
or like this:
- First element of a numbered or ordered list.
- Second element.
Bullet lists are block elements that contain more block elements. So we have used the same principle as for designing paragraphs, we make a bullet list behave like a block and use implementation inheritance to take advantage of the code of AbstractComposite
.
We can see the two types of bullet lists available, ordered and unordered.
Finally, similar to Paragraph
, we can just add elements in the constructor.
Headings
Headings, or section titles, are texts that behave like blocks. Depending on the hierarchy of the heading, they will have a level (1, 2, etc.) to have “Heading 1”, “Heading 2”, and so on.
As can be expected, we will find several different types of textual objects in the document. Therefore, we have created a class AbstractText
whose responsibility is to encapsulate a text, and in the future, to add some additional functionality (as we will see in the tasks you have to perform later).
As the headers must behave as blocks and take advantage of the implementation of AbstractText
we use the layout shown in the following diagram.
We can see how we could create a document with headings only by adding Heading
blocks to the document.
We have included the exception EditorException
that is throw if Heading
is created with a level less than one.
Code blocks
Another type of text block similar to headers is the one that allows us to display source code embedded in a document. A code block is a text (AbstractText
) that behaves like an IBlock
and optionally has information about the programming language in which the code it contains is written.
Separator lines
The last type of block element we include in the library is horizontal separators in the form of a line like this one:
We have created an IMark
interface to represent all blocks that have no additional content (such as text or images). The class representing these horizontal separators is HorizontalRule
.
Images
The first and simplest of the elements of type inline is the image, as can be seen in the UML class diagram below.
We have left the paragraphs and the document in the diagram to show how a document could be created with images only. To do this we would create one or more images (which implement IParagraphContent
), and we would construct one or more paragraphs by passing those images as parameters. Finally, we would add those paragraphs to the IDocument
.
Texts
We are going to find two types of texts. Those that are inside a paragraph block and therefore must behave as inline text (IParagraphContent
) and others not included in a paragraph and hat act by themselves as blocks (IBlock
). The latter will be used in cases such as bullet list items (AbstractListBlock
inherited by OrderedListBlock
and UnorderedListBlock
).
We use the ability to apply multiple interface inheritance to model this requirement on any text element through the IText
interface.
We show in the UML the paragraphs and bullet lists to show how we could insert text into both. If we need to insert text inside a paragraph, we create a Text
object and pass it to the constructor of Paragraph
, which on receiving IParagraphContent
admits a Text
that implements that interface.
If we want to create a bullet list to which to add text, we create a Text
that we will provide in the IBlock
list that we send to the AbstractBlock
constructor, because Text
also implements IBlock
.
Inline code
A special text is the InlineCode
. Its behaviour and implementation are pretty much the same as Text
. We have not made it inherit from it because in the future we might add functionality to Text
that we don’t want InlineCode
to have.
Below you have the full diagram with all the classes and interfaces we have introduced.
Pause your reading of this assignment description. Before continuing, make sure you understand what the different types of elements in a document are and how they relate to each other.
Export
To export the document to various formats we could add a method to export each of the formats in each class (exportHTML(), exportMarkdown(),…). However, this approach has, among others, an important problem: it forces us to modify the classes for each new format.
To avoid this problem, and to allow us to extend the number of export formats without having to modify the classes of the model, we created two interfaces: IExportable
and IExporter
.
Each class that we want to be exportable must implement the IExportable
interface. The code of the export
method only delegates the responsibility of exporting to the IExporter
that it receives as parameter with a code similar to this one:
class X implements IExportable {
····public void export(IExporter e) {
export(this);
e.
}
···· }
In order to know how to export the X
class, a String export(X)
method must be added to the IExporter
interface. This procedure will be repeated for each class to be exported.
To export to different formats we will only have to create concrete classes that implement IExporter
.
In the class diagram above, we can see how we have made Document
implement the IExportable
interface so that the whole document can be exported. To force all elements in the hierarchy to implement the behaviour of being exported, we make IDocumentElement
inherit the IExportable
interface. This way, all the classes in the model will have at the end a export
method like the one described above. In the previous diagram we have added Image
and Paragraph
as an example, but as you can see in all the diagrams already shown, all classes overload the export
method because they indirectly get the need to implement it from IExportable
.
For each format we want to export we create a class that implements IExporter
. So, for this practical assignment we have created two classes, HTMLExporter
and MarkdownExporter
. You can see the source code in the attached Eclipse project to see how each type of element in a document is exported.
Additional behaviours
It is common to have to add more capabilities to the classes we have in the current class hierarchy without having to change them. This is especially necessary when the classes to which we want to add a new behaviour are in a library that we do not own and cannot modify.
Boldface and italics
In order to add new features to classes, such as adding hyperlinks, boldface and italics, we add a new class for each new behaviour. To add italics and boldface we created classes ItalicsTextDecorator
and BoldTextDecorator
respectively.
An AbstractDecorator
is a class containing an element (decoratedElement
) to which it will add an additional feature via a subclass. An AbstractTextDecorator
is simply an AbstractDecorator
that will behave like an IText
. So, wherever we can use an IText
we can replace it with any class that inherits from AbstractTextDecorator
. This way, when we want to use boldface, we will use instead of Text
a BoldTextDecorator
that we will have built with a code like this:
new BoldTextDecorator(new Text("Texto")); IText element =
If we wanted to create a text in boldface and italics we would just surround the bold with an italic:
new ItalicsTextDecorator(new BoldTextDecorator(new Text("Texto"))); IText element =
See how these new elements in the hierarchy, by indirectly implementing IExportable
, force the new classes to overload the export
method and IExporter
to have methods to deal with them (in the diagram we have omitted the rest of the IExporter
methods for ease of reading).
Hyperlinks
We’ll do something similar to add links to both text and images.
A LinkParagraphContentDecorator
, through AbstractParagraphContentDecorator
, is an IParagraphContent
. If we wanted to replace an image with an image that has a hyperlink we would do:
new LinkParagraphContentDecorator(new Image("logo_ua.png", "UA Logo"), "https://www.ua.es/"); IParagraphContent imageWithLink =
Below you have the classes and interfaces introduced in the two class diagrams shown in Figures 14 and 15:
Tasks to perform
Up to this point, we have explained all the code of the model and the exporters that are implemented in the base project. We describe hereafter the work to be done.
New functionalities
Using as an example similar functionalities that are already designed you should extend the class hierarchy with three new classes that should inherit and/or implement whatever is necessary to pass the unit tests that are included in the base project. Note that every time you add a new class to the model you will have to add that component to the IExporter
interface with the changes that this will generate in the descendant classes.
- Currently we have a
HorizontalRule
class that goes down the line and draws a horizontal line. You have to create aBreakLine
class that only causes a line break. In markdown that jump is simply encoded with a double\n
, i.e.\n\n
. In HTML, the<br>
element is used. You have more information on this page. As this class can be included as an isolated block but also within a paragraph, you must additionally implement theIParagraphContent
interface. - The system includes ways to specify boldface and italics with the
BoldTextDecorator
andItalicsTextDecorator
classes. You must add theStrikeThroughDecorator
class for cross out text. In markdown you must surround the text to be cross out with “~~” (e.g. “~~this is cross out~~” would appear asthis is cross out). In HTML, the text must be embedded in a “<del>
” element (e.g. “<del>this is cross out</del>
”). You have more information on this page. - Finally you must add the
Quote
class to write a quote paragraph as you can see in this page. In markdown you just prefix the content with a ‘>’ character. In HTML you include the content in a “<blockquote>
” element (see this page). You can consider a quote paragraph to be a paragraph with this rich functionality by using inheritance. You must overload the two constructors inherited fromParagraph
. Note that making it inherit fromParagraph
is not decorating it but replacing how it is exported. To do this you must overload theexport
method and add theexport(Quote)
method inIExporter
. In the HTML export you should change the<p>
tag used in paragraph to<blockquote>
.
Implementation of an anonymous class
In this part you must make the Image
and AbstractTextContent
classes implement the ITextCaseModifiable
interface. It is convenient that you implement the changeCase
method in AbstractTextContent
and not in its subclasses to avoid repeating the same code several times.
The changeCase
operation will modify the text
attribute in the case of AbstractTextContent
with the code this.text = modifier.changeCase(this.text);
. In the case of Image
, it will modify the alt
attribute with the code this.alt = modifier.changeCase(this.alt);
.
To implement this you will need to complete the TextsToUpperCase
class so that the createTextModifier()
method creates and returns an anonymous class object that implements the ITextCaseModifier
interface and passes a text to uppercase (it will be checked that it is indeed anonymous and a named class is not created). You can use the method toUpperCase
of the String
class to perform the transformation.
Using the library
To make use of the complete class hierarchy, you must fill in the code of the es.ua.dlsi.prog3.p5.client.ExampleDocumentCreator
class to return a document that generates the example_document.md
and example_document.html
files that are included in the unit tests so that the ExampleDocumentCreatorTest
unit test does not fail.
To be able to do this part of the assignment you will need to have a good understanding of the meaning of the interfaces and abstract classes, and why implementation inheritance or interface inheritance has been used.
You can see an example of creating another similar object structure in the setUp() method of the es.ua.dlsi.prog3.p5.model.DocumentTest
unit test provided in the base project.
When you use the Heading
constructor you should catch the exception EditorException
and rethrow it as an unchecked exception because we are creating the classes and this exception should never be thrown because we are creating correct headers.
Unit tests
We provide unit tests in the test/
folder of the base project that check the proper behaviour of the classes. It is important that you understand what is tested and how it is done.
Documentation
It is not necessary to document this practical assignment
You can document the code you write, in fact we recommend it, but it will not be evaluated.
Minimal requirements for grading your assignment
- Your program must run with no errors.
- Unless otherwise stated, your program must not emit any kind of message or text through the standard output or standard error streams. Also avoid error output messages.
- The format of the name of all properties must be strictly respected, both in terms of scope of visibility and in terms of type and way of writing. Make sure that you respect the distinction between class and instance attributes, as well as the uppercase and lowercase letters in the identifiers.
- Your code must be conveniently documented and significant content has to be obtained after running the javadoc tool.
Submission of the assignment
Submission is done during the control to the DLSI practice server.
You must upload a compressed file with your source code (only .java files). In a terminal, place yourself in the ‘src’ directory of your Eclipse project and enter the command
tar czvf prog3-p5.tgz *
This will compress all the code in src/, including those classes that were already implemented. This is correct and should be delivered as is.
Upload the file prog3-p5.tgz
to the practice server. Follow the instructions on the page to log in and upload your work.
Grading
Testing of your assignment will be done automatically. This means that your program must strictly conform to the input and output formats given in this document, as well as to the public interfaces of all the classes: do not change the method signatures (name of the method, number, type and order of arguments, and data type returned) or their behaviour. So, for example, the Clase(int,int) method must have exactly two arguments of type int.
You can find more information about the grading of programming assignments in the subject description sheet.
In addition to the automatic grading, a plagiarism detection application will be used.
The applicable regulations of the University of Alicante Polytechnic School in the event of plagiarism are indicated below:
“Theoretical/practical work must be original. The detection of copy or plagiarism will suppose the qualification of”0" in the corresponding assignment. The corresponding Department and the University of Alicante Polytechnic School will be informed about the incident. Repeated conduct in this or any other subject will result in notification of the offences committed to the pertinent vice canchellor’s office so that they study the case and punish it in accordance with the legislation in force".
Clarifications
- Although not recommended, you can add private attributes and methods to the classes. Notice, however, that you must implement ALL the methods indicated in this document and make sure that they work as expected, even if they are never called in your implementation.
- Any additional remark will be published in this page. It is recommended that you use this page as the primary source of the instructions.
- If you want to know more, this assignment has been designed using design patterns, specifically the Composite, Visitor and Decorator patterns. Design patterns are not studied in this subject but in later courses.