clearimportaddeditdeletedeleteShownlistfindcopyexportaddExamdeleteExamselectExamdeselectExamimportExamScoresaddScoreeditScoredeleteScoreThis 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 ModelThe 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.
helpThe 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.
clearThe 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.
listThe 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.
addThe 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:
NameEmailPhoneAddressThe following fields are optional as they may not be available for all persons:
MatricReflectionStudioTageditThe 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.
deleteThe 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.
findThe 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.
deleteShownThe 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.
importThe 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.AddCommandAddCommand 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.
copyThe 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.
exportThe 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.
addFor 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.
editFor 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.
addExamThe 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.
deleteExamThe 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).
selectExamThe 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.
deselectExamThe 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.
addScoreThe 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:
editScoreThe 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.
deleteScoreThe 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.
importExamScoresThe 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 EmailCurrently, only Email is used as a unique identifier for Person objects. However, this means that two Person objects can have different Emails 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.
exitSaving 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.
helpCommand: help
More information on usage: Getting Help
Getting more information on the usage of the app.
helpclearCommand: clear
More information on usage: Clearing All Entries
Clearing all contact information from the app.
clearimportCommand: 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.csvAlicealice@gmail.comwonderland123Importing data from a CSV File that does not exist
C:\path\to\file.csvimport i|C:\path\to\file.csvImporting data from a file that is not a CSV file
C:\path\to\file.txtimport i|C:\path\to\file.txtImporting 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.csvAlicealice@gmail.comwonderland123Importing 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.csvname 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.csvBobbob@gmail.comtown123Importing 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.csvAlicealice@gmail.com123123Importing 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.csvBobbob@gmail.comtown123Importing data from an empty CSV file
C:\path\to\file.csvimport i|C:\path\to\file.csvaddCommand: 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|excellingAlice98765432Halle09123456@u.nus.eduA1234567XR2S1excelling, studentNote: 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.eduAddress 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|A1234567XName 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.eduAdding a person with only compulsory fields.
add n|Alice p|98765432 a|Hall e|e09123456@u.nus.eduAlice98765432Halle09123456@u.nus.edu Adding a person with matriculation number
add n|Alice p|98765432 a|Hall e|alice@example.com m|A1234567XAlice98765432Hallalice@example.comA1234567Xstudent student tag is automatically added to the new person.
add n|Alice p|98765432 a|Hall e|alice@example.comAlice98765432Hallalice@example.com editCommand: 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|tag2Editing a person with repeated prefixes.
n| prefix): edit 1 n|new name n|new name 2 p|123 a|new addressName 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.comdeleteCommand: 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 1delete 0delete 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 1delete 0delete commands to try: delete, delete xDeleting 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 1deleteShownCommand: 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.
deleteShowndeleteShown xDeleting all persons.
find command such that all persons are shown, or list all persons with list.
deleteShowndeleteShown commands to try: deleteShown xlistCommand: list
More information on usage: Listing All Persons
Starting with sample data.
listlist xStarting with a filtered list.
find command such that there are multiple, but not all, persons in the list.
listfindCommand: find
More information on usage: Filtering Persons
Finding persons by contact details.
find n|Alicefind e|alicefind p|123find a|Ang Mo Kiofind t|studentfind m|A123find r|R01find s|S01Finding 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|50find mt|50find lt|-1score provided is invalid.
find mt|101score provided is greater than the maximum score of the selected exam.
Finding persons by multiple prefixes.
find n|Alice e|Alicefind n|Alice n|Bobn is duplicated.
p|, a|, e|, m|, r|, s|, t|, mt|, lt| prefixes.copyCommand: copy
More information on usage: Copying Emails
Copying the emails of all persons.
list command.
copyCopying the emails of a specific group.
find command.
copyexportCommand: export
More information on usage: Exporting Data to a CSV File
Exporting data while all persons are displayed.
list command.
exportaddressbookdata 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.
exportaddressbookdata 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 exportaddressbookdata 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.addExamCommand: addExam
More information on usage: Adding an Exam
Adding an exam with valid data
addExam n|Midterm s|100addExam n|Final s|100Adding an exam that already exists
addExam n|Final s|100Adding an exam with missing fields
addExam n|FinaldeleteExamCommand: deleteExam
More information on usage: Deleting an Exam
Deleting an exam
deleteExam 1deleteExam 2deleteExamselectExamCommand: selectExam
More information on usage: Selecting an Exam
Selecting an exam
selectExam 1selectExam 0selectExam 2selectExamdeselectExamCommand: deselectExam
More information on usage: Deselecting an Exam
Deselecting an exam
deselectExamdeselectExamimportExamScoresCommand: 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.csvalexyeoh@example.com now has a Midterm score of 50.
Importing an invalid file.
Midterm exam.invalid.json.
importExamScores i|invalid.jsonImporting 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.csvimportExamScores 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.csvMidterm 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.
addScoreCommand: 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|100100 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.2550.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|100addScore s|100addScore 3 s|addScore 3 s|101addScore 3 s|-50addScore 1 s|50.25addScore 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.
editScoreCommand: 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|90100 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|90editScore s|90editScore 1 s|editScore 1 s|101editScore 2 s|90editScore 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.
deleteScoreCommand: 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.
deleteScore100 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 0deleteScore 2deleteScore 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 PersonOur 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.