MangaTantou
’s structure has been greatly inspired by the team developers’ respective iPs as listed below:
The following third-party libraries were used:
Additionally, the following resources/websites were heavily used (they are amazing):
The above UML class diagram shows the overall structure of author and manga data an editor using MangaTantou would be
interested in.
NOTE: There is circular reference (bidirectional navigability) between
Author
andManga
throughMangaList
.
Command generation first begins with the
Parser
class. Parser
first determines the command that the user
wishes to execute based on the first keyword provided. Then, Parser
will employ various
ArgumentFinder
s to extract the arguments of interest. Each specific implementation of the
abstract ArgumentFinder
makes use of specific patterns generated in the
Regex
class to extract their respective arguments of interest. These arguments are then packaged into a container
ArgumentResult
object for Parser
to later unpack to generate the right
Command
with the required details. More information about Command
s down below!
The above diagram was generated
on Regex Vis.
It visualizes the control flow of the RegEx engine used to extract fields from user inputs. In this case, the RegEx
pattern is used to isolate the author’s name, excluding anything after the flags
-s
or -b
. In MangaTantou
’s
actual implementation, all valid flags in the app are excluded (including the author flag, for this case). A split
in the diagram means that both branches are valid matches.
Notice that the topmost branch matches an empty string is nothing is inputted after the -a
flag.
This is intended, and this case is handled outside the RegEx in Parser
. The bottom branch tries to match
everything between the -a
author flag and any of the excluded flags, or the end of the string. Other unused flags,
such as -x
, are included. This allows user to be more flexible in the allowed author names
To extend the parsing system to suit various operations i.e. generate new
Command
s, the following guidelines outline the key aspects for working and expanding the parsing architecture:
Command
class in the constants
package and update the switch
statements under the
getUserCommand
method to return the relevant Command
.ArgumentFinder
s offer great reusability, should the need to implement a custom
ArgumentFinder
not yet implemented arise, do add the new flag/option of interest
to the Options
class under the constants
package and expand the
OPTIONS_ARRAY
accordingly to register the newly added option. After which, implement the custom extractor
Pattern
under the Regex
class and the custom ArgumentFinder
.And with that, you’ve successfully expanded Parser
to generate new
Command
s, each with their specific arguments of interest!
The current list of viable Commands
are as follows:
AddAuthorCommand
AddMangaCommand
DeleteAuthorCommand
DeleteMangaCommand
ViewAuthorsCommand
ViewMangasCommand
AddSalesCommand
AddDeadlineCommand
GreetCommand
ByeCommand
All child Command
classes must inherit from the abstract
Command
class. Each child class is required to implement the abstract execute
method.
While child classes may or may not modify the AuthorList
, they are encouraged to utilize the
Ui
class to interact with users, such as displaying success messages.
When adding new command classes, developers must follow the same method of implementation by
inheriting from the abstract Command
class. Ensure that each new command class includes
an implementation of the execute
method and appropriately interacts with the Ui
class
for user feedback. Additionally, developers should update the Parser
class to gather the
relevant arguments from the user for their commands. Lastly, it is important that developers expand the static CommandValidator
and use it to
verify the correctness of the arguments provided and to throw the relevant exceptions otherwise.
AuthorList
data accumulated by the user can be saved with the provided Storage
and StorageHelper
classes.
The above UML class diagram outlines the structure of the classes related to saving data.
The Storage
class uses the Singleton
design pattern, which means only a maximum of one
Storage
instance can exist during the program’s lifespan. To access it, call the static method Storage::getInstance
.
The StorageHelper
utility class wraps the methods to access Storage
for ease of use.
Data is by default stored in a JSON file catalog.json
in the
data
directory at the program root location, or if ran via a .jar
file, the .jar
file location. This is determined
at runtime in Tantou.BASE_LOCATION
via Tantou::getBaseDirectory
. The location can be changed via the
public static final String DATA_PATH
constant in the Storage.java
file.
The class makes use of the Gson
third-party library to de/serialize data.
When needed, call StorageHelper::readFile
to return the deserialized AuthorList
from catalog.json
.
Whenever a user action that modifies the state of the AuthorList
is performed, the corresponding overridden
Command::execute
method should call StorageHelper::saveFile
after modifying the data.
The following UML sequence diagrams outline the behaviour of the program when the user inputs a command that modifies
the
AuthorList
.
Instead of using the default deserializers provided by
Gson
, this project defines custom ones. This enables us to perform validity checks, via the CommandValidator
class, on the key-value pairs in the data file
every step of the way, providing detailed and relevant information in the event deserialization is not successful. The
following is a code snippet showcasing some of the checks performed during the deserialization of data.
@Override
public MangaList deserialize(JsonElement json, Type typeOfMangaList, JsonDeserializationContext context)
throws JsonParseException {
// Ensure mangaList is a JSON array
if (json == null || !json.isJsonArray()) {
throw new JsonParseException("invalid MangaList array");
}
JsonArray mangaListJsonArray = json.getAsJsonArray();
MangaList mangaList = new MangaList();
for (int i = 0; i < mangaListJsonArray.size(); i++) {
JsonElement mangaJsonElement = mangaListJsonArray.get(i);
// Ensure manga is valid, skipping if not
try {
// pass Author reference
Manga manga = new MangaDeserializer(author, mangaList)
.deserialize(mangaJsonElement, Manga.class, context);
mangaList.add(manga);
} catch (JsonParseException e) {
Ui.printString(generateErrorMessage(e, i));
}
}
// Assertion: mangaList is either empty, or contains only valid mangas
return mangaList;
}
Additionally, to prevent infinite recursion due to the circular reference between an Author
and their
Manga
(stemming from bidirectional navigability; refer
to Representing Data in MangaTantou)
, a custom @ExcludeInSerialization
annotation was created to signal to
Gson
to ignore the annotated class attribute when serializing the data. The following is a code snippet demonstrating
how the
author
field is excluded in the Manga
class.
public class Manga {
private String mangaName;
@ExcludeInSerialization
private Author author;
private String deadline;
private Sale salesData;
...
}
[
{
"authorName": "test1",
"mangaList": [
{
"mangaName": "manga 1-1",
"deadline": "None",
"salesData": {}
},
{
"mangaName": "manga 1-2",
"deadline": 31415,
"salesData": {}
}
]
}
]
When providing the above catalog.json
file and inputting view -a test1
, the following output is given.
Author "test1": skipping invalid manga entry at index 1 due to invalid deadline
Data restored!
Wake up and slave~
view -a test1
Mangas authored by "test1", Total: 1
no. | Manga Name
----------------------------------------------
1 | manga 1-1
As observed, the deadline
of
manga 1-2
is invalid. Instead of discarding the whole data file and starting with an empty
AuthorList
, the deserializer skips manga 1-2
altogether and tries to deserialize the rest.
This allows the user to manually edit their
catalog.json
file with peace of mind that the program can catch their errors.
AuthorList
data can be displayed with view commands. The
Ui
class aids in presenting readable data to the user.
The above UML class diagram outlines the structure of the Ui
and related classes.
The
PrintColumn<T>
class represents the table columns to be printed. It contains attributes that help with the formatting
of a table column, such as width, header name, and a reference to getter methods (also known as
valueProvider
s) in the Author
and Manga
data classes that return
String
s. Default values for these attributes are provided in PrintFormat.java
in the constants
package.
Within the PrintColumn<T>
class, methods format the values provided by the data class (Author
, Manga
, etc.) with
String::format
(which is similar to printf
in C/C++) into fixed-width columns.
With the list class that comprises instances of the data class (e.g. AuthorList
, MangaList
), a static method (e.g.
MangaList::mangaColumnsToPrint
) is provided to get the print configuration for the data class given the argument flags
passed in the method. It returns an
ArrayList
of PrintColumn<T>
s configured based on what data should be in the column.
This static method should be called in the corresponding view Command::execute
method, similar to the following form:
Ui.printList(mangaList, mangaColumnsToPrint(...));
.
Refer to the view command interaction for an example walkthrough of the methods mentioned in this section.
All commands follow the command processing sequence shown below:
The ref
block indicates a placeholder for the individual commands and their execution below.
The AddAuthorCommand
is responsible for adding new Author
s to MangaTantou
. The command creates a new
Author
instance and verifies its existence. If it
is a new and undocumented Author
, it is then added to MangaTantou
’s AuthorList
, allowing the user to keep track
of their manga authors. The AuthorList
is saved via Storage
for data persistence.
The following diagram illustrates the interactions that take place when the
user provides "catalog -a Kubo Tite"
as an input.
If the Author
instance already exists, a TantouException
is thrown, informing the user that
they are already tracking this employee.
The AddMangaCommand
is responsible for adding new Manga
s to Author
s in
MangaTantou
. The command first creates a new Author
and Manga
instance.
If the newly created Author
is undocumented by MangaTantou
, the Author
is added to the
AuthorList
and the newly created Manga
is added to
the Author
’s MangaList
. If the Author
already exists,
MangaTantou
will check for the existence of the newly created Manga
. If there is an existing
association between the Manga
and Author
, a
TantouException
is thrown, informing the user that they are adding an existing Manga
. Otherwise,
the Manga
is similarly added to the Author
’s MangaList
and the current state of AuthorList
is saved via
Storage
for data persistence.
The following diagram illustrates the interactions that take place when the
user provides "catalog -a Kubo Tite -m Bleach"
as an input.
The DeleteAuthorCommand
is responsible for removing Author
s from MangaTantou
. The command creates a new
Author
instance and verifies its existence. If it
is a new and undocumented Author
, a TantouException
is thrown, informing the user that this
Author
does not exist and hence cannot be removed.
Otherwise, the Author
is removed from the AuthorList
, which is then saved via Storage
for data persistence.
The following diagram illustrates the interactions that take place when the
user provides "catalog -a Kubo Tite -d"
as an input.
The DeleteMangaCommand
is responsible for removing Manga
s from Author
s in
MangaTantou
. The command first creates a new Author
and Manga
instance.
If the newly created Author
is undocumented by MangaTantou
, a
TantouException
is thrown, informing the user that this Author
does not exist and the Manga
cannot be removed.
If the Author
instead exists, MangaTantou
will check for the existence of the newly created
Manga
. If there is no existing
association between the Manga
and Author
, a
TantouException
is thrown, informing the user that they are deleting a non-existing Manga
. Otherwise,
the Manga
is removed from the Author
’s MangaList
and the current state of AuthorList
is saved via Storage
for
data persistence.
The following diagram illustrates the interactions that take place when the
user provides "catalog -a Kubo Tite -m Bleach -d"
as an input.
The ViewAuthorsCommand
and ViewMangasCommand
are responsible for displaying a list of the various data entries in
AuthorList
. Using the Ui
class, it formats the data into a table.
For example, view -a test1 -b -s
gives the following output (b
for by-date/deadline, s
for sales data).
view -a test1 -b -s
Mangas authored by "test1", Total: 2
no. | Manga Name | Deadline | Unit Price | Units Sold | Revenue
-----------------------------------------------------------------------------------------------------------------
1 | manga 1-1 | None | N/A | N/A | N/A
2 | manga 1-2 | None | N/A | N/A | N/A
The following UML sequence diagrams illustrate the interactions that take place when the user provides a valid
ViewMangasCommand
command (e.g. view -a test1 -b -s
, where test1
is an author that already wrote some manga).
ViewAuthorsCommand
works similarly, but with only 2 required columns to print (row number and author name).
The AddSalesCommand is responsible for adding sales data to a Manga. The command replaces the current sales values with
the newly-entered values.
The Sale data consists of two attributes: quantitySold
and unitPrice
.
For the AddSalesCommand to be successful, the manga that the sales data is associated with must exist.
If the sales
command is successful, the Sales
data is then saved via Storage.
The following sequence diagram illustrates the interactions that occur when the parser creates a new AddSalesCommand
.
**NOTE: ** The list of possible errors in parsing argument are as follows: missing arguments or flags, length of
author
/manga
name exceeded maximum value of 40 characters, length ofdeadline
string exceeded maximum value of 20 characters, negative values forquantitySold
orunitPrice
, wrong number formats forquantitySold
orunitPrice
, and numbers exceeding the value of 1,000,000,000.
AddDeadlineCommand changes the deadline on a specified manga. The deadline is kept as a String attribute
deadline
. This is set to "None"
by default when a manga is created.
When using AddDeadlineCommand
, if the manga or author inputted does not exist, they are automatically created.
The following sequence diagram illustrates the interactions that occur when the user inputs
schedule -a Kubo Tite -m Bleach -b October 2 2018
The following object diagram illustrates object structure after the above interaction is successfully run
with the input schedule -a Kubo Tite -m Bleach -b October 2 2018
.
MangaTantou
’s target users are mainly chief editors at manga publishing companies. They are usually in charge of
monitoring the work of multiple authors under them, as well as deadlines and financial information. These editors should
also have a non-trivial amount of authors to keep track of, leading to tedious work if it were to be done manually.
Additionally, they are reasonably quick at typing and are competent with CLI apps.
Can manage author and manga information more easily than a physical ledger or a mouse-oriented GUI app.
Version | As a … | I want to … | So that I can … |
---|---|---|---|
v1.0 | editor of a manga company | add authors to a list | keep track of my authors and potentially their work progress. |
v1.0 | editor | add mangas to their respective authors | keep track of what each of my authors are working on. |
v1.0 | editor | delete authors under my charge | discharge under-performing authors under my charge. |
v1.0 | editor | delete mangas under an author | discontinue a series that is unpopular with the audiences. |
v1.0 | editor | view all the authors under me | keep track of them. |
v1.0 | editor | view all the mangas of an author under me | keep track of their works. |
v1.0 | editor | be able to save the data from the app | access it again in the future. |
v2.0 | business-minded editor | add the quantity of copies sold for a manga | track the manga’s popularity amongst audiences. |
v2.0 | business-minded editor | add the unit price of each copy sold for manga | calculate the revenue earned by the series. |
v2.0 | editor | view the deadlines of the manga of an author under me | monitor their progress. |
v2.0 | business-minded editor | view the sales data of the manga of an author under me | monitor the company’s finances. |
v2.0 | business-minded editor | be able to save the sales data from the app | access it again in the future. |
MangaTantou
should work on any mainstream OS with Java 17 or above installed.For a comprehensive list of all available commands, their purposes, and expected behavior, refer to the User Guide. This guide outlines both typical and edge cases, providing a detailed reference to support manual testing and validation.
All JUnit test cases are organized within the test directory, with tests segmented by package and class to maintain focus and modularity. This structure enhances test isolation, making it easier to validate specific functionalities. Each test is designed to verify the core components of the application, ensuring that key features operate as expected.
All files required for Text UI testing are located in the text-ui-test
directory. While the input.txt
file contains
limited sample input
due to the coverage provided by JUnit tests, future developers can freely modify input.txt
and EXPECTED.txt
to
tailor tests for additional
scenarios as needed.
To execute the text UI test:
./runtest.bat
./runtest.sh