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 ct/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, ContactListPanel, 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 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 it displays Contact object residing in the Model.API : Logic.java
Here's a (partial) class diagram of the Logic component:
The sequence diagram below illustrates the interactions within the Logic component, taking execute("delete ct/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.
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., DeleteContactCommand) which is executed by the LogicManager.Model when it is executed (e.g. to delete a contact).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., AddContactCommandParser) which uses the other classes shown above to parse the user command and create a XYZCommand object (e.g., AddContactCommand) which the AddressBookParser returns back as a Command object.XYZCommandParser classes (e.g., AddContactCommandParser, DeleteCommandParser, ...) inherit from the Parser interface so that they can be treated similarly where possible e.g, during testing.API : Model.java
Model (current design): shows AddressBook and its relations to the 3 main entities: Contact, ClassGroup, and Assignment through the Unique{Entity}List counterparts. The Model component also includes a UserPref class to store user preferences (e.g., file path of the address book data, GUI settings).
Contacts view: highlights classes and contact-related entities.
ClassGroup view: highlights classes and class group-related entities.
Assignments view: highlights classes and assignment-related entities and their relations to Contact.
The Model component,
Contact, ClassGroup, and Assignment).Unique{Entity}List (e.g., UniqueContactList, UniqueClassGroupList, UniqueAssignmentList) to manage the list of that entity and enforce uniqueness constraints.ObservableList<{Entity}> for UI binding.UserPref object exposed as a ReadOnlyUserPref.UI, Logic or Storage implementations.API : Storage.java
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 cpp.commons package.
This section describes some noteworthy details on how certain features are implemented.
The Model component manages ClassGroup entities using UniqueClassGroupList. This enforces uniqueness constraints and provides methods to add, delete, and update the entities.
Within the ClassGroup entities, the Contact entities that belong to the class are stored as a set of Strings representing the Contact's id field. This design allows us to easily make edits to contacts without having to worry about updating the ClassGroup entities, improving the efficiency of edit operations.
The Model component manages Assignment entities using UniqueAssignmentList. This enforces uniqueness constraints and provides methods to add, delete, and update the entities.
Assignment entities adopt a different approach, by introducing a separate association class ContactAssignment to represent the association between a Contact and an Assignment. This allows us to easily manage the submission status and grading details including the score of an assignment for each contact, without having to modify the Contact or Assignment entities themselves. These functionalities are abstracted out into the AssignmentManager class, which manages the ContactAssignment entities, and provides methods to add, delete, update, and retrieve the entities efficiently.
Similar to the ClassGroup design, the ContactAssignment class also stores the Contact's id field instead of a reference to the Contact object itself, for easier management of edits to Contacts. ContactAssignment objects also store the Assignment's id field so that any edits to the Assignment can be easily managed as well.
A separate UniqueContactAssignmentList is used to manage the list of ContactAssignments, and enforce uniqueness constraints for them.
As you might already have noticed, ContactAssignment objects do not store any information about classes. We chose this design to allow users to have greater control over the contacts who are allocated assignments. For example, a user can choose to allocate an assignment to the entire class initially and choose to unallocate it from certain contacts later on, without having to worry about the class information being stored in the ContactAssignment objects.
As a result of this design, any class group operations will not affect the ContactAssignment entities. Hence, users will have to manually allocate or unallocate assignments to contacts when they allocate or unallocate contacts from class groups. We believe this is a reasonable trade-off to give users more control over the assignment allocation.
The delete command now supports deleting contacts, assignments, and class groups through a single command word. DeleteCommandParser acts as a dispatcher: it inspects which prefix is present in the user input (ct/ for contacts, ass/ for assignments, c/ for class groups) and constructs the corresponding subcommand (DeleteContactCommand, DeleteAssignmentCommand, or DeleteClassGroupCommand).
The sequence diagram below illustrates the interactions within the Logic component for the command delete c/CS2103T10:
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 list command displays all contacts, assignments, or class groups to the user. This command is implemented using a family of subcommands: ListContactCommand, ListAssignmentCommand, and ListClassCommand, all extending the abstract ListCommand class.
When a user executes list contacts, the following sequence occurs:
ListCommandParser parses the contacts subcommand and creates a ListContactCommand.LogicManager executes the ListContactCommand.model.updateFilteredContactList(Model.PREDICATE_SHOW_ALL_CONTACTS) to update the filtered list in the model.Model updates its observable FilteredList<Contact>, which automatically triggers UI updates via JavaFX data binding.CommandResult is returned with ListView.CONTACTS, directing the UI to display the contact list panel.The sequence diagram below illustrates the interactions within the Logic component, taking list contacts as an example.
UI Integration:
The UI layer handles list display through a hierarchy of components:
MainWindow contains three list panel placeholders: contactListPanelPlaceholder, assignmentListPanelPlaceholder, and classListPanelPlaceholder.ContactListPanel, AssignmentListPanel, and ClassGroupListPanel each accept an ObservableList<T> and render it as a list of individual cards through a JavaFX ListView.ContactCard, AssignmentCard, ClassGroupCard) displays the entity with its index and details.CommandResult.ListView enum controls which panel is displayed to the user.The class diagram below illustrates the structure of the UI components involved in displaying list data.
Design:
Both list and find-related commands use predicates to filter data, providing a consistent approach to data manipulation. The observable lists in JavaFX automatically update the UI whenever the filtered list changes, eliminating the need for manual UI refresh calls.
The find-related commands allow users to search for contacts, assignments, or class groups using various search criteria. Three separate find commands are implemented: findcontact (findct), findass, and findclass (findc), each supporting different search modes and predicates.
Here is a class diagram showing the structure of the Find command hierarchy:
findcontact / findct)FindContactCommand (alias: findct) supports three search modes:
n/CONTACT_NAME_SEARCH_STRING): Substring-based name matching (case-insensitive). Uses ContactNameContainsKeywordsPredicate.p/PHONE_NUMBER_SEARCH_STRING): Substring-based phone number match. Uses ContactPhoneMatchesKeywordsPredicate.e/EMAIL_SEARCH_STRING): Substring-based email address match (case-insensitive). Uses ContactEmailMatchesKeywordsPredicate.Exactly one of the three search modes must be specified per command.
findass)FindAssignmentCommand supports two search modes:
ass/ASSIGNMENT_NAME_SEARCH_STRING): Substring-based matching (case-insensitive). Uses AssignmentNameContainsKeywordsPredicate.ds/DEADLINE and/or de/DEADLINE): Date range matching supporting formats dd-MM-yyyy or dd-MM-yyyy HH:mm. Uses AssignmentDeadlineInRangePredicate.Exactly one of the two search modes must be specified per command. For deadline search, at least one of ds/ or de/ must be provided.
findclass / findc)FindClassCommand (alias: findc) supports one search mode:
c/CLASS_NAME_SEARCH_STRING): Substring-based matching (case-insensitive). Uses ClassNameContainsKeywordsPredicate.Each Find*CommandParser is responsible for:
Find*Command with the predicate.When a user executes findcontact n/alice l:
FindContactCommandParser identifies the name search mode and extracts the substring "alice l".ContactNameContainsKeywordsPredicate with this substring.FindContactCommand is instantiated with this predicate.LogicManager executes the command, which calls model.updateFilteredContactList(predicate).Model updates its FilteredList<Contact>, automatically triggering UI refresh through JavaFX observables.CommandResult with ListView.CONTACTS directs the UI to display the filtered contact list.The sequence diagram below illustrates the interactions within the Logic component for the findcontact n/alice command:
Each predicate implements the Predicate<T> interface to evaluate whether an entity matches the search criteria:
ContactNameContainsKeywordsPredicate: Checks if the contact's name contains the search string as a substring (case-insensitive).ContactPhoneMatchesKeywordsPredicate: Checks if the contact's phone contains the search string as a substring (case-insensitive).ContactEmailMatchesKeywordsPredicate: Checks if the contact's email contains the search string as a substring (case-insensitive).AssignmentNameContainsKeywordsPredicate: Checks if the assignment name contains the search string as a substring (case-insensitive).AssignmentDeadlineInRangePredicate: Checks if the assignment deadline falls within the specified date/time range, supporting partial matching (e.g., 31-12-2024 matches 31-12-2024 23:59).ClassNameContainsKeywordsPredicate: Checks if the class name contains the search string as a substring (case-insensitive).findcontact, findass, findclass), making the API clear and preventing confusion about which command to use.UI Integration for Find:
The find commands uses the same UI components as the list commands. Results are displayed through ContactListPanel, AssignmentListPanel, and ClassGroupListPanel, which render the filtered data as lists of cards. This reuse ensures a consistent user experience between list and find operations.
The proposed undo/redo mechanism is facilitated by VersionedAddressBook. It extends AddressBook with an undo/redo history, stored internally as an addressBookStateList and currentStatePointer. Additionally, it implements the following operations:
VersionedAddressBook#commit() — Saves the current address book state in its history.VersionedAddressBook#undo() — Restores the previous address book state from its history.VersionedAddressBook#redo() — Restores a previously undone address book state from its history.These operations are exposed in the Model interface as Model#commitAddressBook(), Model#undoAddressBook() and Model#redoAddressBook() respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedAddressBook will be initialized with the initial address book state, and the currentStatePointer pointing to that single address book state.
Step 2. The user executes delete ct/5 command to delete the 5th contact in the address book. The delete command calls Model#commitAddressBook(), causing the modified state of the address book after the delete ct/5 command executes to be saved in the addressBookStateList, and the currentStatePointer is shifted to the newly inserted address book state.
Step 3. The user executes addcontact n/David ... to add a new contact. The addcontact command also calls Model#commitAddressBook(), causing another modified address book state to be saved into the addressBookStateList.
Note: If a command fails its execution, it will not call Model#commitAddressBook(), so the address book state will not be saved into the addressBookStateList.
Step 4. The user now decides that adding the contact was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoAddressBook(), which will shift the currentStatePointer once to the left, pointing it to the previous address book state, and restores the address book to that state.
Note: If the currentStatePointer is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The undo command uses Model#canUndoAddressBook() to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
The following sequence diagram shows how an undo operation goes through the Logic component:
Note: The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Similarly, how an undo operation goes through the Model component is shown below:
The redo command does the opposite — it calls Model#redoAddressBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the address book to that state.
Note: If the currentStatePointer is at index addressBookStateList.size() - 1, pointing to the latest address book state, then there are no undone AddressBook states to restore. The redo command uses Model#canRedoAddressBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
Step 5. The user then decides to execute the command list. Commands that do not modify the address book, such as list, will usually not call Model#commitAddressBook(), Model#undoAddressBook() or Model#redoAddressBook(). Thus, the addressBookStateList remains unchanged.
Step 6. The user executes clear, which calls Model#commitAddressBook(). Since the currentStatePointer is not pointing at the end of the addressBookStateList, all address book states after the currentStatePointer will be purged. Reason: It no longer makes sense to redo the addcontact n/David ... command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire address book.
Pros: Easy to implement.
Cons: May have performance issues in terms of memory usage.
Alternative 2: Individual command knows how to undo/redo by itself.
Pros: Will use less memory (e.g. for delete, just save the contact, class, or assignment being deleted).
Cons: We must ensure that the implementation of each individual command are correct.
Target user profile:
Value proposition:
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a ... | I want to ... | So that I can ... |
|---|---|---|---|
* * * | new user | see usage instructions | refer to instructions when I forget how to use the App |
* * * | user | delete a contact | remove entries that I no longer need |
* * * | user | find a contact by name | locate details of contacts without having to go through the entire list |
* * * | user | find contacts by a specific field | narrow down my search to quickly locate the right contact |
* * * | user | add contacts | easily retrieve them |
* * * | user | see all the assignments/contacts/classes | keep track of what has been created |
* * | busy user | add deadlines/events | keep track of my things easily |
* * * | teacher managing many students | organize my students into separate groups by class | easily identify which students belong to which class |
* * * | teacher | see all assignment status for each student | keep track of every individual's performance |
* * * | teacher changing classes every year | easily delete multiple old contacts | keep only the contacts I need accessible |
* * * | CLI user | quickly exit the program | avoid wasting time clicking the close button |
* * | user | hide private contact details | minimize chance of someone else seeing them by accident |
* * | user pursuing efficiency | sort contacts by date accessed | easily find the most recently contacted contacts |
* * | forgetful teacher | find students even when I mistype their names | easily find them even if I don't remember the exact spelling of their name |
* * | user with multiple devices | export and import selected contacts | easily switch between devices |
* * | meticulous teacher | add private notes (e.g., allergies) | recall critical student welfare details |
* * | user | retrieve my input history | avoid retyping the entire command when I make a small typo |
* * | careless user | undo my actions | correct mistakes without losing my work |
* * | form teacher | retrieve emails of a specific group | blast announcements via email without manual entry |
* * | teacher managing committees | assign custom tags (e.g., "Prefect") | filter students by extra-curricular roles |
* * | normal user | edit contact details | keep my contact information up to date |
* * | teacher | edit class details | keep class information up to date |
* * | teacher | edit assignment details | keep assignment information up to date |
* * | teacher | mark an assignment as submitted for a student | track which students have handed in their work |
* * | teacher | unmark a submission for a student | correct accidental submission records |
* * | teacher | grade an assignment for a student | record the student's score for that assignment |
* * | teacher | remove a grade for a student | correct grading mistakes |
* | user with many contacts in the address book | sort contacts by name | locate a contact easily |
* | forgetful user | have reminders for my deadlines | do not forget my tasks |
* | busy user | view all events/deadlines in a calendar view | see my schedule at a glance |
(For all use cases below, the System is ClassroomPlusPlus and the Actor is the user (a teacher), unless specified otherwise)
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
MSS
Extensions
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.
Adding a class group and checking that it is added successfully
Prerequisites: The class group "Class 1" does not exist in the address book, and the user is currently on the Classes tab in the GUI.
Test case: addclass c/Class 1
Expected outcome:
New class group added: Class 1
Adding a class group that already exists
Prerequisites: The class group "Class 1" already exists in the address book.
Test case: addclass c/Class 1
Expected outcome:
This class group already exists in the address book
Adding an assignment and checking that it is added successfully
Prerequisites: The assignment "Assignment 1" does not exist in the address book, and the user is currently on the Assignments tab in the GUI.
Test case: addass ass/Assignment 1 d/12-12-2026 23:59
Expected outcome:
New assignment added: Assignment 1; Deadline: 12-12-2026 23:59
Adding an assignment that already exists
Prerequisites: The assignment "Assignment 1" already exists in the address book.
Test case: addass ass/Assignment 1 d/12-12-2026 23:59
Expected outcome:
This assignment already exists in the address book
Grading an assignment before it is submitted should fail
Prerequisites: The assignment "Assignment 1" exists in the address book, and the contact with index 1 (Alex Yeoh) is allocated the assignment but has not submitted it.
Test case: grade ass/Assignment 1 ct/1 s/90.654
Expected outcome:
Failed to grade any contacts for the assignment.
Contacts not graded (already graded): None
Contacts not graded (not submitted yet): Alex Yeoh
Contacts not graded (not allocated the assignment): None
Contacts not graded (grade time before submission time): None
Grading a submitted assignment
Prerequisites: The assignment "Assignment 1" exists in the address book, and the contact with index 1 (Alex Yeoh) is allocated the assignment and has submitted it.
Test case: grade ass/Assignment 1 ct/1 s/90.654
Expected outcome:
Graded assignment: Assignment 1; Deadline: 12-12-2026 23:59 on <Date and time at which command is executed> for 1 contact(s) with score 90.7.
Contacts graded: Alex Yeoh
Contacts not graded (already graded): None
Contacts not graded (not submitted yet): None
Contacts not graded (not allocated the assignment): None
Contacts not graded (grade time before submission time): None
The difficulty level of CPP is estimated to be around a moderate level. Extensive features have been introduced to CPP in a short amount of time.
Some achievements of our team include the introduction of 2 entirely new entities (Classes and Assignments) to the address book, which took a significant amount of effort to implement, given the complexity of integrating them into the existing system which resulted in many areas of development.
We also introduced various features related to them, such as the ability to allocate and unallocate classes and assignments, update submission status and grading details for each contact's assignment submission, and view the details of each contact, class, and assignment.
Other features that were modified to fit our new design include the edit command, which now allows editing of class and assignment details, and the delete command, which now also deletes the associated classes and assignments for a contact.
The GUI had to be modified to accommodate the new entities, to be able to display the details of each contact, class, and assignment, and to provide a way for users to navigate between the different tabs. The list and find (now findcontact, findclass, findass) commands also had a rework to be able to list and filter the different entities based on the current tab.
To make the user experience smoother, we also implemented various features such as allocation options during contact, class, and assignment creation.
Overall, the introduction of the new entities and their associated features required a significant amount of effort in terms of design, implementation, and testing, covering a wide range of areas in the codebase and requiring 21091 lines of code changes (accurate as of 29-03-2026).
Team size: 5
Filtering contacts by their submission and grading status for a specific assignment
Description: Allow users to filter contacts based on their submission and grading status for a specific assignment. For example, users can filter to see only those contacts who have submitted an assignment but have not been graded yet.
Proposed implementation: Introduce new filter options in the view command that allow users to specify the assignment and the desired submission/grading status. The system will then display the filtered list of contacts accordingly.
Example usage:
Command: view ass/Assignment 1 sub/true grade/false
Expected outcome: List of contacts who have submitted the assignment but have not been graded yet.
Sorting contacts by their scores for a specific assignment
Description: Allow users to sort contacts based on their scores for a specific assignment.
Proposed implementation: Introduce a new sort option in the view command that allows users to specify the assignment and the sorting criteria (e.g., ascending or descending order of scores). The system will then display the list of contacts accordingly.
Example usage:
Command: view ass/Assignment 1 sort/score asc
Expected outcome: List of contacts sorted by their scores for the specified assignment in ascending order.
Archiving contacts, classes, and assignments
Description: Allows users to archive unused contacts, classes, and assignments, so that they can focus on the currently relevant ones.
Proposed implementation: Introduce a new archive command that allows users to move contacts, classes, and assignments to an archive state, making them hidden from the default view.
Example usage:
Command: archive ct/1 or archive c/Class 1 or archive ass/Assignment 1
Expected outcome: The specified contact, class, or assignment is moved to the archive state and hidden from the default view.
Taking attendance for classes
Description: Allows users to mark the attendance of contacts for specific classes.
Proposed implementation: Introduce a new attend and absent command that allows users to mark the attendance of contacts for a specific class.
Example usage:
Command: attend c/Class 1 ct/1 2 3 4 5
Expected outcome: The specified contacts with index 1, 2, 3, 4, 5 are marked as present for the given class.
Command: absent c/Class 1 ct/6 7 8 9 10
Expected outcome: The specified contacts with index 6, 7, 8, 9, 10 are marked as absent for the given class.
Exporting data as a CSV file
Description: Allows users to export the data in the address book to a file.
Proposed implementation: Introduce a new export command that allows users to export the data in the address book to a CSV file.
Example usage:
Command: export f/contacts.csv
Expected outcome: The data in the address book is exported to a file named contacts.csv, with the contacts, classes, and assignments organized in a structured format.