clear
import
add
edit
delete
deleteShown
list
find
copy
export
addExam
deleteExam
selectExam
deselectExam
importExamScores
addScore
editScore
deleteScore
This project is based on the AddressBook-Level3 project created by the SE-EDU initiative.
Features related to the creation and reading of CSV files were made possible through the use of the OpenCSV library.
Our project made use of AI assistance from GitHub Copilot to finish small snippets of code and to provide suggestions.
Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
(consisting of classes Main
and MainApp
) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI
: The UI of the App.Logic
: The command executor.Model
: Holds the data of the App in memory.Storage
: Reads data from, and writes data to, the hard disk.Commons
represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
Each of the four main components (also shown in the diagram above),
interface
with the same name as the Component.{Component Name}Manager
class (which follows the corresponding API interface
mentioned in the previous point.)For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI.
The UI
component uses the JavaFx UI framework. The layout of these UI parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The CommandBox
takes in user input which is passed onto the Logic
component for the user input to be parsed and executed. A CommandResult
is returned after execution and the feedback is displayed to the user through the ResultDisplay
component of the UI.
For the updating of other components in the UI, after each command execution, MainWindow
runs an update that calls the update method on PersonListPanel
, ExamListPanel
and StatusBarFooter
.
PersonListPanel
and ExamListPanel
update themselves by retrieving the filteredPersonList
and examList
from the Model
component and updating the displayed lists accordingly.
StatusBarFooter
contains the mean and median feature, and it updates itself by retrieving ScoreStatistics
from the Model
on update.
In summary, the UI
component:
Logic
component.Model
data so that the UI can be updated with the modified data.Logic
component, because the UI
relies on the Logic
to execute commands.Model
component, as the UI
updates based on items that are stored in Model
The sequence diagram below illustrates a more in-depth view of the interactions within the UI component
The UI is designed to update dynamically based on changes in the Model
. We narrowed down to two design choices for updating the UI components. They are:
Model
(e.g. adding a listener to filteredPersons in ExamListPanel). This would allow for a more loosely coupled system, but would involve more complex implementation which could get messy as the number of listeners increase.update
method in the MainWindow
that would call an update
method in all other UI components after every command. This would involve a more tightly coupled system and may involve unnecessary updates, but would be easier to implement and maintain.We chose the second design choice as having a centralized update method would allow for easier maintenance, as there is a clear indicator of how UI components are updated from MainWindow
. Adding extensions would also be more straightforward as future developers would know where to look for the update logic.
With listeners, the update logic would be scattered across multiple UI component classes, making it much harder to search and add upon the update logic.
One of our main goals was to make our codebase easy to understand and maintain, and we felt that the centralized update method would be more in line with this goal despite the slight increase in coupling and inefficiency.
API : Logic.java
Here's a (partial) class diagram of the Logic
component:
How the Logic
component works:
Logic
is called upon to execute a command, it is passed to an AddressBookParser
object which in turn creates a parser that matches the command (e.g., DeleteCommandParser
) and uses it to parse the command.Command
object (more precisely, an object of one of its subclasses e.g., DeleteCommand
) which is executed by the LogicManager
.Model
when it is executed (e.g. to delete a person).Model
) to achieve.CommandResult
object which is returned back from Logic
.Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
AddressBookParser
class creates an XYZCommandParser
(XYZ
is a placeholder for the specific command name e.g., AddCommandParser
)XYZCommandParser
uses the other classes shown above to parse the user command and create a XYZCommand
object (e.g., AddCommand
) which the AddressBookParser
returns back as a Command
object.XYZCommandParser
classes (e.g., AddCommandParser
, DeleteCommandParser
, ...) inherit from the Parser
interface so that they can be treated similarly where possible e.g, during testing.delete
CommandThe sequence diagram below illustrates the interactions within the Logic
component, taking a simple execute("delete 1")
API call as an example.
Note: The lifeline for DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
The following is a more detailed explanation on how user input is parsed into a Command
object (not mentioned above for simplicity).
XYZCommandParser
is instantiated by the AddressBookParser
, it uses the ArgumentTokenizer
class to tokenize the user input string into the arguments.
tokenize
method which returns an ArgumentMultimap
object.ArgumentMultimap
class is then used to extract the relevant arguments.Mandatory Arguments and Multiple Arguments
arePrefixesPresent
method is used to check if the arguments (i.e the corresponding prefixes) are present in the user input. If not, an exception is thrown.ArgumentMultimap
class is used to check for undesirable multiple arguments using the verifyNoDuplicatePrefixesFor
method. If multiple arguments are present, an exception is thrown.Validation of Arguments
ParserUtil
class. This class contains methods to validate different arguments extracted by the ArgumentMultimap
class based on the VALIDATION_REGEX
defined in component classes (Name.java
, Score.java
, etc.).XYZCommand
object to be executed.Note: Some commands do not require any arguments (e.g., help
, clear
, list
, exit
). In such cases, the XYZCommand
class is directly instantiated by the AddressBookParser
class without the parsing of arguments. As such, any arguments passed to these commands are ignored.
The Logic
component is designed to be the central component that executes all user commands.
This design choice was made to ensure that all commands are executed in a consistent manner, and to prevent the duplication of command execution logic across different components.
By centralizing the command execution logic in the Logic
component, we ensure that all commands are executed in the same way, regardless of the component that initiates the command execution.
This design choice also allows for easier maintenance and extensibility, as any changes to the command execution logic can be made in a single location.
API : Model.java
The Model
component,
Person
objects (which are contained in a UniquePersonList
object) and all Exam
objects (which are contained in a UniqueExamList
object).Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Person>
that can be 'observed' e.g. the UI can be bound to this list so that the UI can update when the data in the list changes.Exam
which is exposed to outsiders as an unmodifiable ObservableValue<Exam>
. This is used in conjunction with the exam and exam score implementation, and also used to update the highlighted exam on the UI.ScoreStatistics
for the currently selected Exam
. This statistic is used in conjunction with the mean and median feature. It is also exposed to outsiders as an unmodifiable ObservableValue<ScoreStatistics>
so that the UI can be bound to this value for updating.UserPref
object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref
objects.Model
represents data entities of the domain, they should make sense on their own without depending on other components)Note: An alternative (arguably, a more OOP) model is given below relating to the Person
class. It has a Tag
list in the AddressBook
, which Person
references. This allows AddressBook
to only require one Tag
object per unique tag, instead of each Person
needing their own Tag
objects.
However, we opted not to use this model. As much as possible, we tried to keep the attributes of Person
unlinked to other classes to prevent complications in our saving, import and export functionalities.
API : Storage.java
The Storage
component uses the Jackson
library to convert objects to JSON format. The conversion methods are predefined in the JsonAdapted
classes for their corresponding objects.
The Logic
class stores a StorageManager
object that implements the methods in the Storage
class. For every command that is executed, Logic
uses StorageManager
to save the updated AddressBook
through the saveAddressBook
method.
The StorageManager
class calls on the JsonAddressBookStorage
class to convert all objects in the AddressBook
to JSON formatting. The converted JSON objects are consolidated in the JsonSerializableAddressBook
class and it is serialized to JSON format and saved using the saveJsonToFile
method.
The sequence diagram below illustrates how data is saved within the Storage
component when the user issues a command.
When the application is initialized, the Storage
component reads the JSON objects from the save file and converts them back to objects that can be used to initialize the Model
component.
This is done using the readJsonFile
method of the JsonUtil
class which utilizes the methods defined in the JsonAdapted
classes to convert the saved JSON data back to objects that can be used by the Model
component.
The sequence diagram below illustrates how data is loaded within the Storage
component when the application is initialized.
In summary, the Storage
component:
AddressBookStorage
and UserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed).Model
component (because the Storage
component's job is to save/retrieve objects that belong to the Model
)Classes used by multiple components are in the seedu.addressbook.commons
package.
These classes provide utility functions that are used across different components such as
CollectionUtil
, StringUtil
, JsonUtil
etc. It also contains app wide constants and exceptions.
This section describes some noteworthy details on how certain features are implemented
As these general features do not require any arguments, the AddressBookParser
directly instantiates the corresponding command classes.
help
The help
command utilizes the java.awt.Toolkit
class to copy the user guide link to the user's clipboard.
On execution of the HelpCommand
, the copyToClipboard
method is called which retrieves the system clipboard
through Toolkit.getDefaultToolkit().getSystemClipboard()
and copies the user guide link to the clipboard by using
setContents
method.
We designed the help command to copy the user guide link directly to the clipboard as we wanted our application to be CLI optimized. This allows our target users to easily access the user guide without having to use their mouse to navigate to the user guide link.
clear
The clear
command allows users to clear all persons and exams from the persons and exams list.
The ClearCommand
simply sets the AddressBook
in the Model
component to a new AddressBook
object, effectively clearing all persons and exams from the persons and exams list.
We designed the clear
command to clear all persons and exams from their respective lists to provide users with a quick and easy way to reset the application to its initial state. This is useful for users who want to start over or clear the application for a fresh start.
list
The list
command allows users to list all persons in the persons list.
The ListCommand
retrieves the filteredPersonList
from the Model
component and returns a CommandResult
object containing the list of persons to be displayed on the UI.
We designed the list
command to list all persons in the persons list to provide users with a quick and easy way to view all persons in the persons list. This is useful to revert the UI back to the default view after a find command has been executed which filters the persons displayed on the UI.
All contacts are stored as Person
objects in the UniquePersonList
object under the AddressBook
of the Model
component.
There is an additional filteredPersons
list stored in the Model
component that stores the persons currently displayed in the PersonListPanel
on the UI. This list is updated whenever the user issues a command that might change the persons displayed in the PersonListPanel
.
add
The add
command allows users to add a person to the persons list.
The user can specify the person's:
Name
),Phone
),Address
),Email
),and optionally provide additional information such as their:
Matric
),Reflection
),Studio
),Tag
).The AddCommandParser
class is responsible for parsing user input to extract the details of the person to be added. It uses the ArgumentTokenizer
to tokenize the input string, extracting prefixes and their associated values. It ensures that all mandatory fields are present and that there are no duplicate prefixes in the user input.
The AddCommand
class creates a new Person
object with the parsed details.
The Person
object is then added to the UniquePersonList
through the addPerson
method in the Model
component.
The sequence diagram below illustrates a more in-depth view of the interactions regarding the parsing of user input.
It takes an add command: execute(add n|Dohn Joe p|98765432 a|123 e|dohn@gm.com m|A1234567X s|S1 r|R1)
as an example.
Note: The lifeline for AddCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
The parsing is detailed as follows:
Use of Email
Field as Unique Identifier
We have chosen to use the Email
field as a unique identifier. Due to the real-world implementation of email addresses, and specifically in NUS, email addresses are unique to each person. This allows for easy identification of persons and prevents the creation of duplicate persons with the same email address.
This is opposed to using the Name
field as a unique identifier, as an app with our proposed scale will likely be handling a large number of persons potentially having the same name. This would make it difficult to identify or keep track of persons with the same name.
Compulsory and Non-compulsory Fields
We have chosen to make the following fields compulsory as they are essentials and most likely available to the head TA:
Name
Email
Phone
Address
The following fields are optional as they may not be available for all persons:
Matric
Reflection
Studio
Tag
edit
The edit
command allows a user to edit the details of an existing person.
The EditCommandParser
class is responsible for parsing user input to extract the index of the person to be edited and the new details of the person.
It uses the ArgumentTokenizer
class to tokenize the user input string, extracting the index of the person to be edited and the new details of the person. It ensures that the index is valid and that there are no duplicate prefixes in the user input.
The EditCommand
first retrieves the person to be edited from the Model
component.
This is done by first retrieving the filteredPersonList
from the Model
component using the getFilteredPersonList
method
The person to be edited is then retrieved from the filteredPersonList
using the index provided by the user.
The EditCommand
then creates a new Person
object with the new details provided by the user and the selected person's existing details. The Person
object is then updated in the UniquePersonList
through the setPerson
method in the Model
component.
The activity diagram below illustrates the workflow involved in executing the edit
command. In practice, a Reject
activity will result in a CommandException
being thrown.
delete
The delete
command allows a user to delete a person with the specified index.
The DeleteCommandParser
class is responsible for parsing user input to extract the index of the person to be deleted. It uses the ArgumentTokenizer
to tokenize the input string, extracting the index of the person to be deleted and ensuring that the index is valid.
The DeleteCommand
class first retrieves the person to be deleted from the Model
component. This is done by first retrieving the filteredPersonList
from the Model
component using the getFilteredPersonList
method. The person to be deleted is then retrieved from the filteredPersonList
using the index provided by the user. The DeleteCommand
then deletes the person from the UniquePersonList
through the deletePerson
method in the Model
component.
For more details on the implementation of the delete
command, refer to the Delete Command Sequence Diagram.
We have chosen to implement the delete
command to accept the index of the person to be deleted to maximize convenience for the user. The numbering of the lists will be displayed to the user, making indexing very intuitive.
find
The find
command lets users search for persons by substring matching. The user can select any parameter to search under:
NAME
, EMAIL
, TAG
, MATRIC
, REFLECTION
, STUDIO
, and TAGS
can all be used. E.g. to search for all persons under studio S2
, the user can use find s|s2
.
The user can also use two other prefixes: lt
and mt
to search for persons with scores less than or more than a certain value respectively.
E.g. find mt|50
will return all persons with scores more than 50.
The find
feature makes use of the predicate classes PersonDetailPredicate
and ExamPredicate
, as well as the method updateFilteredPersonList
to update the model to show only persons that fulfill the criteria that the user has keyed in.
The FindCommandParser
class is responsible for parsing user input to extract search criteria. It uses the ArgumentTokenizer
to tokenize the input string,
extracting prefixes and their associated values. Next, the method verifyNoDuplicatePrefixesFor
ensures that there are no duplicate prefixes in the user input.
Following that, the extractPrefixForFindCommand
method ensures that only one valid, non-empty prefix is provided in the input.
After which, the extractValidKeyword
method ensures that the keyword provided in the input is valid in the case that the prefix is mt|
or lt|
,
since these two prefixes specifically require a numerical value as the keyword instead of a string value.
The FindCommand
class is responsible for executing the command for filtering the list in the application.
Using the prefix and keyword from parsing user input, a FindCommand
is created. the execute
method is then called by the LogicManager
.
Creating Predicate
Note: The PersonDetailPredicate
and ExamPredicate
classes implement the Predicate
interface to filter contacts based on the search criteria.
A brief overview of the two classes is given below:
PersonDetailPredicate
takes a prefix and keyword as parameters, allowing it to filter contacts based on specific details like name, phone number, etc.ExamPredicate
takes a prefix, a keyword, and an exam as parameters, allowing it to filter contacts based on exam scores of a specific exam.The find command first checks if an exam is required by checking if the prefix is mt|
or lt|
.
If an exam is required, the selectedExam
is retrieved from the model
and passed to the ExamPredicate
constructor along with the prefix and keyword.
Otherwise, the PersonDetailPredicate
class is created with the prefix and keyword.
Updating Filtered Person List
The ModelManager
class implements the Model
interface and manages the application's data. It maintains a filteredPersons
list,
which is a filtered list of contacts based on the applied predicate. The updateFilteredPersonList
method implemented in ModelManager
updates the filtered list based on the predicate provided.
When the FindCommand
is executed, the updateFilteredPersonList
method is called with either the PersonDetailPredicate
or ExamPredicate
as a parameter.
This updates the filteredPersons
list to show only persons that fulfill the conditions set in the test
method in either of the predicates.
User Interface Interaction
After the filteredPersons
list is updated, the user interface is updated such that the PersonListPanel
now shows persons that fulfill the predicate generated by the original user input.
The following sequence diagram illustrates the find
command with the user input find n|Alice
.
The next sequence diagram details the creation of the predicate, as well as the updating of the filteredPersons
list in the Model
component.
The following activity Diagram illustrates the user execution of the find
command.
The next activity diagram is an expansion of the previous diagram, detailing the case where the user searches for contacts based on exam scores.
User Interface Consistency
The choice of implementing the command to use prefixes to determine the filter criteria ensures consistency with other commands in the application. As this command follows a similar structure to all other commands, it is easier for users to learn and use the application.
Flexibility in Search Criteria
By allowing users to specify search criteria using different prefixes (name, phone, email, etc.), the implementation offers flexibility. Users can search for contacts based on various details, enhancing the usability of the feature.
In the context of our potential users, we considered that users would likely have to sometimes filter students by their classes, or filter people by their roles (student, tutor, professor). So we opted to implement this feature with the flexibility of using all prefixes to account for all these potential use cases.
Furthermore, with consideration that our potential users will interact with exam scores, we wanted to integrate the find functionality
to search for contacts based on exam scores. Hence, we decided to introduce the mt|
and lt|
prefixes to allow users to search for contacts based on exam scores.
Two Predicate Classes
The implementation of two predicate classes, PersonDetailPredicate
and ExamPredicate
, allows for a clear separation of concerns.
The PersonDetailPredicate
class is responsible for filtering contacts based on details like name, phone number, etc.,
while the ExamPredicate
class is responsible for filtering contacts based on exam scores.
The alternative would be to have a single predicate class that handles all filtering, but this would make this supposed class more complex and harder to maintain.
Predicate-based Filtering
As the Model
class was built prior to the implementation of this feature, we did our best to re-use available methods
instead of unnecessarily reprogramming already existing logic. Hence, we decided to craft the command around the idea of a
custom predicate as the Model
class already had a updateFilteredPersonList
method implemented that would filter persons using a predicate.
Extensibility
This design allows for easy extension to accommodate future enhancements or additional search criteria.
New prefixes can be added to support additional search criteria without significant changes as we merely need to update our Predicate
logic.
This ensures that the implementation remains adaptable to evolving requirements and we can upgrade and improve the feature whenever required.
deleteShown
The deleteShown
command relies on the filteredPersons
list in the Model
component to delete the persons currently displayed in the PersonListPanel
.
The deleteShown
command first retrieves the filteredPersons
list from the Model
component using the getFilteredPersonList
method. The deleteShown
command then iterates through the filteredPersons
list and deletes all currently shown Persons
from the UniquePersonList
.
If the currently filtered list does is not showing between 0 and the total number of existing persons, the deleteShown
command will throw a CommandException
.
After deleting all persons currently displayed in the PersonListPanel
, the filteredPersons
list in the Model
component is updated to show all remaining persons in the persons list.
The following activity diagram illustrates the workflow of the execution of the deleteShown
command:
Reliance on find
Command
Similarly to the copy
command, the deleteShown
command is designed to be used with the find command, which filters the persons displayed in the PersonListPanel
. Consequently, the flexibility of the deleteShown
command relies heavily on the implementation of the find
command. Due to this dependency, any changes to the find
command may affect the functionality of the deleteShown
command.
import
The import
command allows users to import contacts from a CSV file. Users can specify the file path of the CSV file to
import contacts from and with the validation and checking of the CSV rows, person objects can be added to the persons list in the application.
The ImportCommandParser
class is responsible for parsing user input to extract the file path of the CSV file to be imported. It uses the ArgumentTokenizer
to tokenize the input string, extracting the file path of the CSV file to be imported.
The ImportCommand
class first makes use OpenCSV
library which parses the CSV file into a List<String[]>
, with each String[]
representing a row in the CSV file. The List<String[]>
is further parsed row by row by the readCsvFile
method, which
returns a Pair
. The key of the returned Pair
is a personsData
list containing the Person
objects successfully parsed from the CSV file and the value is an error report containing all the errors that occurred during the process of reading from the CSV file.
The ImportCommand
then iterates through the personsData
list and adds each Person
object to the Model
component
through repeated use of the AddCommand
. Errors that occur during this process are also added to the error report.
In summary, The import process is done in the following steps:
ImportCommand
reads the CSV file with the given file path.AddCommand
AddCommand
is then executed passing the same model as import command.AddCommand
then adds the person to the model.Handling duplicate persons
Duplicate records in the imported CSV file is handled by AddCommand
, which will check if the person already exists in the model. If the person already exists, the AddCommand
throws a CommandException
which is caught by the ImportCommand
and added to an error report.
Handling invalid CSV files
Invalid files are handled by ImportCommand
, with the help of ImportCommandParser
and CsvUtil
. ImportCommandParser
will check if is a CSV file.
CsvUtil
will check if the CSV file is valid and will return a list of persons and an error report. The error report will be displayed to the user if there are any errors.
Overall, the conditions checked are:
name
, email
, address
, phone
)are present. Optional headers will be read if present. Headers in the CSV that are not a field in Person
will be ignored.If the file is not valid, an error message will be returned.
Handling duplicate headers in the CSV file
Handled by CsvUtil. The first occurrence of the header will be used and the rest will be ignored.
The sequence diagrams below illustrates the interactions within the Logic
component when the user issues the command import
.
Parsing
Execution
Reference Diagram for each addCommand in importCommand
Usage of AddCommand
The main concern in the increased coupling between ImportCommand
and AddCommand
. However, we established that this coupling was actually a good thing, as the incorporation of the AddCommand
allowed us to reuse the validation and error handling that was already implemented in the AddCommand
. Furthermore, should we ever need to change the validation and error handling in the AddCommand
, the ImportCommand
would automatically inherit these changes. By making AddCommand
the gate in which all persons are added to the model, we ensure that all persons added to the model are validated and handled in the same way.
copy
The copy
command enables users to quickly copy the email addresses of the persons currently displayed to them in the
PersonListPanel
. The copied emails are stored in the users' clipboard and can be pasted into an email client.
This feature is useful when users need to send emails to a group of persons.
The copy command is a child of the command
class and relies on the filteredPersons
list in the Model
component,
as well as the java.awt
package to copy the emails of all currently displayed persons to the users' clipboard.
The CopyCommand
class is instantiated directly by the AddressBookParser
class when the user inputs the copy
command.
This is because the copy
command does not require any additional arguments from the user.
The CopyCommand
class is responsible for executing the command for obtaining the emails of the filtered persons and copying them to the clipboard.
It iterates through the filteredPersons
list in the Model
component and extracts the email addresses of each person.
The email addresses are then concatenated into a single string, separated by commas, and copied to the clipboard using the java.awt
package.
User Interface Interaction
After the CopyCommand
is executed, the UI
component updates the ResultDisplay
to show a message indicating that the emails have been copied to the clipboard.
The following activity diagram summarizes the steps involved in executing the copy
command:
Reliance on find
Command
The copy
command is designed to be used with the find command, which filters the persons displayed in the PersonListPanel
.
Consequently, the flexibility of the copy
command relies heavily on the implementation of the find
command.
Due to this dependency, any changes to the find
command may affect the functionality of the copy
command.
Due to the simplicity of the copy
command, there are limited opportunities for extending its functionality.
However, future enhancements could include the ability to copy other details of persons, such as phone numbers or addresses.
Alternative 1: Copying emails of all persons
Copies the emails of all persons in the persons list, regardless of whether they are currently displayed in the PersonListPanel
.
However, this approach may lead to users copying a large number of emails unintentionally, which could be overwhelming.
Furthermore, it may not be clear to users which emails are being copied.
Alternative 2: Copying emails into a file
Instead of copying the emails to the clipboard, the emails could be saved into a file. This approach would allow users to access the emails at a later time and would prevent the loss of copied emails if the clipboard is cleared. However, it may be less convenient for users who want to paste the emails directly into an email client.
export
The export
command allows users to export the details of each person currently displayed in the PersonListPanel
to a CSV file. The CSV file is generated in the file ./addressbookdata/avengersassemble.csv
.
The user can first use the find
feature to filter out the relevant persons, which will be displayed in the PersonListPanel
.
The export
feature also relies on the Jackson Dataformat CSV module and the Jackson Databind module to write the details of persons to the CSV file ./addressbookdata/avengersassemble.csv
.
The export
command does not require any additional arguments from the user. Hence, an ExportCommandParser
class is not required.
AddressBookParser
directly creates an ExportCommand
object.
Data Retrieval
The execute
method retrieves the filteredPersons
list in Model
by calling the getFilteredPersonList()
method in Model
.
This list stores the relevant persons currently displayed in the PersonListPanel
.
It then creates a temporary AddressBook
object and iterates through the filteredPersons
list to add each person from the list into the AddressBook
.
The data is then written to a JSON file named filteredaddressbook.json
with the writeToJsonFile
method in ExportCommand
.
The execute
method also retrieves the address book file path by calling the getAddressBookFilePath()
method in Model
(this AddressBook
stores information of all persons and exams).
This file path is retrieved to obtain information on the examinations added in the application
The sequence diagram illustrates the interactions between the Logic
and Model
components when data is being retrieved from Model
when export
is executed:
JSON File Handling
The contents of both the JSON files retrieved in the above section is read with the readJsonFile()
method in ExportCommand
and returned as JSON trees, filteredJsonTree
and unfilteredJsonTree
.
This method uses Jackson's ObjectMapper
.
filteredJsonTree
, the persons array is extracted using the readPersonsArray()
method in ExportCommand
to obtain the filtered persons and their data.unfilteredJsonTree
, the exams array is extracted using the readExamsArray()
method in ExportCommand
to obtain the exams.CSV Conversion
A CSV file, avengersassemble.csv
, to write the data to, is created.
Its directory is also created using the createCsvDirectory()
method in ExportCommand
if the directory does not exist.
The CSV schema is dynamically built based on the structure of the JSON array using the buildCsvSchema()
method in CsvUtil
. This method relies on the Jackson Dataformat CSV module to build the CSV schema.
The CSV schema and JSON data are used to write to the CSV file using Jackson's CsvMapper
.
The following sequence diagram shows the interactions within the different classes in the JSON file handling section and CSV conversion section when the export
command is executed:
Obtaining Exam Names from exams
Array in the Address Book File Path
exams
array in the address book file path.
persons
array in the filtered person's JSON file.
Adding Exam:
to Exam Names in the CSV Column Headings
Since users have the flexibility to determine the names of exams added, there's a possibility of adding an exam with the same name as a field (e.g. reflection
).
This could lead to confusion when mapping the CSV schema and JSON data.
Therefore, appending Exam:
to the beginning of exam names in the CSV column headings can help mitigate this potential confusion.
The optional Matric
field enables the user to store the matriculation number of a person. The field is stored as a Matric
in the Person
object.
Note: The optional Studio
and Reflection
fields are similarly implemented.
The Matric
class is a simple wrapper class that ensures it is valid according to NUS matriculation number format and is not empty.
The Matric
field is used by the add
and edit
commands.
add
For the add
command, as opposed to the name
and other fields, the parser does not check if a prefix for Matric
is present. This is because we define the Matric
field to be optional as contacts (e.g. professors) do not need to have a matriculation number.
The parser also verifies that there are no duplicate prefixes for Matric
in a single add
command. A new Person is then created with the Matric
field set to the parsed Matric
object.
If there is no Matric
field present, the Matric
field of the new Person
object is set to null
.
edit
For the edit
command, the parser will add or update the Matric
field of the person being edited.
A student
tag is automatically added during the parsing of the add
command based on the presence of the Matric
field of the person being added.
During the parsing of the add
command, the parser will check if the Matric
field is present, indicating that they are a student.
The parser also generates Tag
objects based on the user input. The existing tags are updated with the new automatically generated tag.
The activity diagram is as follows:
There are 4 main commands that are used to interact with the exam feature: addExam
, deleteExam
, selectExam
and deselectExam
.
All exams are stored in the UniqueExamList
object in AddressBook
of the Model
component. The Model
component also stores the currently selected exam in the selectedExam
field.
addExam
The addExam
command allows users to add an exam to the application.
The user can specify the name of the exam and the maximum score of the exam.
The exam is then added and stored in the UniqueExamList
.
The AddExamCommandParser
is responsible for parsing user input to extract the name
and the maxScore
of the exam.
It uses the ArgumentTokenizer
to tokenize the input string, extracting name
and maxScore
.
It ensures that name
and maxScore
are valid and present in the user input, and that there are no duplicate prefixes in the user input.
The name
and maxScore
are then used to instantiate an AddExamCommand
.
The AddExamCommand
class creates a new Exam
object with the parsed arguments
It adds the Exam
to the UniqueExamList
through the addExam
method in the Model
component.
If the exam already exists in the list, a CommandException
is thrown.
deleteExam
The deleteExam
command allows users to delete an exam from the application.
The user can specify the index of the exam to be deleted.
The exam is then removed from the UniqueExamList
.
The DeleteExamParser
is responsible for parsing user input to extract the index
of the exam to be deleted.
It uses the ArgumentTokenizer
to tokenize the input string, extracting the index
.
It ensures that the index
is valid and present in the user input, and that there are no other prefixes in the user input.
The index
is used to instantiate a DeleteExamCommand
.
The DeleteExamCommand
uses the index to delete the exam from the UniqueExamList
in the Model
component.
It first retrieves the UniqueExamList
by using the getExamList
method in the Model
component.
It then retrieves the exam from the UniqueExamList
using the user provided index.
If the index is greater than the size of the list, a CommandException
is thrown.
Using the retrieved exam, it then deletes the exam from the UniqueExamList
through the deleteExam
method in the Model
component.
The following two sequence diagram illustrates the interactions between the Logic and Model when an exam is modified. This diagram uses the addExam
command as an example.
Parsing
Execution
Note: deleteExam
follows a similar structure, differing in the arguments parsed and the methods called on the Model
component (e.g. deleting from UniqueExamList
instead of adding to it).
selectExam
The selectExam
command allows users to select an exam from the UniqueExamList
.
The selection of exams is heavily used in conjunction with our exam score features.
The SelectExamCommandParser
is responsible for parsing user input to extract the index
of the exam to be selected.
It uses the ArgumentTokenizer
to tokenize the input string, extracting the index
.
It ensures that the index
is valid and present in the user input, and that there are no other prefixes in the user input.
The SelectExamCommand
uses the index to select an exam from the UniqueExamList
in the Model
component.
It first retrieves the UniqueExamList
by using the getExamList
method in the Model
component.
It then retrieves the exam from the UniqueExamList
using the user provided index.
If the index is greater than the size of the list, a CommandException
is thrown.
Using the retrieved exam, it then sets the selectedExam
field in the Model
component using the selectExam
method.
deselectExam
The deselectExam
command allows users to deselect the currently selected exam.
The deselectExam
command does not take any arguments from the user.
Hence, a DeselectExamCommandParser
is not required. AddressBookParser
directly creates a DeselectExamCommand
object.
The DeselectExamCommand
uses the deselectExam
method in the Model
component to deselect the currently selected exam.
It sets the selectedExam
field in the Model
component to null
.
If there is no exam selected, a CommandException
is thrown.
The following sequence diagram illustrates the interactions between the Logic and Model when the SelectExamCommand
is executed.
Notes:
ObservableList<Exam>
object is what is returned when retrieving the UniqueExamList
. This prevents unwanted modifications to the UniqueExamList
when retrieving the selected exam.deselectExam
follows a similar structure as the diagram above, differing in the arguments parsed and the methods called on the Model
component (i.e. calling deselectExam
on Model
instead of selectExam
).We decided to implement a selection system for exams to complement the exam score feature. The application would only display the scores of the selected exam, making it easier for users to manage and view the scores.
Our alternative design was to display the scores of all exams at once on every person. However, this alternative design would have made the UI cluttered and less user-friendly. The selection system allows users to focus on the scores of a specific exam, making it easier to view and manage the scores.
We were initially torn between the selection of exams using the exam name or the index. We eventually settled on using the index as it is easier for users to type and remember short numeric codes rather than potentially long and complex exam names which are more prone to typographical errors.
We decided to allow users to deselect exams as the exam scores and score statistics are displayed based on the selected exam. Deselecting the exam allows users to get rid of the displayed scores and statistics when they are no longer needed.
The design of the exam feature allows for easy extension to accommodate future enhancements or additional functionalities. Methods for managing exams are implemented in the Model
component, and the updating of UI for Exams is abstracted into the UI component, Making it easy to add new commands or features related to exams.
There are 3 main commands that are used to interact with exam scores of each person: addScore
, editScore
and deleteScore
.
addScore
The addScore
command allows users to add a score for an exam to a person displayed in the application.
The user should select the exam they want to add a score for, then specify the index of the person they want to add a score for, and the score they want to add.
The score is then stored in a hashmap named scores
within the Person
object in the Model
component.
This hashmap maps the selected exam (an Exam
object) to the specified score (a Score
object).
The AddScoreCommandParser
is responsible for parsing the user input to extract the index of the person in the displayed list to add a score to, and the score to add.
It uses the ArgumentTokenizer
to tokenize the input string, extracting the index
and score
.
It also ensures that the index
and score
input value is valid, and that there are no duplicate prefixes in the user input.
The index
and score
is then used in instantiating the AddScoreCommand
by the AddScoreCommandParser
.
The following sequence diagram illustrates the parsing of an addScore
command with the user input addScore 1 s|100
:
The execute
method in AddScoreCommand
retrieves the filteredPersons
list in Model
, and validates the target index against the list of filtered persons to ensure it is not out of bounds.
It then fetches the person to add the score for based on the target index.
It also retrieves the currently selected exam from the Model
, and validates that the score to be added is not more than the maximum score of the selected exam.
It adds the score to the person's existing scores
hashmap using the addExamScoreToPerson
method in the Model
.
The following sequence diagram illustrates the execution of an addScore
command:
editScore
The editScore
command allows users to edit a score for an exam of a person displayed in the application.
The user should select the exam they want to edit the score for, then specify the index of the person they want to edit the score for, and the new score they want to edit to.
The updated score is then stored in a hashmap named scores
within the Person
object in the Model
component.
This hashmap maps the selected exam (an Exam
object) to the updated specified score (a Score
object).
The EditScoreCommandParser
is responsible for parsing the user input to extract the index of the person in the displayed list to edit the score for, and the new score to edit to.
It uses the ArgumentTokenizer
to tokenize the input string, extracting the index
and score
.
It also ensures that the index
and score
input value is valid, and that there are no duplicate prefixes in the user input.
The index
and score
is then used in instantiating the EditScoreCommand
by the EditScoreCommandParser
.
The execute
method in EditScoreCommand
retrieves the filteredPersons
list in Model
, and validates the target index against the list of filtered persons to ensure it is not out of bounds.
It then fetches the person to edit the score for based on the target index.
It also retrieves the currently selected exam from the Model
, and validates that the score to be added is not more than the maximum score of the selected exam.
It updates the score for the selected exam in the person's existing scores
hashmap using the addExamScoreToPerson
method in Model
.
deleteScore
The deleteScore
command allows users to delete a score for an exam from a person displayed in the application.
The user should select the exam they want to delete the score for, then specify the index of the person they want to delete the score for.
The key-value pair (exam-score) is removed from the scores
hashmap within the Person
object.
This operation removes both the selected exam (key) and the score (value), effectively deleting the score from Person
.
The DeleteScoreCommandParser
is responsible for parsing the user input to extract the index of the person in the displayed list to delete the score for.
It uses the ParserUtil
to parse the input string, extracting the index
.
It also ensures that the index
is valid, and that there are no duplicate prefixes (i.e. there is only one index
value) in the user input.
The index
is then used in instantiating the DeleteScoreCommand
by the DeleteScoreCommandParser
.
The following sequence diagram illustrates the parsing of an deleteScore
command with the user input deleteScore 1
:
The execute
method in DeleteScoreCommand
retrieves the filteredPersons
list in Model
, and validates the target index against the list of filtered persons to ensure it is not out of bounds.
It then fetches the person to delete the score for based on the target index.
It also retrieves the currently selected exam from the Model
.
It removes the score for the selected exam in the person's existing scores
hashmap using the removeExamScoreFromPerson
method in Model
.
importExamScores
The importExamScores
command lets users import exam scores corresponding to existing exams and persons from a CSV file.
The ImportExamScoresParser
class is responsible for parsing the user input. It uses the ArgumentTokenizer
to tokenize the input string, extracting the file path of the CSV file to be imported.
Parsing CSV File
The ImportExamScoresCommand
class reads the CSV file with the given file path.
The CSV file is parsed with the OpenCSV
library and a List<String[]>
is created, with each String[]
representing a row in the CSV file.
File Validation
After parsing, a mapping of Exam
objects to an inner mapping of an email
string to a Double
score is created. This mapping is used to validate the data in the CSV file.
If the file is invalid, an error message is returned.
The validation workflow for the file is as follows:
If the file is valid, any invalid entries will be ignored, with the rest being successfully processed.
A column will be ignored if:
email
column, but does not start with Exam:
.Exam
object. (i.e. Anything after Exam:
is not an existing exam name.)A row will be ignored if:
email
value does not correspond to an existing Person
.A cell will be ignored if:
Double
representing the score for an existing Person
and Exam
is not a valid Score
.Value Validation
For every valid row:
The Double
is parsed into a Score
object.
The Model
object is then used to:
Exam
object corresponding to the exam name in the row;Person
object corresponding to the email in the row;Score
object to the correct Person
for the correct Exam
.For concrete examples of the validation process, refer to the manual testing section of the importExamScores
command.
The exam statistics feature allows users to view the mean and median scores of the selected exam. The statistics are displayed in the StatusBarFooter
element of the UI on the right side.
The statistics are automatically updated whenever the selected exam is changed or when there are potential modifications to the scores of the selected exam.
When there are no scores for the selected exam, the statistics are displayed as No scores available
. When no exam is selected, the statistics are not displayed at all.
The ScoreStatistics
class is used to store the mean and median scores of the selected exam. The Model
component stores the ScoreStatistics
object for the currently selected exam as a SimpleObjectProperty<ScoreStatistics>
.
the ModelManager
class implements a updateSelectedExamStatistics
and getSelectedExamStatistics
method to update the statistics.
updateSelectedExamStatistics
is called whenever the selected exam is changed or when there are potential modifications to the scores of the selected exam (deletion of a Person, adding of Score, etc.). This ensures that the selectedExamStatistics
object is always kept up-to-date with the scores of the selected exam.
The sequence diagram below illustrates the interactions within the Model
component when the score statistics are updated using the selectExam
command as an example.
The StatusBarFooter
element of the UI is initialized with an ObservableValue<ScoreStatistics>
object. This object is bound to the selectedExamStatistics
object in the Model
component and is retrieved using the getSelectedExamStatistics
method.
Whenever a command is executed, the StatusBarFooter
retrieves the updated statistics and displays them on the right side of the footer which can be seen at the bottom of the UI.
Storage of Exam Statistics
There were considerations to just avoid the storage of the statistics and calculate them on the fly whenever needed. However, this would have been inefficient as the statistics would have to be recalculated every time the selected exam is changed or when there are potential modifications to the scores of the selected exam. By storing the statistics, we can limit recalculations to only when necessary.
Furthermore, storing the statistics allows us to maintain the code structure of our UI component, which is designed to observe and retrieve data from the Model
component. If the statistics were to be calculated on the fly, the UI component would have to either calculate the statistics itself or request the Model
component to calculate the statistics, which would have complicated the code structure by introducing more dependencies between the UI and Model components.
Using ScoreStatistics
Class
The ScoreStatistics
class was used to store the mean and median scores of the selected exam. This class was chosen as it provides a clean and structured way to store the statistics. The class also provides extensibility, as additional statistics can easily be added in the future by extending the class.
find
CommandCurrently, the find
command only validates the lt
and mt
prefixes, where other prefixes are not validated. This means that users may search for persons with fields that do not exist to begin with, which is guaranteed to return no results.
We plan to enhance the find
command to validate all prefixes other than lt
and mt
. This will ensure that users are not able to search for persons with fields that do not exist in the Person
object.
However, we need to be careful about overzealous input validation where users may still want to search for fields using incomplete parts of a field, and hence we have to balance these two considerations.
For example, an extreme case will be to search for persons with the Name
field with ~
, which is disallowed to begin with as ~
is not a valid character for a name. We plan to inform the user outright that the search is invalid and will not return any results.
Currently, the ResultDisplay
box does not wrap text, which means that long lines of text will extend beyond the width of the box. This results in the need for two scroll bars, a horizontal one for the result box and a vertical one for the currently shown list of persons. This is not ideal as it makes the UI less optimized for the target audience, who prefer using a CLI-optimized application and prefer not to use mouse controls to scroll through scroll boxes.
We plan to modify the ResultDisplay
box to wrap text so that there is no longer a need for the horizontal scroll bar in the ResultDisplay
box.
In the case where the wrapped text still exceeds the height of the ResultDisplay
box, we plan to enable it to dynamically adjust its height as needed.
Matric
and Email
Currently, only Email
is used as a unique identifier for Person
objects. However, this means that two Person
objects can have different Email
s but the same Matric
number. This clashes with the real-life constraint that NUS students, in particular CS1101S students, are put under, where Matriculation numbers are supposed to be unique for each student. Our planned enhancement hence aims to better reflect real-life constraints.
Currently, the hasPerson
method in the Model
class checks for the existence of a Person
object based on the Email
field. We plan to modify this method to check for the existence of a Person
object based on both the Email
and Matric
fields. This will ensure that two Person
objects cannot have the same Matric
number.
However, more checking needs to be done to ensure persons cannot have different overall unique identifiers, but the same Email
or Matric
field. (E.g. two persons cannot have the same Email
but different Matric
numbers.)
Additionally, some persons such as staff members and course instructors may not have a Matric
field. Hence, careful consideration needs to be made to implement this new method of checking for unique identifiers.
Currently, the sample data tags are not very helpful to the user, having tags like friends
, neighbors
and family
. This may pose confusion to users about the context of the application, which is the head TA's management of persons related to CS1101S.
Remove all Tag
objects that are in the sample data that border on irrelevancy. This can be done by modifying the SampleDataUtil
class to not add these tags to the sample data.
Retain all other relevant Tag
objects like colleagues
and student
to better reflect the context of the application.
Target user profile:
Name: Sarah Johnson, Age: 23, Occupation: Head Tutor for CS1101S
Value proposition:
Problem scope:
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * | potential user exploring the app | see the app populated with sample data | immediately see an example of the app in use |
* * * | new user | see usage instructions | refer to instructions when I forget how to use the App |
* * * | new user | easily clear the example data | start using the app with real-life data |
* * | experienced user | use the application offline | update and interact with it anywhere |
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * | head tutor using the app | import persons from a CSV file | easily add a large number of persons to the application |
* * * | new user | save the data I input into the app | don't lose the information I've entered |
* * * | user | add a new person | make minor additions to the persons in the application |
* * * | user | update and edit person details | keep my persons list accurate |
* * * | user | delete a person | remove entries that I no longer need |
* * * | user | delete a specific group of entries | remove multiple entries that I no longer need more efficiently |
* * * | user | view all saved contacts | oversee the data stored within my app |
* * * | user | find a person through their particulars | locate details of persons without having to go through the entire list |
* * * | head tutor using the app | categorize my persons into groups | manage different groups of students effectively |
* * * | head tutor using the app | copy email addresses of a group | effectively communicate with target groups |
* * * | head tutor using the app | export the details of persons to a CSV | easily share the details of a group with others |
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * | head tutor using the app | import assessment scores from a CSV file | easily add a large number of scores to the application |
* * * | head tutor using the app | add exams to the app | keep track of student performance |
* * * | head tutor using the app | delete exams from the app | remove exams that are no longer relevant |
* * * | head tutor using the app | view scores for a specific exam | analyze student scores |
* * * | head tutor using the app | add scores to the app | keep track of student performance |
* * * | head tutor using the app | edit scores in the app | correct errors in the scores |
* * * | head tutor using the app | delete scores from the app | remove scores that are no longer relevant |
* * * | head tutor using the app | export scores to a CSV file | easily share the scores with others |
* * * | head tutor using the app | view statistics of scores | analyze student performance |
(For all use cases below, the System is the AvengersAssemble
and the Actor is the user
, unless specified otherwise)
MSS:
User requests help information.
AvengersAssemble copies the link to the user guide to the user's clipboard.
User pastes the link into a browser to access the user guide.
Use case ends.
MSS:
User requests to clear the sample data.
AvengersAssemble clears the sample data.
AvengersAssemble displays a message indicating that the sample data has been cleared.
Use case ends.
MSS:
User requests to import person details from a CSV file.
AvengersAssemble imports the person details from the CSV file.
AvengersAssemble displays a message indicating that the person details have been imported.
Use case ends.
Extensions:
1a. The file to be imported is not a CSV file.
1a1. AvengersAssemble displays an error message indicating that the file type is not recognized and should be a CSV file.
Use case ends.
1b. AvengersAssemble cannot find the file to be imported.
1b1. AvengersAssemble displays a message indicating that the file is not recognized.
Use case ends.
MSS:
User requests to add a new person and inputs details for the new person.
AvengersAssemble saves the new person's information.
AvengersAssemble confirms the addition of the new person.
Use case ends.
Extensions:
1a. User does not input all compulsory parameters along with the person.
1a1. AvengersAssemble prompts the user on the proper usage of the command.
Step 1a1 is repeated until the data entered is correct.
Use case resumes at step 2.
1b. User tries to add a person with an existing email address.
1b1. AvengersAssemble displays an error message informing the user that the email address already exists.
Step 1b1 is repeated until a valid email address is entered.
Use case resumes at step 2.
MSS:
User requests to edit a specific person with updated details.
AvengersAssemble saves the updated details.
AvengersAssemble confirms the successful update.
Use case ends.
Extensions:
1a. User does not input enough parameters along with the person.
1a1. AvengersAssemble prompts the user on the proper usage of the command.
Step 1a1 is repeated until the data entered is correct.
Use case resumes at step 2.
1b. The selected person does not exist.
1b1. AvengersAssemble displays an error message indicating that the person does not exist.
Use case ends.
MSS:
User requests to list persons (UC08)
AvengersAssemble shows a list of persons
User requests to delete a specific person in the list
AvengersAssemble deletes the person
Use case ends.
Extensions:
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. AvengersAssemble shows an error message.
Use case resumes at step 2.
MSS:
User requests to find group of persons (UC09) by desired requirements
User requests to delete all listed persons.
AvengersAssemble deletes all listed persons.
AvengersAssemble displays a message to confirm that all listed persons have been deleted.
Use case ends.
Extensions:
2a. No persons are listed.
2a1. AvengersAssemble displays a message indicating that there is no persons to delete.
Use case ends.
2b. User has a filtered view that contains all existing persons.
2b1. AvengersAssemble displays a message indicating that all persons cannot be deleted at once.
Use case ends.
MSS:
User requests to list persons.
AvengersAssemble shows the list of persons.
User views the list of persons.
Use case ends.
Extensions:
2a. The list is empty.
2a1. AvengersAssemble displays a message indicating that the list is empty.
Use case ends.
MSS:
User requests to find a specific group of persons matching the search criteria.
AvengersAssemble displays a list of persons matching the criteria.
Use case ends.
Extensions:
1a. No persons match the search criteria.
1a1. AvengersAssemble displays a message indicating that no persons match the search criteria.
Use case ends.
MSS:
User requests to copy emails of currently displayed persons.
AvengersAssemble copies the emails of currently displayed persons into user's clipboard.
AvengersAssemble notifies the user that emails have been copied.
User can paste emails when composing emails.
Use case ends.
Extensions:
2a. No persons currently displayed.
2a1. AvengersAssemble displays a message indicating that no persons are currently displayed.
Use case ends.
MSS:
User requests to filter persons (UC09) by desired requirements
User requests to export all listed persons and details to a CSV file.
AvengersAssemble exports the persons to a CSV file.
AvengersAssemble displays a message to confirm that all listed persons have been exported to a CSV file.
Use case ends.
Extensions:
2a. No persons are listed.
2a2. AvengersAssemble displays a message indicating that there is no persons to export.
Use case ends.
MSS:
User requests to import exam results from a CSV file.
AvengersAssemble displays a message that all exam results have been imported.
Use case ends.
Extensions:
2a. AvengersAssemble cannot find the file specified.
2a1. AvengersAssemble displays a message indicating that the file is not recognized.
Use case ends.
2b. The file to be imported is not a CSV file.
2b1. AvengersAssemble displays an error message indicating that the file type is not recognized and should be a CSV file
Use case ends.
2c. There are duplicate entries in the CSV file.
2c1. AvengersAssemble displays a message indicating that there are duplicate entries in the CSV file, and only the first instance has been kept.
Use case ends.
2d. The CSV file contains invalid entries.
2d1. AvengersAssemble displays a message indicating that there are invalid entries in the CSV file, and all other valid entries have been imported.
Use case ends.
MSS:
User requests to add an exam.
AvengersAssemble displays a message that the exam has been added.
Use case ends.
Extensions:
1a. User does not input all compulsory parameters along with the exam.
1a1. AvengersAssemble prompts the user on the proper usage of the command.
Step 1a1 is repeated until the data entered is correct.
Use case resumes at step 2.
1b. User tries to add an exam with an existing name.
1b1. AvengersAssemble displays an error message informing the user that the exam name already exists.
Step 1b1 is repeated until a valid exam name is entered.
Use case resumes at step 2.
1c. User tries to add an exam with an invalid score.
1c1. AvengersAssemble displays an error message informing the user that the score is invalid.
Step 1c1 is repeated until a valid score is entered.
Use case resumes at step 2.
1d. User tries to add an exam with an invalid name.
1d1. AvengersAssemble displays an error message informing the user that the name is invalid.
Step 1d1 is repeated until a valid name is entered.
Use case resumes at step 2.
MSS:
User requests to delete an exam.
AvengersAssemble displays a message that the exam has been deleted.
Use case ends.
Extensions:
1a. The exam does not exist.
1a1. AvengersAssemble displays an error message indicating that the exam does not exist.
Use case ends.
MSS:
User requests to select an exam.
AvengersAssemble displays the scores of the selected exam.
Use case ends.
Extensions:
1a. The exam does not exist.
1a1. AvengersAssemble displays an error message indicating that the exam does not exist.
Use case ends.
MSS:
User requests to deselect an exam.
AvengersAssemble displays the persons without the scores of the selected exam.
Use case ends.
Extensions:
1a. The exam does not exist.
1a1. AvengersAssemble displays an error message indicating that the exam does not exist.
Use case ends.
MSS:
User requests to select an exam (UC15) to add scores to.
User requests to add scores to a student for the selected exam.
AvengersAssemble displays a message that the scores have been added.
Use case ends.
Extensions:
2a. The student does not exist.
2a1. AvengersAssemble displays an error message indicating that the student does not exist.
Use case ends.
2b. The student already has a score for the exam.
2b1. AvengersAssemble displays an error message indicating that the student already has a score for the exam.
Use case ends.
MSS:
User requests to select an exam (UC15) to edit scores for.
User requests to edit scores for a student for the selected exam.
AvengersAssemble displays a message that the scores have been edited.
Use case ends.
Extensions:
2a. The student does not exist.
2a1. AvengersAssemble displays an error message indicating that the student does not exist.
Use case ends.
2b. The student does not have a score for the exam.
2b1. AvengersAssemble displays an error message indicating that the student does not have a score for the exam.
Use case ends.
2c. The score is invalid.
2c1. AvengersAssemble displays an error message indicating that the score is invalid.
Use case ends.
MSS:
User requests to select an exam (UC15) to delete scores for.
User requests to delete scores for a student for the selected exam.
AvengersAssemble displays a message that the scores have been deleted.
Use case ends.
Extensions:
2a. The student does not exist.
2a1. AvengersAssemble displays an error message indicating that the student does not exist.
Use case ends.
2b. The student does not have a score for the exam.
2b1. AvengersAssemble displays an error message indicating that the student does not have a score for the exam.
Use case ends.
MSS:
User requests to select an exam (UC15) to view statistics of scores for.
AvengersAssemble displays the statistics of scores for the selected exam.
Use case ends.
Extensions:
2a. There are no scores for the exam.
2a1. AvengersAssemble does not display any statistics.
Use case ends.
MSS:
User requests to exit the application.
AvengersAssemble exits the application.
Use case ends.
Java 11
or above installed.Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Initial launch.
java -jar avengersassemble.jar
Expected: Shows the GUI with a set of sample persons. The window size may not be optimal.
Saving window preferences.
Shutdown.
exit
Saving of data.
data/avengersassemble.json
file is created. This is the storage file.
Dealing with missing or corrupted data files.
data/avengersassemble.json
file populated with sample data is created.
data/avengersassemble.json
file by adding random text to it.data/avengersassemble.json
file when launched and interacted with.
help
Command: help
More information on usage: Getting Help
Getting more information on the usage of the app.
help
clear
Command: clear
More information on usage: Clearing All Entries
Clearing all contact information from the app.
clear
import
Command: import
More information on usage: Importing Persons
The import command requires the use of an external CSV file. The test cases below assume that the tests are run on a Windows system, and that the CSV file is located at the path C:\path\to\file.csv
. Please modify the filepath accordingly based on where your file is stored and your operating system.
pwd
command to get the current directory and append the file name to it.
Importing data from a CSV file
C:\path\to\file.csv
with the following content:name,email,address,phone
Alice,alice@gmail.com,wonderland,123
import i|C:\path\to\file.csv
Alice
alice@gmail.com
wonderland
123
Importing data from a CSV File that does not exist
C:\path\to\file.csv
import i|C:\path\to\file.csv
Importing data from a file that is not a CSV file
C:\path\to\file.txt
import i|C:\path\to\file.txt
Importing data from a CSV File with duplicate compulsory headers in header row
C:\path\to\file.csv
with the following content:name,email,address,phone,name
Alice,alice@gmail.com,wonderland,123,bob
import i|C:\path\to\file.csv
Alice
alice@gmail.com
wonderland
123
Importing data from a CSV file with missing compulsory headers in header row
C:\path\to\file.csv
with the following content (missing the name
header):email,address,phone
alice@gmail.com,wonderland,123
import i|C:\path\to\file.csv
name
header is missing in the error report. No change in list of persons.Importing data from a CSV file with missing compulsory values in a row
C:\path\to\file.csv
with the following content:name,email,address,phone
Alice,,wonderland,123
Bob,bob@gmail.com,town,123
import i|C:\path\to\file.csv
Bob
bob@gmail.com
town
123
Importing data from a CSV file with extra headers in header row
C:\path\to\file.csv
with the following content:name,email,address,phone,extra
Alice,alice@gmail.com,123,123,extra
import i|C:\path\to\file.csv
Alice
alice@gmail.com
123
123
Importing data from a CSV file with unequal number of values in a row as the number of headers
C:\path\to\file.csv
with the following content:name,email,address,phone
Alice,alice@gmail.com,wonderland,123,123
Bob,bob@gmail.com,town,123
import i|C:\path\to\file.csv
Bob
bob@gmail.com
town
123
Importing data from an empty CSV file
C:\path\to\file.csv
import i|C:\path\to\file.csv
add
Command: add
More information on usage: Adding a Person
Adding a person with all fields.
add n|Alice p|98765432 a|Hall e|e09123456@u.nus.edu m|A1234567X r|R2 s|S1 t|excelling
Alice
98765432
Hall
e09123456@u.nus.edu
A1234567X
R2
S1
excelling
, student
Note: If a Matric
number is provided, the person is automatically tagged as a student
.
Address
and Phone
fields): add n|Alice e|e09123456@u.nus.edu
Address
and Phone
fields are missing.
Adding a person with repeated prefixes.
Name
field): add n|Ali n|Ali p|98765432 a|Hall e|test@test.com m|A1234567X
Name
field is repeated.
Adding a person whose Email
already exists.
e1234567@u.nus.edu
already exists in the list.
Email
already exists): add n|Alice p|987 a|Hall e|e1234567@u.nus.edu
Adding a person with only compulsory fields.
add n|Alice p|98765432 a|Hall e|e09123456@u.nus.edu
Alice
98765432
Hall
e09123456@u.nus.edu
Adding a person with matriculation number
add n|Alice p|98765432 a|Hall e|alice@example.com m|A1234567X
Alice
98765432
Hall
alice@example.com
A1234567X
student
student
tag is automatically added to the new person.
add n|Alice p|98765432 a|Hall e|alice@example.com
Alice
98765432
Hall
alice@example.com
edit
Command: edit
More information on usage: Editing a Person
Editing a person with all fields.
edit 1 n|new name p|123 a|new home e|newemail@eg.com m|A0000000X r|R1 s|S1 t|tag1 t|tag2
Editing a person with repeated prefixes.
n|
prefix): edit 1 n|new name n|new name 2 p|123 a|new address
Name
field is repeated.
edit
commands to try: Commands with repeated p|
, a|
, e|
, m|
, r|
, s|
, t|
prefixes.Editing a Person's Email
to an Existing Email
.
edit 1 e|berniceyu@example.com
delete
Command: delete
More information on usage: Deleting a Person
Deleting a person while all persons are being shown.
list
command. Multiple persons in the list.
delete 1
delete 0
delete
commands to try: delete
, delete x
, ...
(where x is larger than the list size)Deleting a person while some persons are being shown.
find
command. Multiple but not all persons in the list.
delete 1
delete 0
delete
commands to try: delete
, delete x
Deleting a person while no persons are being shown.
find
command such that there are no persons in the list, or delete all persons with clear
.
delete 1
deleteShown
Command: deleteShown
More information on usage: Deleting Filtered Persons
Deleting a proper subset of all persons.
find
command such that there are multiple, but not all, persons in the list.
deleteShown
deleteShown x
Deleting all persons.
find
command such that all persons are shown, or list all persons with list
.
deleteShown
deleteShown
commands to try: deleteShown x
list
Command: list
More information on usage: Listing All Persons
Starting with sample data.
list
list x
Starting with a filtered list.
find
command such that there are multiple, but not all, persons in the list.
list
find
Command: find
More information on usage: Filtering Persons
Finding persons by contact details.
find n|Alice
find e|alice
find p|123
find a|Ang Mo Kio
find t|student
find m|A123
find r|R01
find s|S01
Finding persons by score.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select the test exam
.
find lt|50
find mt|50
find lt|-1
score
provided is invalid.
find mt|101
score
provided is greater than the maximum score of the selected exam.
Finding persons by multiple prefixes.
find n|Alice e|Alice
find n|Alice n|Bob
n
is duplicated.
p|
, a|
, e|
, m|
, r|
, s|
, t|
, mt|
, lt|
prefixes.copy
Command: copy
More information on usage: Copying Emails
Copying the emails of all persons.
list
command.
copy
Copying the emails of a specific group.
find
command.
copy
export
Command: export
More information on usage: Exporting Data to a CSV File
Exporting data while all persons are displayed.
list
command.
export
addressbookdata
containing avengersassemble.csv
is created in the same directory where the JAR file of the Avengers Assemble is located. All currently displayed persons and their details are exported to the CSV file.
Exporting data while person list is filtered.
find
command.
Exporting data with exams and exam scores added.
addExam
command. For this example, we shall add an exam with name Test Exam
.list
command.
export
addressbookdata
containing avengersassemble.csv
is created in the same directory where the JAR file of the Avengers Assemble is located. All currently displayed persons and their details are exported to the CSV file. A column with column heading Exam:Test Exam
is present in the same CSV file, but no values are present in that column.
addScore
, then export
addressbookdata
containing avengersassemble.csv
is created in the same directory where the JAR file of the Avengers Assemble is located. All currently displayed persons and their details are exported to the CSV file. A column with column heading Exam:Test Exam
is present in the same CSV file, with corresponding exam scores for each person included in that column.addExam
Command: addExam
More information on usage: Adding an Exam
Adding an exam with valid data
addExam n|Midterm s|100
addExam n|Final s|100
Adding an exam that already exists
addExam n|Final s|100
Adding an exam with missing fields
addExam n|Final
deleteExam
Command: deleteExam
More information on usage: Deleting an Exam
Deleting an exam
deleteExam 1
deleteExam 2
deleteExam
selectExam
Command: selectExam
More information on usage: Selecting an Exam
Selecting an exam
selectExam 1
selectExam 0
selectExam 2
selectExam
deselectExam
Command: deselectExam
More information on usage: Deselecting an Exam
Deselecting an exam
deselectExam
deselectExam
importExamScores
Command: importExamScores
More information on usage: Importing Exam Scores
Importing exam scores from a CSV file.
Exam
to the sample data: addExam n|Midterm s|100
./path/to/file.csv
:email,Exam:Midterm
alexyeoh@example.com,50
importExamScores i|/path/to/file.csv
alexyeoh@example.com
now has a Midterm
score of 50
.
Importing an invalid file.
Midterm
exam.invalid.json
.
importExamScores i|invalid.json
Importing a CSV file with incorrect formatting.
Midterm
exam./path/to/file.csv
:email,Exam:Midterm,email
alexyeoh@example.com,50,alexyeoh@example.com
importExamScores i|/path/to/file.csv
importExamScores
commands to try: CSV files where email is not the first header.Importing a CSV file with duplicate entries.
Midterm
exam./path/to/file.csv
:email,Exam:Midterm,Exam:Midterm
alexyeoh@example.com,50,60
importExamScores i|/path/to/file.csv
Midterm
score for the person with the email of alexyeoh@example.com
is 50
.
Importing a CSV File with invalid entries.
Midterm
exam./path/to/file.csv
:email,Exam:Midterm,Exam:Finals
alexyeoh@example.com,101,50
berniceyu@example.com,50,60
nonexistent@example.com,100,100
Test case: importExamScores i|/path/to/file.csv
Expected: A message is shown indicating that there are invalid entries in the CSV file, and all other valid entries have been imported. The errors shown are as follows:
alexyeoh@example.com
for the Midterm
exam is invalid.nonexistent@example.com
does not exist in the given list.Finals
exam does not exist.
Note that the Midterm
score for the person with the email of berniceyu@example.com
is 50
.
Other incorrect importExamScores
commands to try: CSV files with a mix of invalid scores, nonexistent emails, and nonexistent exams.
Expected: Similar to previous.
addScore
Command: addScore
More information on usage: Adding an Exam Score
Adding a score to a person while all persons are displayed.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.list
command.
addScore 1 s|100
100
is added to the first person in the list of displayed persons. The score and the name of the corresponding person will be shown in the status message.
addScore 2 s|50.25
50.25
is added to the second person in the list of displayed persons.The score and the name of the corresponding person will be shown in the status message.
addScore 0 s|100
addScore s|100
addScore 3 s|
addScore 3 s|101
addScore 3 s|-50
addScore 1 s|50.25
addScore
commands to try: addScore
, addScore INDEX s|100
(where INDEX
is larger than the list size), addScore 3 s|SCORE
(where SCORE
is non-numeric, is less than 0, more than the maximum score of the selected exams, and/or has more than 2 digits in its fractional part)Adding a score to a person while person list is filtered.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.find
command.
editScore
Command: editScore
More information on usage: Editing an Exam Score
Editing a score of a person while all persons are displayed.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.list
command.addScore
command. For this example, we shall add a score of 100
to the first person in the list.
editScore 1 s|90
100
is edited to 90
for the first person in the list of displayed persons. The score and the details of the corresponding person will be shown in the status message.
editScore 0 s|90
editScore s|90
editScore 1 s|
editScore 1 s|101
editScore 2 s|90
editScore
commands to try: editScore
, editScore INDEX s|90
(where INDEX
is larger than the list size), editScore 1 s|SCORE
(where SCORE
is non-numeric, is less than 0, more than the maximum score of the selected exam, and/or has more than 2 digits in its fractional part)Editing a score of a person while person list is filtered.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.find
command.addScore
command. For this example, we shall add a score of 100
to the first person in the list.
deleteScore
Command: deleteScore
More information on usage: Deleting an Exam Score
Deleting a score of a person while all persons are displayed.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.list
command.addScore
command. For this example, we shall add a score of 100
to the first person in the list.
deleteScore
100
is deleted from the first person in the list of displayed persons. The details of the corresponding person will be shown in the status message.
deleteScore 0
deleteScore 2
deleteScore
commands to try: deleteScore
, deleteScore INDEX
(where INDEX
is larger than the list size)Deleting a score of a person while person list is filtered.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.find
command.addScore
command. For this example, we shall add a score of 100
to the first person in the list.
More information on usage: Mean and Median of Exam Scores
Mean and median of exam scores while all persons are displayed.
addExam
command. For this example, we shall add a new exam with name test exam
and maximum score 100
.selectExam
command. For this example, we shall select test exam
from above.list
command.
addScore
to add a score of 50
to the first person in the list50
and a median score of 50
is displayed at the bottom, right corner of the GUI.
addScore
to add a score of 25
to the second person in the list and a score of 27.7
to the third person in the list50
, 25
and 27.7
, and the median of the three scores, are displayed at the bottom, right corner of the GUI.This section aims to showcase the effort put into Avengers Assemble by our team. We will highlight the difficulty level, challenges faced, and effort required in this project.
On top of the Person
entity originally implemented by AB3, Avengers Assemble also incorporates an additional entity of
Exam
, with Score
serving as a connection between the two entities.
With this additional entity added, considerations had to be made regarding the implementation of
different features, interactions between each entity, and the management and storage of these
entities. The consideration of these factors turned out to be more challenging than initially anticipated.
Moreover, in addition to enhancing the original features of AB3 to cater to our target users, Avengers Assemble also introduces
many new commands to improve the usability of our application, as well as to handle the diverse behaviors and interactions
of Person
and Exam
. This required a significant amount of effort to ensure that the new features were
implemented correctly and seamlessly integrated with the existing features.
Compared to the individual project, the group project was lower in intensity for each of us in terms of lines of code, but the coordination and communication required to ensure that the features were implemented correctly and seamlessly integrated with the existing features added a layer of complexity to the project.
Addition of New Fields to Persons
New fields such as recitation, studio, matriculation number, was added to persons to align with the context of our application.
Find
Our team improved on the existing find
command of AB3 to allow for more flexibility. With the new improvements, users
can now find not only based on the name field of persons, but also specify their search based on other fields such as
email
and recitation
. With the addition of the exam score features, we also adapted our find
command to allow users
to filter out persons less than or more than a specified score, revamping the way find
is used and handled.
Automatic Tagging of Persons
In the context of our application, it is mainly used to store students', instructors' and teaching assistants' contacts.
Hence, on top of the original behavior of the tag feature, we adapted it to automatically tag contacts with a
matriculation number as students.
User Interface
Enhancements were made to the user interface to improve the user experience. The structure of the user interface was
modified to accommodate the new features, and the theme of the application was changed to follow the theme of the
course that we were developing the application for. Furthermore, the logic for the updating of user interface was also
modified to a more developer-friendly approach which would allow developers to understand and modify the user interface
more easily.
Copy
Our team introduced a new copy command which allows for users to copy the email addresses of the currently displayed persons.
This is to cater to the context of our application, assisting head tutors with the task of making mass announcements.
Import and Export
To facilitate the handling and managing of large amounts of information, our group introduced the import and export feature to
allow for flexible data movement externally and internally. These features required extensive effort due to how bug prone
they were. This is elaborated upon in the challenges section below.
Exams and Exams Scores
The implementation of the exam and exam score features was the most significant addition to our application, requiring adjustments to existing features and the
introduction of many new commands to handle and manage the addition of exams and exam features. This feature was the most
complex and required the most effort to implement, as it involved the introduction of a new entity, Exam
, and the management
of scores for each person for each exam. This is further elaborated upon in the challenges section below.
One of the challenges we faced was understanding the existing codebase of AB3. We had to familiarize ourselves with the structure of the codebase, the interactions between the different classes, and the existing features of AB3. This required us to spend time reading through the code, discussing the existing features, and identifying potential areas where conflicts might arise when adding new features. We also had to consider how to integrate our new features using the existing structure in AB3, and how to ensure that the new features did not conflict with the existing features.
Exam
and its Interactions with Person
Our team wanted to implement a feature that would allow users to manage and store exam scores for each person.
It was clear from the start that this would require the introduction of a new entity, Exam
, to store information about
each exam. However, we found that there was a challenge in determining how to connect the Person
entity with the Exam
entity, and how to
manage and store the scores for each person for each exam. This required careful consideration and planning to ensure that
the interactions between the two entities were seamless and intuitive for users. We also had to consider how to handle the
storage of these entities and how to manage the data effectively.
Limited User Interface Space for Score Interaction
One of the greatest challenges was designing a user-friendly interface for score interaction within the limited screen space. We had to devise intuitive methods for users to view, input and manage the scores of various exams, without overwhelming the interface. This proved to be a greater challenge than initially anticipated, as we had to consider trade-offs between functionality and user experience. Lowering the complexity of the interface would result in an interface that is easier to read, but might not provide all the necessary information at a glance and require more user interactions to access the information. On the other hand, a more complex interface would provide more information at a glance, but might overwhelm users with too much information. Striking a balance between these two trade-offs was a challenge that required extended discussions and iterations before arriving at a solution that we were satisfied with: the selection system for exams.
Implementation of Exam and Exam Score Features
After coming to a consensus in regard to the user interface, implementation for exam features seemed straightforward. However, it turned out to be a lot more complex to implement than initially anticipated. Our exam features consisted of many sub-features which included the management of exams, the management of scores, the storage of scores in persons, and the importing of scores. As we were working in a collaborative environment, we had to consider how to distribute the workload in a manner that would prevent conflicts. This required early discussions of the structure of the exam and score features, and how they would interact with the existing features of AB3. We drafted up diagrams to visualize the interactions between each feature. This helped us to identify potential conflicts early on and resolve them through distributing the workload effectively and meeting regularly to discuss progress and issues.
Data Management for Exams and Scores
Handling the data for exams and scores was another challenge that we faced. We had to consider how to store the data for
each exam, how to store the scores for each person for each exam, and how to manage the data effectively.
The storage for exams was relatively straightforward, as we could create an additional list in the AddressBook
class to store
the exams. However, the storage for scores was more complex. We had to decide whether to store all the exam score data
in corresponding Exam
objects, or store each persons' exam scores in their corresponding Person
objects.
There was once again another trade-off to consider: storing all exam score data in the Exam
objects would make it
easier to implement exam operations, but would require more complex interactions between the score and person objects.
On the other hand, storing each person's score in their corresponding Person
object would make it easier to implement
operations on persons, but would require more complex interactions for exam management. We had to consider the pros and cons
of each approach, before deciding on the latter approach, as we concluded that our application was more person-centric.
A significant challenge we faced was the identification and resolution of bugs. Unit tests for our own features were relatively straightforward to implement, but we found that identifying edge cases proved to be tricky. Certain features were also a lot more bug prone than others, such as the import features, which required extensive testing to ensure that all potential errors were caught and proper error messages were displayed. We also had to ensure that the application handled these errors gracefully and did not crash when these errors occurred.
Overall, our group successfully implemented the planned features while addressing bugs and managing potential feature flaws. Despite initial hesitations about implementing significant new features like exams and exam scores, we overcame the challenge and achieved our goals.