diff --git a/.gitignore b/.gitignore
index 2873e189e1..fa6282526d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,11 @@ bin/
/text-ui-test/ACTUAL.TXT
text-ui-test/EXPECTED-UNIX.TXT
+
+META-INF/MANIFEST.MF
+myLogger.log*
+src/main/META-INF/
+
+/data
+/text-ui-test/data
+text-ui-test/input.txt
diff --git a/README.md b/README.md
index e243ece764..7af3ebfd69 100644
--- a/README.md
+++ b/README.md
@@ -1,64 +1,41 @@
-# Duke project template
+# TutorLink
-This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
+TutorLink is a command-line submission management assistant for students and teaching staff alike.
-## Setting up in Intellij
-
-Prerequisites: JDK 17 (use the exact version), update Intellij to the most recent version.
-
-1. **Ensure Intellij JDK 17 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 17 in a previous Intellij project.
-1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html).
-1. **Verify the setup**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below:
```
- > Task :compileJava
- > Task :processResources NO-SOURCE
- > Task :classes
+ -------------------------------------------------------------
+ ___________ __ .____ .__ __
+ \__ ___/_ ___/ |_ ___________| | |__| ____ | | __
+ | | | | \ __\/ _ \_ __ \ | | |/ \| |/ /
+ | | | | /| | ( <_> ) | \/ |___| | | \ <
+ |____| |____/ |__| \____/|__| |_______ \__|___| /__|_ \
+ \/ \/ \/
- > Task :Duke.main()
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
-
- What is your name?
+ -------------------------------------------------------------
+ Hello! I'm TutorLink
+ What can I do for you?
+ -------------------------------------------------------------
```
- Type some word and press enter to let the execution proceed to the end.
-
-## Build automation using Gradle
-
-* This project uses Gradle for build automation and dependency management. It includes a basic build script as well (i.e. the `build.gradle` file).
-* If you are new to Gradle, refer to the [Gradle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/gradle.html).
-
-## Testing
-
-### I/O redirection tests
-
-* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script.
-
-### JUnit tests
-
-* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template.
-* If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html).
-
-## Checkstyle
-
-* A sample CheckStyle rule configuration is provided in this project.
-* If you are new to Checkstyle, refer to the [Checkstyle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/checkstyle.html).
-
-## CI using GitHub Actions
-
-The project uses [GitHub actions](https://github.com/features/actions) for CI. When you push a commit to this repo or PR against it, GitHub actions will run automatically to build and verify the code as updated by the commit/PR.
-
-## Documentation
+### Command Summary
+| **Command** | **Description** | **Example** |
+|--------------------|--------------------------------------------------------------|---------------------------------------|
+| `help` | Displays list of commands | `help` |
+| `add_student` | Adds a student to the class roster | `add_student i/A1234567X n/John Doe` |
+| `delete_student` | Deletes a student from the class roster | `delete_student i/A1234567X` |
+| `list_student` | Lists all students in the class | `list_student` |
+| `find_student` | Finds a student in the class roster by name or matric number | `find_student i/A1234567X n/John Doe` |
+| `add_component` | Adds a new grading component to the class | `add_component c/Quiz 1 w/30 m/50` |
+| `delete_component` | Deletes a grading component from the class | `delete_component c/Quiz 1` |
+| `update_component` | Updates a component with a new maxscore or weight | `update_component c/Quiz 1 w/40 m/60` |
+| `list_component` | Lists all grading components | `list_component` |
+| `add_grade` | Adds a grade for a student for a specific component | `add_grade i/A1234567X c/Quiz 1 s/45` |
+| `delete_grade` | Deletes a student's grade for a specific component | `delete_grade i/A1234567X c/Quiz 1` |
+| `list_grade` | Lists all grades for a student | `list_grade i/A1234567X` |
+| `bye` | Exits the program | `bye` |
+
+### Useful links:
+* [User Guide](/docs/UserGuide.md)
+* [Developer Guide](/docs/DeveloperGuide.md)
+* [About Us](/docs/AboutUs.md)
-`/docs` folder contains a skeleton version of the project documentation.
-Steps for publishing documentation to the public:
-1. If you are using this project template for an individual project, go your fork on GitHub.
- If you are using this project template for a team project, go to the team fork on GitHub.
-1. Click on the `settings` tab.
-1. Scroll down to the `GitHub Pages` section.
-1. Set the `source` as `master branch /docs folder`.
-1. Optionally, use the `choose a theme` button to choose a theme for your documentation.
diff --git a/build.gradle b/build.gradle
index ea82051fab..da95c94cbd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,11 +29,11 @@ test {
}
application {
- mainClass.set("seedu.duke.Duke")
+ mainClass.set("tutorlink.TutorLink")
}
shadowJar {
- archiveBaseName.set("duke")
+ archiveBaseName.set("tutorlink")
archiveClassifier.set("")
}
@@ -43,4 +43,5 @@ checkstyle {
run{
standardInput = System.in
+ enableAssertions = true
}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 0f072953ea..8ebc6e2298 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,9 +1,9 @@
# About us
-Display | Name | Github Profile | Portfolio
---------|:----:|:--------------:|:---------:
-![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
+Display | Name | Github Profile | Portfolio
+--------|:----------:|:----------------------------------------:|:---------:
+![](https://avatars.githubusercontent.com/u/66578794?v=4) | Ethan Chua | [Github](https://github.com/rcpilot1604) | [Portfolio](team/rcpilot1604.md)
+![](https://avatars.githubusercontent.com/u/88239903?s=400&v=4) | Jin Zihan | [Github](https://github.com/jinzihan2002) | [Portfolio](team/jinzihan2002.md)
+![](https://avatars.githubusercontent.com/u/123711939?v=4) | Bui The Trung | [Github](https://github.com/TrungBui32) | [Portfolio](team/trungbui32.md)
+![](https://avatars.githubusercontent.com/u/142168995?v=4) | Thien Duc | [Github](https://github.com/ThienDuc3112) | [Portfolio](team/thienduc3112.md)
+![](https://avatars.githubusercontent.com/u/53704991?v=4) | Lim Yee Kian | [Github](https://github.com/yeekian) | [Portfolio](team/yeekian.md)
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 64e1f0ed2b..31e2d61750 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -1,38 +1,434 @@
# Developer Guide
+## Table of Contents
+- [Acknowledgements](#acknowledgements)
+- [Design](#design)
+ - [Architecture](#architecture)
+- [Implementation](#implementation)
+ - [Add/Delete Student/Component Feature](#adddelete-studentcomponent-feature)
+ - [Find Student Feature](#find-student-feature)
+ - [Add/Delete Grade Feature](#adddelete-grade-feature)
+ - [Storage Load Feature](#storage-load-feature)
+- [Appendix A: Product Scope](#appendix-a-product-scope)
+- [Appendix B: User Stories](#appendix-b-user-stories)
+- [Appendix C: Non-Functional Requirements](#appendix-c-non-functional-requirements)
+- [Appendix D: Glossary](#appendix-d-glossary)
+- [Appendix E: Instructions for Manual Testing](#appendix-e-instructions-for-manual-testing)
+
+
+---
+
## Acknowledgements
-{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+This project was inspired by our experiences using the Canvas learning management system. While Canvas serves large educational environments well, we envisioned a simpler, offline tool tailored for small classes that prioritizes essential features like grade tracking, student management, and assessment organization. Thus, TutorLink was born.
+
+The design and feature set of TutorLink were developed from scratch, drawing inspiration from the need for a lightweight, offline solution for managing class assignments and reducing administrative overhead in small class environments. No code or external sources were directly referenced or reused in the development of TutorLink.
+## Design
+
+### Architecture
+
+The high-level design of TutorLink is as depicted in the following **Architecture Diagram**:
+
+![Architecture.png](diagrams/Architecture.png)
+
+**Main Components of the Architecture**
+
+TutorLink
: Main class that serves as the main entry point of the application.
+
+- At app launch, TutorLink initializes components (Parser
, Ui
, Storage
,
+ AppState
).
+- On app shutdown, it shuts down the components and invokes cleanup methods.
+
+The key classes providing functionality to TutorLink are:
+
+1. AppState
: Stores global variables/resources required by TutorLink at run time.
+2. Ui
: Collects data (via Strings sent via CLI) from the user and relays information to the user (via
+ printing back to the CLI).
+3. Parser
: Interprets the raw data from the user; applies data validation and handles necessary exceptions.
+4. Storage
: Handles the loading and storage of data to be retained even after TutorLink is shut down.
+5. CommandResult
: Represents the result of user input.
+
+## Implementation
+
+### Command execution sequence:
+
+All commands follow the sequence as described in the diagram below:
+
+![ArchitectureSequenceGrouped.png](diagrams/ArchitectureSequenceGrouped.png)
+
+Where ref
frame is a placeholder for each command's specific operations:
+- [setup](#setup)
+- [specific command execution](#adddelete-studentcomponent-feature)
+
+### Setup:
+
+During the setup phase of `TutorLink`, the following operations are performed:
+1. `StudentStorage`, `ComponentStorage` and `GradeStorage` objects are instantiated
+2. `ArrayList` of Student, Component and Grade are obtained from the respective Storage classes
+3. `AppState` object is instantiated, passing the `ArrayList`s in step 3
+4. `Ui` displays welcome message
+
+![Setup.png](diagrams/Setup.png)
+
+The specific implementation of noteworthy operations are presented below:
+
+### Storage Load feature
+
+#### Implementation Details
+
+The `StudentStorage`, `GradeStorage` and `ComponentStorage` classes implement the feature to load data from the
+data `.txt` files into their respective List objects at the start of the program.
+
+The load list methods for the Storage classes have largely similar logic flows. To avoid repetition,
+only the implementation for `GradeStorage` is shown.
+
+The following section and sequence diagram elaborate on the implementation of the `loadGradeList` method in `GradeStorage`,
+as referenced in [Setup](#setup):
+
+![GradeStorage.png](diagrams/GradeStorage.png)
+
+1. TutorLink constructs a new `GradeStorage`.
+2. `GradeStorage` creates a new `ArrayList` of `String`s for discarded entries.
+3. TutorLink calls `loadGradeList`.
+4. `GradeStorage` creates a new `ArrayList` of `Grade`s.
+5. While there are next lines in the data file:
+ - FileScanner returns the current file line as a String and moves to the next file line.
+ - `GradeStorage` calls its `getGradeFromFileLine` method with the file line.
+ - If the file line references a valid `Component` and a valid `Student`, a `Grade` is returned and added to the `ArrayList`.
+ - If not (e.g. file line was corrupted), the file line is added to `discardedEntries`,
+ and the loop continues to the next iteration.
+6. The `ArrayList` of `Grade`s is returned to TutorLink.
+7. TutorLink calls `getDiscardedEntries`, and the discarded entries are displayed by UI.
+
+### Add/Delete Student/Component Feature
+
+#### Implementation Details
+The `AddStudentCommand`, `DeleteStudentCommand`, `AddComponentCommand` and `DeleteComponentCommand` handles the addition and deletion of `Student` and `Component` within the TutorLink application, respectively.
+Each command validates user input to ensure accuracy and consistency before making changes, preserving data integrity. Students are stored as `Student` objects within a `StudentList`. Components are stored as `Component` objects within a `ComponentList`.
+
+#### Key Operations
+
+The flow of logic for both `Student` and `Component` commands can be summarized as follows:
+
+- `AddStudentCommand.execute(AppState appState, HashMap arguments)`: Adds a student to the application by
+ performing the following steps:
+
+ 1. Retrieves and validates the matriculation number and name from `arguments`, throwing relevant exception in the case
+ of failure.
+ 2. Creates and adds a `Student` object to `StudentList` in `AppState`
+ 3. Return `CommandResult` that contains the result of the Add/Delete operation.
+
+The following sequence diagrams depict the exact steps involved in the `AddStudentCommand`:
+
+![AddStudentCommand.png](diagrams/AddStudentCommand.png)
+
+- `DeleteStudentCommand.execute(AppState appState, HashMap arguments)`: Removes a student via the following
+ steps:
+ 1. Retrieves and validates the matriculation number from arguments, throwing `IllegalValueException` exception
+ if matriculation number is null.
+ 2. Searches for and deletes the student from `AppState`. Throws `StudentNotFoundException` if no student matching the matriculation number
+ is found.
+ 3. Searches for and deletes `Grade` objects in `GradeList` containing a student matching the matriculation number.
+
+![DeleteStudentCommand.png](diagrams/DeleteStudentCommand.png)
+
+*Note: Step (iii) is performed because a `Grade` object is only well-defined when there are both `Student` and `Component` objects to be refrenced by `Grade`,
+whenever a `Student` or `Component` object is deleted, the corresponding `Grade` object is queried and then deleted as well.*
+
+The logic for `AddComponentCommand` is very similar (replacing `matriculation number` with `component description`and is therefore not depicted.
+
+### Find Student Feature
+
+#### Implementation Details
+The `FindStudentCommand` searches for and returns matching `Students` stored in the TutorLink application.
+`FindStudentCommand` can accept either `matric number` or `name` as argument. If `matric number` is supplied, the query
+will be executed using `matric number`, else `name` will be used for the search query.
+
+#### Key Operations
+
+The flow of logic for `FindStudentCommand` can be summarized as follows:
+
+- `FindStudentCommand.execute(AppState appState, HashMap arguments)`: Adds a student to the application by
+ performing the following steps:
+
+ 1. Retrieves and validates the matriculation number/name from `arguments`, throwing relevant exception in the case
+ of failure.
+ 2. Calls `AppState.findStudentByMatricNumber` and `AppState.findStudentByName` respectively to fetch list of `Student`
+objects matching the supplied `matric number`/`name`.
+ 3. Return `CommandResult` that contains the matching `Students`.
+
+The following sequence diagrams depict the exact steps involved in the `FindStudentCommand`:
+![FindStudentCommand.png](diagrams/FindStudentCommand.png)
+
+### Add/Delete Grade Feature
+
+#### Implementation Details
+
+The `AddGradeCommand` and `DeleteGradeCommand` classes handle the addition and deletion of grades for students within the TutorLink application. Each command validates user input to ensure accuracy and consistency before making changes, preserving data integrity. Grades are stored as `Grade` objects within a `GradeList`.
+
+#### Key Operations
+
+- **`AddGradeCommand.execute(AppState appState, HashMap arguments)`**: Adds a grade to a student by performing the following steps:
+ 1. Retrieves and validates the matric number, component description, and score from `arguments`.
+ 2. Checks that the specified component and student exist.
+ 3. Ensures the score is within the allowable range for the specified component.
+ 4. Creates a new `Grade` object and adds it to the `GradeList` in `AppState`.
+
+The sequence diagram of the AddGradeCommand is shown below.
+
+![AddGradeCommand.png](diagrams/AddGradeCommand.png)
+
+- **`DeleteGradeCommand.execute(AppState appState, HashMap arguments)`**: Removes a grade from a student by performing these steps:
+ 1. Retrieves and validates the matric number and component description from `arguments`.
+ 2. Confirms the existence of the specified component and student.
+ 3. Locates and deletes the `Grade` object from the `GradeList` in `AppState`.
+
+
+The sequence diagram of the DeleteGradeCommand is shown below.
+
+![DeleteGradeCommand.png](diagrams/DeleteGradeCommand.png)
+
+## Appendix A: Product Scope
+
+### Target User Profile
+
+The target users for **TutorLink** are professors at NUS who manage small, single-staffed classes. These professors
+typically have strong technical expertise but are often overwhelmed by time-consuming administrative tasks that detract
+from their ability to focus on teaching and curriculum development.
+
+#### Key Characteristics:
+
+- **Time-Constrained**: Professors have limited time for lesson preparation due to the administrative burden of managing
+ grades, assignments, and attendance.
+- **Technologically Savvy**: While proficient in using educational platforms, they often find existing tools slow,
+ overly complex, or requiring constant internet connectivity.
+- **Desire for Simplicity**: They prefer tools that are easy to use, automate repetitive tasks, and function offline,
+ allowing them to streamline administrative work without unnecessary complexity.
+
+The target user values efficiency, reliability, and simplicity, seeking a solution that reduces administrative workload
+and enables them to focus more on the core aspects of teaching.
+
+### Value Proposition
+
+**TutorLink** solves the problem of administrative overload by automating routine tasks such as managing assignments,
+and monitoring student performance. Professors often struggle with time-consuming admin work that takes away from their
+primary focus: teaching and preparing lessons.
+
+By offering an offline, lightweight solution that simplifies these processes, TutorLink helps professors:
+
+- Save time by automating tedious administrative tasks.
+- Access important information quickly, without needing an internet connection.
+- Focus on teaching and lesson development instead of being bogged down by admin work.
+
+In contrast to bloated systems, TutorLink is designed to be fast, simple, and effective—freeing up valuable time and
+enhancing teaching efficiency.
+
+## Appendix B: User Stories
+
+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 them when I forget how to use the application |
+| * * * | professor | add a student | start to record his grades after he enrolls in the class |
+| * * * | professor | check the list of students | see how many students are in my class |
+| * * * | professor | find a student | check whether the student is enrolled in the class without having to go through the entire list |
+| * * * | professor | delete a student | remove the student if he decides to drop out of the class |
+| * * * | professor | add a grade of an individual student | record his/her grade after marking |
+| * * * | professor | check the grade of an individual student | see how the student is doing |
+| * * * | professor | delete a grade of an individual student | remove incorrectly inputted grades |
+
+## Appendix C: Non-Functional Requirements
+
+1. Performance: The system should respond to any command within a few seconds
+2. Reliability: The system should not lose any user data even if it crashes unexpectedly
+3. Usability: A new user should be able to use basic features without confusion, upon consulting the User Guide
+4. Compatibility: The system should work on any mainstream OS (Windows, macOS, Linux) that has Java 17 installed
+5. Offline Capability: Since the system is designed to operate offline, it should not require internet connectivity for any core features, supporting a fully local data storage solution.
+6. Maintainability: The system should be able to export all data in a human-readable format for backup purposes
+
+## Appendix D: Glossary
+
+
+* *AppState* - A class in TutorLink responsible for storing and managing global application data, such as lists of students, grades, and components, needed at runtime.
+
+* *CLI (Command-Line Interface)* - An interface through which users interact with TutorLink by typing commands into a command-line or terminal window.
+
+* *Component* - An assessment component that is graded, such as an assignment, exam, or project, represented as an object in the application.
+
+* *ComponentList* - A object that is a collection of `Component` objects within TutorLink, storing all components for a course, such as assignments, exams, or other graded items.
+
+* *CommandResult* - An object that encapsulates the outcome of a command execution, containing information about the command's success or failure and any relevant output for the user.
+
+* *Grade* - An score assigned to a student for a particular component, stored as a `Grade` object within the application.
+
+* *GradeList* - A object that is a collection of `Grade` objects within TutorLink, storing all grades assigned to students for various components.
+
+* *HashMap* - A data structure used in TutorLink to store key-value pairs, commonly used to handle arguments passed into command methods.
+
+* *Matric Number* - A unique identifier assigned to each student, used within TutorLink to manage and retrieve student records.
+
+* *Parser* - A class responsible for interpreting and validating user input commands, returning the requested commands, and the respective command arguments in a HashMap.
+
+* *Student* - A object in TutorLink representing an individual enrolled in a course, containing relevant data such as name, and matric number.
+
+* *StudentList* - A object that is a collection of `Student` objects managed by TutorLink, representing all students enrolled in a course.
+
+* *Storage* - A component in TutorLink responsible for saving and loading data to and from files, allowing persistence of student, component, and grade information across sessions.
+
+* *Validation* - The process of verifying that user input or file data meets specific requirements and constraints to maintain application integrity and avoid errors.
+
+## Appendix E: Instructions for manual testing
+
+This appendix provides a guide for manually testing various features of TutorLink, such as launching the application, modifying window preferences, adding/removing students and components, and managing grades.
+
+---
+
+### Launch and Shutdown
+
+#### Initial Launch
+
+1. **Download the .jar file**: Download and copy the `TutorLink.jar` file in an empty folder on your computer.
+2. **Launch TutorLink**: Open a command terminal, navigate to the folder containing the `.jar` file, and enter the command `java -jar TutorLink.jar`.
+
+ **Expected**: The console interface opens with a welcome message and awaits commands. No data is loaded initially if this is the first launch.
+
+---
+
+#### Saving Data Automatically
+
+1. **Add some sample data** (e.g., students, components, and grades) using the respective commands (`add_student`, `add_component`, etc.).
+2. **Exit the application** by typing `bye`.
+
+ **Expected**: Data is saved automatically to files in the `[JAR file location]/data/` directory (`studentlist.txt`, `componentlist.txt`, `gradelist.txt`).
+
+3. **Re-launch TutorLink** and use the `list_student`, `list_component`, and `list_grade` commands to verify data persistence.
+
+ **Expected**: Previously added data should appear, confirming successful data loading from files.
+
+---
+
+### Managing Students
+
+#### Adding a Student
+
+1. **Test Case**: `add_student i/A1234567X n/John Doe`
+
+ **Expected**: John Doe with matric number `A1234567X` is added to the student list. Use `list_student` to confirm.
+
+2. **Invalid Input Cases**:
+ - `add_student n/John Doe` (missing matric number)
+ - `add_student i/A1234567X` (missing name)
+
+ **Expected**: Error messages indicating missing fields. No student is added.
+
+---
+
+#### Deleting a Student
+
+1. **Prerequisites**: Ensure the list of students is displayed using `list_student` and contains multiple entries.
+
+2. **Test Case**: `delete_student i/A1234567X`
+
+ **Expected**: The student with matric number `A1234567X` is removed from the list. A message confirms the deletion.
+
+3. **Invalid Input Cases**:
+ - `delete_student i/INVALID_MATRIC_NUMBER` (nonexistent matric number)
+ - `delete_student` (missing matric number)
+
+ **Expected**: Error messages indicating invalid or missing inputs. No student is deleted.
+
+---
+
+### Managing Components
+
+#### Adding a Component
+
+1. **Test Case**: `add_component c/Midterm w/30 m/100`
+
+ **Expected**: The "Midterm" component with a weight of 30% and max score of 100 is added. Use `list_component` to verify.
+
+2. **Invalid Input Cases**:
+ - `add_component w/30 m/100` (missing component name)
+ - `add_component c/Midterm w/110 m/100` (weight exceeds 100%)
+
+ **Expected**: Error messages indicating missing or invalid fields. No component is added.
+
+---
+
+#### Deleting a Component
+
+1. **Prerequisites**: Use `list_component` to ensure components are available.
+
+2. **Test Case**: `delete_component c/Midterm`
+
+ **Expected**: The "Midterm" component is removed from the list. A message confirms the deletion.
+
+3. **Invalid Input Cases**:
+ - `delete_component c/Nonexistent` (component not found)
+ - `delete_component` (missing component name)
+
+ **Expected**: Error messages indicating invalid or missing inputs. No component is deleted.
+
+---
+
+### Managing Grades
+
+#### Adding a Grade for a Student
+
+1. **Prerequisites**: Ensure both a student and a component exist in the lists.
+
+2. **Test Case**: `add_grade i/A1234567X c/Midterm s/85`
+
+ **Expected**: An 85 score for the "Midterm" component is recorded for the student with matric number `A1234567X`. Use `list_grade i/A1234567X` to confirm.
+
+3. **Invalid Input Cases**:
+ - `add_grade c/Midterm s/85` (missing matric number)
+ - `add_grade i/A1234567X s/85` (missing component)
+ - `add_grade c/Midterm s/105` (input score exceeds the maximum score of the component, set when the component was first added.)
+
+ **Expected**: Error messages indicating missing or invalid fields. No grade is added.
+
+---
+
+#### Deleting a Grade for a Student
+
+1. **Prerequisites**: Ensure the student has a recorded grade for a component.
-## Design & implementation
+2. **Test Case**: `delete_grade i/A1234567X c/Midterm`
-{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.}
+ **Expected**: The "Midterm" grade for the student with matric number `A1234567X` is removed. Confirmation message is displayed.
+3. **Invalid Input Cases**:
+ - `delete_grade i/InvalidID c/Midterm` (nonexistent student)
+ - `delete_grade i/A1234567X` (missing component)
-## Product scope
-### Target user profile
+ **Expected**: Error messages indicating invalid or missing inputs. No grade is deleted.
-{Describe the target user profile}
+---
-### Value proposition
+### Exiting the Program
-{Describe the value proposition: what problem does it solve?}
+- **Command**: `bye`
-## User Stories
+ **Expected**: The program terminates smoothly, returning to the command prompt without errors.
-|Version| As a ... | I want to ... | So that I can ...|
-|--------|----------|---------------|------------------|
-|v1.0|new user|see usage instructions|refer to them when I forget how to use the application|
-|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list|
+---
-## Non-Functional Requirements
+### Handling Missing Files or Corrupted Data
-{Give non-functional requirements}
+1. **Simulate Missing Data Files**:
+ - Delete one or more files from the `data` folder (`studentlist.txt`, `componentlist.txt`, `gradelist.txt`).
+ - Re-launch TutorLink.
-## Glossary
+ **Expected**: TutorLink creates new empty files if missing. The application should not crash, and it should operate normally.
-* *glossary item* - Definition
+2. **Simulate Corrupted Data**:
+ - Open any data file and add random text or invalid data entry, then save.
+ - Re-launch TutorLink.
-## Instructions for manual testing
+ **Expected**: TutorLink should detect the corrupted or invalid data,
+ display them to the user as entries to be discarded, and only load valid data entries.
+ The application should not crash.
-{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing}
+---
diff --git a/docs/README.md b/docs/README.md
index bbcc99c1e7..7317baee98 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,10 @@
-# Duke
+### TutorLink
-{Give product intro here}
+TutorLink is a desktop CLI application designed to help
+University professors better manage the grades of students
+reading their course.
+
+![tutorlink_startup.png](tutorlink_startup.png)
Useful links:
* [User Guide](UserGuide.md)
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index d6cf4c3b3a..abed8579f1 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,42 +1,356 @@
-# User Guide
+# TutorLink
## Introduction
-{Give a product intro}
+**TutorLink** is a streamlined, offline application developed for professors at NUS who manage single-staffed classes. It is designed to simplify the management of class assignments, and other administrative tasks, allowing professors to focus on teaching and curriculum development. By automating repetitive tasks and providing quick access to essential information, TutorLink saves valuable time and reduces the burden of administrative work.
+
+## Table of Contents
+
+- [Quick Start](#quick-start)
+- [Important Notes on Commands](#important-notes-on-commands)
+- [Important Notes for Grade Calculation](#important-notes-for-grade-calculation)
+- [Features](#features)
+ - [Viewing help: `help`](#viewing-help-help)
+ - [Adding a Student: `add_student`](#adding-a-student-add_student)
+ - [Deleting a Student: `delete_student`](#deleting-a-student-delete_student)
+ - [Listing All Students: `list_student`](#listing-all-students-list_student)
+ - [Finding a Student: `find_student`](#finding-a-student-find_student)
+ - [Adding a Component: `add_component`](#adding-a-component-add_component)
+ - [Deleting a Component: `delete_component`](#deleting-a-component-delete_component)
+ - [Listing Components: `list_component`](#listing-components-list_component)
+ - [Adding a Grade of a Component for a Student: `add_grade`](#adding-a-grade-of-a-component-for-a-student-add_grade)
+ - [Deleting a Grade of a Component for a Student: `delete_grade`](#deleting-a-grade-of-a-component-for-a-student-delete_grade)
+ - [Listing Grades: `list_grade`](#listing-grades-list_grade)
+ - [Exiting the Program: `bye`](#exiting-the-program-bye)
+ - [Saving the Data](#saving-the-data)
+- [FAQ](#faq)
+- [Command Summary](#command-summary)
+
+
+
## Quick Start
-{Give steps to get started quickly}
+1. Ensure you have Java 17 or above installed in your Computer.
+2. Download the latest .jar file of `TutorLink` from [here](https://github.com/AY2425S1-CS2113-W13-4/tp/releases/tag/v2.1).
+3. Copy the file to the folder you want to use as the home folder for your TutorLink.
+4. Open a command terminal, cd into the folder you put the jar file in, and use the java -jar TutorLink.jar command to run the application.
+
+Your command terminal should look similar to the one below.
+
+![tutorlink_startup.png](tutorlink_startup.png)
+
+## Important Notes on Commands:
+When inputting commands into `TutorLink`, kindly take note of the following:
+- Commands with duplicate parameters will be rejected. *i.e* `add_student n/John Doe n/John Doe i/A1234567X`
+- Parameters must be separated by at least one space character, otherwise the entire continuous string following a prefix
+will be considered a single parameter. *i.e* `add_student i/A1234567X n/John Doei/A1234567X` will be intepreted as adding
+a student with the name of `John Doei/A1234567X` and matric number `A1234567X`.
+- Parameters must be passed **directly** after the corresponding prefix *i.e* `i/MATRIC_NUMBER`.
+Rouge spaces in between the prefix *i.e* `i/ MATRIC_NUMBER` will invalidate the command and be treated as a `null parameter`.
+- Parameters can be supplied in any order. *i.e* `add_student n/John i/A1234567X` is the same as `add_student i/A1234567X n/John`
+- **IMPORTANT**: Descriptions should **NOT** contain any separator tokens: `|` as this character is used for storage).
+Including these may yield unpredictable results with the `Storage` component.
+- Matric Number (`i/` argument) is case-sensitive. Therefore, only `A1234567X` is the accepted and not `a1234567x`. Matric numbers
+will be remain in uppercase for storage.
+- In general, unless otherwise specified, parameters are case-insensitive. *I.e*, `test` will match against `TEST` and `Test`.
+
+## Important Notes for Grade Calculation:
+1. Total Weight (sum) of `Components` **cannot** exceed `100%`. An error will be displayed should you attempt to add a component
+that causes the overall weight to exceed `100%`. *For illustration, consider the following scenario:*
+
+```angular2html
+-------------------------------------------------------------
+list_component
+------------------------- Result -------------------------
+ 1: Quiz 2 (maxScore: 20.0, weight: 30%)
+ 2: Quiz 4 (maxScore: 20.0, weight: 25%)
+-------------------------------------------------------------
+add_component c/Quiz 1 w/60 m/10
+------------------------- Error -------------------------
+Error! Total weighting must not exceed 100%.
+Current weighting (after addition): 115%
+-------------------------------------------------------------
+```
+
+In this case, an error was thrown because total `component weighting` exceeded 100%
+2. Total Weight of Components **do not need to sum to 100** for grade computation to be meaningful (see below).
+3. `percentage_score` computation follows the following equation: `sum(grade score [of student]/max component score * component_weight) / total component weight`
+To illustrate:
+
+```angular2html
+list_component
+------------------------- Result -------------------------
+1: Quiz 2 (maxScore: 20.0, weight: 30%)
+2: Quiz 4 (maxScore: 20.0, weight: 25%)
+-------------------------------------------------------------
+list_grade
+------------------------- Result -------------------------
+List of All Grades:
+
+1: Ethan Chua (A0276007H):
+ 1. Quiz 2 : 15.00
+ 2. Quiz 4 : 10.00
+ Final Percentage Score: 63.64%
+
+
+-------------------------------------------------------------
+```
+The computation of the final score is as follows:
+`(15/20*30 [Quiz 2] + 10/20*25 [Quiz 1])/(30 + 25 [Total Weighting]) = 63.64% (2d.p)`
+
+## Features
+
+### Viewing help: `help`
+
+Shows a message explaining different features of the app.
+
+- **Format**: `help`
+
+---
+
+### Adding a Student: `add_student`
+
+Adds a student to your class.
+
+- **Format**: `add_student i/MATRIC_NUMBER n/STUDENT_NAME`
+- **Parameters**:
+ - `STUDENT_NAME`: The full name of the student.
+ - `MATRIC_NUMBER`: The unique identifier of the student. It should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+
+- **Example**: `add_student i/A1234567X n/John Doe ` adds a new student named John Doe with the matric number of A1234567X to the class.
+
+---
+
+### Deleting a Student: `delete_student`
+
+Removes a student from the class. Note that a student can only be deleted using his/her matric number to prevent identification errors.
+
+- **Format**: `delete_student i/MATRIC_NUMBER`
+- **Parameters**:
+ - `MATRIC_NUMBER`: The unique identifier of the student. It should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+
+- **Example**:
+ - `delete_student i/A1234567X` deletes a student with the matric number of A1234567X.
+
+---
+
+
+
+
+### Listing All Students: `list_student`
+
+Displays a list of all students in the class.
+
+- **Format**: `list_student`
+
+- **Example**:
+ - `list_student`
+
+---
+
+### Finding a Student: `find_student`
+
+Searches for a student and returns a list of matching results, complete with information such as `matric number` and `percentage score`
+
+- **Format**:
+ - `find_student i/MATRIC_NUMBER`
+ - `find_student n/STUDENT_NAME`
+ - **Note**: If both `i/MATRIC_NUMBER` and `n/STUDENT_NAME` are supplied, `n/STUDENT_NAME` is disregarded for the query.
+- **Parameters**:
+ - `STUDENT_NAME`: The name of the student.
+ - `MATRIC_NUMBER`: The matriculation number of the student. It should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+ - **Note**
+ - `STUDENT_NAME` is case-sensitive; a search query for `n/John Doe` will not match against `john doe`
+ - `STUDENT_NAME` matches substrings; a search query for `n/jo` will match against `john doe`
+- **Example**:
+ - `find_student i/A1234567X` find the student with the matric number of `A1234567X` among the list of students and prints out the student information.
+ - `find_student n/John Doe` find the student with the name `John Doe` among the list of students and prints out the student information.
+
+---
+
+### Adding a Component: `add_component`
+
+Adds a new grading component to the class (e.g., "Homework," "Midterm," "Final Exam").
+
+- **Format**: `add_component c/COMPONENT w/WEIGHT m/MAX_SCORE`
+- **Parameters**:
+ - `COMPONENT`: The name of the grading component to add. Note that when adding, the component name is case-insensitive,
+ *i.e* if `Quiz` exists in the app, then `quiz` cannot be added.
+ Moreover, whitespace after the component string is trimmed.
+ - `WEIGHT`: The weight of the component as a percentage, input as an integer from 0 - 100 (inclusive).
+ - `MAX_SCORE`: The max_score of the component. **Must be a `double` between 0 and 10,000 (inclusive).**
+ *Use case for `WEIGHT = 0`, `MAX_SCORE = 0`:* ungraded components like optional assignments etc
-1. Ensure that you have Java 17 or above installed.
-1. Down the latest version of `Duke` from [here](http://link.to/duke).
+- **Example**:
+ - `add_component c/Quiz 1 w/30 m/50` adds a Quiz 1 component with a weightage of 30%, it has a max score of 50 marks.
-## Features
+---
-{Give detailed description of each feature}
+
+
-### Adding a todo: `todo`
-Adds a new item to the list of todo items.
+### Deleting a Component: `delete_component`
-Format: `todo n/TODO_NAME d/DEADLINE`
+Removes an existing grading component from the class.
-* The `DEADLINE` can be in a natural language format.
-* The `TODO_NAME` cannot contain punctuation.
+- **Format**: `delete_component c/COMPONENT`
+- **Parameters**:
+ - `COMPONENT`: The name of the grading component to delete. Note that component name is case-insensitive, *i.e* `Test` is the same as `test`.
+ Moreover, whitespace after the component string is trimmed.
+- **Example**:
+ - `delete_component c/Quiz 1` deletes Quiz 1 component from the list of components that form the final grade.
-Example of usage:
+---
-`todo n/Write the rest of the User Guide d/next week`
+### Updating Components: `update_component`
-`todo n/Refactor the User Guide to remove passive voice d/13/04/2020`
+Updates an existing component in the class
+
+- **Format**: `update_component c/COMPONENT w/WEIGHT m/MAX_SCORE`
+- **Parameters**:
+ - `COMPONENT`: The name of the grading component to update. Note that when updating, the component name case-insensitive,
+ *i.e* if there exists a component by the name of `Quiz 2` in the applicaiton, supplying `update_component` with a parameter `c/QUIZ 2` will update it.
+ Moreover, whitespace after the component string is trimmed.
+ - `WEIGHT`: The weight of the component as a percentage, input as an integer from 0 - 100 (inclusive).
+ - `MAX_SCORE`: The max_score of the component. **Must be a `double` between 0 and 10,000 (inclusive).**
+ *Refer to `add_component` documentation for use cases of `WEIGHT=0`, `MAX_SCORE=0`.*
+
+- **Example**:
+ - `update_component c/Quiz 1 w/40 m/50` updates component by the name of `Quiz 1` to now bear a weightage of 40% and a max score of 50 marks.
+- **Important Note**:
+ - `update_component` checks through all the `Grade` objects in the app and if any `Grade` object has a score that is greater than
+ the updated max score, the score in the `Grade` object is set to be the newly updated component max score. This is done to
+ prevent the case where a grade has a score that is greater than the max score of the corresponding component.
+ - Although a loss of grade data is inevitable in this case, this is unavoidable as from a practical standpoint, changing
+ the max score of an assignment/exam/quiz **after** it has already been graded is bound to run into this exact problem.
+
+---
+
+### Listing Components: `list_component`
+
+Displays all grading components and their respective weights for a class.
+
+- **Format**: `list_component`
+
+- **Example**:
+ - `list_component`
+
+---
+
+### Adding a Grade of a Component for a Student: `add_grade`
+
+Records a grade for a specific student in a particular assignment or exam component.
+- **Format**: `add_grade i/MATRIC_NUMBER c/COMPONENT s/SCORE`
+- **Parameters**:
+ - `MATRIC_NUMBER`: The unique identifier of the student. It should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+ - `COMPONENT`: The name of the grading component. Note that component name is case-insensitive, *i.e* `Test` is the same as `test`.
+ Moreover, whitespace after the component string is trimmed.
+ - `SCORE`: The score to be recorded. Must be a numerical value that will be interpreted as a `double`. Note that score cannot exceed the max score of the component.
+
+- **Example**:
+ - `add_grade i/A1234567X c/Quiz 1 s/45` adds the grade of Quiz 1 for the student with the matric number of A1234567X with a score of 45.
+ - `add_grade i/A1234567X c/Quiz 2 s/30.5` adds the grade of Quiz 2 for the student with the matric number of A1234567X with a score of 30.5.
+- **Note:** Scores of grades are stored to double precision but during display the value is rounded off to 2 decimal places.
+
+---
+
+
+
+
+### Deleting a Grade of a Component for a Student: `delete_grade`
+
+Removes a previously recorded grade for a specific student and component.
+
+- **Format**: `delete_grade i/MATRIC_NUMBER c/COMPONENT`
+- **Parameters**:
+ - `MATRIC_NUMBER`: The unique identifier of the student. It should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+ - `COMPONENT`: The name of the grading component. Note that component name is case-insensitive, *i.e* `Test` is the same as `test`.
+ Moreover, whitespace after the component string is trimmed.
+- **Example**:
+ - `delete_grade i/A1234567X c/Quiz 1` deletes the grade of Quiz 1 for the student with the matric number of A1234567X.
+
+---
+
+### Listing Grades: `list_grade`
+
+Views all recorded grades for a specific student or all students, and final percentage calculation. If the weightage of components does not add up to 100% (i.e., the course is still in progress), "IP" (In Progress) will be shown instead of a final percentage.
+
+- **For a Specific Student**:
+ - Lists all recorded grades and the final percentage for the specified student.
+ - **Format**: `list_grade i/MATRIC_NUMBER`
+ - **Parameters**:
+ - `MATRIC_NUMBER`: The unique identifier of the student. It should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+ - **Example**:
+ - `list_grade i/A1234567X`
+
+
+- **For All Students**:
+ - Lists all recorded grades for each student in a numbered format, with individual grade details and a final percentage for each student.
+ - **Format**: `list_grade`
+
+
+---
+
+
+
+
+### Exiting the program: `bye`
+
+Exits the program.
+- **Format**: `bye`
+
+---
+### Saving the data
+
+TutorLink data is saved in the hard disk automatically after every command execution. There is no need to save manually.
+Existing data from previous application runs are loaded on startup.
+
+The data from the student, component and grade lists are stored in `studentlist.txt`, `componentlist.txt` and `gradelist.txt`
+respectively, located in the `[JAR file location]/data/` directory.
+
+---
## FAQ
-**Q**: How do I transfer my data to another computer?
+**Q**: How do I transfer my data to another computer?
+
+**A**: To transfer data, simply copy the `TutorLink` home folder (where the `.jar` file and data files are located) to your new computer. Then, download Java 17 (if not already installed), place the `.jar` file in the copied folder, and run `java -jar TutorLink.jar` from that folder.
-**A**: {your answer here}
+**Q**: Can I update data by directly editing the data files?
+
+**A**: Do so at your own risk. If changes to the data file alter its format, invalid file lines will discarded
+during startup, and displayed in the command line for verification. While TutorLink can detect most invalid file entries,
+certain edits can cause unexpected behaviour. Therefore, it is not recommended to edit the data files
+unless you are confident you can do so correctly.
+
+---
+
+
+
## Command Summary
-{Give a 'cheat sheet' of commands here}
+| **Command** | **Description** | **Example** |
+|--------------------|--------------------------------------------------------------|---------------------------------------|
+| `help` | Displays list of commands | `help` |
+| `add_student` | Adds a student to the class roster | `add_student i/A1234567X n/John Doe` |
+| `delete_student` | Deletes a student from the class roster | `delete_student i/A1234567X` |
+| `list_student` | Lists all students in the class | `list_student` |
+| `find_student` | Finds a student in the class roster by name or matric number | `find_student i/A1234567X n/John Doe` |
+| `add_component` | Adds a new grading component to the class | `add_component c/Quiz 1 w/30 m/50` |
+| `delete_component` | Deletes a grading component from the class | `delete_component c/Quiz 1` |
+| `update_component` | Updates a component with a new maxscore or weight | `update_component c/Quiz 1 w/40 m/60` |
+| `list_component` | Lists all grading components | `list_component` |
+| `add_grade` | Adds a grade for a student for a specific component | `add_grade i/A1234567X c/Quiz 1 s/45` |
+| `delete_grade` | Deletes a student's grade for a specific component | `delete_grade i/A1234567X c/Quiz 1` |
+| `list_grade` | Lists all grades for a student | `list_grade i/A1234567X` |
+| `bye` | Exits the program | `bye` |
+
+---
-* Add todo `todo n/TODO_NAME d/DEADLINE`
+## Coming Soon...
+- `update_student`: updates a student entry in the app
+- `update_grade`: updates a grade entry in the app
+- `filter_student`: filters students by percentage score
+- `filter_grade`: filters grades by percentage score
diff --git a/docs/diagrams/AddComponentCommand.png b/docs/diagrams/AddComponentCommand.png
new file mode 100644
index 0000000000..79410cee8a
Binary files /dev/null and b/docs/diagrams/AddComponentCommand.png differ
diff --git a/docs/diagrams/AddComponentCommand.puml b/docs/diagrams/AddComponentCommand.puml
new file mode 100644
index 0000000000..22f5d08e01
--- /dev/null
+++ b/docs/diagrams/AddComponentCommand.puml
@@ -0,0 +1,54 @@
+@startuml
+!include Style.puml
+group sd AddComponentCommand
+participant ":AddComponentCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":HashMap" as H LOGIC_COLOR_7
+participant ":CommandResult" as R LOGIC_COLOR_8
+
+[-> C
+C -> H : get("c/")
+activate H
+C <-- H : componentName
+deactivate H
+C -> H : get("w/")
+activate H
+C <-- H : weightageNumber
+deactivate H
+C -> H : get("m/")
+activate H
+C <-- H : maxScoreNumber
+deactivate H
+destroy H
+
+
+opt componentName or weightageNumber or maxScoreNumber is null
+[<-- C : throw IllegalValueException
+end
+
+C -> C : convertWeightageToValidDouble(weightageNumber)
+activate C
+C --> C : weightageNumber
+deactivate C
+
+C -> C : convertMaxScoreToValidDouble(maxScore)
+activate C
+C --> C : maxScore
+deactivate C
+
+C -> A : addComponent(new Component(componentName, maxScore, weightage))
+activate A
+C <-- A
+deactivate A
+
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+[<-- C : CommandResult
+end
+destroy R
+destroy C
+@enduml
diff --git a/docs/diagrams/AddGradeCommand.png b/docs/diagrams/AddGradeCommand.png
new file mode 100644
index 0000000000..57888a95d4
Binary files /dev/null and b/docs/diagrams/AddGradeCommand.png differ
diff --git a/docs/diagrams/AddGradeCommand.puml b/docs/diagrams/AddGradeCommand.puml
new file mode 100644
index 0000000000..f50c218b67
--- /dev/null
+++ b/docs/diagrams/AddGradeCommand.puml
@@ -0,0 +1,61 @@
+'@@author yeekian
+@startuml
+!include Style.puml
+group sd AddGradeCommand
+participant ":AddGradeCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":CommandResult" as R LOGIC_COLOR_8
+participant ":Grade" as G LOGIC_COLOR_5
+
+[->C : execute(appState, arguments)
+activate C
+
+C -> C : findComponentFromComponents
+activate C
+C -> A : findComponent(componentDescription)
+activate A
+C <-- A : componentFilteredList
+deactivate A
+C --> C : component
+deactivate C
+
+C -> C : findStudentFromStudents
+activate C
+C -> A : findStudentByMatricNumber(matricNumber)
+activate A
+C <-- A : studentFilteredList
+deactivate A
+C --> C : student
+deactivate C
+
+C -> C : convertScoreToValidDouble(scoreNumber)
+activate C
+C --> C : score
+deactivate C
+
+Create G
+C -> G : Grade(component, student, score)
+activate G
+C <-- G : grade
+deactivate G
+
+C -> A : addGrade(grade)
+activate A
+C <-- A
+deactivate A
+
+
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+[<-- C : CommandResult
+deactivate C
+
+end
+destroy R
+destroy C
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/AddStudentCommand.png b/docs/diagrams/AddStudentCommand.png
new file mode 100644
index 0000000000..1ea2eb7de9
Binary files /dev/null and b/docs/diagrams/AddStudentCommand.png differ
diff --git a/docs/diagrams/AddStudentCommand.puml b/docs/diagrams/AddStudentCommand.puml
new file mode 100644
index 0000000000..f30a281df5
--- /dev/null
+++ b/docs/diagrams/AddStudentCommand.puml
@@ -0,0 +1,26 @@
+@startuml
+!include Style.puml
+group sd AddStudentCommand
+participant ":AddStudentCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":CommandResult" as R LOGIC_COLOR_8
+
+[-> C: execute(appState, arguments)
+activate C
+
+C -> A : addStudent(matricNumber, name)
+activate A
+C <-- A
+deactivate A
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+[<-- C : CommandResult
+deactivate C
+end
+destroy R
+destroy C
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/Architecture.png b/docs/diagrams/Architecture.png
new file mode 100644
index 0000000000..40803ca19f
Binary files /dev/null and b/docs/diagrams/Architecture.png differ
diff --git a/docs/diagrams/Architecture.puml b/docs/diagrams/Architecture.puml
new file mode 100644
index 0000000000..c6aa368af3
--- /dev/null
+++ b/docs/diagrams/Architecture.puml
@@ -0,0 +1,35 @@
+@startuml
+
+!include Style.puml
+skinparam rectangle{
+ fontColor white
+}
+skinparam defaultFontSize 17
+
+actor User
+rectangle {
+rectangle Ui LOGIC_COLOR_1
+rectangle Parser LOGIC_COLOR_2
+rectangle Command LOGIC_COLOR_3
+rectangle AppState LOGIC_COLOR_4
+rectangle TutorLink LOGIC_COLOR_5
+rectangle Storage LOGIC_COLOR_6
+rectangle CommandResult LOGIC_COLOR_7
+}
+
+User -down-> TutorLink
+TutorLink -down-> Parser
+TutorLink -> Ui
+TutorLink -up-> CommandResult
+Parser .> TutorLink
+Ui .> TutorLink
+TutorLink -> AppState
+Parser .down.> Command
+Command ..> AppState
+TutorLink -down-> Storage
+Parser.>CommandResult
+
+file "Data Files" as DataFiles #lightgreen
+DataFiles <.right. Storage
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ArchitectureSequence.puml b/docs/diagrams/ArchitectureSequence.puml
new file mode 100644
index 0000000000..98d1740977
--- /dev/null
+++ b/docs/diagrams/ArchitectureSequence.puml
@@ -0,0 +1,37 @@
+@startuml
+
+participant "TutorLink" as TL
+participant "Ui" as UI
+participant "Parser" as P
+participant "Storage" as S
+participant "CommandResult" as CR
+
+-> TL
+TL->UI: displayWelcomeMessage()
+TL -> S: loadStoredData()
+TL <-- S: appState
+loop until exit command issued
+TL -> UI : getUserInput()
+TL <-- UI : String
+TL -> P : getCommand(line)
+TL <-- P : Command
+TL -> "<>\nCommand" : getArgumentPrefixes()
+TL <-- "<>\nCommand" : String[] argumentPrefixes
+TL -> P : getArguments(argumentPrefixes)
+TL <-- P : HashMap arguments
+TL -> Command : execute(appState, arguments)
+alt success
+ TL <-- Command : CommandResult
+ opt storage required
+ TL -> S : saveData(appState)
+ end
+ TL -> UI: displayResult(commandResult)
+else exception
+ TL <-- Command : TutorLinkException
+ TL -> UI: displayException(TutorLinkException)
+end
+end
+TL->UI: displayGoodbyeMessage()
+<--TL
+
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceGrouped.png b/docs/diagrams/ArchitectureSequenceGrouped.png
new file mode 100644
index 0000000000..606270e5c4
Binary files /dev/null and b/docs/diagrams/ArchitectureSequenceGrouped.png differ
diff --git a/docs/diagrams/ArchitectureSequenceGrouped.puml b/docs/diagrams/ArchitectureSequenceGrouped.puml
new file mode 100644
index 0000000000..677f54e91e
--- /dev/null
+++ b/docs/diagrams/ArchitectureSequenceGrouped.puml
@@ -0,0 +1,66 @@
+@startuml
+!include Style.puml
+participant ":TutorLink" as TL LOGIC_COLOR_5
+participant ":Ui" as UI LOGIC_COLOR_6
+participant ":Parser" as P LOGIC_COLOR_7
+participant ":XYZCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":XYZStorage" as S LOGIC_COLOR_4
+[->TL
+activate TL
+ref over TL, UI : setup
+loop until exit command issued
+ TL -> UI: getUserInput()
+ activate UI
+ TL <-- UI: line
+ deactivate UI
+ TL -> P : getCommand(line)
+ activate P
+ create C
+ P -> C
+ activate C
+ P <-- C : XYZCommand
+ deactivate C
+ TL <-- P: XYZCommand
+ deactivate P
+ TL -> C : getArgumentPrefixes()
+ activate C
+ TL <-- C : argumentPrefixes
+ deactivate C
+ TL -> P: getArguments(argumentPrefixes, line)
+ activate P
+ TL <-- P : HashMap
+ deactivate P
+ TL -> C : execute(appState, arguments)
+ activate C
+ ref over C, A : specific command execution
+ alt command execution success
+ TL <-- C : CommandResult
+ deactivate C
+ destroy C
+ TL -> UI : displayResult()
+ activate UI
+ TL <-- UI
+ deactivate UI
+ else TutorLinkException
+ TL -> UI : displayException(exception)
+ activate UI
+ TL <-- UI
+ deactivate UI
+ end
+ TL -> TL : saveAllLists()
+ activate TL
+ TL -> S : save Student, Component and Grade Lists
+ activate S
+ TL <-- S
+ deactivate S
+ TL --> TL
+ deactivate TL
+end
+TL->UI: displayGoodbyeMessage()
+activate UI
+TL <-- UI
+deactivate UI
+[<--TL
+deactivate TL
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/Assets.puml b/docs/diagrams/Assets.puml
new file mode 100644
index 0000000000..88c1c93ddb
--- /dev/null
+++ b/docs/diagrams/Assets.puml
@@ -0,0 +1,127 @@
+@startuml App Diagram
+
+'https://plantuml.com/class-diagram
+' !include Style.puml
+skinparam classAttributeIconSize 0
+skinparam classMethodIconSize 0
+skinparam linetype ortho
+
+package TutorLink <>{
+
+class CommandResult {
+ + CommandResult(msg: String)
+ + toString(): String
+}
+
+class Ui {
+ + displayResult(result: CommandResult)
+ + displayException(error: TutorLinkException)
+ + displayWelcomeMessage()
+ + getUserInput(): String
+}
+
+Ui .. CommandResult
+
+Ui .. TutorLinkException
+
+abstract class TutorLinkException
+
+abstract class Command {
+ + {static}COMMAND_WORD: String
+ + Command() {abstract}
+ + {abstract} getArgumentsPrefixes() : String[]
+ + execute(state: AppState, arguments: HashMap): CommandResult {abstract}
+ + isExit(): boolean
+}
+
+Command ..right.. CommandResult
+
+
+abstract class Component {
+ - name: String
+ - maxScore: double
+ - weight: double
+ + getName(): String
+ + getMaxScore(): double
+ + getWeight(): double
+ + equals(Object obj):boolean
+ + toString(): String
+}
+class Exam
+class ClassParticipation
+class Assignment
+
+Exam --|> Component
+ClassParticipation --|> Component
+Assignment --|> Component
+
+
+class Student{
+ - name: String
+ - matricNumber: String
+ + getName(): String
+ + getMatricNumber(): String
+ + equals(Object obj): boolean
+ + toString(): String
+}
+
+class Grade{
+ - score: double
+ - component: Component
+ - student: Student
+ + Grade(component: Component, student: Student, score: double)
+ + equals(Object obj):boolean
+ + toString(): String
+}
+
+
+class AppState
+class GradeList{
+ - size(): int
+ + getGradeArrayList(): ArrayList
+ + findGrade(matricNumber: String, componentDescription: String): GradeList
+ + addGrade(grade: Grade): void
+ + deleteGrade(matricNumber: String, componentDescription: String): void
+ + deleteGradesByMatric(matricNumber: String) : void
+ + deleteGradesByComponent(componentDescription: String) : void
+ + calculateStudentGPA(matricNumber: String): double
+ + toString(): String
+}
+class StudentList {
+ + findStudentByMatricNumber(matricNumber: String): StudentList
+ + findStudentByName(name: String): StudentList
+ + getStudentArrayList() : StudentList
+ + addStudent(matricNumber: String, name: String) void
+ + deleteStudent(matricNumber: String, name: String): void
+ + size(): int
+ + toString(): String
+}
+class ComponentList {
+ + findComponent(id: String): ComponentList
+ + addComponent(component: Component): void
+ + deleteComponent(component: Component): void
+ + findAllComponents(): ArrayList
+ + size(): int
+ + toString(): String
+}
+
+AppState -> "1" GradeList
+AppState --> "1" StudentList
+AppState -> "1" ComponentList
+
+GradeList -> "*" Grade
+ComponentList -> "*" Component
+StudentList -> "*" Student
+
+Command .. AppState
+
+class Parser {
+ + getArguments(argumentList: String[], line: String): HashMap
+ + getCommand(line: String) : Command
+}
+
+Parser .. Command
+
+}
+
+@enduml
diff --git a/docs/diagrams/DeleteComponentCommand.png b/docs/diagrams/DeleteComponentCommand.png
new file mode 100644
index 0000000000..880eeaf8a2
Binary files /dev/null and b/docs/diagrams/DeleteComponentCommand.png differ
diff --git a/docs/diagrams/DeleteComponentCommand.puml b/docs/diagrams/DeleteComponentCommand.puml
new file mode 100644
index 0000000000..fe7e83aa80
--- /dev/null
+++ b/docs/diagrams/DeleteComponentCommand.puml
@@ -0,0 +1,50 @@
+@startuml
+!include Style.puml
+group sd DeleteComponentCommand
+participant ":DeleteComponentCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":HashMap" as H LOGIC_COLOR_7
+participant ":ComponentList" as CL LOGIC_COLOR_6
+participant ":CommandResult" as R LOGIC_COLOR_8
+
+[-> C
+
+C -> H : get("n/")
+activate H
+C <-- H : componentName
+deactivate H
+destroy H
+
+opt componentName is null
+ [<-- C : throw IllegalValueException
+end
+
+C -> A : findComponent(componentName)
+activate A
+A --> C : componentsToDelete
+deactivate A
+
+alt componentsToDelete.size() == 0
+ <-- C : throw ComponentNotFoundException
+else componentsToDelete.size() > 1
+ <-- C : throw DuplicateComponentException
+end
+
+C -> A : deleteComponent(componentsToDelete.getComponentArrayList().get(0))
+activate A
+C <-- A
+deactivate A
+
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+
+[<-- C : CommandResult
+end
+destroy R
+destroy C
+
+@enduml
diff --git a/docs/diagrams/DeleteGradeCommand.png b/docs/diagrams/DeleteGradeCommand.png
new file mode 100644
index 0000000000..173a4e3338
Binary files /dev/null and b/docs/diagrams/DeleteGradeCommand.png differ
diff --git a/docs/diagrams/DeleteGradeCommand.puml b/docs/diagrams/DeleteGradeCommand.puml
new file mode 100644
index 0000000000..be71af4c27
--- /dev/null
+++ b/docs/diagrams/DeleteGradeCommand.puml
@@ -0,0 +1,45 @@
+'@@author yeekian
+@startuml
+!include Style.puml
+group sd DeleteGradeCommand
+participant ":DeleteGradeCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":CommandResult" as R LOGIC_COLOR_8
+
+[->C : execute(appState, arguments)
+activate C
+
+C -> C : findComponentFromComponents
+activate C
+C -> A : findComponent(componentDescription)
+activate A
+C <-- A : componentFilteredList
+deactivate A
+C --> C : component
+deactivate C
+
+
+C -> A : findStudentByMatricNumber(matricNumber)
+activate A
+C <-- A : studentFilteredList
+deactivate A
+
+C -> A : deleteGrade(matricNumber, componentDescription)
+activate A
+C <-- A
+deactivate A
+
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+[<-- C : CommandResult
+deactivate C
+
+end
+destroy R
+destroy C
+
+@enduml
diff --git a/docs/diagrams/DeleteStudentCommand.png b/docs/diagrams/DeleteStudentCommand.png
new file mode 100644
index 0000000000..f8ddb273db
Binary files /dev/null and b/docs/diagrams/DeleteStudentCommand.png differ
diff --git a/docs/diagrams/DeleteStudentCommand.puml b/docs/diagrams/DeleteStudentCommand.puml
new file mode 100644
index 0000000000..35fed323c7
--- /dev/null
+++ b/docs/diagrams/DeleteStudentCommand.puml
@@ -0,0 +1,35 @@
+@startuml
+!include Style.puml
+group sd DeleteStudentCommand
+participant ":DeleteStudentCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":CommandResult" as R LOGIC_COLOR_8
+
+[-> C: execute(appState, arguments)
+activate C
+C -> A : deleteStudent(matricNumber)
+activate A
+alt student found and deleted
+C <-- A
+deactivate A
+else StudentNotFoundException
+end
+deactivate A
+
+C -> A : deleteGradesByMatricNumber(matricNumber)
+activate A
+C <-- A
+deactivate A
+
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+[<-- C : CommandResult
+deactivate C
+end
+destroy R
+destroy C
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/FindStudentCommand.png b/docs/diagrams/FindStudentCommand.png
new file mode 100644
index 0000000000..4190c9e7af
Binary files /dev/null and b/docs/diagrams/FindStudentCommand.png differ
diff --git a/docs/diagrams/FindStudentCommand.puml b/docs/diagrams/FindStudentCommand.puml
new file mode 100644
index 0000000000..140c97a80d
--- /dev/null
+++ b/docs/diagrams/FindStudentCommand.puml
@@ -0,0 +1,35 @@
+@startuml
+!include Style.puml
+group sd FindStudentCommand
+participant ":FindStudentCommand" as C LOGIC_COLOR_3
+participant ":AppState" as A LOGIC_COLOR_2
+participant ":CommandResult" as R LOGIC_COLOR_8
+
+[-> C : execute(appState, arguments)
+activate C
+
+alt matricNumber not null
+C -> A : findStudentByMatricNumber(matricNumber)
+activate A
+C <-- A : StudentList
+deactivate A
+else matricNumber null
+C -> A : findStudentByName(name)
+activate A
+C <-- A : StudentList
+deactivate A
+end
+
+deactivate A
+create R
+C -> R : new CommandResult
+activate R
+C <-- R : CommandResult
+deactivate R
+
+[<-- C : CommandResult
+deactivate C
+end
+destroy R
+destroy C
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/GradeStorage.png b/docs/diagrams/GradeStorage.png
new file mode 100644
index 0000000000..be7054cf96
Binary files /dev/null and b/docs/diagrams/GradeStorage.png differ
diff --git a/docs/diagrams/GradeStorage.puml b/docs/diagrams/GradeStorage.puml
new file mode 100644
index 0000000000..1c8726c810
--- /dev/null
+++ b/docs/diagrams/GradeStorage.puml
@@ -0,0 +1,70 @@
+@startuml
+
+!include Style.puml
+participant ":TutorLink" as TL LOGIC_COLOR_5
+participant ":Ui" as UI LOGIC_COLOR_6
+
+participant "gradeStorage: GradeStorage" as GS LOGIC_COLOR_4
+participant "grades: ArrayList" as AL LOGIC_COLOR_3
+participant "discardedEntries: ArrayList" as DE LOGIC_COLOR_7
+
+group sd load grade data
+
+create GS
+TL -> GS: new GradeStorage(filepath, components, students)
+activate GS
+create DE
+GS -> DE: new ArrayList()
+activate DE
+GS <-- DE: discardedEntries: String
+deactivate DE
+TL <-- GS: gradeStorage: GradeStorage
+deactivate GS
+
+TL -> GS: loadGradeList()
+activate GS
+
+create AL
+GS -> AL: new ArrayList()
+activate AL
+GS <-- AL: grades: Grade
+deactivate AL
+
+loop file has next line
+ GS -> GS: getGradeFromFileLine(currentLine: String, grades: Grade)
+ activate GS
+ GS --> GS: newGrade: Grade
+ deactivate GS
+
+ alt read grade success
+ GS -> AL: add(newGrade)
+ activate AL
+ GS <-- AL
+ deactivate AL
+ else InvalidDataFileLineException
+ GS -> DE: add(currentLine: String)
+ activate DE
+ GS <-- DE
+ deactivate DE
+ end
+
+end
+
+TL <-- GS: grades: Grade
+deactivate GS
+
+destroy AL
+
+TL -> GS: getDiscardedEntries()
+activate GS
+TL <-- GS: discardedGrades: String
+deactivate GS
+
+TL -> UI: displayDiscardedEntries(discardedGrades)
+activate UI
+TL <-- UI
+deactivate UI
+
+end
+
+@enduml
diff --git a/docs/diagrams/Setup.png b/docs/diagrams/Setup.png
new file mode 100644
index 0000000000..8c28b80832
Binary files /dev/null and b/docs/diagrams/Setup.png differ
diff --git a/docs/diagrams/Setup.puml b/docs/diagrams/Setup.puml
new file mode 100644
index 0000000000..8affaf4b31
--- /dev/null
+++ b/docs/diagrams/Setup.puml
@@ -0,0 +1,40 @@
+@startuml
+!include Style.puml
+participant ":TutorLink" as TL LOGIC_COLOR_5
+participant ":Ui" as UI LOGIC_COLOR_6
+participant ":AppState" as A LOGIC_COLOR_2
+participant "studentStorage:Storage" as SS LOGIC_COLOR_4
+participant "componentStorage:Storage" as CS LOGIC_COLOR_4
+participant "gradeStorage:Storage" as GS LOGIC_COLOR_4
+
+group sd setup
+
+alt setup success
+
+ ref over TL, UI, SS: load student data
+
+ ref over TL, UI, CS: load component data
+
+ ref over TL, UI, GS: load grade data
+
+ create A
+ TL -> A : new AppState(students, grades, components)
+ activate A
+ TL <-- A : appState
+ deactivate A
+else TutorLinkException
+ TL -> UI : displayException(exception)
+ activate UI
+ TL <-- UI
+ deactivate UI
+end
+
+deactivate UI
+
+TL->UI: displayWelcomeMessage()
+activate UI
+TL <-- UI
+deactivate UI
+
+end
+@enduml
diff --git a/docs/diagrams/Style.puml b/docs/diagrams/Style.puml
new file mode 100644
index 0000000000..9098acacdb
--- /dev/null
+++ b/docs/diagrams/Style.puml
@@ -0,0 +1,33 @@
+!define LOGIC_COLOR_1 #1A1A1A
+!define LOGIC_COLOR_2 #2C3E50
+!define LOGIC_COLOR_3 #8E44AD
+!define LOGIC_COLOR_4 #D35400
+!define LOGIC_COLOR_5 #C0392B
+!define LOGIC_COLOR_6 #2980B9
+!define LOGIC_COLOR_7 #27AE60
+!define LOGIC_COLOR_8 #F39C12
+!define LOGIC_COLOR_9 #8E44AD
+!define LOGIC_COLOR_10 #34495E
+
+!define USER_COLOR #000000
+
+skinparam ArrowFontStyle bold
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
+
+
\ No newline at end of file
diff --git a/docs/team/jinzihan2002.md b/docs/team/jinzihan2002.md
new file mode 100644
index 0000000000..fe8bc427c7
--- /dev/null
+++ b/docs/team/jinzihan2002.md
@@ -0,0 +1,62 @@
+# Jin Zihan's Project Portfolio Page
+
+## Project: TutorLink
+
+TutorLink is a desktop CLI application designed to help
+University professors better manage the grades of students
+reading their course.
+
+TutorLink keeps a running tally of grades (GPA, overall score) of each
+`Student` by tagging `Components` to each student via a `Grade` object.
+
+### Summary of Contributions
+
+- **New Feature:** Added Storage classes to handle interactions with data files
+ - What it does: The `StudentStorage`, `ComponentStorage` and `GradeStorage` classes handle the loading of
+ `.txt` data files when the application starts up, and the saving of the Student, Component and Grade lists into
+ the data files after every user command.
+ - Justification: This feature improves the product by automatically saving list data after every command execution,
+ and allowing data from the application to be retained between runs, thus removing the need for the user to re-enter
+ past data manually.
+ - Highlights: Making the load file function robust enough to handle invalid data in the storage files was a challenge,
+ as there were many ways storage data could be modified or corrupted to cause errors. The function also had to be
+ modified to display the discarded data to the user, instead of silently dropping them when loading.
+ The implementation allows individual invalid file lines to be discarded, instead of resetting to an empty file.
+
+- **New Feature:** Added `ComponentList` class to manage the list of components
+ - What it does: This class contains the list of `Components`, as well as add, delete, find and toString methods.
+ - Justification: The methods facilitate modifications to the list of components through the user commands,
+ as well as printing of the list to the command line.
+
+
+- **Code contributed:** [RepoSense link](
+ https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=jinzihan2002&breakdown=true)
+
+
+- **Project Management:**
+ - Assisted with release [`v1.0`](https://github.com/AY2425S1-CS2113-W13-4/tp/releases/tag/v1.0) and the
+ migration of outstanding issues to `v2.0`, as well as the release of
+ [`v2.0`](https://github.com/AY2425S1-CS2113-W13-4/tp/releases/tag/v2.0).
+
+- **Enhancements to existing features:**
+ - Updated `runtest` scripts to accept initial data files, to facilitate I/O redirection tests on handling of
+ data file entries: [#206](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/206)
+ - Added tests for data file error handling: [#221](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/221)
+ - Created string of list of commands for use in the help command:
+ [#209](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/209)
+
+- **Documentation:**
+ - Developer Guide:
+ - Created sequence diagram and documentation for the Storage classes:
+ [#104](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/104)
+ - Updated GradeStorage and Setup sequence diagrams to match code changes:
+ [#134](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/134),
+ [#220](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/220)
+ - User Guide:
+ - Added documentation for Storage
+
+- **Community:**
+ - PRs reviewed:
+ [#25](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/25),
+ [#99](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/99),
+ [#216](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/216)
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index ab75b391b8..0000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# John Doe - Project Portfolio Page
-
-## Overview
-
-
-### Summary of Contributions
diff --git a/docs/team/rcpilot1604.md b/docs/team/rcpilot1604.md
new file mode 100644
index 0000000000..0011375c26
--- /dev/null
+++ b/docs/team/rcpilot1604.md
@@ -0,0 +1,74 @@
+# Ethan Chua's Project Portfolio Page
+
+## Project: TutorLink
+TutorLink is a desktop CLI application designed to help
+University professors better manage the grades of students
+reading their course.
+
+TutorLink keeps a running tally of grades (GPA, overall score) of each
+`Student` by tagging `Components` to each student via a `Grade` object.
+
+Basic `CRUD` operations are supported.
+
+TutorLink thus offers a lightweight CLI-friendly course management experience.
+
+### Summary of Contributions
+
+- **New Feature:** `add_student` command:
+ - What it does: Allows professors to add a student (with parameters `name` and `matriculation number`)
+ to the registry of students in the application.
+ - Justification: This feature allows professors to input all students in their course into the application.
+ - Highlights: Implementing input validation for entering matriculation number (checking that matric number complies
+with given formatting).
+- **New Feature:** `delete_student` command:
+ - What it does: This feature searches for and deletes students from TutorLink.
+ - Justification: This feature significantly improves the product by allowing professors to remove students who have withdrawn
+ or dropped the course.
+ - Highlights: Remove all grades associated with the student when executed. This is because a `Grade` object is only
+ well-defined when it is tagged to a corresponding `Student` object; removing the latter should automatically remove the former.
+
+- **New Feature:** `find_student` command:
+ - What it does: Allows professors to search for students using either `matric number` or `name`.
+ - Justification: This feature allows professors to quickly search for students in their course.
+- **New Feature:** `list_student` command:
+ - What it does: Displays a list of all students in the course.
+ - Justification: This feature improves the product significantly as professors receive key information about their students
+ such as their `matric number` and, more importantly, their current percentage score.
+ - Highlights: We faced challenges devising a suitable method to store and update the percentage score that would hold
+ across all other operations that could affect it, i.e `add_grade`, `delete_grade` etc.
+ - Credits: Special thanks to @ThienDuc3112 and @yeekian for making this feature possible through their work in the
+ `add_grade` and `delete_grade` command.
+
+- **New Feature:** `delete_component` command:
+ - What it does: Allows professors to delete a component (exam, assignment, class part etc) from the app
+ - Justification: This feature allows professors to remove components that are entered incorrectly or are
+no longer part of the course.
+ - Highlights: Similar to `delete_student`, `delete_component` removes all grades tagged to the specific component upon
+ execution. This is because a `Grade` object is only well-defined when it is tagged to a corresponding `Component` object; removing the latter should automatically remove the former.
+
+
+- **Code Contribution**: [RepoSense](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=rcpilot1604&breakdown=true)
+
+- Project Management:
+ - Managed `V1.0`. Set up project structure for team [#16](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/16),
+[#17](https:Or//github.com/AY2425S1-CS2113-W13-4/tp/pull/17)
+ - Setup GitHub Organization and Repo for team
+ - Conducted bug triaging for `PE-D`
+- Enhancement to existing features:
+ - Modified `delete_student` and `delete_component` commands to delete all matching grades: [#81](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/81)
+ - Implemented major restructuring/refactoring to codebase: [#25](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/25),
+[#52](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/52),
+[#86](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/86)
+ - Improved error messages to the user [#165](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/219), [#204](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/204), [#205](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/205)
+ - Wrote tests for `GradeList` and `DeleteComponentCommand`
+- **Documentation**:
+ - User Guide
+ - Added documentation for features implemented
+ - Added documentation for input validation [#101](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/101)
+ - Added notes for commands to UG [#214](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/214), [#217](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/217)
+ - Developer Guide
+ - Created Overarching Class Diagram: [#36](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/36)
+ - Created Class and Sequence Diagram for Architecture: [#65](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/65/files)
+ - Created Sequence Diagram for overall logic flow and standardized colour template for DG: [#99](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/99)
+ - Community
+ - PRs reviewed with nontrivial comments: [#17](https://github.com/nus-cs2113-AY2425S1/tp/pull/17), [#83](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/83), [#198](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/198), [#200](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/200) among others
\ No newline at end of file
diff --git a/docs/team/thienduc3112.md b/docs/team/thienduc3112.md
new file mode 100644
index 0000000000..68af381e15
--- /dev/null
+++ b/docs/team/thienduc3112.md
@@ -0,0 +1,33 @@
+# Nguyen Thien Duc's Project Portfolio Page
+
+## Project: TutorLink
+TutorLink is a desktop CLI application designed to help
+University professors better manage the grades of students
+reading their course.
+
+TutorLink keeps a running tally of grades (GPA, overall score) of each
+`Student` by tagging `Components` to each student via a `Grade` object.
+
+TutorLink provides useful summary statistics of a professor's course that
+can be further filtered down for analysis.
+
+### Summary of Contributions
+
+- Redesigning the project for v2.0
+- Ui class
+- TutorLink class
+- Calculate student gpa
+- Update component command
+- Accompanying unit tests
+
+
+- **Code Contribution**: [RepoSense](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=thienduc3112&breakdown=true)
+- Project Management:
+ - Managed `V2.0`. Restructure v1.0 to current structure [#36](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/36)
+ - PRs reviewed: [#36](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/36) [#52](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/52) [#58](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/58) [#54](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/54) [#55](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/55) [#49](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/49) [#96](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/96) [#72](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/72)
+- Enhancement to existing features:
+ - Major refactoring to codebase: [#38](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/38) [#51](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/51)
+- **Documentation**:
+ - Developer Guide
+ - Created UML of v2.0 [#36](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/36)
+ - Created sequence diagram for find command [#103](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/103)
\ No newline at end of file
diff --git a/docs/team/trungbui32.md b/docs/team/trungbui32.md
new file mode 100644
index 0000000000..30aa96ff97
--- /dev/null
+++ b/docs/team/trungbui32.md
@@ -0,0 +1,79 @@
+# Bui The Trung - Project Portfolio Page
+
+## Project: TutorLink
+
+TutorLink is a desktop CLI application designed to help
+University professors better manage the grades of students
+reading their course.
+
+TutorLink keeps a running tally of grades (GPA, overall score) of each
+`Student` by tagging `Components` to each student via a `Grade` object.
+
+TutorLink provides useful summary statistics of a professor's course that
+can be further filtered down for analysis.
+
+### Summary of Contributions
+
+- **Command**:
+ - `ListGradeCommand` command:
+ - Lists all student grades in the system, showing each student's name, matriculation number, component scores,
+ and
+ final percentage score in a formatted report
+ - Can filter to show grades for a specific student when given their matriculation number (using i/ prefix),
+ displaying their individual component scores with maximum possible points and final percentage score
+ - `ListComponentCommand` command:
+ - Simply lists all assessment components in the system by returning the string representation of the components
+ stored in AppState
+ - Takes no arguments/parameters (returns null for argument prefixes) and displays components directly from the
+ application's state
+ - `HelpCommand` command:
+ - The HelpCommand class is used to display a list of available commands and
+ their usage when the "help" command is triggered.
+ - This command does not require any arguments and returns a CommandResult with the help message defined in
+ Commons.HELP_MESSAGE
+
+- **Component**:
+ - `Component`:
+ - Represents an assessment component in the system with three attributes: name (identifier), maxScore (maximum
+ possible points), and weight (percentage contribution to final grade)
+ - Provides getter/setter methods for its attributes and overrides equals() to compare components by name (
+ case-insensitive) and toString() to display the component's details in format "name (maxScore: X, weight: Y%)"
+ - `Grade`:
+ - Represents a student's grade for a specific component, linking three pieces of information together: the
+ student who received the grade, the component being graded, and the actual score (with automatic capping at
+ component's max score)
+ - Provides methods to access grade information and overrides equals() to compare grades based on both component
+ and student (two grades are equal if they're for the same component and student) and toString() to display all
+ grade details in a formatted string
+- **Diagram**:
+ - `DeleteComponentCommandUML`:
+ - UML diagram for DeleteComponentCommand command
+ - `AddComponentCommandUML`
+ - UML diagram for AddComponentCommandUML
+- **Test**:
+ - `ComponentTest`:
+ - Tests the constructor and getter methods of the Component class by creating test components ("Assignment 1"
+ and "
+ Final
+ Exam") and verifying their name, maxScore, and weight values are correctly stored
+ - Tests the equals() method by verifying that components with the same name (case-insensitive) are considered
+ equal
+ while components with different names, null values, or different object types are considered not equal
+
+- **Code contribution
+ **: [RepoSense](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=TrungBui32&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-09-20&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+- Project Management:
+ - Assisted with release `V2.0`
+ - PRs reviewed: [#51](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/51)
+- Enhancement to existing features:
+ - Wrote tests for Component [#77](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/77)
+- **Documentation**:
+ - Developer Guide:
+ - Create UML for DeleteComponentCommand [#107](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/107) and
+ AddComponentCommand [#105](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/105)
+ - Non-Functional Requirements
+- **Features implemented:**
+ - Implemented ListGradeCommand [#91](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/91),
+ ListComponentCommand [#83](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/83), and HelpCommand [#216](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/216)
+ - Implemented Component [#49](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/49) and
+ Grade [#55](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/55)
\ No newline at end of file
diff --git a/docs/team/yeekian.md b/docs/team/yeekian.md
new file mode 100644
index 0000000000..a23512fafb
--- /dev/null
+++ b/docs/team/yeekian.md
@@ -0,0 +1,67 @@
+# Lim Yee Kian's - Project Portfolio Page
+
+
+## Project: TutorLink
+TutorLink is a desktop CLI application designed to help
+University professors better manage the grades of students
+reading their course.
+
+TutorLink keeps a running tally of grades (GPA, overall score) of each
+`Student` by tagging `Components` to each student via a `Grade` object.
+
+
+### Summary of Contributions
+
+- **New Feature:** Added a parser to decipher commands and command arguments from user input strings
+ - What it does: Given a user input string, the parser identifies the command that the user is trying to execute and returns the command object. The parser will also be able to extract the input arguments for the command from the rest of the string and store them in a HashMap
+ - Justification: This feature allows the sense-making of user input data. It provides a bridge between the user and the other features of the app.
+
+
+- **New Feature:** `add_grade` and `delete_grade` commands:
+ - What it does: `add_grade` command allows the user to add grades for individuals for individual graded assessment components one at a time. Using the `delete_grade` command, added grades can also be individually deleted for a specific student and his/her component.
+ - Justification: This feature improves the product significantly because a user needs to be able to record and remove scores of assessment components, such as assignments, for students. This way, the user will be able to keep a record of these grades and use them to calculate their final grade.
+ - Credits: While the processing of input user arguments and the handling of errors are done on my own, the method to add a Grade object into the list of grades, GradeList, was created by @TrungBui32.
+
+
+- **New Feature:** `add_component` command:
+ - What it does: Allows the user to add new assessment components.
+ - Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
+ - Highlights: The command checks for duplicates and prevents additional components from being added.
+
+
+- **Improved Feature:** `list_grade` command:
+ - What it does: Prints a list of all the grades of all students and their individual final percentage points.
+ - Contribution:
+ - Re-designed output list to be more user-friendly and print the final percentage points for each student.
+ - Provided 2 options for the command:
+ 1. Print grade for all students
+ 2. Print grade for specific student using the student's MATRIC_NUMBER.
+ - Justification: This improved feature ensures that grades are presented in a more informative and cleaner manner.
+
+
+- **Code contributed:** [RepoSense link](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=yeekian&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-09-20&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+
+- **Project management:**
+ - Assisted with release [`v1.0`](https://github.com/AY2425S1-CS2113-W13-4/tp/releases/tag/v1.0) and the
+ re-scoping of project scope in `v2.0`.
+ - Managed the issuing of issues
+
+
+- **Enhancements to existing features:**
+ - Wrote tests for `Parser`, `AddGradeCommand`, `DeleteGradeCommand`, and `AddComponentCommand`
+
+
+- **Documentation:**
+ - **User Guide:**
+ - Authored the initial user guide draft, including documentation for all basic features [#94](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/94).
+ - **Developer Guide:**
+ - Documented implementation details for `AddGradeCommand` and `DeleteGradeCommand` features [#106](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/106).
+ - Wrote appendices: Product Scope, User Stories, Non-Functional Requirements, Glossary, and Instructions for Manual Testing.
+
+- **Community:**
+ - PRs reviewed (with non-trivial review comments): [#25](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/25), [#38](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/38), [#83](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/83), [#101](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/101)
+
+
+- **Tools:**
+ - Set up logger to log messages during execution [#29](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/29)
diff --git a/docs/tutorlink_startup.png b/docs/tutorlink_startup.png
new file mode 100644
index 0000000000..03e80039b5
Binary files /dev/null and b/docs/tutorlink_startup.png differ
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/main/java/tutorlink/TutorLink.java b/src/main/java/tutorlink/TutorLink.java
new file mode 100644
index 0000000000..ce30f4da38
--- /dev/null
+++ b/src/main/java/tutorlink/TutorLink.java
@@ -0,0 +1,137 @@
+package tutorlink;
+
+import tutorlink.appstate.AppState;
+import tutorlink.command.Command;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.StorageOperationException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.grade.Grade;
+import tutorlink.result.CommandResult;
+import tutorlink.storage.ComponentStorage;
+import tutorlink.storage.GradeStorage;
+import tutorlink.storage.StudentStorage;
+import tutorlink.student.Student;
+import tutorlink.ui.Ui;
+import tutorlink.parser.Parser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import java.util.logging.FileHandler;
+
+/**
+ * Represents the main class containing the entry point for the TutorLink application
+ */
+public class TutorLink {
+ private static final String STUDENT_FILE_PATH = "./data/studentlist.txt";
+ private static final String COMPONENT_FILE_PATH = "./data/componentlist.txt";
+ private static final String GRADE_FILE_PATH = "./data/gradelist.txt";
+
+ private static final Ui ui = new Ui();
+ private static final Parser parser = new Parser();
+ private static AppState appState;
+ private static StudentStorage studentStorage;
+ private static ComponentStorage componentStorage;
+ private static GradeStorage gradeStorage;
+
+ private static final Logger LOGGER = Logger.getLogger(TutorLink.class.getName());
+
+
+ /**
+ * Main entry-point for TutorLink
+ */
+ public static void main(String[] args) {
+
+ setUpLogger();
+ LOGGER.log(Level.INFO, "Test log message successful");
+
+ try {
+ setupAllLists();
+ saveAllLists();
+ } catch (IOException | StorageOperationException e) {
+ System.out.println(Commons.ERROR_FILESTORAGE_EXCEPTION + e.getMessage());
+ throw new RuntimeException(e);
+ }
+
+ assert studentStorage != null;
+ assert componentStorage != null;
+ assert gradeStorage != null;
+
+ ui.displayWelcomeMessage();
+
+ while (true) {
+ try {
+ String line = ui.getUserInput();
+
+ Command currentCommand = parser.getCommand(line);
+ HashMap arguments = parser.getArguments(currentCommand.getArgumentPrefixes(), line);
+
+ CommandResult res = currentCommand.execute(appState, arguments);
+
+ ui.displayResult(res);
+
+ saveAllLists();
+
+ if (currentCommand.isExit()) {
+ break;
+ }
+
+ } catch (TutorLinkException e) {
+ ui.displayException(e);
+ } catch (IOException e) {
+ System.out.println(Commons.ERROR_FILESTORAGE_EXCEPTION + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static void setupAllLists() throws IOException, StorageOperationException {
+ studentStorage = new StudentStorage(STUDENT_FILE_PATH);
+ ArrayList initialStudentList = studentStorage.loadStudentList();
+ ArrayList discardedStudents = studentStorage.getDiscardedEntries();
+ ui.displayDiscardedEntries(discardedStudents, "Discarded student data:");
+
+ componentStorage = new ComponentStorage(COMPONENT_FILE_PATH);
+ ArrayList initialComponentList = componentStorage.loadComponentList();
+ ArrayList discardedComponents = componentStorage.getDiscardedEntries();
+ ui.displayDiscardedEntries(discardedComponents, "Discarded component data:");
+
+ gradeStorage = new GradeStorage(GRADE_FILE_PATH, initialComponentList, initialStudentList);
+ ArrayList initialGradeList = gradeStorage.loadGradeList();
+ ArrayList discardedGrades = gradeStorage.getDiscardedEntries();
+ ui.displayDiscardedEntries(discardedGrades, "Discarded grade data:");
+
+ appState = new AppState(initialStudentList, initialGradeList, initialComponentList);
+ appState.updateAllStudentPercentageScores();
+
+ }
+
+ private static void saveAllLists() throws IOException {
+ studentStorage.saveStudentList(appState.students.getStudentArrayList());
+ componentStorage.saveComponentList(appState.components.getComponentArrayList());
+ gradeStorage.saveGradeList(appState.grades.getGradeArrayList());
+ }
+
+ private static void setUpLogger() {
+ LogManager.getLogManager().reset();
+ Logger rootLogger = Logger.getLogger("");
+ rootLogger.setLevel(Level.WARNING);
+ ConsoleHandler ch = new ConsoleHandler();
+ ch.setLevel(Level.INFO);
+ rootLogger.addHandler(ch);
+
+ try {
+ FileHandler fh = new FileHandler("myLogger.log");
+ fh.setLevel(Level.FINE);
+ rootLogger.addHandler(fh);
+ } catch (IOException e) {
+ //ignore
+ rootLogger.log(Level.SEVERE, "File logger not working", e);
+ }
+ }
+}
diff --git a/src/main/java/tutorlink/appstate/AppState.java b/src/main/java/tutorlink/appstate/AppState.java
new file mode 100644
index 0000000000..788eb30e91
--- /dev/null
+++ b/src/main/java/tutorlink/appstate/AppState.java
@@ -0,0 +1,38 @@
+package tutorlink.appstate;
+
+import tutorlink.component.Component;
+import tutorlink.grade.Grade;
+import tutorlink.lists.ComponentList;
+import tutorlink.lists.GradeList;
+import tutorlink.lists.StudentList;
+import tutorlink.student.Student;
+
+import java.util.ArrayList;
+
+public class AppState {
+ public StudentList students;
+ public GradeList grades;
+ public ComponentList components;
+
+ public AppState() {
+ students = new StudentList();
+ grades = new GradeList();
+ components = new ComponentList();
+ }
+
+ public AppState(ArrayList studentArrayList,
+ ArrayList gradeArrayList,
+ ArrayList componentArrayList) {
+ students = new StudentList(studentArrayList);
+ grades = new GradeList(gradeArrayList);
+ components = new ComponentList(componentArrayList);
+ }
+
+ public void updateAllStudentPercentageScores() {
+ for (Student student : students.getStudentArrayList()) {
+ student.setPercentageScore(
+ grades.calculateStudentPercentageScore(student.getMatricNumber(), components)
+ );
+ }
+ }
+}
diff --git a/src/main/java/tutorlink/attendance/Attendance.java b/src/main/java/tutorlink/attendance/Attendance.java
new file mode 100644
index 0000000000..bc151bde12
--- /dev/null
+++ b/src/main/java/tutorlink/attendance/Attendance.java
@@ -0,0 +1,23 @@
+package tutorlink.attendance;
+
+public class Attendance {
+ private String attendanceVenue;
+ private String attendanceDateTime; //modify this to accept LocalDateTime
+ private boolean isPresent;
+
+ public Attendance(String attendanceVenue, String localDateTime, boolean isPresent){
+ this.attendanceVenue = attendanceVenue;
+ this.attendanceDateTime = localDateTime;
+ this.isPresent = isPresent;
+ }
+
+ public String getAttendanceDateTime() {
+ return attendanceDateTime;
+ }
+ public String getAttendanceVenue() {
+ return attendanceVenue;
+ }
+ public boolean getIsPresent() {
+ return isPresent;
+ }
+}
diff --git a/src/main/java/tutorlink/command/AddComponentCommand.java b/src/main/java/tutorlink/command/AddComponentCommand.java
new file mode 100644
index 0000000000..e635414d4b
--- /dev/null
+++ b/src/main/java/tutorlink/command/AddComponentCommand.java
@@ -0,0 +1,165 @@
+package tutorlink.command;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.InvalidWeightingException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+/**
+ * Command to add a new component to the list of grade components in the application.
+ * The component includes a name, weightage, and maximum score.
+ */
+public class AddComponentCommand extends Command {
+
+ public static final String[] ARGUMENT_PREFIXES = {"c/", "w/", "m/"};
+ public static final String COMMAND_WORD = "add_component";
+ private static final double MAX_SCORE = 10000.0;
+
+ /**
+ * Executes the add component command, creating a new component with the specified name,
+ * weightage, and maximum score.
+ *
+ * @param appState The current state of the application.
+ * @param hashmap Contains the arguments passed to the command.
+ * @return The result of executing the add component command.
+ * @throws TutorLinkException If there is an error in processing the command, including invalid values.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ String componentName = hashmap.get(ARGUMENT_PREFIXES[0]);
+ String weightageNumber = hashmap.get(ARGUMENT_PREFIXES[1]);
+ String maxScoreNumber = hashmap.get(ARGUMENT_PREFIXES[2]);
+
+ validateArguments(componentName, weightageNumber, maxScoreNumber);
+
+ int weightage = parseAndValidateWeightage(weightageNumber, appState);
+ double maxScore = parseMaxScore(maxScoreNumber);
+
+ addComponentToAppState(appState, componentName, weightage, maxScore);
+
+ return new CommandResult(
+ String.format(Commons.ADD_COMPONENT_SUCCESS, componentName, weightageNumber, maxScoreNumber));
+ }
+
+ /**
+ * Validates that all required arguments are present.
+ *
+ * @param componentName The name of the component.
+ * @param weightageNumber The weightage of the component as a string.
+ * @param maxScoreNumber The maximum score of the component as a string.
+ * @throws IllegalValueException If any argument is null.
+ */
+ private void validateArguments(String componentName, String weightageNumber, String maxScoreNumber)
+ throws IllegalValueException {
+ if (componentName == null || weightageNumber == null || maxScoreNumber == null) {
+ List nullParameters = new ArrayList<>();
+ if (componentName == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[0]);
+ }
+ if (weightageNumber == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[1]);
+ }
+ if (maxScoreNumber == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[2]);
+ }
+ throw new IllegalValueException(String.format(Commons.ERROR_NULL,
+ String.join(", ", nullParameters)));
+ }
+ }
+
+ /**
+ * Parses the weightage from a string to an integer and validates it.
+ * Also checks if adding this weightage would exceed the maximum allowed total weight.
+ *
+ * @param weightageNumber The weightage of the component as a string.
+ * @param appState The current state of the application to get total weight.
+ * @return The validated weightage as an integer.
+ * @throws InvalidWeightingException If the total weight exceeds the maximum allowed.
+ * @throws IllegalValueException If the weightage is invalid.
+ */
+ private int parseAndValidateWeightage(String weightageNumber, AppState appState) throws TutorLinkException {
+ int weightage = parseWeightage(weightageNumber);
+ int totalWeight = weightage + appState.components.getTotalWeighting();
+
+ if (totalWeight > Commons.MAX_WEIGHT) {
+ throw new InvalidWeightingException(String.format(Commons.ERROR_INVALID_TOTAL_WEIGHTING, totalWeight));
+ }
+
+ return weightage;
+ }
+
+ /**
+ * Adds the component with the specified attributes to the application's state and updates all
+ * student percentage scores.
+ *
+ * @param appState The current state of the application.
+ * @param componentName The name of the component.
+ * @param weightage The validated weightage of the component.
+ * @param maxScore The validated maximum score of the component.
+ */
+ private void addComponentToAppState(AppState appState, String componentName, int weightage, double maxScore) {
+ Component component = new Component(componentName, maxScore, weightage);
+ appState.components.addComponent(component);
+ appState.updateAllStudentPercentageScores();
+ }
+
+ /**
+ * Parses the weightage from a string to an integer and validates the value.
+ *
+ * @param weightageNumber The weightage of the component as a string.
+ * @return The validated weightage as an integer.
+ * @throws IllegalValueException If the weightage is not a valid integer within the allowed range.
+ */
+ private int parseWeightage(String weightageNumber) throws IllegalValueException {
+ try {
+ int weightage = Integer.parseInt(weightageNumber);
+ if (weightage < 0 || weightage > 100) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_WEIGHTAGE);
+ }
+ return weightage;
+ } catch (NumberFormatException e) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_WEIGHTAGE);
+ }
+ }
+
+ /**
+ * Parses the maximum score from a string to a double and validates the value.
+ *
+ * @param maxScoreNumber The maximum score of the component as a string.
+ * @return The validated maximum score as a double.
+ * @throws IllegalValueException If the maximum score is not a valid double or is negative.
+ */
+ private double parseMaxScore(String maxScoreNumber) throws IllegalValueException {
+ try {
+ double maxScore = Double.parseDouble(maxScoreNumber);
+ if (maxScore < 0.0) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_MAX_SCORE);
+ }
+
+ if (maxScore > MAX_SCORE) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_MAX_SCORE);
+ }
+
+ return maxScore;
+ } catch (NumberFormatException e) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_MAX_SCORE);
+ }
+ }
+
+ /**
+ * Returns the argument prefixes for this command.
+ *
+ * @return An array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/AddGradeCommand.java b/src/main/java/tutorlink/command/AddGradeCommand.java
new file mode 100644
index 0000000000..a5c5271488
--- /dev/null
+++ b/src/main/java/tutorlink/command/AddGradeCommand.java
@@ -0,0 +1,201 @@
+package tutorlink.command;
+
+import java.util.ArrayList;
+import java.util.List;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.DuplicateComponentException;
+import tutorlink.exceptions.DuplicateMatricNumberException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.grade.Grade;
+import tutorlink.lists.ComponentList;
+import tutorlink.lists.StudentList;
+import tutorlink.result.CommandResult;
+import tutorlink.student.Student;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static tutorlink.lists.StudentList.STUDENT_NOT_FOUND;
+
+/**
+ * Command to add a grade to a student's record based on their matriculation number,
+ * the specified component description, and score.
+ */
+public class AddGradeCommand extends Command {
+ public static final String[] ARGUMENT_PREFIXES = {"i/", "c/", "s/"};
+ public static final String COMMAND_WORD = "add_grade";
+ private static final String ERROR_DUPLICATE_GRADE_ON_ADD = "Error! Grade (%s, %s) already exists in the list!";
+
+ /**
+ * Executes the add grade command, creating a new grade record for the specified student
+ * and updating their GPA.
+ *
+ * @param appState The current state of the application.
+ * @param hashmap Contains the arguments passed to the command.
+ * @return The result of executing the add grade command.
+ * @throws TutorLinkException If there is an error in processing the command, including invalid arguments or
+ * component/student not found.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ String matricNumber = hashmap.get(ARGUMENT_PREFIXES[0]);
+ String componentDescription = hashmap.get(ARGUMENT_PREFIXES[1]);
+ String scoreNumber = hashmap.get(ARGUMENT_PREFIXES[2]);
+
+ validateArguments(matricNumber, componentDescription, scoreNumber);
+
+ matricNumber = formatMatricNumber(matricNumber);
+
+ Component component = findComponent(appState, componentDescription);
+ Student student = findStudent(appState, matricNumber);
+
+ double score = parseScore(scoreNumber, component);
+
+ addGradeAndCalculateGPA(appState, component, student, score);
+
+ return new CommandResult(
+ String.format(Commons.ADD_GRADE_SUCCESS, scoreNumber, componentDescription, matricNumber));
+ }
+
+ /**
+ * Validates that all required arguments are present.
+ *
+ * @param matricNumber The matriculation number of the student.
+ * @param componentDescription The description of the grade component.
+ * @param scoreNumber The score for the grade component.
+ * @throws IllegalValueException If any argument is null.
+ */
+ private void validateArguments(String matricNumber, String componentDescription, String scoreNumber)
+ throws IllegalValueException {
+ if (matricNumber == null || componentDescription == null || scoreNumber == null) {
+ List nullParameters = new ArrayList<>();
+ if (matricNumber == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[0]);
+ }
+ if (componentDescription == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[1]);
+ }
+ if (scoreNumber == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[2]);
+ }
+ throw new IllegalValueException(String.format(Commons.ERROR_NULL,
+ String.join(", ", nullParameters)));
+ }
+ }
+
+ /**
+ * Formats and validates the matriculation number.
+ *
+ * @param matricNumber The matriculation number to format and validate.
+ * @return The formatted matriculation number.
+ * @throws IllegalValueException If the matriculation number does not match the required format.
+ */
+ private String formatMatricNumber(String matricNumber) throws IllegalValueException {
+ matricNumber = matricNumber.toUpperCase();
+ Pattern pattern = Pattern.compile(Commons.MATRIC_NUMBER_REGEX);
+ Matcher matcher = pattern.matcher(matricNumber);
+ if (!matcher.find()) {
+ throw new IllegalValueException(Commons.ERROR_ILLEGAL_MATRIC_NUMBER);
+ }
+ return matricNumber;
+ }
+
+ /**
+ * Finds a component based on its description.
+ *
+ * @param appState The current state of the application.
+ * @param componentDescription The description of the component to find.
+ * @return The component with the specified description.
+ * @throws DuplicateComponentException If multiple components are found with the same description.
+ * @throws ComponentNotFoundException If no component is found with the specified description.
+ */
+ private Component findComponent(AppState appState, String componentDescription)
+ throws DuplicateComponentException, ComponentNotFoundException {
+ ComponentList componentFilteredList = appState.components.findComponent(componentDescription);
+ if (componentFilteredList.size() == 1) {
+ return componentFilteredList.getComponentArrayList().get(0);
+ } else if (componentFilteredList.size() == 0) {
+ throw new ComponentNotFoundException(
+ String.format(Commons.ERROR_COMPONENT_NOT_FOUND, componentDescription));
+ } else {
+ throw new DuplicateComponentException(
+ String.format(Commons.ERROR_MULTIPLE_QUERY_RESULT, componentDescription));
+ }
+ }
+
+ /**
+ * Finds a student based on their matriculation number.
+ *
+ * @param appState The current state of the application.
+ * @param matricNumber The matriculation number of the student to find.
+ * @return The student with the specified matriculation number.
+ * @throws StudentNotFoundException If no student with the matriculation number is found.
+ * @throws DuplicateMatricNumberException If more than one student is found with the same matriculation number.
+ */
+ private Student findStudent(AppState appState, String matricNumber)
+ throws StudentNotFoundException, DuplicateMatricNumberException {
+ StudentList studentFilteredList = appState.students.findStudentByMatricNumber(matricNumber);
+ if (studentFilteredList.size() == 1) {
+ return studentFilteredList.getStudentArrayList().get(0);
+ } else if (studentFilteredList.size() == 0) {
+ throw new StudentNotFoundException(String.format(STUDENT_NOT_FOUND, matricNumber));
+ } else {
+ throw new DuplicateMatricNumberException(String.format(Commons.ERROR_DUPLICATE_STUDENT, matricNumber));
+ }
+ }
+
+ /**
+ * Converts the score string to a double and validates it against the component's maximum score.
+ *
+ * @param scoreNumber The score in string format.
+ * @param component The component associated with the score.
+ * @return The score as a valid double.
+ * @throws IllegalValueException If the score is invalid (e.g., non-numeric or out of range).
+ */
+ private double parseScore(String scoreNumber, Component component) throws IllegalValueException {
+ try {
+ double score = Double.parseDouble(scoreNumber);
+ if (score < 0.0 || score > component.getMaxScore()) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_SCORE);
+ }
+ return score;
+ } catch (NumberFormatException e) {
+ throw new IllegalValueException(Commons.ERROR_INVALID_SCORE);
+ }
+ }
+
+ /**
+ * Adds the grade to the student's record and updates the student's GPA.
+ *
+ * @param appState The current state of the application.
+ * @param component The component for which the grade is being added.
+ * @param student The student receiving the grade.
+ * @param score The score achieved by the student in the specified component.
+ * @throws TutorLinkException If the grade already exists.
+ */
+ private void addGradeAndCalculateGPA(AppState appState, Component component, Student student, double score)
+ throws TutorLinkException {
+ Grade grade = new Grade(component, student, score);
+ appState.grades.addGrade(grade);
+
+ double newPercentageScore = appState.grades.calculateStudentPercentageScore(student.getMatricNumber(),
+ appState.components);
+ student.setPercentageScore(newPercentageScore);
+ }
+
+ /**
+ * Returns the argument prefixes for this command.
+ *
+ * @return An array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/AddStudentCommand.java b/src/main/java/tutorlink/command/AddStudentCommand.java
new file mode 100644
index 0000000000..45b30dded9
--- /dev/null
+++ b/src/main/java/tutorlink/command/AddStudentCommand.java
@@ -0,0 +1,79 @@
+package tutorlink.command;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+/**
+ * Command to add a student to the system. It requires the student's matriculation number and name.
+ * The matriculation number is validated using a regular expression.
+ */
+public class AddStudentCommand extends Command {
+
+ // Prefixes for the arguments (matriculation number and name)
+ public static final String[] ARGUMENT_PREFIXES = {"i/", "n/"};
+
+ // Command word to identify the add_student command
+ public static final String COMMAND_WORD = "add_student";
+
+ /**
+ * Executes the add student command. It validates the matriculation number and adds the student
+ * to the system if valid. If the matriculation number or name is missing, an exception is thrown.
+ *
+ * @param appState The current state of the application which holds all data.
+ * @param hashmap A map containing arguments passed to the command (expected to have matriculation number and name).
+ * @return The result of the command execution, including a success message.
+ * @throws TutorLinkException If there is an error with the matriculation number or if required parameters are
+ * missing.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ // Retrieve the matriculation number and name from the hashmap
+ String matricNumber = hashmap.get(ARGUMENT_PREFIXES[0]);
+ String name = hashmap.get(ARGUMENT_PREFIXES[1]);
+
+ // Ensure both matric number and name are provided
+ if (matricNumber == null || name == null) {
+ List nullParameters = new ArrayList<>();
+ if (matricNumber == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[0]);
+ }
+ if (name == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[1]);
+ }
+ throw new IllegalValueException(String.format(Commons.ERROR_NULL, String.join(", ", nullParameters)));
+ }
+
+ // Validate the matriculation number using a regular expression
+ Pattern pattern = Pattern.compile(Commons.MATRIC_NUMBER_REGEX);
+ Matcher matcher = pattern.matcher(matricNumber);
+ if (!matcher.find()) {
+ throw new IllegalValueException(Commons.ERROR_ILLEGAL_MATRIC_NUMBER);
+ }
+
+ // Add the student to the system
+ appState.students.addStudent(matricNumber, name);
+
+ // Return a success message
+ return new CommandResult(String.format(Commons.ADD_STUDENT_SUCCESS, name, matricNumber));
+ }
+
+ /**
+ * Returns the prefixes for the arguments expected by this command.
+ * In this case, the prefixes are "i/" for the matriculation number and "n/" for the name.
+ *
+ * @return The array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/Command.java b/src/main/java/tutorlink/command/Command.java
new file mode 100644
index 0000000000..aa8f44b275
--- /dev/null
+++ b/src/main/java/tutorlink/command/Command.java
@@ -0,0 +1,34 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+
+import tutorlink.appstate.AppState;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+/**
+ * Represents an interpreted Command from the user. A Command
object corresponds to a
+ * single user-issued command from the terminal.
+ */
+public abstract class Command {
+
+ /**
+ * Executes the required operations to perform the command issued by the user.
+ */
+ public abstract CommandResult execute(
+ AppState appState,
+ HashMap parameters
+ ) throws TutorLinkException;
+
+ public abstract String[] getArgumentPrefixes();
+
+ /**
+ * Checks if the command is an exit command.
+ *
+ * @return whether the current command is an ExitCommand
+ */
+ public boolean isExit() {
+ return false;
+ }
+}
+
diff --git a/src/main/java/tutorlink/command/DeleteComponentCommand.java b/src/main/java/tutorlink/command/DeleteComponentCommand.java
new file mode 100644
index 0000000000..b4c0b4e558
--- /dev/null
+++ b/src/main/java/tutorlink/command/DeleteComponentCommand.java
@@ -0,0 +1,71 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.lists.ComponentList;
+import tutorlink.result.CommandResult;
+
+/**
+ * Command to delete a component from the system. It will delete the component from the component list
+ * and remove associated grades. It also updates the percentage scores of all students.
+ */
+public class DeleteComponentCommand extends Command {
+
+ // Prefix for the argument (component name)
+ public static final String[] ARGUMENT_PREFIXES = {"c/"};
+
+ // Command keyword to identify the delete_component command
+ public static final String COMMAND_WORD = "delete_component";
+
+ /**
+ * Executes the delete component command. Deletes the component from the system's list and
+ * removes all associated grades.
+ *
+ * @param appState The current state of the application which holds all data.
+ * @param hashmap A map containing arguments passed to the command (expected to have the component name with
+ * prefix "c/").
+ * @return The result of the command execution, including success message.
+ * @throws TutorLinkException If the component name is invalid or not found.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ // Retrieve the component name from the hashmap
+ String componentName = hashmap.get(ARGUMENT_PREFIXES[0]);
+
+ // Ensure the component name is provided
+ if (componentName == null) {
+ throw new IllegalValueException(String.format(Commons.ERROR_NULL, ARGUMENT_PREFIXES[0]));
+ }
+
+ // Search for the component in the list
+ ComponentList componentsToDelete = appState.components.findComponent(componentName);
+
+ // If no component is found, throw an exception
+ if (componentsToDelete.size() == 0) {
+ throw new ComponentNotFoundException(String.format(Commons.ERROR_COMPONENT_NOT_FOUND, componentName));
+ }
+
+ // Delete the component and its associated grades, then update student scores
+ appState.components.deleteComponent(componentsToDelete.getComponentArrayList().get(0));
+ appState.grades.deleteGradesByComponent(componentName);
+ appState.updateAllStudentPercentageScores();
+
+ // Return a success message
+ return new CommandResult(String.format(Commons.DELETE_COMPONENT_SUCCESS, componentName));
+ }
+
+ /**
+ * Returns the prefixes for the arguments expected by this command.
+ * In this case, the prefix is "c/" for the component name.
+ *
+ * @return The array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/DeleteGradeCommand.java b/src/main/java/tutorlink/command/DeleteGradeCommand.java
new file mode 100644
index 0000000000..1b2a1b33f7
--- /dev/null
+++ b/src/main/java/tutorlink/command/DeleteGradeCommand.java
@@ -0,0 +1,147 @@
+package tutorlink.command;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.DuplicateMatricNumberException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.lists.StudentList;
+import tutorlink.result.CommandResult;
+import tutorlink.student.Student;
+
+import static tutorlink.lists.StudentList.STUDENT_NOT_FOUND;
+
+/**
+ * Command to delete a grade from a student's record based on the specified matriculation number
+ * and component description.
+ */
+public class DeleteGradeCommand extends Command {
+
+ public static final String[] ARGUMENT_PREFIXES = {"i/", "c/"};
+ public static final String COMMAND_WORD = "delete_grade";
+
+ /**
+ * Executes the delete grade command, deleting a specific grade component for a student and updating their GPA.
+ *
+ * @param appState The current state of the application.
+ * @param hashmap Contains the arguments passed to the command.
+ * @return The result of executing the delete grade command.
+ * @throws TutorLinkException If there is an error in processing the command, including invalid arguments or
+ * student not found.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ String matricNumber = hashmap.get(ARGUMENT_PREFIXES[0]);
+ String componentDescription = hashmap.get(ARGUMENT_PREFIXES[1]);
+
+ validateArguments(matricNumber, componentDescription);
+ matricNumber = formatMatricNumber(matricNumber);
+
+ Student student = getStudent(appState, matricNumber);
+ deleteGrade(appState, matricNumber, componentDescription);
+
+ updateStudentGPA(appState, student, matricNumber);
+
+ return new CommandResult(String.format(Commons.DELETE_GRADE_SUCCESS, componentDescription, matricNumber));
+ }
+
+ /**
+ * Validates the required arguments for the delete grade command.
+ *
+ * @param matricNumber The matriculation number of the student.
+ * @param componentDescription The description of the grade component to be deleted.
+ * @throws IllegalValueException If any required argument is null.
+ */
+ private void validateArguments(String matricNumber, String componentDescription) throws IllegalValueException {
+ if (matricNumber == null || componentDescription == null) {
+ List nullParameters = new ArrayList<>();
+ if (matricNumber == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[0]);
+ }
+ if (componentDescription == null) {
+ nullParameters.add(ARGUMENT_PREFIXES[1]);
+ }
+ throw new IllegalValueException(String.format(Commons.ERROR_NULL,
+ String.join(", ", nullParameters)));
+ }
+ }
+
+ /**
+ * Formats and validates the matriculation number.
+ *
+ * @param matricNumber The matriculation number to format.
+ * @return The formatted matriculation number.
+ * @throws IllegalValueException If the matriculation number does not match the required format.
+ */
+ private String formatMatricNumber(String matricNumber) throws IllegalValueException {
+ matricNumber = matricNumber.toUpperCase();
+ Pattern pattern = Pattern.compile(Commons.MATRIC_NUMBER_REGEX);
+ Matcher matcher = pattern.matcher(matricNumber);
+ if (!matcher.find()) {
+ throw new IllegalValueException(Commons.ERROR_ILLEGAL_MATRIC_NUMBER);
+ }
+ return matricNumber;
+ }
+
+ /**
+ * Finds a student based on their matriculation number.
+ *
+ * @param appState The current state of the application.
+ * @param matricNumber The matriculation number of the student.
+ * @return The student found with the specified matriculation number.
+ * @throws StudentNotFoundException If no student with the matriculation number is found.
+ * @throws DuplicateMatricNumberException If more than one student is found with the same matriculation number.
+ */
+ private static Student getStudent(AppState appState, String matricNumber) {
+ StudentList filteredList = appState.students.findStudentByMatricNumber(matricNumber);
+
+ if (filteredList.size() == 0) {
+ throw new StudentNotFoundException(String.format(STUDENT_NOT_FOUND, matricNumber));
+ } else if (filteredList.size() > 1) {
+ String errorMessage = String.format(Commons.ERROR_DUPLICATE_STUDENT, matricNumber);
+ throw new DuplicateMatricNumberException(errorMessage);
+ }
+
+ return filteredList.getStudentArrayList().get(0);
+ }
+
+ /**
+ * Deletes the specified grade component for a student.
+ *
+ * @param appState The current state of the application.
+ * @param matricNumber The matriculation number of the student.
+ * @param componentDescription The description of the grade component to be deleted.
+ */
+ private void deleteGrade(AppState appState, String matricNumber, String componentDescription) {
+ appState.grades.deleteGrade(matricNumber, componentDescription);
+ }
+
+ /**
+ * Updates the student's GPA by calculating their new percentage score after a grade component has been deleted.
+ *
+ * @param appState The current state of the application.
+ * @param student The student whose GPA is to be updated.
+ * @param matricNumber The matriculation number of the student.
+ */
+ private void updateStudentGPA(AppState appState, Student student, String matricNumber) {
+ double newGPA = appState.grades.calculateStudentPercentageScore(matricNumber, appState.components);
+ student.setPercentageScore(newGPA);
+ }
+
+ /**
+ * Returns the argument prefixes for this command.
+ *
+ * @return An array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/DeleteStudentCommand.java b/src/main/java/tutorlink/command/DeleteStudentCommand.java
new file mode 100644
index 0000000000..9c6409c1d5
--- /dev/null
+++ b/src/main/java/tutorlink/command/DeleteStudentCommand.java
@@ -0,0 +1,69 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+/**
+ * Command to delete a student from the system. It requires the student's matriculation number,
+ * validates the matriculation number format, and deletes the student and their associated grades.
+ */
+public class DeleteStudentCommand extends Command {
+
+ // Prefix for the argument (matriculation number)
+ public static final String[] ARGUMENT_PREFIXES = {"i/"};
+
+ // Command word to identify the delete_student command
+ public static final String COMMAND_WORD = "delete_student";
+
+ /**
+ * Executes the delete student command. It validates the matriculation number format,
+ * deletes the student from the system and removes their associated grades.
+ *
+ * @param appState The current state of the application which holds all data.
+ * @param hashmap A map containing arguments passed to the command (expected to have the matriculation number).
+ * @return The result of the command execution, including a success message.
+ * @throws TutorLinkException If the matriculation number is invalid or not found.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ // Retrieve the matriculation number from the hashmap
+ String matricNumber = hashmap.get(ARGUMENT_PREFIXES[0]);
+
+ // Ensure matriculation number is provided
+ if (matricNumber == null) {
+ throw new IllegalValueException(String.format(Commons.ERROR_NULL, ARGUMENT_PREFIXES[0]));
+ }
+
+ // Validate the matriculation number using a regular expression
+ Pattern pattern = Pattern.compile(Commons.MATRIC_NUMBER_REGEX);
+ Matcher matcher = pattern.matcher(matricNumber);
+ if (!matcher.find()) {
+ throw new IllegalValueException(Commons.ERROR_ILLEGAL_MATRIC_NUMBER);
+ }
+
+ // Delete the student and their associated grades
+ appState.students.deleteStudent(matricNumber);
+ appState.grades.deleteGradesByMatric(matricNumber);
+
+ // Return a success message
+ return new CommandResult(String.format(Commons.DELETE_STUDENT_SUCCESS, matricNumber));
+ }
+
+ /**
+ * Returns the prefixes for the arguments expected by this command.
+ * In this case, the prefix is "i/" for the matriculation number.
+ *
+ * @return The array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/ExitCommand.java b/src/main/java/tutorlink/command/ExitCommand.java
new file mode 100644
index 0000000000..34d465770b
--- /dev/null
+++ b/src/main/java/tutorlink/command/ExitCommand.java
@@ -0,0 +1,30 @@
+package tutorlink.command;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+import java.util.HashMap;
+
+public class ExitCommand extends Command {
+ public static final String COMMAND_WORD = "bye";
+
+ public ExitCommand() {
+ }
+
+ @Override
+ public CommandResult execute(AppState state, HashMap arguments) throws TutorLinkException {
+ return new CommandResult(Commons.EXIT);
+ }
+
+ @Override
+ public String[] getArgumentPrefixes() {
+ return null;
+ }
+
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+}
diff --git a/src/main/java/tutorlink/command/FindStudentCommand.java b/src/main/java/tutorlink/command/FindStudentCommand.java
new file mode 100644
index 0000000000..bf4736fbea
--- /dev/null
+++ b/src/main/java/tutorlink/command/FindStudentCommand.java
@@ -0,0 +1,69 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.lists.StudentList;
+import tutorlink.result.CommandResult;
+import tutorlink.appstate.AppState;
+
+/**
+ * Command to find a student by either their matriculation number or their name.
+ * The command can search for a student by matriculation number or by name, but not both.
+ * If neither is provided, an exception is thrown.
+ */
+public class FindStudentCommand extends Command {
+
+ // Prefixes for the arguments (matriculation number and name)
+ public static final String[] ARGUMENT_PREFIXES = {"i/", "n/"};
+
+ // Command word to identify the find_student command
+ public static final String COMMAND_WORD = "find_student";
+
+ /**
+ * Executes the find student command. It searches for a student by their matriculation number
+ * or by their name, depending on which argument is provided.
+ *
+ * @param appstate The current state of the application which holds all data.
+ * @param hashmap A map containing arguments passed to the command (either matriculation number or name).
+ * @return The result of the command execution, which includes a string representation of the student(s).
+ * @throws TutorLinkException If neither a matriculation number nor a name is provided,
+ * or if an invalid search is made.
+ */
+ @Override
+ public CommandResult execute(AppState appstate, HashMap hashmap) throws TutorLinkException {
+ // Retrieve the matriculation number and name from the hashmap
+ String matricNumber = hashmap.get(ARGUMENT_PREFIXES[0]);
+ String name = hashmap.get(ARGUMENT_PREFIXES[1]);
+ StudentList students;
+
+ // Ensure at least one argument (matriculation number or name) is provided
+ if (name == null && matricNumber == null) {
+ throw new IllegalValueException(Commons.ERROR_STUDENT_BOTH_NULL);
+ }
+
+ // If a matriculation number is provided, search by matriculation number
+ if (hashmap.containsKey(ARGUMENT_PREFIXES[0])) {
+ students = appstate.students.findStudentByMatricNumber(matricNumber);
+ assert students.getStudentArrayList().size() <= 1;
+ } else {
+ students = appstate.students.findStudentByName(name);
+ }
+
+ // Return the result as a string representation of the found student(s)
+ return new CommandResult(students.toString());
+ }
+
+ /**
+ * Returns the prefixes for the arguments expected by this command.
+ * In this case, the prefixes are "i/" for the matriculation number and "n/" for the name.
+ *
+ * @return The array of argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/HelpCommand.java b/src/main/java/tutorlink/command/HelpCommand.java
new file mode 100644
index 0000000000..66b809a744
--- /dev/null
+++ b/src/main/java/tutorlink/command/HelpCommand.java
@@ -0,0 +1,52 @@
+package tutorlink.command;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+import java.util.HashMap;
+
+/**
+ * Represents a help command that displays usage information.
+ * This command shows a list of all available commands and their proper usage.
+ * This command is triggered using the "help" keyword.
+ */
+public class HelpCommand extends Command {
+ /**
+ * Command word that triggers this command
+ */
+ public static final String COMMAND_WORD = "help";
+
+ /**
+ * Creates a new HelpCommand instance.
+ */
+ public HelpCommand() {
+ }
+
+ /**
+ * Executes the help command.
+ * Returns a command result containing the help message that lists
+ * all available commands and their usage.
+ *
+ * @param state The current state of the application
+ * @param arguments Command arguments (not used for help command)
+ * @return CommandResult containing the help message
+ * @throws TutorLinkException if there's an error executing the command
+ */
+ @Override
+ public CommandResult execute(AppState state, HashMap arguments) throws TutorLinkException {
+ return new CommandResult(Commons.HELP_MESSAGE);
+ }
+
+ /**
+ * Gets the argument prefixes that this command accepts.
+ * Help command doesn't accept any arguments.
+ *
+ * @return null since this command takes no arguments
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return null;
+ }
+}
diff --git a/src/main/java/tutorlink/command/InvalidCommand.java b/src/main/java/tutorlink/command/InvalidCommand.java
new file mode 100644
index 0000000000..130dc0ded8
--- /dev/null
+++ b/src/main/java/tutorlink/command/InvalidCommand.java
@@ -0,0 +1,41 @@
+package tutorlink.command;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.InvalidCommandException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+import java.util.HashMap;
+
+/**
+ * Represents a command that is triggered when an invalid command word is provided.
+ * This command, when executed, throws an {@code InvalidCommandException} indicating
+ * that the command is not recognized.
+ */
+public class InvalidCommand extends Command {
+
+ /**
+ * Executes the invalid command, resulting in an exception to notify the user
+ * that an invalid command has been entered.
+ *
+ * @param appState The current application state, not used by this command.
+ * @param hashMap A map of argument prefixes to their values, not used by this command.
+ * @return This method does not return normally, as it always throws an {@code InvalidCommandException}.
+ * @throws TutorLinkException Always thrown to indicate an invalid command.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashMap) throws TutorLinkException {
+ throw new InvalidCommandException(Commons.ERROR_INVALID_COMMAND);
+ }
+
+ /**
+ * Returns the argument prefixes required by this command.
+ *
+ * @return {@code null} as this command does not require any argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return null;
+ }
+}
diff --git a/src/main/java/tutorlink/command/ListComponentCommand.java b/src/main/java/tutorlink/command/ListComponentCommand.java
new file mode 100644
index 0000000000..f9594e8eb9
--- /dev/null
+++ b/src/main/java/tutorlink/command/ListComponentCommand.java
@@ -0,0 +1,42 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+
+import tutorlink.appstate.AppState;
+import tutorlink.result.CommandResult;
+
+/**
+ * Represents a command to list all components. The command retrieves the components
+ * from the application state and returns them in a formatted list.
+ */
+public class ListComponentCommand extends Command {
+
+ /** The command word used to trigger this command. */
+ public static final String COMMAND_WORD = "list_component";
+ private static final String MESSAGE_NO_COMPONENTS = "No components have been recorded yet.";
+
+ /**
+ * Executes the command to list all components.
+ *
+ * @param appState The current application state containing the component data.
+ * @param parameters A map of argument prefixes to their values, not used for this command.
+ * @return A {@code CommandResult} containing the formatted list of components.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap parameters) {
+ if(appState.components.size() <= 0) {
+ return new CommandResult(MESSAGE_NO_COMPONENTS);
+ }
+ return new CommandResult(appState.components.toString());
+ }
+
+ /**
+ * Returns the argument prefixes required by this command.
+ *
+ * @return {@code null} as this command does not require any argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return null;
+ }
+}
diff --git a/src/main/java/tutorlink/command/ListGradeCommand.java b/src/main/java/tutorlink/command/ListGradeCommand.java
new file mode 100644
index 0000000000..92e1a7d2ff
--- /dev/null
+++ b/src/main/java/tutorlink/command/ListGradeCommand.java
@@ -0,0 +1,152 @@
+package tutorlink.command;
+
+import tutorlink.appstate.AppState;
+import tutorlink.grade.Grade;
+import tutorlink.result.CommandResult;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.student.Student;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.Comparator;
+
+/**
+ * Represents a command to list grades of all students or a specific student.
+ * The grades are displayed with a breakdown of scores by component, and the final percentage score (GPA) is calculated.
+ */
+public class ListGradeCommand extends Command {
+
+ public static final String COMMAND_WORD = "list_grade";
+ public static final String[] ARGUMENT_PREFIXES = {"i/"};
+ private static final String MESSAGE_NO_GRADES = "No grades have been recorded yet.";
+ private static final String MESSAGE_STUDENT_NOT_FOUND = "No grades found for student with matriculation number %s.";
+
+ /**
+ * Executes the command to list grades. If a matriculation number is provided, only that student's grades are shown;
+ * otherwise, grades for all students are displayed.
+ *
+ * @param appState The current application state containing the student and grade data.
+ * @param hashMap A map of argument prefixes to their values.
+ * @return A {@code CommandResult} containing the formatted grades list for the specified student or all students.
+ * @throws StudentNotFoundException if no student is found with the specified matriculation number.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashMap) throws StudentNotFoundException {
+ ArrayList grades = appState.grades.getGradeArrayList();
+
+ if (grades.isEmpty()) {
+ return new CommandResult(MESSAGE_NO_GRADES);
+ }
+
+ String matricNumber = hashMap.get("i/");
+
+ // If a specific student's grades are requested
+ if (matricNumber != null) {
+ matricNumber = matricNumber.toUpperCase();
+ String finalMatricNumber = matricNumber;
+ Student student = appState.students.findStudentByMatricNumber(matricNumber)
+ .getStudentArrayList().stream().findFirst()
+ .orElseThrow(() -> new StudentNotFoundException(
+ String.format(MESSAGE_STUDENT_NOT_FOUND, finalMatricNumber)));
+
+ ArrayList studentGrades = grades.stream()
+ .filter(grade -> grade.getStudent().getMatricNumber().equals(finalMatricNumber))
+ .collect(Collectors.toCollection(ArrayList::new));
+
+ if (studentGrades.isEmpty()) {
+ return new CommandResult(String.format(MESSAGE_STUDENT_NOT_FOUND, matricNumber));
+ }
+
+ return generateStudentGradeReport(student, studentGrades, appState);
+ }
+
+ // Otherwise, display all students' grades
+ return generateAllGradesReport(grades, appState);
+ }
+
+ /**
+ * Generates a formatted report of grades for a specific student.
+ *
+ * @param student The student for whom the report is generated.
+ * @param studentGrades The list of grades for the specified student.
+ * @param appState The application state containing component data.
+ * @return A {@code CommandResult} containing the formatted grade report for the student.
+ */
+ private CommandResult generateStudentGradeReport(Student student, ArrayList studentGrades,
+ AppState appState) {
+ StringBuilder output = new StringBuilder(
+ String.format("Grades for %s (%s):\n", student.getName(), student.getMatricNumber()));
+
+ int gradeIndex = 1;
+ for (Grade grade : studentGrades.stream()
+ .sorted(Comparator.comparing(g -> g.getComponent().getName()))
+ .collect(Collectors.toList())) {
+ output.append(
+ String.format("%d: %-15s: %.2f / %.2f\n",
+ gradeIndex++,
+ grade.getComponent().getName(),
+ grade.getScore(),
+ grade.getComponent().getMaxScore()
+ ));
+ }
+
+ double percentageScore = appState.grades.calculateStudentPercentageScore(student.getMatricNumber(),
+ appState.components);
+ output.append(String.format("\nFinal score: %.2f%%\n", percentageScore));
+
+ return new CommandResult(output.toString());
+ }
+
+ /**
+ * Generates a formatted report of grades for all students.
+ *
+ * @param grades The list of all grades.
+ * @param appState The application state containing component data.
+ * @return A {@code CommandResult} containing the formatted grade report for all students.
+ */
+ private CommandResult generateAllGradesReport(ArrayList grades, AppState appState) {
+ StringBuilder output = new StringBuilder("List of All Grades:\n\n");
+
+ Map> gradesByStudent = grades.stream()
+ .collect(Collectors.groupingBy(
+ grade -> grade.getStudent().getMatricNumber(),
+ TreeMap::new,
+ Collectors.toCollection(ArrayList::new)
+ ));
+
+ int studentIndex = 1;
+ for (Map.Entry> entry : gradesByStudent.entrySet()) {
+ Student student = entry.getValue().get(0).getStudent();
+ output.append(
+ String.format("%d: %s (%s):\n", studentIndex++, student.getName(), student.getMatricNumber()));
+
+ int gradeIndex = 1;
+ for (Grade grade : entry.getValue().stream()
+ .sorted(Comparator.comparing(g -> g.getComponent().getName()))
+ .collect(Collectors.toList())) {
+ output.append(
+ String.format(" %d. %-15s: %.2f\n", gradeIndex++,
+ grade.getComponent().getName(), grade.getScore()));
+ }
+
+ double percentageScore = appState.grades.calculateStudentPercentageScore(student.getMatricNumber(),
+ appState.components);
+ output.append(String.format(" Final Percentage Score: %.2f%%\n\n", percentageScore));
+ }
+
+ return new CommandResult(output.toString());
+ }
+
+ /**
+ * Returns the argument prefixes required by this command.
+ *
+ * @return An array of argument prefixes: {"i/"}, representing the matriculation number prefix.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/command/ListStudentCommand.java b/src/main/java/tutorlink/command/ListStudentCommand.java
new file mode 100644
index 0000000000..e528dbd00b
--- /dev/null
+++ b/src/main/java/tutorlink/command/ListStudentCommand.java
@@ -0,0 +1,41 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+import tutorlink.appstate.AppState;
+import tutorlink.result.CommandResult;
+
+/**
+ * Represents a command to list all students in the application.
+ * When executed, this command retrieves the list of students from the application state
+ * and returns it as a command result.
+ */
+public class ListStudentCommand extends Command {
+
+ public static final String COMMAND_WORD = "list_student";
+ private static final String MESSAGE_NO_STUDENTS = "No students have been recorded yet.";
+
+ /**
+ * Executes the command to list all students, retrieving the student list from the application state.
+ *
+ * @param appState The current application state containing the student list.
+ * @param hashMap A map of argument prefixes to their values (unused in this command).
+ * @return A {@code CommandResult} containing the list of students as a string.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashMap) {
+ if(appState.students.size() <= 0) {
+ return new CommandResult(MESSAGE_NO_STUDENTS);
+ }
+ return new CommandResult(appState.students.toString());
+ }
+
+ /**
+ * Returns the argument prefixes required by this command.
+ *
+ * @return {@code null} as this command does not require any argument prefixes.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return null;
+ }
+}
diff --git a/src/main/java/tutorlink/command/UpdateComponentCommand.java b/src/main/java/tutorlink/command/UpdateComponentCommand.java
new file mode 100644
index 0000000000..c6edd3e018
--- /dev/null
+++ b/src/main/java/tutorlink/command/UpdateComponentCommand.java
@@ -0,0 +1,114 @@
+package tutorlink.command;
+
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.InvalidWeightingException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.lists.ComponentList;
+import tutorlink.result.CommandResult;
+
+import java.util.HashMap;
+
+/**
+ * Represents a command to update an existing component's weight or maximum score.
+ * The command requires the component's name and either a new weight or maximum score
+ * (or both) to update the component.
+ */
+public class UpdateComponentCommand extends Command {
+ public static final String[] ARGUMENT_PREFIXES = {"c/", "w/", "m/"};
+ public static final String COMMAND_WORD = "update_component";
+
+ /**
+ * Executes the update component command, updating the specified component's
+ * weight and/or maximum score in the application state.
+ *
+ * @param appState The current application state containing the component list.
+ * @param hashmap A map of argument prefixes to their values for this command.
+ * @return A {@code CommandResult} indicating the result of the update.
+ * @throws TutorLinkException If the component is not found, the new values are invalid,
+ * or if the new weight causes the total weight to exceed the limit.
+ */
+ @Override
+ public CommandResult execute(AppState appState, HashMap hashmap) throws TutorLinkException {
+ String name = hashmap.get(ARGUMENT_PREFIXES[0]);
+ String cWeight = hashmap.get(ARGUMENT_PREFIXES[1]);
+ String cMark = hashmap.get(ARGUMENT_PREFIXES[2]);
+
+ // Validate the presence of required arguments
+ if (name == null || (cWeight == null && cMark == null)) {
+ StringBuilder sb = new StringBuilder("Error! ");
+ if (name == null) {
+ sb.append("c/ is required! ");
+ }
+ if (cWeight == null && cMark == null) {
+ sb.append("Either w/ or m/ or both are required!");
+ }
+ throw new IllegalValueException(sb.toString());
+ }
+
+ Integer weight = null;
+ Double mark = null;
+
+ // Parse weight and mark values from the arguments
+ try {
+ if (cWeight != null) {
+ weight = Integer.parseInt(cWeight);
+ }
+ if (cMark != null) {
+ mark = Double.parseDouble(cMark);
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalValueException("Error! weight is not an integer or mark is not a real positive number");
+ }
+
+ // Validate the parsed values for mark and weight
+ if (mark != null && (mark.isNaN() || mark.isInfinite() || mark < 0)) {
+ throw new IllegalValueException("Error! Mark is not a real positive number");
+ }
+ if (weight != null && (weight < 0 || weight > 100)) {
+ throw new IllegalValueException("Error! weight must be between 0 and 100");
+ }
+
+ // Find the component by name
+ ComponentList componentsToEdit = appState.components.findComponent(name);
+ if (componentsToEdit.size() == 0) {
+ throw new ComponentNotFoundException(String.format(Commons.ERROR_COMPONENT_NOT_FOUND, name));
+ }
+
+ Component component = componentsToEdit.getComponentArrayList().get(0);
+
+ // Update weight if provided, ensuring total weighting remains valid
+ int oldWeight = component.getWeight();
+ if (weight != null) {
+ component.setWeight(weight);
+ int totalWeight = appState.components.getTotalWeighting();
+ if (totalWeight > 100) {
+ component.setWeight(oldWeight);
+ throw new InvalidWeightingException(String.format(Commons.ERROR_INVALID_TOTAL_WEIGHTING, totalWeight));
+ }
+ }
+
+ // Update maximum score if provided
+ if (mark != null) {
+ component.setMaxScore(mark);
+ }
+
+ // Update percentage scores for all students based on new component values
+ appState.updateAllStudentPercentageScores();
+
+ return new CommandResult("Here is the updated component\n" + component.toString());
+ }
+
+ /**
+ * Returns the argument prefixes required by this command.
+ *
+ * @return An array of argument prefixes required by this command.
+ */
+ @Override
+ public String[] getArgumentPrefixes() {
+ return ARGUMENT_PREFIXES;
+ }
+}
diff --git a/src/main/java/tutorlink/commons/Commons.java b/src/main/java/tutorlink/commons/Commons.java
new file mode 100644
index 0000000000..cf119b05e6
--- /dev/null
+++ b/src/main/java/tutorlink/commons/Commons.java
@@ -0,0 +1,94 @@
+package tutorlink.commons;
+
+public class Commons {
+
+ public static final String ERROR_NULL = "Error! Null parameters %s passed!";
+
+ //@@author RCPilot1604
+
+ //Input Validation
+ public static final String MATRIC_NUMBER_REGEX = "A\\d{7}[A-Z]";
+ public static final String ERROR_ILLEGAL_MATRIC_NUMBER = "Error! Matric Number should start with \"A\", " +
+ "followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)";
+ //Student
+ public static final String ADD_STUDENT_SUCCESS = "Student %s (%s) added successfully!";
+ public static final String ERROR_DUPLICATE_STUDENT =
+ "Error! There is more than 1 student with the Matric Number, %s!";
+ public static final String ERROR_STUDENT_BOTH_NULL = "Error! Both parameters passed are null!";
+ public static final String DELETE_STUDENT_SUCCESS = "Student %s successfully deleted";
+ public static final String LIST_STUDENT_SUCCESS = "Here are your students:";
+
+ //@@author yeekian
+ //Grade
+ public static final String DELETE_GRADE_SUCCESS = "Grade: Component %s for student %s successfully deleted";
+ public static final String ADD_GRADE_SUCCESS = "Score of %s added successfully to %s for %s!";
+ public static final String ERROR_INVALID_SCORE =
+ "Error! Score must be a numerical value and be between 0 and the max score of the component!";
+
+ //@@author TrungBui32
+ //Component
+ public static final int MAX_WEIGHT = 100;
+ public static final String ADD_COMPONENT_SUCCESS =
+ "Component %s of weight %s%%, with max score %s added successfully!";
+ public static final String DELETE_COMPONENT_SUCCESS = "Component %s successfully deleted";
+ public static final String ERROR_COMPONENT_NOT_FOUND = "Error! Component (Name %s) not found";
+ public static final String ERROR_DUPLICATE_COMPONENT = "Error! Component (Name %s) already exists in the list!";
+ public static final String ERROR_MULTIPLE_QUERY_RESULT = "Error! Multiple query results for keyword: %s found " +
+ "in list!";
+ public static final String ERROR_INVALID_WEIGHTAGE = "Error! Weightage must be integer that is between 0 and 100!";
+ public static final String ERROR_INVALID_MAX_SCORE =
+ "Error! Max Score must be double that is between 0 and 10000!";
+ //@@author RCPilot1604
+ public static final String ERROR_INVALID_TOTAL_WEIGHTING = "Error! Total weighting must not exceed 100%%.\n" +
+ "Current weighting (after addition): %s%%";
+
+ //Invalid
+ public static final String ERROR_INVALID_COMMAND = "Error! Invalid command given!";
+
+ public static final String ERROR_FILESTORAGE_EXCEPTION = "Error! File storage exception encountered: ";
+
+ //Exit
+ public static final String EXIT = "Goodbye! See you soon!";
+
+ //Help
+ public static final String HELP_MESSAGE = """
+ help: Displays list of commands
+ Example: help
+
+ add_student: Adds a student to the class roster
+ Example: add_student i/A1234567X n/John Doe
+
+ delete_student: Deletes a student from the class roster
+ Example: delete_student i/A1234567X
+
+ list_student: Lists all students in the class
+ Example: list_student
+
+ find_student: Finds a student in the class roster by name or matric number
+ Example: find_student i/A1234567X n/John Doe
+
+ add_component: Adds a new grading component to the class
+ Example: add_component c/Quiz 1 w/30 m/50
+
+ delete_component: Deletes a grading component from the class
+ Example: delete_component c/Quiz 1
+
+ update_component: Updates a component with a new maxscore or weight
+ Example: update_component c/Quiz 1 w/40 m/60
+
+ list_component: Lists all grading components
+ Example: list_component
+
+ add_grade: Adds a grade for a student for a specific component
+ Example: add_grade i/A1234567X c/Quiz 1 s/45
+
+ delete_grade: Deletes a student's grade for a specific component
+ Example: delete_grade i/A1234567X c/Quiz 1
+
+ list_grade: Lists all grades for a student
+ Example: list_grade i/A1234567X
+
+ bye: Exits the program
+ Example: bye
+ """;
+}
diff --git a/src/main/java/tutorlink/component/Component.java b/src/main/java/tutorlink/component/Component.java
new file mode 100644
index 0000000000..40236a1f12
--- /dev/null
+++ b/src/main/java/tutorlink/component/Component.java
@@ -0,0 +1,47 @@
+package tutorlink.component;
+
+//@@author TrungBui32
+public class Component {
+ private String name;
+ private double maxScore;
+ private int weight;
+
+ public Component(String name, double maxScore, int weight) {
+ this.name = name;
+ this.maxScore = maxScore;
+ this.weight = weight;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public double getMaxScore() {
+ return maxScore;
+ }
+
+ public int getWeight() {
+ return weight;
+ }
+
+ public void setMaxScore(double maxScore) {
+ this.maxScore = maxScore;
+ }
+
+ public void setWeight(int weight) {
+ this.weight = weight;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Component comp) {
+ return comp.getName().equalsIgnoreCase(this.getName());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return name + " (maxScore: " + maxScore + ", weight: " + weight + "%)";
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/ComponentNotFoundException.java b/src/main/java/tutorlink/exceptions/ComponentNotFoundException.java
new file mode 100644
index 0000000000..b1a35b90d7
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/ComponentNotFoundException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class ComponentNotFoundException extends TutorLinkException {
+ public ComponentNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/DuplicateComponentException.java b/src/main/java/tutorlink/exceptions/DuplicateComponentException.java
new file mode 100644
index 0000000000..fdcb2b7806
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/DuplicateComponentException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class DuplicateComponentException extends TutorLinkException {
+ public DuplicateComponentException (String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/DuplicateGradeException.java b/src/main/java/tutorlink/exceptions/DuplicateGradeException.java
new file mode 100644
index 0000000000..d5cef284d7
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/DuplicateGradeException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class DuplicateGradeException extends TutorLinkException {
+ public DuplicateGradeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/DuplicateMatricNumberException.java b/src/main/java/tutorlink/exceptions/DuplicateMatricNumberException.java
new file mode 100644
index 0000000000..c9b76ab941
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/DuplicateMatricNumberException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class DuplicateMatricNumberException extends TutorLinkException {
+ public DuplicateMatricNumberException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/GradeNotFoundException.java b/src/main/java/tutorlink/exceptions/GradeNotFoundException.java
new file mode 100644
index 0000000000..76230c8fba
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/GradeNotFoundException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class GradeNotFoundException extends TutorLinkException {
+ public GradeNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/IllegalValueException.java b/src/main/java/tutorlink/exceptions/IllegalValueException.java
new file mode 100644
index 0000000000..ce72c5c5e0
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/IllegalValueException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class IllegalValueException extends TutorLinkException {
+ public IllegalValueException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/IncompleteGradesException.java b/src/main/java/tutorlink/exceptions/IncompleteGradesException.java
new file mode 100644
index 0000000000..8e4624960b
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/IncompleteGradesException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class IncompleteGradesException extends TutorLinkException {
+ public IncompleteGradesException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/InvalidCommandException.java b/src/main/java/tutorlink/exceptions/InvalidCommandException.java
new file mode 100644
index 0000000000..52469b5e98
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/InvalidCommandException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class InvalidCommandException extends TutorLinkException {
+ public InvalidCommandException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/InvalidComponentException.java b/src/main/java/tutorlink/exceptions/InvalidComponentException.java
new file mode 100644
index 0000000000..6897c2ebdc
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/InvalidComponentException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class InvalidComponentException extends IllegalArgumentException {
+ public InvalidComponentException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/InvalidDataFileLineException.java b/src/main/java/tutorlink/exceptions/InvalidDataFileLineException.java
new file mode 100644
index 0000000000..85e1b18c62
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/InvalidDataFileLineException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class InvalidDataFileLineException extends TutorLinkException {
+ public InvalidDataFileLineException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/InvalidWeightingException.java b/src/main/java/tutorlink/exceptions/InvalidWeightingException.java
new file mode 100644
index 0000000000..f36c13bfea
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/InvalidWeightingException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class InvalidWeightingException extends TutorLinkException {
+ public InvalidWeightingException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/ItemNotFoundException.java b/src/main/java/tutorlink/exceptions/ItemNotFoundException.java
new file mode 100644
index 0000000000..98ed0b3db4
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/ItemNotFoundException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class ItemNotFoundException extends TutorLinkException {
+ public ItemNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/MissingNextLineException.java b/src/main/java/tutorlink/exceptions/MissingNextLineException.java
new file mode 100644
index 0000000000..1a752bd1ad
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/MissingNextLineException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class MissingNextLineException extends TutorLinkException {
+ public MissingNextLineException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/StorageOperationException.java b/src/main/java/tutorlink/exceptions/StorageOperationException.java
new file mode 100644
index 0000000000..14cef73ba6
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/StorageOperationException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class StorageOperationException extends TutorLinkException {
+ public StorageOperationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/StudentNotFoundException.java b/src/main/java/tutorlink/exceptions/StudentNotFoundException.java
new file mode 100644
index 0000000000..d02aece318
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/StudentNotFoundException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public class StudentNotFoundException extends TutorLinkException {
+ public StudentNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/exceptions/TutorLinkException.java b/src/main/java/tutorlink/exceptions/TutorLinkException.java
new file mode 100644
index 0000000000..daa8bd7e27
--- /dev/null
+++ b/src/main/java/tutorlink/exceptions/TutorLinkException.java
@@ -0,0 +1,7 @@
+package tutorlink.exceptions;
+
+public abstract class TutorLinkException extends RuntimeException {
+ public TutorLinkException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/tutorlink/grade/Grade.java b/src/main/java/tutorlink/grade/Grade.java
new file mode 100644
index 0000000000..fab1c37f3e
--- /dev/null
+++ b/src/main/java/tutorlink/grade/Grade.java
@@ -0,0 +1,104 @@
+package tutorlink.grade;
+
+import tutorlink.component.Component;
+import tutorlink.student.Student;
+
+/**
+ * Represents a grade assigned to a student for a specific component of an assessment.
+ * Each grade has a score, a corresponding component, and a student to whom it belongs.
+ */
+public class Grade {
+ private double score;
+ private Component component;
+ private Student student;
+
+ /**
+ * Constructs a {@code Grade} object with the specified component, student, and score.
+ *
+ * @param component The component associated with the grade.
+ * @param student The student associated with the grade.
+ * @param score The score awarded to the student for the component.
+ */
+ public Grade(Component component, Student student, double score) {
+ this.component = component;
+ this.student = student;
+ this.score = score;
+ }
+
+ /**
+ * Returns the student associated with this grade.
+ *
+ * @return The {@code Student} object linked to this grade.
+ */
+ public Student getStudent() {
+ return student;
+ }
+
+ /**
+ * Returns the component associated with this grade.
+ *
+ * @return The {@code Component} object linked to this grade.
+ */
+ public Component getComponent() {
+ return component;
+ }
+
+ /**
+ * Returns the score for this grade. If the score exceeds the component's maximum
+ * score, it is capped to the maximum score of the component.
+ *
+ * @return The score for this grade, capped at the component's maximum score if necessary.
+ */
+ public double getScore() {
+ if (component != null && component.getMaxScore() < score) {
+ score = component.getMaxScore();
+ }
+ return score;
+ }
+
+ /**
+ * Checks if this grade is equal to another object. Two grades are considered equal if
+ * they have the same component and student.
+ *
+ * @param obj The object to compare with this grade.
+ * @return {@code true} if the object is a grade with the same component and student; {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Grade grade) {
+ return this.component.equals(grade.component)
+ && this.student.equals(grade.student);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the matriculation number of the student associated with this grade.
+ *
+ * @return The matriculation number of the student.
+ */
+ public String getStudentMatricNumber() {
+ return student.getMatricNumber();
+ }
+
+ /**
+ * Returns the name of the component associated with this grade.
+ *
+ * @return The name of the component.
+ */
+ public String getComponentName() {
+ return component.getName();
+ }
+
+ /**
+ * Returns a string representation of the grade, including the student, component,
+ * and score.
+ *
+ * @return A string representing the grade.
+ */
+ @Override
+ public String toString() {
+ return "Student: " + this.student + ", Component: " + this.component
+ + ", Score: " + this.score;
+ }
+}
diff --git a/src/main/java/tutorlink/lists/ComponentList.java b/src/main/java/tutorlink/lists/ComponentList.java
new file mode 100644
index 0000000000..39634739a4
--- /dev/null
+++ b/src/main/java/tutorlink/lists/ComponentList.java
@@ -0,0 +1,135 @@
+package tutorlink.lists;
+
+import tutorlink.component.Component;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.DuplicateComponentException;
+
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Represents a list of components. Provides methods for adding, deleting, and searching for components,
+ * as well as calculating the total weight of all components in the list.
+ */
+public class ComponentList {
+ private static final String ERROR_COMPONENT_NOT_FOUND = "Error! Component %s does not exist in the list!";
+ private static final String ERROR_DUPLICATE_COMPONENT = "Error! Component already exists in the list!";
+
+ private ArrayList componentArrayList;
+
+ /**
+ * Creates an empty {@code ComponentList}.
+ */
+ public ComponentList() {
+ this.componentArrayList = new ArrayList<>();
+ }
+
+ /**
+ * Creates a {@code ComponentList} initialized with the given list of components.
+ *
+ * @param componentArrayList The initial list of components.
+ */
+ public ComponentList(ArrayList componentArrayList) {
+ this.componentArrayList = componentArrayList;
+ }
+
+ /**
+ * Searches for components in the list by name.
+ *
+ * @param name The name or partial name of the component to search for.
+ * @return A {@code ComponentList} containing all components that match the search criteria.
+ * @throws ComponentNotFoundException If no matching components are found.
+ */
+ public ComponentList findComponent(String name) throws ComponentNotFoundException {
+ ComponentList filteredList = new ComponentList();
+ filteredList.componentArrayList = componentArrayList
+ .stream()
+ .filter(comp -> comp.getName().equalsIgnoreCase(name))
+ .collect(Collectors.toCollection(ArrayList::new));
+ if (filteredList.componentArrayList.isEmpty()) {
+ throw new ComponentNotFoundException(String.format(ERROR_COMPONENT_NOT_FOUND, name));
+ }
+ return filteredList;
+ }
+
+ /**
+ * Adds a component to the list if it does not already exist.
+ *
+ * @param component The component to add.
+ * @throws DuplicateComponentException If a component with the same properties already exists in the list.
+ */
+ public void addComponent(Component component) throws DuplicateComponentException {
+ for (Component comp : componentArrayList) {
+ if (comp.equals(component)) {
+ throw new DuplicateComponentException(ERROR_DUPLICATE_COMPONENT);
+ }
+ }
+ componentArrayList.add(component);
+ }
+
+ /**
+ * Deletes a component from the list.
+ *
+ * @param component The component to delete.
+ * @throws ComponentNotFoundException If the component is not found in the list.
+ */
+ public void deleteComponent(Component component) throws ComponentNotFoundException {
+ for (Component comp : componentArrayList) {
+ if (comp.equals(component)) {
+ componentArrayList.remove(comp);
+ return;
+ }
+ }
+ throw new ComponentNotFoundException(String.format(ERROR_COMPONENT_NOT_FOUND, component));
+ }
+
+ /**
+ * Calculates the total weight of all components in the list.
+ *
+ * @return The total weight as an integer.
+ */
+ public int getTotalWeighting() {
+ return componentArrayList.stream().mapToInt(Component::getWeight).sum();
+ }
+
+ /**
+ * Returns a formatted string representation of the components in the list.
+ *
+ * @return A numbered list of components as a string.
+ */
+ @Override
+ public String toString() {
+ return "\t" +
+ IntStream.range(0, componentArrayList.size())
+ .mapToObj(i -> (i + 1) + ": " + componentArrayList.get(i))
+ .collect(Collectors.joining("\n\t"));
+ }
+
+ /**
+ * Retrieves all components in the list as a new {@code ArrayList}.
+ *
+ * @return A new list containing all components in this list.
+ */
+ public ArrayList findAllComponents() {
+ return new ArrayList<>(componentArrayList);
+ }
+
+ /**
+ * Returns the underlying list of components.
+ *
+ * @return The list of components as an {@code ArrayList}.
+ */
+ public ArrayList getComponentArrayList() {
+ return componentArrayList;
+ }
+
+ /**
+ * Returns the number of components in the list.
+ *
+ * @return The size of the component list.
+ */
+ public int size() {
+ return componentArrayList.size();
+ }
+}
diff --git a/src/main/java/tutorlink/lists/GradeList.java b/src/main/java/tutorlink/lists/GradeList.java
new file mode 100644
index 0000000000..334e7a3727
--- /dev/null
+++ b/src/main/java/tutorlink/lists/GradeList.java
@@ -0,0 +1,130 @@
+package tutorlink.lists;
+
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+
+import tutorlink.exceptions.DuplicateGradeException;
+import tutorlink.exceptions.GradeNotFoundException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.grade.Grade;
+
+/**
+ * Represents a list of grades.
+ */
+public class GradeList {
+ private static final String ERROR_DUPLICATE_GRADE_ON_ADD =
+ "Error! Grade for component %s for student %s already exists in the list!";
+ private static final String ERROR_NO_GRADE_FOUND =
+ "Error! Grade for component %s for student %s does not exist in the list!";
+
+
+ private ArrayList gradeArrayList;
+
+ public GradeList() {
+ this.gradeArrayList = new ArrayList<>();
+ }
+
+ public GradeList(ArrayList gradeArrayList) {
+ this.gradeArrayList = gradeArrayList;
+ }
+
+ //@@author RCPilot1604
+ public void deleteGrade(String matricNumber, String componentDescription) throws GradeNotFoundException {
+ matricNumber = matricNumber.toUpperCase();
+ for (Grade grade : gradeArrayList) {
+ if (grade.getStudent().getMatricNumber().equals(matricNumber.toUpperCase())
+ && grade.getComponent().getName().toUpperCase().equals(componentDescription.toUpperCase())) {
+ gradeArrayList.remove(grade);
+ return;
+ }
+ }
+ throw new GradeNotFoundException(String.format(ERROR_NO_GRADE_FOUND, componentDescription, matricNumber));
+ }
+
+ public void deleteGradesByMatric(String matricNumber) {
+ matricNumber = matricNumber.toUpperCase();
+ ArrayList gradesToDelete = new ArrayList<>();
+ for (Grade grade : gradeArrayList) {
+ if (grade.getStudent().getMatricNumber().equals(matricNumber)) {
+ gradesToDelete.add(grade);
+ }
+ }
+ gradeArrayList.removeAll(gradesToDelete);
+ }
+
+ public void deleteGradesByComponent(String componentDescription) {
+ ArrayList gradesToDelete = new ArrayList<>();
+ for (Grade grade : gradeArrayList) {
+ if (grade.getComponent().getName().equalsIgnoreCase(componentDescription)) {
+ gradesToDelete.add(grade);
+ }
+ }
+ gradeArrayList.removeAll(gradesToDelete);
+ }
+
+ public void addGrade(Grade grade) throws DuplicateGradeException {
+ for (Grade gradeToCompare : gradeArrayList) {
+ if (grade.equals(gradeToCompare)) {
+ throw new DuplicateGradeException(
+ String.format(ERROR_DUPLICATE_GRADE_ON_ADD,
+ grade.getComponentName(), grade.getStudentMatricNumber()));
+ }
+ }
+ gradeArrayList.add(grade);
+ }
+
+ @Override
+ public String toString() {
+ return IntStream.range(0, gradeArrayList.size())
+ .mapToObj(i -> (i + 1) + ": " + gradeArrayList.get(i)) // 1-based index
+ .collect(Collectors.joining("\n\t"));
+ }
+
+ public GradeList findGrade(String matricNumber, String componentDescription) throws TutorLinkException {
+ GradeList filteredList = new GradeList();
+ filteredList.gradeArrayList = gradeArrayList
+ .stream()
+ .filter(grade -> grade.getStudent().getMatricNumber().equals(matricNumber.toUpperCase())
+ && grade.getComponent().getName().toUpperCase().equals(componentDescription.toUpperCase()))
+ .collect(Collectors.toCollection(ArrayList::new));
+ if (filteredList.gradeArrayList.isEmpty()) {
+ throw new GradeNotFoundException(String.format(ERROR_NO_GRADE_FOUND, componentDescription, matricNumber));
+ }
+ return filteredList;
+ }
+ //@@author
+
+ public double calculateStudentPercentageScore(String matricNumber, ComponentList componentList) {
+ ArrayList studentGrades = gradeArrayList
+ .stream()
+ .filter(grade -> grade.getStudent().getMatricNumber().equals(matricNumber.toUpperCase()))
+ .collect(Collectors.toCollection(ArrayList::new));
+
+ if (studentGrades.isEmpty()) {
+ return 0;
+ }
+
+ int totalWeighting = componentList.getTotalWeighting();
+
+
+ if(totalWeighting == 0) {
+ return 0;
+ }
+
+ return studentGrades
+ .stream()
+ .mapToDouble(grade ->
+ grade.getScore()
+ / grade.getComponent().getMaxScore()
+ * grade.getComponent().getWeight()
+ )
+ .sum() / totalWeighting * 100;
+ }
+
+ public ArrayList getGradeArrayList() {
+ return gradeArrayList;
+ }
+
+}
diff --git a/src/main/java/tutorlink/lists/StudentList.java b/src/main/java/tutorlink/lists/StudentList.java
new file mode 100644
index 0000000000..c690965a64
--- /dev/null
+++ b/src/main/java/tutorlink/lists/StudentList.java
@@ -0,0 +1,100 @@
+package tutorlink.lists;
+
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.DuplicateMatricNumberException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.student.Student;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class StudentList {
+ //Use Java String formatting to replace %s with matricNumber
+ public static final String STUDENT_NOT_FOUND = "Error! Student (Matric Number %s) not found";
+ private static final String ERROR_DUPLICATE_MATRIC_NUMBER_ON_ADD =
+ "Error! Student with Matric Number, %s, already exists in the list!";
+ private static final String TO_STRING_DELIMITER = "\n";
+ private ArrayList studentArrayList;
+
+ public StudentList() {
+ this.studentArrayList = new ArrayList<>();
+ }
+
+ public StudentList(ArrayList studentArrayList) {
+ this.studentArrayList = studentArrayList;
+ }
+
+ public void deleteStudent(String matricNumber) throws StudentNotFoundException {
+ matricNumber = matricNumber.toUpperCase();
+ for (Student student : studentArrayList) {
+ if (student.getMatricNumber().equals(matricNumber)) {
+ studentArrayList.remove(student);
+ return;
+ }
+ }
+ throw new StudentNotFoundException(String.format(STUDENT_NOT_FOUND, matricNumber));
+ }
+
+ public void addStudent(String matricNumber, String name) throws DuplicateMatricNumberException {
+ matricNumber = matricNumber.toUpperCase();
+ Student student = new Student(matricNumber, name);
+ for (Student s : studentArrayList) {
+ if (s.getMatricNumber().equals(matricNumber)) {
+ String errorMessage = String.format(ERROR_DUPLICATE_MATRIC_NUMBER_ON_ADD, matricNumber);
+ throw new DuplicateMatricNumberException(errorMessage);
+ }
+ }
+ studentArrayList.add(student);
+ }
+
+ public int size() {
+ return studentArrayList.size();
+ }
+
+ public ArrayList getStudentArrayList() {
+ return studentArrayList;
+ }
+
+ @Override
+ public String toString() {
+ return IntStream.range(0, studentArrayList.size())
+ .mapToObj(i -> ("\t" + (i + 1)) + ": " + studentArrayList.get(i)) // 1-based index
+ .collect(Collectors.joining(TO_STRING_DELIMITER));
+ }
+
+ public StudentList findStudentByMatricNumber(String matricNumber) throws TutorLinkException {
+ StudentList filteredList = new StudentList();
+
+ Pattern pattern = Pattern.compile(Commons.MATRIC_NUMBER_REGEX);
+ Matcher matcher = pattern.matcher(matricNumber);
+ if (!matcher.find()) {
+ throw new IllegalValueException(Commons.ERROR_ILLEGAL_MATRIC_NUMBER);
+ }
+
+ filteredList.studentArrayList = studentArrayList
+ .stream()
+ .filter(student -> student.getMatricNumber().equals(matricNumber.toUpperCase()))
+ .collect(Collectors.toCollection(ArrayList::new));
+ if (filteredList.studentArrayList.isEmpty()) {
+ throw new StudentNotFoundException("No students with matricNumber " + matricNumber + " found");
+ }
+ return filteredList;
+ }
+
+ public StudentList findStudentByName(String name) throws StudentNotFoundException {
+ StudentList filteredList = new StudentList();
+ filteredList.studentArrayList = studentArrayList
+ .stream()
+ .filter(student -> student.getName().contains(name))
+ .collect(Collectors.toCollection(ArrayList::new));
+ if (filteredList.studentArrayList.isEmpty()) {
+ throw new StudentNotFoundException("No students with name " + name + " found");
+ }
+ return filteredList;
+ }
+}
diff --git a/src/main/java/tutorlink/parser/Parser.java b/src/main/java/tutorlink/parser/Parser.java
new file mode 100644
index 0000000000..1eb1d3e665
--- /dev/null
+++ b/src/main/java/tutorlink/parser/Parser.java
@@ -0,0 +1,147 @@
+package tutorlink.parser;
+
+import tutorlink.command.AddStudentCommand;
+import tutorlink.command.Command;
+import tutorlink.command.DeleteComponentCommand;
+import tutorlink.command.DeleteGradeCommand;
+import tutorlink.command.DeleteStudentCommand;
+import tutorlink.command.ExitCommand;
+import tutorlink.command.FindStudentCommand;
+import tutorlink.command.InvalidCommand;
+import tutorlink.command.ListComponentCommand;
+import tutorlink.command.ListGradeCommand;
+import tutorlink.command.ListStudentCommand;
+import tutorlink.command.AddGradeCommand;
+import tutorlink.command.AddComponentCommand;
+import tutorlink.command.UpdateComponentCommand;
+import tutorlink.command.HelpCommand;
+import tutorlink.exceptions.IllegalValueException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The {@code Parser} class is responsible for parsing user input into commands and arguments.
+ * It interprets the first word of the input as the command and maps it to a corresponding {@code Command} object.
+ * It also provides a utility for extracting command arguments based on specific prefixes.
+ */
+public class Parser {
+ private static final Logger LOGGER = Logger.getLogger(Parser.class.getName());
+ private static final String ERROR_PARSER_MULTIPLE_PREFIX = "Duplicate prefix detected: ";
+
+ /**
+ * Extracts the command word from the given input by splitting the input
+ * and taking the first word.
+ *
+ * @param input The user input as a string.
+ * @return The command word extracted from the input.
+ */
+ private String extractCommandWord(String input) {
+ String[] words = input.trim().split("\\s+");
+ return words[0]; // Return the first word
+ }
+
+ /**
+ * Parses the user input and returns the corresponding {@code Command} object.
+ * It identifies the command by the first word of the input and maps it to a specific command class.
+ * If the command is invalid, it returns an {@code InvalidCommand}.
+ *
+ * @param line The user input line containing the command and arguments.
+ * @return The {@code Command} object representing the parsed command.
+ */
+ public Command getCommand(String line) {
+ String commandWord = extractCommandWord(line);
+
+ switch (commandWord.toLowerCase()) {
+ case AddStudentCommand.COMMAND_WORD:
+ return new AddStudentCommand();
+
+ case DeleteStudentCommand.COMMAND_WORD:
+ return new DeleteStudentCommand();
+
+ case FindStudentCommand.COMMAND_WORD:
+ return new FindStudentCommand();
+
+ case ListStudentCommand.COMMAND_WORD:
+ return new ListStudentCommand();
+
+ case AddGradeCommand.COMMAND_WORD:
+ return new AddGradeCommand();
+
+ case DeleteGradeCommand.COMMAND_WORD:
+ return new DeleteGradeCommand();
+
+ case AddComponentCommand.COMMAND_WORD:
+ return new AddComponentCommand();
+
+ case DeleteComponentCommand.COMMAND_WORD:
+ return new DeleteComponentCommand();
+
+ case ListComponentCommand.COMMAND_WORD:
+ return new ListComponentCommand();
+
+ case ListGradeCommand.COMMAND_WORD:
+ return new ListGradeCommand();
+
+ case UpdateComponentCommand.COMMAND_WORD:
+ return new UpdateComponentCommand();
+
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+
+ default:
+ return new InvalidCommand();
+ }
+ }
+
+ /**
+ * Extracts command arguments from the input string based on the given argument prefixes.
+ * Arguments are identified by specific prefixes (e.g., "n/" for name, "i/" for ID)
+ * and are extracted into a map where each prefix is a key and the corresponding argument is the value.
+ *
+ * @param argumentPrefixes An array of valid argument prefixes (e.g., "n/", "i/").
+ * @param line The user input containing command arguments.
+ * @return A {@code HashMap} where keys are prefixes (e.g., "n/", "i/") and values are the corresponding arguments.
+ */
+ public HashMap getArguments(String[] argumentPrefixes, String line) throws IllegalValueException {
+ HashMap arguments = new HashMap<>();
+
+ if (argumentPrefixes == null) {
+ return arguments;
+ }
+
+ // Build regex pattern dynamically from the provided argument prefixes
+ StringBuilder regexBuilder = new StringBuilder("(?i)(");
+ for (String prefix : argumentPrefixes) {
+ regexBuilder.append(Pattern.quote(prefix)).append("|");
+ }
+ regexBuilder.setLength(regexBuilder.length() - 1); // Remove the last "|"
+ // Match argument that could have spaces, but ends before another prefix or end of the string
+ regexBuilder.append(")([^\\s].*?)(?=(\\s+[^\\s]+/|$))");
+
+ String regex = regexBuilder.toString();
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(line);
+ // Set to track prefixes we've already encountered
+ Set seenPrefixes = new HashSet<>();
+ // Iterate through all found tags and arguments
+ while (matcher.find()) {
+ String tag = matcher.group(1); // Group 1 is the tag (e.g., n/, i/, etc.)
+ String argument = matcher.group(2).trim(); // Group 2 is the argument after the tag
+ if (seenPrefixes.contains(tag)) {
+ throw new IllegalValueException(ERROR_PARSER_MULTIPLE_PREFIX + tag);
+ }
+ seenPrefixes.add(tag);
+ arguments.put(tag, argument); // Store the tag and argument
+ }
+
+ return arguments;
+ }
+}
diff --git a/src/main/java/tutorlink/result/CommandResult.java b/src/main/java/tutorlink/result/CommandResult.java
new file mode 100644
index 0000000000..ff103762c4
--- /dev/null
+++ b/src/main/java/tutorlink/result/CommandResult.java
@@ -0,0 +1,25 @@
+package tutorlink.result;
+
+/**
+ * Represents the result of a command execution.
+ */
+public class CommandResult {
+
+ private static final String LINE_SEPARATOR = "\n\t";
+
+ public final String message;
+
+ public CommandResult(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Returns a string representation of the object.
+ *
+ * @return the message string
+ */
+ @Override
+ public String toString() {
+ return message;
+ }
+}
diff --git a/src/main/java/tutorlink/storage/ComponentStorage.java b/src/main/java/tutorlink/storage/ComponentStorage.java
new file mode 100644
index 0000000000..87b1514964
--- /dev/null
+++ b/src/main/java/tutorlink/storage/ComponentStorage.java
@@ -0,0 +1,112 @@
+package tutorlink.storage;
+
+import tutorlink.component.Component;
+import tutorlink.exceptions.InvalidDataFileLineException;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * Handles the loading and saving of {@code Component} objects from and to a file.
+ * The file contains data on each component with its name, maximum score, and weight.
+ */
+public class ComponentStorage extends Storage {
+ private static final double MAX_SCORE = 10000.0;
+
+ /**
+ * Constructs a {@code ComponentStorage} with the specified file path.
+ *
+ * @param filePath The file path for storing the component data.
+ */
+ public ComponentStorage(String filePath) {
+ super(filePath);
+ }
+
+ /**
+ * Loads the list of components from the file specified in the file path.
+ * Each line in the file represents a component's data: name, maximum score, and weight.
+ *
+ * @return A list of {@code Component} objects loaded from the file.
+ * @throws IOException If an I/O error occurs when reading the file.
+ */
+ public ArrayList loadComponentList() throws IOException {
+ ArrayList components = new ArrayList<>();
+ int totalWeight = 0;
+ Scanner fileScanner = new Scanner(path);
+ while (fileScanner.hasNext()) {
+ try {
+ Component newComponent = getComponentFromFileLine(fileScanner.nextLine(), components, totalWeight);
+ totalWeight += newComponent.getWeight();
+ components.add(newComponent);
+ } catch (InvalidDataFileLineException e) {
+ discardedEntries.add(e.getMessage());
+ }
+ }
+ return components;
+ }
+
+ /**
+ * Saves the list of components to the file specified in the file path.
+ * Each component is written to a new line in the file in the format:
+ * name, maximum score, and weight.
+ *
+ * @param components The list of components to save to the file.
+ * @throws IOException If an I/O error occurs when writing to the file.
+ */
+ public void saveComponentList(ArrayList components) throws IOException {
+ FileWriter fileWriter = new FileWriter(path.toFile());
+ for (Component comp : components) {
+ fileWriter.write(getFileInputForComponent(comp) + System.lineSeparator());
+ }
+ fileWriter.close();
+ }
+
+ /**
+ * Parses a line from the file and creates a {@code Component} object.
+ *
+ * @param fileLine The line from the file to parse.
+ * @param components The current list of components for duplicate checks.
+ * @param totalWeight The cumulative weight of all components read so far.
+ * @return A new {@code Component} object parsed from the line.
+ * @throws InvalidDataFileLineException If the line is malformed or represents
+ * invalid component data.
+ */
+ private Component getComponentFromFileLine(
+ String fileLine, ArrayList components, int totalWeight)
+ throws InvalidDataFileLineException {
+ String[] stringParts = fileLine.split(READ_DELIMITER);
+ String name;
+ double maxScore;
+ int weight;
+ try {
+ name = stringParts[0].strip();
+ maxScore = Double.parseDouble(stringParts[1].strip());
+ weight = Integer.parseInt(stringParts[2].strip());
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+
+ boolean isValidMaxScore = (maxScore >= 0 && maxScore <= MAX_SCORE);
+ boolean isValidWeight = (weight >= 0 && (weight + totalWeight) <= 100);
+ Component newComponent = new Component(name, maxScore, weight);
+ if (!isValidMaxScore || !isValidWeight || components.contains(newComponent)) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+ return newComponent;
+ }
+
+ /**
+ * Formats a {@code Component} object into a string suitable for saving to a file.
+ *
+ * @param component The {@code Component} to format.
+ * @return A string representing the component in the file format.
+ */
+ private String getFileInputForComponent(Component component) {
+ String convertedString = component.getName() + WRITE_DELIMITER + component.getMaxScore()
+ + WRITE_DELIMITER + component.getWeight();
+ return convertedString;
+ }
+
+}
diff --git a/src/main/java/tutorlink/storage/GradeStorage.java b/src/main/java/tutorlink/storage/GradeStorage.java
new file mode 100644
index 0000000000..69ccd86b28
--- /dev/null
+++ b/src/main/java/tutorlink/storage/GradeStorage.java
@@ -0,0 +1,142 @@
+package tutorlink.storage;
+
+import tutorlink.component.Component;
+import tutorlink.exceptions.InvalidDataFileLineException;
+import tutorlink.grade.Grade;
+import tutorlink.student.Student;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * Represents a storage manager for grades, handling loading and saving of grade data to and from a file.
+ * Utilizes a list of components and students to validate grade entries.
+ */
+public class GradeStorage extends Storage {
+ private final ArrayList componentList;
+ private final ArrayList studentList;
+
+ //@@author RCPilot1604
+ /**
+ * Constructs a {@code GradeStorage} object with the specified file path, component list, and student list.
+ *
+ * @param filePath The file path to store grades.
+ * @param componentList The list of components for validation.
+ * @param studentList The list of students for validation.
+ */
+ //@@author jinzihan2002
+ public GradeStorage(String filePath, ArrayList componentList, ArrayList studentList) {
+ super(filePath);
+ this.componentList = componentList;
+ this.studentList = studentList;
+ }
+
+ //@@author RCPilot1604
+ /**
+ * Loads the grade list from the file.
+ *
+ * @return An {@code ArrayList} of {@code Grade} objects loaded from the file.
+ * @throws IOException If an I/O error occurs while reading the file.
+ */
+ //@@author jinzihan2002
+ public ArrayList loadGradeList() throws IOException {
+ ArrayList grades = new ArrayList<>();
+ Scanner fileScanner = new Scanner(path);
+ while (fileScanner.hasNext()) {
+ try {
+ Grade newGrade = getGradeFromFileLine(fileScanner.nextLine(), grades);
+ grades.add(newGrade);
+ } catch (InvalidDataFileLineException e) {
+ discardedEntries.add(e.getMessage());
+ }
+ }
+ return grades;
+ }
+
+ //@@author RCPilot1604
+ /**
+ * Saves the provided list of grades to the file.
+ *
+ * @param grades The {@code ArrayList} of grades to be saved.
+ * @throws IOException If an I/O error occurs while writing to the file.
+ */
+ //@@author jinzihan2002
+ public void saveGradeList(ArrayList grades) throws IOException {
+ FileWriter fileWriter = new FileWriter(path.toFile());
+ for (Grade grade : grades) {
+ fileWriter.write(getFileInputForGrade(grade) + System.lineSeparator());
+ }
+ fileWriter.close();
+ }
+
+ //@@author RCPilot1604
+ /**
+ * Parses a line from the grade file and returns a {@code Grade} object if the data is valid.
+ *
+ * @param fileLine A line from the file representing a grade entry.
+ * @param grades The current list of grades to check for duplicates.
+ * @return A {@code Grade} object created from the parsed data.
+ * @throws InvalidDataFileLineException If the line data is invalid or contains duplicates.
+ */
+ //@@author jinzihan2002
+ private Grade getGradeFromFileLine(String fileLine, ArrayList grades)
+ throws InvalidDataFileLineException {
+ String componentName;
+ String matricNumber;
+ double score;
+ String[] stringParts = fileLine.split(READ_DELIMITER);
+
+ try {
+ componentName = stringParts[0].strip();
+ matricNumber = stringParts[1].strip();
+ score = Double.parseDouble(stringParts[2].strip());
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+
+ Component selectedComp = null;
+ for (Component comp : componentList) {
+ if (comp.getName().equals(componentName)) {
+ selectedComp = comp;
+ break;
+ }
+ }
+
+ Student selectedStudent = null;
+ for (Student student : studentList) {
+ if (student.getMatricNumber().equals(matricNumber)) {
+ selectedStudent = student;
+ break;
+ }
+ }
+
+ if (selectedComp == null || selectedStudent == null) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+
+ boolean isValidScore = (score >= 0 && score <= selectedComp.getMaxScore());
+ Grade newGrade = new Grade(selectedComp, selectedStudent, score);
+ if (!isValidScore || grades.contains(newGrade)) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+ return newGrade;
+ }
+
+ //@@author RCPilot1604
+ /**
+ * Formats a {@code Grade} object for storage in a file.
+ *
+ * @param grade The grade to format.
+ * @return A string representing the grade in file format.
+ */
+ //@@author jinzihan2002
+ private String getFileInputForGrade(Grade grade) {
+ String componentName = grade.getComponent().getName();
+ String matricNumber = grade.getStudent().getMatricNumber();
+ double score = grade.getScore();
+ return componentName + WRITE_DELIMITER + matricNumber + WRITE_DELIMITER + score;
+ }
+
+}
diff --git a/src/main/java/tutorlink/storage/Storage.java b/src/main/java/tutorlink/storage/Storage.java
new file mode 100644
index 0000000000..7f1882bf4a
--- /dev/null
+++ b/src/main/java/tutorlink/storage/Storage.java
@@ -0,0 +1,41 @@
+package tutorlink.storage;
+
+import tutorlink.exceptions.StorageOperationException;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+
+public class Storage {
+ protected static final String READ_DELIMITER = " \\| ";
+ protected static final String WRITE_DELIMITER = " | ";
+
+ protected final Path path;
+ protected ArrayList discardedEntries;
+
+ /**
+ * Initializes a Storage object with the specified file path.
+ * Creates the necessary directories and file if they do not exist.
+ *
+ * @param filePath The path to the storage file.
+ * @throws StorageOperationException If an error occurs while creating the file or directories.
+ */
+ public Storage(String filePath) throws StorageOperationException {
+ path = Paths.get(filePath);
+ discardedEntries = new ArrayList<>();
+ try {
+ Files.createDirectories(path.getParent());
+ if (!Files.exists(path)) {
+ Files.createFile(path);
+ }
+ } catch (IOException e) {
+ throw new StorageOperationException("Error while creating file: " + path);
+ }
+ }
+
+ public ArrayList getDiscardedEntries() {
+ return discardedEntries;
+ }
+}
diff --git a/src/main/java/tutorlink/storage/StudentStorage.java b/src/main/java/tutorlink/storage/StudentStorage.java
new file mode 100644
index 0000000000..08e4cd9631
--- /dev/null
+++ b/src/main/java/tutorlink/storage/StudentStorage.java
@@ -0,0 +1,71 @@
+package tutorlink.storage;
+
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.InvalidDataFileLineException;
+import tutorlink.student.Student;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The StudentStorage class extends the Storage class and provides methods to load and save a list of students
+ * from and to a file.
+ */
+public class StudentStorage extends Storage {
+ public StudentStorage(String filePath) {
+ super(filePath);
+ }
+
+ public ArrayList loadStudentList() throws IOException {
+ ArrayList students = new ArrayList<>();
+ Scanner fileScanner = new Scanner(path);
+ while (fileScanner.hasNext()) {
+ try {
+ Student newStudent = getStudentFromFileLine(fileScanner.nextLine(), students);
+ students.add(newStudent);
+ } catch (InvalidDataFileLineException e) {
+ discardedEntries.add(e.getMessage());
+ }
+ }
+ return students;
+ }
+
+ public void saveStudentList(ArrayList students) throws IOException {
+ FileWriter fileWriter = new FileWriter(path.toFile());
+ for (Student student : students) {
+ fileWriter.write(getFileInputForStudent(student) + System.lineSeparator());
+ }
+ fileWriter.close();
+ }
+
+ private Student getStudentFromFileLine(String fileLine, ArrayList students)
+ throws InvalidDataFileLineException {
+ String[] stringParts = fileLine.split(READ_DELIMITER);
+ try {
+ String matricNumber = stringParts[0].strip().toUpperCase();
+ String name = stringParts[1].strip();
+ Pattern pattern = Pattern.compile(Commons.MATRIC_NUMBER_REGEX);
+ Matcher matcher = pattern.matcher(matricNumber);
+ if (!matcher.find()) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+
+ Student newStudent = new Student(matricNumber, name);
+ if (students.contains(newStudent)) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+ return newStudent;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new InvalidDataFileLineException(fileLine);
+ }
+ }
+
+ private String getFileInputForStudent(Student student) {
+ return student.getMatricNumber() + WRITE_DELIMITER + student.getName();
+ }
+
+}
diff --git a/src/main/java/tutorlink/student/Student.java b/src/main/java/tutorlink/student/Student.java
new file mode 100644
index 0000000000..33bf3e4d06
--- /dev/null
+++ b/src/main/java/tutorlink/student/Student.java
@@ -0,0 +1,57 @@
+package tutorlink.student;
+
+public class Student {
+ private String matricNumber;
+ private String name;
+ private double percentageScore;
+
+ public Student(String matricNumber, String name) {
+ this.name = name;
+ this.matricNumber = matricNumber.toUpperCase();
+ this.percentageScore = 0.0;
+ }
+
+ /**
+ * Constructs a new Student with the specified matriculation number, name, and GPA.
+ *
+ * @param matricNumber The matriculation number of the student.
+ * @param name The name of the student.
+ * @param gpa The GPA of the student.
+ */
+ public Student(String matricNumber, String name, double gpa) {
+ this.name = name;
+ this.matricNumber = matricNumber.toUpperCase();
+ this.percentageScore = gpa;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getMatricNumber() {
+ return matricNumber;
+ }
+
+ public double getPercentageScore() {
+ return percentageScore;
+ }
+
+ public void setPercentageScore(double percentageScore) {
+ this.percentageScore = percentageScore;
+ }
+
+ @Override
+ public String toString() {
+ return this.name + " (matric no: " + this.matricNumber + ", percentage score: " +
+ String.format("%.2f", this.percentageScore) + ")";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Student)) {
+ return false;
+ }
+ Student s = (Student) obj;
+ return this.matricNumber.equalsIgnoreCase(s.getMatricNumber());
+ }
+}
diff --git a/src/main/java/tutorlink/ui/Ui.java b/src/main/java/tutorlink/ui/Ui.java
new file mode 100644
index 0000000000..a09dea5862
--- /dev/null
+++ b/src/main/java/tutorlink/ui/Ui.java
@@ -0,0 +1,107 @@
+package tutorlink.ui;
+
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.result.CommandResult;
+
+import java.util.ArrayList;
+import java.util.Scanner;
+
+public class Ui {
+
+ private static final String LOGO = "___________ __ .____ .__ __\n"
+ + "\\__ ___/_ ___/ |_ ___________| | |__| ____ | | __\n"
+ + " | | | | \\ __\\/ _ \\_ __ \\ | | |/ \\| |/ /\n"
+ + " | | | | /| | ( <_> ) | \\/ |___| | | \\ <\n"
+ + " |____| |____/ |__| \\____/|__| |_______ \\__|___| /__|_ \\\n"
+ + " \\/ \\/ \\/\n";
+ private static final String HALF_BREAK_LINE =
+ "-------------------------";
+ private static final String FULL_BREAK_LINE =
+ "-------------------------------------------------------------";
+
+ private static final String HELP_MESSAGE = """
+ ------------------- List of Commands --------------------
+ help: Displays list of commands
+ Example: help
+
+ add_student: Adds a student to the class roster
+ Example: add_student i/A1234567X n/John Doe
+
+ delete_student: Deletes a student from the class roster
+ Example: delete_student i/A1234567X
+
+ list_student: Lists all students in the class
+ Example: list_student
+
+ find_student: Finds a student in the class roster by name or matric number
+ Example: find_student i/A1234567X n/John Doe
+
+ add_component: Adds a new grading component to the class
+ Example: add_component c/Quiz 1 w/30 m/50
+
+ delete_component: Deletes a grading component from the class
+ Example: delete_component c/Quiz 1
+
+ update_component: Updates a component with a new maxscore or weight
+ Example: update_component c/Quiz 1 w/40 m/60
+
+ list_component: Lists all grading components
+ Example: list_component
+
+ add_grade: Adds a grade for a student for a specific component
+ Example: add_grade i/A1234567X c/Quiz 1 s/45
+
+ delete_grade: Deletes a student's grade for a specific component
+ Example: delete_grade i/A1234567X c/Quiz 1
+
+ list_grade: Lists all grades for a student
+ Example: list_grade i/A1234567X
+
+ bye: Exits the program
+ Example: bye
+ -------------------------------------------------------------""";
+
+ private Scanner in = new Scanner(System.in);
+
+ public Ui() {
+ }
+
+ public String getUserInput() {
+ return in.nextLine();
+ }
+
+ public void displayWelcomeMessage() {
+ System.out.println(FULL_BREAK_LINE);
+ System.out.println(LOGO);
+ System.out.println(FULL_BREAK_LINE);
+ System.out.println("Hello! I'm TutorLink\nWhat can I do for you?");
+ System.out.println(FULL_BREAK_LINE);
+ }
+
+ public void displayHelpMessage() {
+ System.out.println(HELP_MESSAGE);
+ }
+
+ public void displayResult(CommandResult result) {
+ System.out.println(HALF_BREAK_LINE + " Result " + HALF_BREAK_LINE);
+ System.out.println(result.toString());
+ System.out.println(FULL_BREAK_LINE);
+ }
+
+ public void displayException(TutorLinkException error) {
+ System.out.println(HALF_BREAK_LINE + " Error " + HALF_BREAK_LINE);
+ System.out.println(error.getMessage());
+ System.out.println(FULL_BREAK_LINE);
+ }
+
+ public void displayDiscardedEntries(ArrayList discardedEntries, String header) {
+ if (discardedEntries.isEmpty()) {
+ return;
+ }
+ System.out.println(FULL_BREAK_LINE);
+ System.out.println(header);
+ for (String entry : discardedEntries) {
+ System.out.println(" " + entry);
+ }
+ }
+}
diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java
deleted file mode 100644
index 2dda5fd651..0000000000
--- a/src/test/java/seedu/duke/DukeTest.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package seedu.duke;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-
-class DukeTest {
- @Test
- public void sampleTest() {
- assertTrue(true);
- }
-}
diff --git a/src/test/java/tutorlink/command/AddComponentCommandTest.java b/src/test/java/tutorlink/command/AddComponentCommandTest.java
new file mode 100644
index 0000000000..c277a1869e
--- /dev/null
+++ b/src/test/java/tutorlink/command/AddComponentCommandTest.java
@@ -0,0 +1,240 @@
+//@@author yeekian
+package tutorlink.command;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.DuplicateComponentException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.InvalidWeightingException;
+import tutorlink.grade.Grade;
+import tutorlink.result.CommandResult;
+import tutorlink.student.Student;
+
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class AddComponentCommandTest {
+ private AppState appState;
+ private HashMap arguments;
+ private AddComponentCommand command;
+
+ @BeforeEach
+ void setup() {
+ appState = new AppState();
+ arguments = new HashMap<>();
+ command = new AddComponentCommand();
+ }
+
+ @Test
+ void execute_validArguments_componentAddedSuccessfully() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "30");
+ arguments.put("m/", "100");
+ int initialWeighting = appState.components.getTotalWeighting();
+ CommandResult result = command.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Component Quiz 1 of weight 30%, with max score 100 added successfully!", result.toString());
+ assertEquals(1, appState.components.getComponentArrayList().size());
+ assertEquals(initialWeighting + 30, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_nullArguments_throwsIllegalValueException() {
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals("Error! Null parameters c/, w/, m/ passed!", exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_missingWeightageArgument_throwsIllegalValueException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("m/", "100");
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals("Error! Null parameters w/ passed!", exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_extraArgument_componentAddedSuccessfully() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "50");
+ arguments.put("m/", "100");
+ arguments.put("extra/", "extra value");
+ int initialWeighting = appState.components.getTotalWeighting();
+ CommandResult result = command.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Component Quiz 1 of weight 50%, with max score 100 added successfully!", result.toString());
+ assertEquals(1, appState.components.getComponentArrayList().size());
+ assertEquals(initialWeighting + 50, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_weightageOutOfRange_throwsIllegalValueException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "1.5");
+ arguments.put("m/", "100");
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(Commons.ERROR_INVALID_WEIGHTAGE, exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_negativeWeighting_throwsIllegalValueException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "-1");
+ arguments.put("m/", "100");
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(Commons.ERROR_INVALID_WEIGHTAGE, exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_negativeMaxScore_throwsIllegalValueException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "0.4");
+ arguments.put("m/", "-10");
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(Commons.ERROR_INVALID_WEIGHTAGE, exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_weightageNotInt_throwsIllegalValueException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "one");
+ arguments.put("m/", "100");
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(Commons.ERROR_INVALID_WEIGHTAGE, exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_maxScoreNotDouble_throwsIllegalValueException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "0.4");
+ arguments.put("m/", "hundred");
+ int initialWeighting = appState.components.getTotalWeighting();
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(Commons.ERROR_INVALID_WEIGHTAGE, exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_maxWeightageExceeded_throwsInvalidWeightingException() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "100");
+ arguments.put("m/", "100");
+
+ CommandResult result = command.execute(appState, arguments);
+ assertNotNull(result);
+
+ int initialWeighting = appState.components.getTotalWeighting();
+
+ arguments.clear();
+ arguments.put("c/", "Quiz 2");
+ arguments.put("w/", "1");
+ arguments.put("m/", "100");
+
+ InvalidWeightingException exception = assertThrows(InvalidWeightingException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(String.format(Commons.ERROR_INVALID_TOTAL_WEIGHTING, appState.components.getTotalWeighting() + 1),
+ exception.getMessage());
+ assertEquals(initialWeighting, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_duplicateComponents_totalWeightingUnchanged() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "20");
+ arguments.put("m/", "100");
+
+ assertEquals(0, appState.components.getTotalWeighting());
+
+ command.execute(appState, arguments);
+
+ assertEquals(20, appState.components.getTotalWeighting());
+
+ DuplicateComponentException exception = assertThrows(DuplicateComponentException.class, () -> {
+ command.execute(appState, arguments);
+ });
+
+ assertEquals(20, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void execute_negativeWeighting_totalWeightingUnchanged() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "20");
+ arguments.put("m/", "100");
+
+ assertEquals(0, appState.components.getTotalWeighting());
+ command.execute(appState, arguments);
+ assertEquals(20, appState.components.getTotalWeighting());
+
+ arguments.clear();
+ arguments.put("c/", "Quiz 2");
+ arguments.put("w/", "-20");
+ arguments.put("m/", "100");
+ IllegalValueException exception = assertThrows(IllegalValueException.class, () -> {
+ command.execute(appState, arguments);
+ });
+ assertEquals(Commons.ERROR_INVALID_WEIGHTAGE, exception.getMessage());
+ assertEquals(20, appState.components.getTotalWeighting());
+ }
+
+ @Test
+ void update_gpa_accordingly () {
+ appState.students.addStudent("A1234567X", "John Doe");
+ Student student = appState.students.getStudentArrayList().get(0);
+ Component component = new Component("midterm", 50, 50);
+ appState.components.addComponent(component);
+ appState.grades.addGrade(new Grade(component, student, 50));
+ appState.updateAllStudentPercentageScores();
+
+ assertEquals(student.getPercentageScore(), 100);
+
+ arguments.put("c/", "Final");
+ arguments.put("w/", "50");
+ arguments.put("m/", "50");
+
+ command.execute(appState, arguments);
+ assertEquals(student.getPercentageScore(), 50);
+ }
+
+ @Test
+ void execute_maxScoreLargerThanMax_exception() {
+ arguments.put("c/", "Quiz 1");
+ arguments.put("w/", "100");
+ arguments.put("m/", "20000");
+ assertThrows(IllegalValueException.class, () -> command.execute(appState, arguments));
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/command/AddGradeCommandTest.java b/src/test/java/tutorlink/command/AddGradeCommandTest.java
new file mode 100644
index 0000000000..160d2bf889
--- /dev/null
+++ b/src/test/java/tutorlink/command/AddGradeCommandTest.java
@@ -0,0 +1,342 @@
+//@@author yeekian
+package tutorlink.command;
+
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.DuplicateGradeException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.grade.Grade;
+import tutorlink.lists.GradeList;
+import tutorlink.parser.Parser;
+import tutorlink.result.CommandResult;
+
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+
+public class AddGradeCommandTest {
+ @Test
+ void addGrade_allArgumentsComponent_successful() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create component
+ String examName = "Exam Under Test";
+ double examMaxScore = 100.0;
+ int examWeight = 50;
+ Component exam = new Component(examName,examMaxScore, examWeight);
+
+ appState.components.addComponent(exam);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Exam Under Test";
+ String scoreNumber = "75.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+ CommandResult gradeResult = addGradeCommand.execute(appState, gradeArguments);
+
+ //Test grade added
+ assertNotNull(gradeResult);
+ assertEquals(String.format(Commons.ADD_GRADE_SUCCESS, scoreNumber, componentDescription, matricNumber),
+ gradeResult.toString());
+ }
+
+ @Test
+ void addGrade_missingComponentDescriptionComponent_illegalValueExceptionThrown() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create component
+ String assignmentName = "Assignment Under Test";
+ double assignmentMaxScore = 100.0;
+ int assignmentWeight = 50;
+ Component assignment = new Component(assignmentName,assignmentMaxScore, assignmentWeight);
+
+ appState.components.addComponent(assignment);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Assignment Under Test";
+ String scoreNumber = "75.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+
+ //Test grade added
+ assertThrows(IllegalValueException.class, () -> {
+ addGradeCommand.execute(appState, gradeArguments);
+ });
+ }
+
+ @Test
+ void addGrade_nonDoubleScoreComponent_illegalValueExceptionThrown() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create component
+ String examName = "Exam Under Test";
+ double examMaxScore = 100.0;
+ int examWeight = 50;
+ Component exam = new Component(examName,examMaxScore, examWeight);
+
+ appState.components.addComponent(exam);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Exam Under Test";
+ String scoreNumber = "Non-double String";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+
+ assertThrows(IllegalValueException.class, () -> addGradeCommand.execute(appState, gradeArguments));
+ }
+
+ @Test
+ void addGrade_scoreMoreThanMax_throwIllegalValueException() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create component
+ String examName = "Exam Under Test";
+ double examMaxScore = 100.0;
+ int examWeight = 50;
+ Component exam = new Component(examName,examMaxScore, examWeight);
+
+ appState.components.addComponent(exam);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Exam Under Test";
+ String scoreNumber = "-50.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+
+ assertThrows(IllegalValueException.class, () -> addGradeCommand.execute(appState, gradeArguments));
+ }
+
+ @Test
+ void addGrade_scoreNegative_throwIllegalValueException() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create component
+ String examName = "Exam Under Test";
+ double examMaxScore = 100.0;
+ int examWeight = 50;
+ Component exam = new Component(examName,examMaxScore, examWeight);
+
+ appState.components.addComponent(exam);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Exam Under Test";
+ String scoreNumber = "-50.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+
+ assertThrows(IllegalValueException.class, () -> addGradeCommand.execute(appState, gradeArguments));
+ }
+
+ @Test
+ void addGrade_componentNotFound_throwXXXXException() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //No component created
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Exam Under Test";
+ String scoreNumber = "75.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+
+ assertThrows(ComponentNotFoundException.class, () -> addGradeCommand.execute(appState, gradeArguments));
+ }
+
+ @Test
+ void addGrade_studentNotFoundComponent_throwStudentNotFoundException() {
+ AppState appState = new AppState();
+
+ //No student created
+
+ //Create component
+ String assignmentName = "Assignment Under Test";
+ double assignmentMaxScore = 100.0;
+ int assignmentWeight = 50;
+ Component assignment = new Component(assignmentName,assignmentMaxScore, assignmentWeight);
+
+ appState.components.addComponent(assignment);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Assignment Under Test";
+ String scoreNumber = "75.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+
+ assertThrows(StudentNotFoundException.class, () -> addGradeCommand.execute(appState, gradeArguments));
+ }
+
+ @Test
+ void addGrade_duplicateGradeComponentDiffScores_throwDuplicateGradeException() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create Component component
+ String examName = "Test";
+ double examMaxScore = 100.0;
+ int examWeight = 50;
+ Component exam = new Component(examName,examMaxScore, examWeight);
+
+ appState.components.addComponent(exam);
+
+ //Add exam grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = "Test";
+ String originalScoreNumber = "75.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", originalScoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+ CommandResult gradeResult = addGradeCommand.execute(appState, gradeArguments);
+
+ //Test grade added
+ assertNotNull(gradeResult);
+ assertEquals(String.format(Commons.ADD_GRADE_SUCCESS, originalScoreNumber, componentDescription, matricNumber),
+ gradeResult.toString());
+
+ //Add repeat exam grade
+ HashMap assignmentArguments = new HashMap<>();
+
+ matricNumber = "A1234567X";
+ componentDescription = "Test";
+ String changedScoreNumber = "10.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", changedScoreNumber);
+
+ assertThrows(DuplicateGradeException.class, () -> addGradeCommand.execute(appState, gradeArguments));
+
+ GradeList findGradeList = appState.grades.findGrade(matricNumber, componentDescription);
+ assertEquals(1, findGradeList.getGradeArrayList().size());
+ Grade finalGrade = findGradeList.getGradeArrayList().get(0);
+ assertEquals(Double.parseDouble(originalScoreNumber),finalGrade.getScore());
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/command/AddStudentCommandTest.java b/src/test/java/tutorlink/command/AddStudentCommandTest.java
new file mode 100644
index 0000000000..409cda1235
--- /dev/null
+++ b/src/test/java/tutorlink/command/AddStudentCommandTest.java
@@ -0,0 +1,68 @@
+//@@author RCPilot1604
+package tutorlink.command;
+
+import java.util.HashMap;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.result.CommandResult;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+
+public class AddStudentCommandTest {
+ private AppState appState;
+ private HashMap arguments;
+ private AddStudentCommand command;
+
+ @BeforeEach
+ void setup() {
+ appState = new AppState();
+ arguments = new HashMap<>();
+ command = new AddStudentCommand();
+ }
+
+ @Test
+ void execute_addOne_expectOne() {
+ arguments.put("i/","A1234567X");
+ arguments.put("n/", "John");
+ CommandResult result = command.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals(result.toString(), "Student John (A1234567X) added successfully!");
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+ }
+
+ @Test
+ void execute_emptyInput_exceptionThrown() {
+ arguments.put("i/","A1234567X");
+ try {
+ CommandResult result = command.execute(appState, arguments);
+ fail("Expected an exception to be thrown due to empty input");
+ } catch (IllegalValueException e) {
+ // Assert that the exception message matches the expected outcome
+ assertEquals(String.format(Commons.ERROR_NULL, command.getArgumentPrefixes()[1]), e.getMessage());
+ } catch (Exception e) {
+ // If any other type of exception is thrown, fail the test
+ fail("Expected IllegalArgumentException, but got: " + e.getClass().getSimpleName());
+ }
+ }
+
+ @Test
+ void execute_bothNullInputs_exceptionThrown() {
+ try {
+ CommandResult result = command.execute(appState, arguments);
+ fail("Expected an exception to be thrown due to empty input");
+ } catch (IllegalValueException e) {
+ // Assert that the exception message matches the expected outcome
+ assertEquals("Error! Null parameters i/, n/ passed!", e.getMessage());
+ } catch (Exception e) {
+ // If any other type of exception is thrown, fail the test
+ fail("Expected IllegalArgumentException, but got: " + e.getClass().getSimpleName());
+ }
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/command/DeleteComponentCommandTest.java b/src/test/java/tutorlink/command/DeleteComponentCommandTest.java
new file mode 100644
index 0000000000..44213e744a
--- /dev/null
+++ b/src/test/java/tutorlink/command/DeleteComponentCommandTest.java
@@ -0,0 +1,92 @@
+package tutorlink.command;
+
+import java.util.HashMap;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.grade.Grade;
+import tutorlink.result.CommandResult;
+import tutorlink.student.Student;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class DeleteComponentCommandTest {
+ private AppState appState;
+ private DeleteComponentCommand command = new DeleteComponentCommand();
+ private HashMap arguments;
+ private CommandResult result;
+ @BeforeEach
+ void setup() {
+ appState = new AppState();
+ appState.components.addComponent(new Component("finals", 40.0, 40));
+ appState.components.addComponent(new Component("iP", 20.0, 10));
+ appState.components.addComponent(new Component("lectures", 10.0, 10));
+ arguments = new HashMap<>();
+ }
+
+ @Test
+ void deleteComponent_success() {
+ arguments.put("c/", "finals");
+ int initialWeighting = appState.components.getTotalWeighting();
+ result = command.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals(appState.components.size(), 2);
+ assertEquals(appState.components.getTotalWeighting(), initialWeighting - 40);
+ }
+
+ @Test
+ void deleteComponent_notFound_fail() {
+ arguments.put("c/", "midterms");
+ int initialWeighting = appState.components.getTotalWeighting();
+ try {
+ command.execute(appState, arguments);
+ } catch (ComponentNotFoundException e) {
+ assertEquals(e.getMessage(), "Error! Component midterms does not exist in the list!");
+ } catch (Exception e) {
+ fail("Expected: ComponentNotFoundException, actual: " + e.getMessage());
+ } finally {
+ assertEquals(appState.components.getTotalWeighting(), initialWeighting);
+ }
+ }
+
+ @Test
+ void deleteComponent_emptyParam_fail() {
+ int initialWeighting = appState.components.getTotalWeighting();
+ try {
+ command.execute(appState, arguments);
+ } catch (IllegalValueException e) {
+ assertEquals(e.getMessage(), String.format(Commons.ERROR_NULL, command.getArgumentPrefixes()[0]));
+ } catch (Exception e) {
+ fail("Expected: ComponentNotFoundException, actual: " + e.getMessage());
+ } finally {
+ assertEquals(appState.components.getTotalWeighting(), initialWeighting);
+ }
+ }
+
+ @Test
+ void update_gpa_accordingly () {
+ appState = new AppState();
+ appState.students.addStudent("A1234567X", "John Doe");
+ Student student = appState.students.getStudentArrayList().get(0);
+ Component component = new Component("midterm", 50, 50);
+ appState.components.addComponent(component);
+ appState.grades.addGrade(new Grade(component, student, 0));
+
+ Component component2 = new Component("final", 50, 50);
+ appState.components.addComponent(component2);
+ appState.grades.addGrade(new Grade(component2, student, 50));
+ appState.updateAllStudentPercentageScores();
+ assertEquals(student.getPercentageScore(), 50);
+
+ arguments.put("c/", "Final");
+
+ command.execute(appState, arguments);
+ assertEquals(student.getPercentageScore(), 0);
+ }
+}
diff --git a/src/test/java/tutorlink/command/DeleteGradeCommandTest.java b/src/test/java/tutorlink/command/DeleteGradeCommandTest.java
new file mode 100644
index 0000000000..1719540b0c
--- /dev/null
+++ b/src/test/java/tutorlink/command/DeleteGradeCommandTest.java
@@ -0,0 +1,80 @@
+//@@author yeekian
+package tutorlink.command;
+
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.component.Component;
+import tutorlink.lists.GradeList;
+import tutorlink.parser.Parser;
+import tutorlink.result.CommandResult;
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class DeleteGradeCommandTest {
+
+ private final String successMessage = "Score of %s added successfully to %s for %s!";
+
+ @Test
+ void addGrade_allArgumentsExam_successful() {
+ AppState appState = new AppState();
+ Parser parser = new Parser();
+ HashMap arguments = new HashMap<>();
+ Command command = new DeleteGradeCommand();
+
+ String componentName = "Exam under test";
+
+ GradeList gradeList = addGradeForTest(parser, appState, componentName);
+ assertEquals(1, gradeList.getGradeArrayList().size());
+
+
+ arguments.put("i/","A1234567X");
+ arguments.put("c/", componentName);
+
+ command.execute(appState, arguments);
+ assertEquals(0, gradeList.getGradeArrayList().size());
+ }
+
+ private GradeList addGradeForTest(Parser parser, AppState appState, String componentName) {
+ //Create student
+ String line = "add_student i/A1234567X n/John Doe";
+ Command addStudentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = addStudentCommand.getArgumentPrefixes();
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+ CommandResult result = addStudentCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals("Student John Doe (A1234567X) added successfully!", result.toString());
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+
+ //Create component
+ String examName = componentName;
+ double examMaxScore = 100.0;
+ int examWeight = 50;
+ Component exam = new Component(examName,examMaxScore, examWeight);
+
+ appState.components.addComponent(exam);
+
+ //Add grade
+ HashMap gradeArguments = new HashMap<>();
+
+ String matricNumber = "A1234567X";
+ String componentDescription = componentName;
+ String scoreNumber = "75.0";
+ gradeArguments.put("i/",matricNumber);
+ gradeArguments.put("c/", componentDescription);
+ gradeArguments.put("s/", scoreNumber);
+
+ Command addGradeCommand = new AddGradeCommand();
+ CommandResult gradeResult = addGradeCommand.execute(appState, gradeArguments);
+
+ //Test grade added
+ assertNotNull(gradeResult);
+ assertEquals(String.format(successMessage, scoreNumber, componentDescription, matricNumber),
+ gradeResult.toString());
+
+ return appState.grades;
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/command/DeleteStudentCommandTest.java b/src/test/java/tutorlink/command/DeleteStudentCommandTest.java
new file mode 100644
index 0000000000..a3a4820909
--- /dev/null
+++ b/src/test/java/tutorlink/command/DeleteStudentCommandTest.java
@@ -0,0 +1,84 @@
+//@@author RCPilot1604
+package tutorlink.command;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.component.Component;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.grade.Grade;
+import tutorlink.result.CommandResult;
+import java.util.HashMap;
+import tutorlink.student.Student;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class DeleteStudentCommandTest {
+ private DeleteStudentCommand deleteCommand = new DeleteStudentCommand();
+ private AddStudentCommand addCommand = new AddStudentCommand();
+ private AppState appState;
+ private HashMap arguments = new HashMap<>();
+ private CommandResult result;
+
+ @BeforeEach
+ void setup() {
+ appState = new AppState();
+ appState.students.addStudent("A1234567X", "John Doe");
+ appState.students.addStudent("A7654321B", "Jane Smith");
+ appState.students.addStudent("A2468102C", "Arthur Mueller");
+
+ appState.grades.addGrade(new Grade(new Component("Take Home Quiz 1", 10.0,
+ 20), new Student("A1234567X", "John Doe"), 7.0));
+ appState.grades.addGrade(new Grade(new Component("Take Home Quiz 2", 10.0,
+ 20), new Student("A1234567X", "John Doe"), 9.0));
+ appState.grades.addGrade(new Grade(new Component("Take Home Quiz 1", 10.0,
+ 20), new Student("A7654321B", "Jane Smith"), 9.0));
+ }
+
+ @Test
+ void execute_delete_success() {
+ arguments.clear();
+ arguments.put("i/","A1234567X");
+ result = deleteCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals(result.message, "Student A1234567X successfully deleted");
+ assertEquals(appState.students.getStudentArrayList().size(), 2);
+ assertEquals(appState.grades.getGradeArrayList().size(), 1);
+ arguments.clear();
+ arguments.put("i/","A7654321B");
+ result = deleteCommand.execute(appState, arguments);
+ assertNotNull(result);
+ assertEquals(result.message, "Student A7654321B successfully deleted");
+ assertEquals(appState.students.getStudentArrayList().size(), 1);
+ assertEquals(appState.grades.getGradeArrayList().size(), 0);
+ }
+
+ @Test
+ void execute_delete_matricNull() {
+ arguments.clear();
+ arguments.put("n/","John Doe");
+ try {
+ result = deleteCommand.execute(appState, arguments);
+ } catch (IllegalValueException e) {
+ assertEquals(e.getMessage(), String.format(Commons.ERROR_NULL, deleteCommand.getArgumentPrefixes()[0]));
+ } catch (Exception e) {
+ fail("Expected: IllegalValueException, Actual: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void execute_delete_studentNotFound() {
+ arguments.clear();
+ arguments.put("i/","A9999999X");
+ try {
+ result = deleteCommand.execute(appState, arguments);
+ } catch (StudentNotFoundException e) {
+ assertEquals(e.getMessage(), String.format("Error! Student (Matric Number A9999999X) not found"));
+ }
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/command/FindStudentCommandTest.java b/src/test/java/tutorlink/command/FindStudentCommandTest.java
new file mode 100644
index 0000000000..2568f195bc
--- /dev/null
+++ b/src/test/java/tutorlink/command/FindStudentCommandTest.java
@@ -0,0 +1,81 @@
+//@@author RCPilot1604
+package tutorlink.command;
+
+import java.util.HashMap;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.commons.Commons;
+import tutorlink.exceptions.IllegalValueException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.result.CommandResult;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class FindStudentCommandTest {
+ private FindStudentCommand findCommand;
+ private AddStudentCommand addCommand;
+ private final AppState appState = new AppState();
+ private HashMap arguments;
+ private CommandResult result;
+
+ @BeforeEach
+ void setup() {
+ findCommand = new FindStudentCommand();
+ addCommand = new AddStudentCommand();
+ arguments = new HashMap<>();
+ arguments.put("i/","A1234567X");
+ arguments.put("n/", "John");
+ result = addCommand.execute(appState, arguments);
+ assertNotNull(result);
+ arguments.put("i/","A2234567X");
+ arguments.put("n/", "Jon");
+ result = addCommand.execute(appState, arguments);
+ assertNotNull(result);
+ }
+
+ @Test
+ void execute_matric_one() {
+ arguments.clear();
+ arguments.put("i/","A1234567X");
+ CommandResult result = findCommand.execute(appState,arguments);
+ assertEquals(result.toString(), "\t1: John (matric no: A1234567X, percentage score: 0.00)");
+ }
+
+ @Test
+ void execute_matric_none() {
+ arguments.clear();
+ arguments.put("i/","A1111111X");
+ try {
+ CommandResult result = findCommand.execute(appState, arguments);
+ } catch (StudentNotFoundException e) {
+ assertEquals(e.getMessage(), "No students with matricNumber " + "A1111111X" + " found");
+ } catch (Exception e) {
+ fail("Expected: StudentNotFoundException, Actual: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void execute_name_two() {
+ HashMap arguments = new HashMap<>();
+ arguments.put("n/","Jo");
+ CommandResult result = findCommand.execute(appState,arguments);
+ assertEquals(result.toString(), "\t1: John (matric no: A1234567X, percentage score: 0.00)" + "\n\t"
+ + "2: Jon (matric no: A2234567X, percentage score: 0.00)");
+ }
+
+ @Test
+ void execute_find_bothNull() {
+ HashMap arguments = new HashMap<>();
+ try {
+ CommandResult result = findCommand.execute(appState, arguments);
+ } catch (IllegalValueException e) {
+ assertEquals(e.getMessage(), Commons.ERROR_STUDENT_BOTH_NULL);
+ } catch (Exception e) {
+ fail("Expected: IllegalValueException, Actual: " + e.getMessage());
+ }
+ }
+
+}
+//@author
diff --git a/src/test/java/tutorlink/command/ListComponentCommandTest.java b/src/test/java/tutorlink/command/ListComponentCommandTest.java
new file mode 100644
index 0000000000..cf40e39d86
--- /dev/null
+++ b/src/test/java/tutorlink/command/ListComponentCommandTest.java
@@ -0,0 +1,17 @@
+package tutorlink.command;
+
+import org.junit.jupiter.api.BeforeEach;
+
+import tutorlink.appstate.AppState;
+
+public class ListComponentCommandTest {
+
+ private AppState appState;
+ private ListComponentCommand command;
+
+ @BeforeEach
+ public void setUp() {
+ appState = new AppState();
+ command = new ListComponentCommand();
+ }
+}
diff --git a/src/test/java/tutorlink/command/UpdateComponentCommandTest.java b/src/test/java/tutorlink/command/UpdateComponentCommandTest.java
new file mode 100644
index 0000000000..385fd24ebf
--- /dev/null
+++ b/src/test/java/tutorlink/command/UpdateComponentCommandTest.java
@@ -0,0 +1,186 @@
+package tutorlink.command;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.appstate.AppState;
+import tutorlink.component.Component;
+import tutorlink.exceptions.TutorLinkException;
+import tutorlink.grade.Grade;
+import tutorlink.student.Student;
+
+import java.util.HashMap;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class UpdateComponentCommandTest {
+ private UpdateComponentCommand command;
+ private AppState appState;
+ private Component targetDummy;
+ private HashMap args;
+
+ @BeforeEach
+ void setUp() {
+ command = new UpdateComponentCommand();
+ createMockAppState();
+ args = new HashMap<>();
+ }
+
+ @Test
+ void normal_both_arguments() {
+ args.put("c/", "finals");
+ args.put("m/", "30");
+ args.put("w/", "30");
+
+ assertDoesNotThrow(() -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 30);
+ assertEquals(targetDummy.getWeight(), 30);
+ }
+
+ @Test
+ void normal_only_weight() {
+ args.put("c/", "finals");
+ args.put("w/", "30");
+
+ assertDoesNotThrow(() -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 30);
+ }
+
+ @Test
+ void normal_only_mark() {
+ args.put("c/", "finals");
+ args.put("m/", "30");
+
+ assertDoesNotThrow(() -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 30);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void normal_updated_score() {
+ args.put("c/", "finals");
+ args.put("m/", "30");
+
+ assertDoesNotThrow(() -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 30);
+ assertEquals(targetDummy.getWeight(), 40);
+ for (Grade grade : appState.grades.getGradeArrayList()) {
+ if (grade.getComponent().equals(targetDummy)) {
+ assertEquals(grade.getScore(), 30);
+ }
+ }
+ }
+
+
+ @Test
+ void error_no_args() {
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void error_exceed_weight() {
+ args.put("c/", "finals");
+ args.put("w/", "70");
+
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void error_negative_weight() {
+ args.put("c/", "finals");
+ args.put("w/", "-30");
+
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void error_weight_over100() {
+ args.put("c/", "finals");
+ args.put("w/", "101");
+
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void error_nan_weight() {
+ args.put("c/", "finals");
+ args.put("w/", "abc");
+
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void error_mark_negative() {
+ args.put("c/", "finals");
+ args.put("m/", "-1");
+
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+
+ @Test
+ void error_nan_mark() {
+ args.put("c/", "finals");
+ args.put("m/", "abc");
+
+ assertThrows(TutorLinkException.class, () -> command.execute(appState, args));
+ assertEquals(targetDummy.getMaxScore(), 40);
+ assertEquals(targetDummy.getWeight(), 40);
+ }
+
+ @Test
+ void update_gpa_accordingly () {
+ appState = new AppState();
+ appState.students.addStudent("A1234567X", "John Doe");
+ Student student = appState.students.getStudentArrayList().get(0);
+ Component component = new Component("midterm", 50, 10);
+ appState.components.addComponent(component);
+ appState.grades.addGrade(new Grade(component, student, 0));
+
+ Component component2 = new Component("final", 50, 10);
+ appState.components.addComponent(component2);
+ appState.grades.addGrade(new Grade(component2, student, 50));
+ appState.updateAllStudentPercentageScores();
+ assertEquals(student.getPercentageScore(), 50);
+
+ args.put("c/", "midterm");
+ args.put("w/", "30");
+
+ command.execute(appState, args);
+ assertEquals(student.getPercentageScore(), 25);
+ }
+
+ private void createMockAppState() {
+ appState = new AppState();
+ targetDummy = new Component("finals", 40.0, 40);
+ appState.components.addComponent(targetDummy);
+ appState.components.addComponent(new Component("iP", 20.0, 30));
+ appState.components.addComponent(new Component("lectures", 10.0, 10));
+ appState.students.addStudent("A1234567X", "John Smith");
+ appState.students.addStudent("A2345678A", "John Doe");
+ appState.students.addStudent("A3456789E", "Alan Smith");
+ List stuList = appState.students.getStudentArrayList();
+ List comList = appState.components.getComponentArrayList();
+ for (Student student : stuList) {
+ for (Component component : comList) {
+ Grade newGrade = new Grade(component, student, component.getMaxScore());
+ appState.grades.addGrade(newGrade);
+ }
+ }
+ }
+}
diff --git a/src/test/java/tutorlink/component/ComponentTest.java b/src/test/java/tutorlink/component/ComponentTest.java
new file mode 100644
index 0000000000..c69fdc65ad
--- /dev/null
+++ b/src/test/java/tutorlink/component/ComponentTest.java
@@ -0,0 +1,41 @@
+//@@author TrungBui32
+package tutorlink.component;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+public class ComponentTest {
+ private Component assignment;
+ private Component exam;
+
+ @BeforeEach
+ void setup() {
+ assignment = new Component("Assignment 1", 50.0, 30);
+ exam = new Component("Final Exam", 100.0, 40);
+ }
+
+ @Test
+ void constructor_validInputs_success() {
+ assertEquals("Assignment 1", assignment.getName());
+ assertEquals(50.0, assignment.getMaxScore());
+ assertEquals(30, assignment.getWeight());
+ }
+
+ @Test
+ void equals_sameComponent_returnsTrue() {
+ Component duplicateAssignment = new Component("Assignment 1", 50.0, 30);
+ assertEquals(assignment, duplicateAssignment);
+ }
+
+ @Test
+ void equals_differentComponent_returnsFalse() {
+ Component differentAssignment = new Component("Assignment 2", 50.0, 30);
+ assertNotEquals(assignment, differentAssignment);
+ assertNotEquals(assignment, exam);
+ assertNotEquals(assignment, null);
+ assertNotEquals(assignment, "not a component");
+ }
+}
diff --git a/src/test/java/tutorlink/lists/ComponentListTest.java b/src/test/java/tutorlink/lists/ComponentListTest.java
new file mode 100644
index 0000000000..e42af48e8e
--- /dev/null
+++ b/src/test/java/tutorlink/lists/ComponentListTest.java
@@ -0,0 +1,81 @@
+//@@author jinzihan2002
+package tutorlink.lists;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import tutorlink.component.Component;
+import tutorlink.exceptions.ComponentNotFoundException;
+import tutorlink.exceptions.DuplicateComponentException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ComponentListTest {
+ private ComponentList componentList;
+ private Component comp1;
+ private Component comp2;
+ private Component comp3;
+
+ @BeforeEach
+ void setup() {
+ componentList = new ComponentList();
+ comp1 = new Component("Homework 1", 30, 10);
+ comp2 = new Component("Homework 2", 30, 10);
+ comp3 = new Component("Finals", 100, 50);
+ }
+
+ @Test
+ void addComponent_addOneComponent_success() {
+ componentList.addComponent(comp1);
+ assertEquals(1, componentList.getComponentArrayList().size());
+ }
+
+ @Test
+ void addComponent_addDuplicateComponent_exceptionThrown() {
+ componentList.addComponent(comp1);
+ Component duplicateComp = new Component("Homework 1", 30, 10);
+ assertThrows(DuplicateComponentException.class, () -> componentList.addComponent(duplicateComp));
+ }
+
+ @Test
+ void deleteComponent_deleteOneComponent_success() {
+ componentList.addComponent(comp3);
+ componentList.deleteComponent(comp3);
+ assertEquals(0, componentList.getComponentArrayList().size());
+ }
+
+ @Test
+ void deleteComponent_deleteUnknownComponent_exceptionThrown() {
+ componentList.addComponent(comp2);
+ assertThrows(ComponentNotFoundException.class, () -> componentList.deleteComponent(comp1));
+ }
+
+ @Test
+ void findComponent_findExisting_success() {
+ componentList.addComponent(comp1);
+ componentList.addComponent(comp2);
+ componentList.addComponent(comp3);
+ ComponentList filteredList = componentList.findComponent("Homework 1");
+ assertEquals(1, filteredList.size());
+ String expectedResult = "\t1: Homework 1 (maxScore: 30.0, weight: 10%)";
+ assertEquals(expectedResult, filteredList.toString());
+ }
+
+ @Test
+ void findComponent_findNotInList_exceptionThrown() {
+ componentList.addComponent(comp1);
+ componentList.addComponent(comp2);
+ componentList.addComponent(comp3);
+ assertThrows(ComponentNotFoundException.class, () -> componentList.findComponent("midterm"));
+ }
+
+ @Test
+ void componentList_toString_success() {
+ componentList.addComponent(comp1);
+ componentList.addComponent(comp3);
+ String expectedResult = "\t1: Homework 1 (maxScore: 30.0, weight: 10%)\n" +
+ "\t2: Finals (maxScore: 100.0, weight: 50%)";
+ assertEquals(expectedResult, componentList.toString());
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/lists/GradeListTest.java b/src/test/java/tutorlink/lists/GradeListTest.java
new file mode 100644
index 0000000000..442d11fd45
--- /dev/null
+++ b/src/test/java/tutorlink/lists/GradeListTest.java
@@ -0,0 +1,93 @@
+//@@author RCPilot1604
+package tutorlink.lists;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.exceptions.DuplicateGradeException;
+import tutorlink.exceptions.GradeNotFoundException;
+import tutorlink.grade.Grade;
+import tutorlink.student.Student;
+import tutorlink.component.Component;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class GradeListTest {
+
+ private GradeList gradeList;
+ private Grade grade1;
+ private Grade grade2;
+
+ @BeforeEach
+ void setup() {
+ gradeList = new GradeList();
+
+ // Create two sample grades for testing
+ Student student1 = new Student("A1234567B", "John Doe");
+ Student student2 = new Student("A7654321B", "Jane Smith");
+
+ Component component1 = new Component("Homework", 100, 20);
+ Component component2 = new Component("Exam", 100, 50);
+
+ grade1 = new Grade(component1, student1, 90.0);
+ grade2 = new Grade(component2, student2, 85.0);
+ }
+
+ @Test
+ void add_success() throws DuplicateGradeException {
+ gradeList.addGrade(grade1);
+ assertEquals(1, gradeList.toString().split("\n").length); // Ensure it is added
+ }
+
+ @Test
+ void add_duplicateGradeException() throws DuplicateGradeException {
+ gradeList.addGrade(grade1);
+ assertThrows(DuplicateGradeException.class, () -> gradeList.addGrade(grade1));
+ }
+
+ @Test
+ void delete_success() throws DuplicateGradeException {
+ gradeList.addGrade(grade1);
+ gradeList.deleteGrade(grade1.getStudent().getMatricNumber(), grade1.getComponent().getName());
+ assertEquals(0, gradeList.toString().length()); // Ensure it's removed
+ }
+
+ @Test
+ void delete_notFound() {
+ try {
+ gradeList.deleteGrade("A9999999B", "nonexistentComponent");
+ } catch (GradeNotFoundException e) {
+ assertEquals(
+ "Error! Grade for component nonexistentComponent " +
+ "for student A9999999B does not exist in the list!", e.getMessage());
+ } catch (Exception e) {
+ fail("Expected: StudentNotFoundException, Actual: " + e.getMessage());
+ }
+ }
+
+ @Test
+ void find_success() throws Exception {
+ gradeList.addGrade(grade1);
+ gradeList.addGrade(grade2);
+
+ GradeList result = gradeList.findGrade("A1234567B", "Homework");
+ assertEquals(1, result.toString().split("\n").length);
+ String stringResult = result.toString();
+ assertTrue(stringResult.contains("John Doe"));
+ }
+
+ @Test
+ void find_notFound() {
+ assertThrows(GradeNotFoundException.class, () -> gradeList.findGrade("A9999999B", "nonexistentComponent"));
+ }
+
+ @Test
+ void testToString() throws DuplicateGradeException {
+ gradeList.addGrade(grade1);
+ gradeList.addGrade(grade2);
+ String expectedString = "1: " + grade1.toString() + "\n\t2: " + grade2.toString();
+ assertEquals(expectedString, gradeList.toString());
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/lists/StudentListTest.java b/src/test/java/tutorlink/lists/StudentListTest.java
new file mode 100644
index 0000000000..36e9f63946
--- /dev/null
+++ b/src/test/java/tutorlink/lists/StudentListTest.java
@@ -0,0 +1,94 @@
+//@@author RCPilot1604
+package tutorlink.lists;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import tutorlink.exceptions.DuplicateMatricNumberException;
+import tutorlink.exceptions.StudentNotFoundException;
+import tutorlink.student.Student;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class StudentListTest {
+
+ private StudentList studentList;
+
+ @BeforeEach
+ void setUp() {
+ studentList = new StudentList();
+ }
+
+ @Test
+ void add_success() throws DuplicateMatricNumberException {
+ studentList.addStudent("A1234567B", "John Doe");
+ ArrayList students = studentList.getStudentArrayList();
+ assertEquals(1, students.size());
+ assertEquals("John Doe", students.get(0).getName());
+ }
+
+ @Test
+ void add_duplicateMatricNumberException() throws DuplicateMatricNumberException {
+ studentList.addStudent("A1234567B", "John Doe");
+ assertThrows(DuplicateMatricNumberException.class, () -> studentList.addStudent("A1234567B", "Jane Smith"));
+ }
+
+ @Test
+ void delete_success() throws DuplicateMatricNumberException {
+ studentList.addStudent("A1234567B", "John Doe");
+ studentList.deleteStudent("A1234567B");
+ assertTrue(studentList.getStudentArrayList().isEmpty());
+ }
+
+ @Test
+ void delete_failure() {
+ try {
+ studentList.deleteStudent("A1234567B");
+ } catch (StudentNotFoundException e) {
+ assertEquals(e.getMessage(), "Error! Student (Matric Number A1234567B) not found");
+ }
+ }
+
+ @Test
+ void find_matric_success() throws Exception {
+ studentList.addStudent("A1234567B", "John Doe");
+ studentList.addStudent("A7654321B", "Jane Smith");
+
+ StudentList result = studentList.findStudentByMatricNumber("A1234567B");
+ assertEquals(1, result.getStudentArrayList().size());
+ assertEquals("John Doe", result.getStudentArrayList().get(0).getName());
+ }
+
+ @Test
+ void find_matric_notFound() {
+ assertThrows(StudentNotFoundException.class, () -> studentList.findStudentByMatricNumber("A9999999B"));
+ }
+
+ @Test
+ void find_name_success() throws Exception {
+ studentList.addStudent("A1234567B", "John Doe");
+ studentList.addStudent("A7654321B", "Jane Smith");
+
+ StudentList result = studentList.findStudentByName("Jane");
+ assertEquals(1, result.getStudentArrayList().size());
+ assertEquals("Jane Smith", result.getStudentArrayList().get(0).getName());
+ }
+
+ @Test
+ void find_name_notFound() {
+ assertThrows(StudentNotFoundException.class, () -> studentList.findStudentByName("Nonexistent Name"));
+ }
+
+ @Test
+ void testToString() throws DuplicateMatricNumberException {
+ studentList.addStudent("A1234567B", "John Doe");
+ studentList.addStudent("A7654321B", "Jane Smith");
+ String expectedString = "\t1: John Doe (matric no: A1234567B, percentage score: 0.00)"
+ + "\n\t2: Jane Smith (matric no: A7654321B, percentage score: 0.00)";
+ assertEquals(expectedString, studentList.toString());
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/parser/ParserTest.java b/src/test/java/tutorlink/parser/ParserTest.java
new file mode 100644
index 0000000000..a1027baecd
--- /dev/null
+++ b/src/test/java/tutorlink/parser/ParserTest.java
@@ -0,0 +1,193 @@
+//@@author yeekian
+package tutorlink.parser;
+
+import org.junit.jupiter.api.Test;
+import tutorlink.command.AddStudentCommand;
+import tutorlink.command.Command;
+import tutorlink.command.DeleteStudentCommand;
+import tutorlink.command.ExitCommand;
+import tutorlink.command.FindStudentCommand;
+import tutorlink.command.InvalidCommand;
+import tutorlink.command.ListStudentCommand;
+import tutorlink.exceptions.IllegalValueException;
+
+
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ParserTest {
+ Parser parser = new Parser();
+
+ @Test
+ void parserTestInterchangingArgument() {
+ String input = "add_student i/A1234567X n/John Doe";
+ String input2 = "add_student n/John Doe i/A1234567X";
+ String[] args = new String[]{"i/, n/"};
+
+ var out1 = parser.getArguments(args, input);
+ var out2 = parser.getArguments(args, input2);
+
+ assertEquals(out1, out2);
+ }
+
+ @Test
+ void getCommand_addStudentCommand_addStudentCommandReturned() {
+
+ Parser parser = new Parser();
+ String input = "add_student i/A1234567X n/John";
+ Command actualCommand = parser.getCommand(input);
+
+ assertEquals(AddStudentCommand.class, actualCommand.getClass());
+ }
+
+ @Test
+ void getCommand_deleteStudentCommand_deleteStudentCommandReturned() {
+ Parser parser = new Parser();
+ String input = "delete_student i/A1234567X";
+ Command actualCommand = parser.getCommand(input);
+
+ assertEquals(DeleteStudentCommand.class, actualCommand.getClass());
+ }
+
+ @Test
+ void getCommand_exitCommand_exitCommandReturned() {
+ Parser parser = new Parser();
+ String input = "bye";
+ Command actualCommand = parser.getCommand(input);
+
+ assertEquals(ExitCommand.class, actualCommand.getClass());
+ }
+
+ @Test
+ void getCommand_listStudentCommand_listStudentCommandReturned() {
+ Parser parser = new Parser();
+ String input = "list_student";
+ Command actualCommand = parser.getCommand(input);
+
+ assertEquals(ListStudentCommand.class, actualCommand.getClass());
+ }
+
+ @Test
+ void getCommand_findStudentCommand_findStudentCommandReturned() {
+ Parser parser = new Parser();
+ String input = "find_student i/A1234567X n/John Doe";
+ Command actualCommand = parser.getCommand(input);
+
+ assertEquals(FindStudentCommand.class, actualCommand.getClass());
+ }
+
+ @Test
+ void getCommand_invalidCommand_invalidCommandReturned() {
+ Parser parser = new Parser();
+ String input = "test_input";
+ Command actualCommand = parser.getCommand(input);
+
+ assertEquals(InvalidCommand.class, actualCommand.getClass());
+ }
+
+ @Test
+ void getArguments_addStudentCommand_addStudentCommandHashMapReturned() {
+ Parser parser = new Parser();
+ String line = "add_student i/A1234567X n/John Doe";
+
+ Command currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+
+ assertEquals(2, arguments.size());
+ assertEquals("A1234567X", arguments.get("i/")); // Check matriculation number
+ assertEquals("John Doe", arguments.get("n/")); // Check
+ }
+
+ @Test
+ void getArguments_exitCommandNoArgumentPrefix_exitCommandHashMapReturned() {
+ Parser parser = new Parser();
+ String line = "bye";
+
+ Command currentCommand = new ExitCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+
+ assertEquals(0, arguments.size());
+ }
+
+ @Test
+ void getArguments_addStudentCommandExtraArguments_ignoreExtraTag() {
+ Parser parser = new Parser();
+ String line = "add_student i/A1234567X n/John Doe t/extraTag";
+
+ Command currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+
+ assertEquals(2, arguments.size());
+ assertEquals("A1234567X", arguments.get("i/")); // Check matriculation number
+ assertEquals("John Doe", arguments.get("n/")); // Check
+ }
+
+ @Test
+ void getArguments_addStudentCommandSwappedArguments_addStudentCommandHashMapReturned() {
+ Parser parser = new Parser();
+ String line = "add_student n/John Doe i/A1234567X ";
+
+ Command currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+
+ assertEquals(2, arguments.size());
+ assertEquals("A1234567X", arguments.get("i/")); // Check matriculation number
+ assertEquals("John Doe", arguments.get("n/")); // Check
+ }
+
+ @Test
+ void getArguments_addStudentCommandMissingArguments_hashMapWithOnlyGivenArgumentsReturned() {
+ Parser parser = new Parser();
+ String line = "add_student n/John Doe";
+
+ Command currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+
+ assertEquals(1, arguments.size());
+ assertEquals("John Doe", arguments.get("n/")); // Check
+ }
+
+ @Test
+ void getArguments_addStudentCommandInvalidArguments_emptyHashMapReturned() {
+ Parser parser = new Parser();
+ String line = "add_student j/test";
+
+ Command currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+
+ HashMap arguments = parser.getArguments(argumentPrefixes, line);
+
+ assertEquals(0, arguments.size());
+ }
+
+ @Test
+ void getArguments_duplicatePrefix_exception() {
+ Parser parser = new Parser();
+ String line = "add_student n/Ethan n/Ethan i/A0276007H";
+ AddStudentCommand currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+ assertThrows(IllegalValueException.class, () -> parser.getArguments(argumentPrefixes, line));
+ }
+
+ @Test
+ void getArguments_duplicatePrefixAgain_exception() {
+ Parser parser = new Parser();
+ String line = "add_student n/Ethan i/A0276007H i/A0276007H";
+ AddStudentCommand currentCommand = new AddStudentCommand();
+ String[] argumentPrefixes = currentCommand.getArgumentPrefixes();
+ assertThrows(IllegalValueException.class, () -> parser.getArguments(argumentPrefixes, line));
+ }
+}
+//@@author
diff --git a/src/test/java/tutorlink/student/StudentTest.java b/src/test/java/tutorlink/student/StudentTest.java
new file mode 100644
index 0000000000..26a11f696d
--- /dev/null
+++ b/src/test/java/tutorlink/student/StudentTest.java
@@ -0,0 +1,65 @@
+//@@author RCPilot1604
+package tutorlink.student;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+
+public class StudentTest {
+ private Student student1;
+ private Student student2;
+
+ @BeforeEach
+ void setup() {
+ student1 = new Student("A1234567B", "John Doe");
+ student2 = new Student("A7654321B", "Jane Smith");
+ }
+
+ @Test
+ void testConstructor() {
+ assertEquals("John Doe", student1.getName());
+ assertEquals("A1234567B", student1.getMatricNumber());
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("John Doe (matric no: A1234567B, percentage score: 0.00)", student1.toString());
+ assertEquals("Jane Smith (matric no: A7654321B, percentage score: 0.00)", student2.toString());
+ }
+
+ @Test
+ void testSameObject() {
+ // Test if the same object returns true
+ assertEquals(student1, student1);
+ }
+
+ @Test
+ void diffObj_sameMatric() {
+ // Test with another object having the same matric number
+ Student student3 = new Student("A1234567B", "Johnny");
+ assertEquals(student1, student3);
+ }
+
+ @Test
+ void diffObj_differentMatric() {
+ // Test with another object with a different matric number
+ assertNotEquals(student1, student2);
+ }
+
+ @Test
+ void equals_nullObject() {
+ // Test with null
+ assertNotEquals(null, student1);
+ }
+
+ @Test
+ void equals_differentClass() {
+ // Test with an object of a different class
+ String notAStudent = "Not a Student";
+ assertNotEquals(student1, notAStudent);
+ }
+}
+//@@author
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..495162ed88 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,160 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
-What is your name?
-Hello James Gosling
+-------------------------------------------------------------
+Discarded student data:
+ A1111111B| John Smith
+ A1111111B | John Smith
+ A123B | Beth
+-------------------------------------------------------------
+Discarded component data:
+ Attendance | 5 | 5
+ Finals | 80 | 96
+ Test 1 | 10 | -0.1
+ Test 2 | -0.01 | 5
+ Test 3 | 10000.01 | 5
+-------------------------------------------------------------
+Discarded grade data:
+ Attendance | A1111111B | 5.01
+ Attendance | A1111111B | 4
+ Attendance | A1111111B | 4
+-------------------------------------------------------------
+___________ __ .____ .__ __
+\__ ___/_ ___/ |_ ___________| | |__| ____ | | __
+ | | | | \ __\/ _ \_ __ \ | | |/ \| |/ /
+ | | | | /| | ( <_> ) | \/ |___| | | \ <
+ |____| |____/ |__| \____/|__| |_______ \__|___| /__|_ \
+ \/ \/ \/
+
+-------------------------------------------------------------
+Hello! I'm TutorLink
+What can I do for you?
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Student Ethan Chua (A0276007H) added successfully!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Student John Doe (A9999999Z) added successfully!
+-------------------------------------------------------------
+------------------------- Error -------------------------
+Error! Matric Number should start with "A", followed by 7 digits, and end with an uppercase letter (e.g., A1234567X)
+-------------------------------------------------------------
+------------------------- Result -------------------------
+ 1: John Smith (matric no: A1111111B, percentage score: 80.00)
+ 2: Ethan Chua (matric no: A0276007H, percentage score: 0.00)
+ 3: John Doe (matric no: A9999999Z, percentage score: 0.00)
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Component Quiz 1 of weight 20%, with max score 10 added successfully!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Component Quiz 2 of weight 20%, with max score 20 added successfully!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Component Quiz 3 of weight 20%, with max score 20 added successfully!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Component Quiz 4 of weight 20%, with max score 20 added successfully!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 10 added successfully to Quiz 1 for A0276007H!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 20 added successfully to Quiz 2 for A0276007H!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 20 added successfully to Quiz 3 for A0276007H!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 20 added successfully to Quiz 4 for A0276007H!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+List of All Grades:
+
+1: Ethan Chua (A0276007H):
+ 1. Quiz 1 : 10.00
+ 2. Quiz 2 : 20.00
+ 3. Quiz 3 : 20.00
+ 4. Quiz 4 : 20.00
+ Final Percentage Score: 94.12%
+
+2: John Smith (A1111111B):
+ 1. Attendance : 4.00
+ Final Percentage Score: 4.71%
+
+
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 5 added successfully to Quiz 1 for A9999999Z!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 7.5 added successfully to Quiz 2 for A9999999Z!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 10 added successfully to Quiz 3 for A9999999Z!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 10 added successfully to Quiz 4 for A9999999Z!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+List of All Grades:
+
+1: Ethan Chua (A0276007H):
+ 1. Quiz 1 : 10.00
+ 2. Quiz 2 : 20.00
+ 3. Quiz 3 : 20.00
+ 4. Quiz 4 : 20.00
+ Final Percentage Score: 94.12%
+
+2: John Smith (A1111111B):
+ 1. Attendance : 4.00
+ Final Percentage Score: 4.71%
+
+3: John Doe (A9999999Z):
+ 1. Quiz 1 : 5.00
+ 2. Quiz 2 : 7.50
+ 3. Quiz 3 : 10.00
+ 4. Quiz 4 : 10.00
+ Final Percentage Score: 44.12%
+
+
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 0 added successfully to Quiz 1 for A1111111B!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 0 added successfully to Quiz 2 for A1111111B!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 0 added successfully to Quiz 3 for A1111111B!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Score of 0 added successfully to Quiz 4 for A1111111B!
+-------------------------------------------------------------
+------------------------- Result -------------------------
+List of All Grades:
+
+1: Ethan Chua (A0276007H):
+ 1. Quiz 1 : 10.00
+ 2. Quiz 2 : 20.00
+ 3. Quiz 3 : 20.00
+ 4. Quiz 4 : 20.00
+ Final Percentage Score: 94.12%
+
+2: John Smith (A1111111B):
+ 1. Attendance : 4.00
+ 2. Quiz 1 : 0.00
+ 3. Quiz 2 : 0.00
+ 4. Quiz 3 : 0.00
+ 5. Quiz 4 : 0.00
+ Final Percentage Score: 4.71%
+
+3: John Doe (A9999999Z):
+ 1. Quiz 1 : 5.00
+ 2. Quiz 2 : 7.50
+ 3. Quiz 3 : 10.00
+ 4. Quiz 4 : 10.00
+ Final Percentage Score: 44.12%
+
+
+-------------------------------------------------------------
+------------------------- Result -------------------------
+Goodbye! See you soon!
+-------------------------------------------------------------
diff --git a/text-ui-test/data_init/componentlist.txt b/text-ui-test/data_init/componentlist.txt
new file mode 100644
index 0000000000..b3f9e6d361
--- /dev/null
+++ b/text-ui-test/data_init/componentlist.txt
@@ -0,0 +1,6 @@
+Attendance | 5 | 5
+ Attendance | 5 | 5
+Finals | 80 | 96
+Test 1 | 10 | -0.1
+Test 2 | -0.01 | 5
+Test 3 | 10000.01 | 5
diff --git a/text-ui-test/data_init/gradelist.txt b/text-ui-test/data_init/gradelist.txt
new file mode 100644
index 0000000000..e5143870c3
--- /dev/null
+++ b/text-ui-test/data_init/gradelist.txt
@@ -0,0 +1,4 @@
+Attendance | A1111111B | 5.01
+Attendance | A1111111B | 4
+Attendance | A1111111B | 4
+ Attendance | A1111111B | 4
diff --git a/text-ui-test/data_init/studentlist.txt b/text-ui-test/data_init/studentlist.txt
new file mode 100644
index 0000000000..30afd5fecc
--- /dev/null
+++ b/text-ui-test/data_init/studentlist.txt
@@ -0,0 +1,4 @@
+A1111111B| John Smith
+A1111111B | John Smith
+ A1111111B | John Smith
+A123B | Beth
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..dba3a72b19 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1,24 @@
-James Gosling
\ No newline at end of file
+add_student i/A0276007H n/Ethan Chua
+add_student i/A9999999Z n/John Doe
+add_student i/a1111111b n/John Smith
+list_student
+add_component c/Quiz 1 w/20 m/10
+add_component c/Quiz 2 w/20 m/20
+add_component c/Quiz 3 w/20 m/20
+add_component c/Quiz 4 w/20 m/20
+add_grade c/Quiz 1 s/10 i/A0276007H
+add_grade c/Quiz 2 s/20 i/A0276007H
+add_grade c/Quiz 3 s/20 i/A0276007H
+add_grade c/Quiz 4 s/20 i/A0276007H
+list_grade
+add_grade c/Quiz 1 s/5 i/A9999999Z
+add_grade c/Quiz 2 s/7.5 i/A9999999Z
+add_grade c/Quiz 3 s/10 i/A9999999Z
+add_grade c/Quiz 4 s/10 i/A9999999Z
+list_grade
+add_grade c/Quiz 1 s/0 i/a1111111b
+add_grade c/Quiz 2 s/0 i/a1111111b
+add_grade c/Quiz 3 s/0 i/a1111111b
+add_grade c/Quiz 4 s/0 i/a1111111b
+list_grade
+bye
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
old mode 100644
new mode 100755
index 25ac7a2989..4e664378b9
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -12,8 +12,16 @@ for /f "tokens=*" %%a in (
set jarloc=%%a
)
+:: delete data directory from previous run if it exists
+if exist ".\data" (
+ rmdir /s /q ".\data"
+)
+
+:: copy data_init to data
+xcopy "..\..\text-ui-test\data_init" ".\data" /s /e /i
+
java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TXT
cd ..\..\text-ui-test
-FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed!
+FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed!
\ No newline at end of file
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
index 1dcbd12021..f288746493 100755
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -8,6 +8,15 @@ cd ..
cd text-ui-test
+# delete data directory from previous run if it exists
+if [ -d "./data" ]
+then
+ rm -rf data
+fi
+
+# copy data_init to data
+cp -r data_init data
+
java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT
cp EXPECTED.TXT EXPECTED-UNIX.TXT