diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index e2081a3b4b..c0fee6c0f3 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,11 +1,29 @@ # 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. -## Design & implementation +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 @@ -43,14 +61,13 @@ Where ref frame is a placeholder for each command's specific operat #### Setup: During the setup phase of `TutorLink`, the following operations are performed: -1. `Ui` displays welcome message -2. `StudentStorage`, `ComponentStorage` and `GradeStorage` objects are instantiated -3. `ArrayList` of Student, Component and Grade are obtained from the respective Storage classes -4. `AppState` object is instantiated, passing the `ArrayList`s in step 3 +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: The specific implementation of noteworthy operations are presented below: ### Add/Delete Student/Component Feature @@ -77,7 +94,7 @@ The following sequence diagrams depict the exact steps involved in the `AddStude - `DeleteStudentCommand.execute(AppState appState, HashMap arguments)`: Removes a student via the following steps: - 1. Retrieves and validates the matriculation number from arguments, throwing `IllegaValueException` exception + 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. @@ -148,24 +165,26 @@ The sequence diagram of the DeleteGradeCommand is shown below. 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. +The load list methods for the Storage classes have largely similar logic flows. To avoid repetition, +only the implementation for `GradeStorage` is shown. -#### Key Operations - -The following section and sequence diagram elaborate on the implementation of the `loadGradeList` method in `GradeStorage`: +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. TutorLink calls `loadGradeList`. -3. `GradeStorage` creates a new `ArrayList` of `Grade`s. -4. `GradeStorage` creates a new `Scanner` with the path to the file. +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: - - `Scanner` returns the current file line as a String and moves to the next file line. + - 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. if data file was corrupted), the file line is simply ignored, and the loop continues to the next iteration. + - 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. ## Appendix A: Product Scope @@ -394,7 +413,7 @@ This appendix provides a guide for manually testing various features of TutorLin --- -### Handling Missing or Corrupted Data Files +### Handling Missing Files or Corrupted Data 1. **Simulate Missing Data Files**: - Delete one or more files from the `data` folder (`studentlist.txt`, `componentlist.txt`, `gradelist.txt`). @@ -402,10 +421,12 @@ This appendix provides a guide for manually testing various features of TutorLin **Expected**: TutorLink creates new empty files if missing. The application should not crash, and it should operate normally. -2. **Simulate Corrupted Data Files**: - - Open any data file and add random text or invalid data structure, then save. +2. **Simulate Corrupted Data**: + - Open any data file and add random text or invalid data entry, then save. - Re-launch TutorLink. - **Expected**: TutorLink should detect the corrupted data, provide an error message or recreate a new empty file if unreadable. The application should not crash. + **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. ---- \ No newline at end of file +--- diff --git a/docs/UserGuide.md b/docs/UserGuide.md index edf483b411..7ab1a3d18d 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -7,15 +7,30 @@ ## Quick Start 1. Ensure you have Java 17 or above installed in your Computer. -2. Download the latest .jar file of `TutorLink` from [here](http://link.to/duke). +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 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 insensitive. Therefore, `A1234567X` is the same as `a1234567x`. Matric numbers +will be converted to uppercase for storage. +- Similarly, all other will be converted to lowercase for storage. ## Features +
+ ### Adding a Student: `add_student` Adds a student to your class. @@ -53,6 +68,8 @@ Displays a list of all students in the class. --- +
+ ### Finding a Student: `find_student` Adds a student to your class. @@ -94,6 +111,8 @@ Removes an existing grading component from the class. --- +
+ ### Listing Components: `list_component` Displays all grading components and their respective weights for a class. @@ -115,7 +134,7 @@ Records a grade for a specific student in a particular assignment or exam compon - `SCORE`: The score to be recorded. Note that score cannot exceed the max score of the component. - **Example**: - - `add_grade i/A1234567X c/Quiz 1 s/85` adds the grade of Quiz 1 for the student with the matric number of A1234567X with a score of 85. + - `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. --- @@ -133,6 +152,8 @@ Removes a previously recorded grade for a specific student and component. --- +
+ ### 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. @@ -168,30 +189,40 @@ The data from the student, component and grade lists are stored in `studentlist. respectively, located in the `[JAR file location]/data/` directory. --- -## Notes: -- Matric Number (`i/` argument) is case insensitive. Therefore, `A1234567X` is the same as `a1234567x`. Matric numbers -will be converted to uppercase for storage. -- (coming soon) All other arguments are case insensitive and will be converted to lowercase for storage. + +
+ ## FAQ **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. +**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 | **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/85` | +| `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` | diff --git a/docs/diagrams/AddComponentCommand.png b/docs/diagrams/AddComponentCommand.png index c8b629e640..79410cee8a 100644 Binary files a/docs/diagrams/AddComponentCommand.png and b/docs/diagrams/AddComponentCommand.png differ diff --git a/docs/diagrams/AddComponentCommand.puml b/docs/diagrams/AddComponentCommand.puml index 937efac9b8..22f5d08e01 100644 --- a/docs/diagrams/AddComponentCommand.puml +++ b/docs/diagrams/AddComponentCommand.puml @@ -19,6 +19,8 @@ C -> H : get("m/") activate H C <-- H : maxScoreNumber deactivate H +destroy H + opt componentName or weightageNumber or maxScoreNumber is null [<-- C : throw IllegalValueException @@ -47,4 +49,6 @@ deactivate R [<-- C : CommandResult end +destroy R +destroy C @enduml diff --git a/docs/diagrams/AddGradeCommand.png b/docs/diagrams/AddGradeCommand.png index 04fa4b9e7f..57888a95d4 100644 Binary files a/docs/diagrams/AddGradeCommand.png and b/docs/diagrams/AddGradeCommand.png differ diff --git a/docs/diagrams/AddGradeCommand.puml b/docs/diagrams/AddGradeCommand.puml index f30026a9f1..f50c218b67 100644 --- a/docs/diagrams/AddGradeCommand.puml +++ b/docs/diagrams/AddGradeCommand.puml @@ -55,6 +55,7 @@ deactivate R 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 index e8aa060e31..1ea2eb7de9 100644 Binary files a/docs/diagrams/AddStudentCommand.png and b/docs/diagrams/AddStudentCommand.png differ diff --git a/docs/diagrams/AddStudentCommand.puml b/docs/diagrams/AddStudentCommand.puml index ca7ea1350b..f30a281df5 100644 --- a/docs/diagrams/AddStudentCommand.puml +++ b/docs/diagrams/AddStudentCommand.puml @@ -21,4 +21,6 @@ deactivate R [<-- C : CommandResult deactivate C end +destroy R +destroy C @enduml \ No newline at end of file diff --git a/docs/diagrams/ArchitectureSequenceGrouped.png b/docs/diagrams/ArchitectureSequenceGrouped.png index 3dd7681025..606270e5c4 100644 Binary files a/docs/diagrams/ArchitectureSequenceGrouped.png and b/docs/diagrams/ArchitectureSequenceGrouped.png differ diff --git a/docs/diagrams/ArchitectureSequenceGrouped.puml b/docs/diagrams/ArchitectureSequenceGrouped.puml index 04a9bfd713..90c3d69eda 100644 --- a/docs/diagrams/ArchitectureSequenceGrouped.puml +++ b/docs/diagrams/ArchitectureSequenceGrouped.puml @@ -1 +1 @@ -@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 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 +@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/DeleteComponentCommand.png b/docs/diagrams/DeleteComponentCommand.png index 05beaca33d..880eeaf8a2 100644 Binary files a/docs/diagrams/DeleteComponentCommand.png and b/docs/diagrams/DeleteComponentCommand.png differ diff --git a/docs/diagrams/DeleteComponentCommand.puml b/docs/diagrams/DeleteComponentCommand.puml index 333b1586d7..fe7e83aa80 100644 --- a/docs/diagrams/DeleteComponentCommand.puml +++ b/docs/diagrams/DeleteComponentCommand.puml @@ -13,6 +13,7 @@ C -> H : get("n/") activate H C <-- H : componentName deactivate H +destroy H opt componentName is null [<-- C : throw IllegalValueException @@ -43,4 +44,7 @@ deactivate R [<-- C : CommandResult end +destroy R +destroy C + @enduml diff --git a/docs/diagrams/DeleteGradeCommand.png b/docs/diagrams/DeleteGradeCommand.png index c266847ef4..173a4e3338 100644 Binary files a/docs/diagrams/DeleteGradeCommand.png and b/docs/diagrams/DeleteGradeCommand.png differ diff --git a/docs/diagrams/DeleteGradeCommand.puml b/docs/diagrams/DeleteGradeCommand.puml index c0a64fe95d..be71af4c27 100644 --- a/docs/diagrams/DeleteGradeCommand.puml +++ b/docs/diagrams/DeleteGradeCommand.puml @@ -39,6 +39,7 @@ deactivate R deactivate C end - +destroy R +destroy C @enduml diff --git a/docs/diagrams/DeleteStudentCommand.png b/docs/diagrams/DeleteStudentCommand.png index c45dbd2c11..f8ddb273db 100644 Binary files a/docs/diagrams/DeleteStudentCommand.png and b/docs/diagrams/DeleteStudentCommand.png differ diff --git a/docs/diagrams/DeleteStudentCommand.puml b/docs/diagrams/DeleteStudentCommand.puml index 3d62ed8222..35fed323c7 100644 --- a/docs/diagrams/DeleteStudentCommand.puml +++ b/docs/diagrams/DeleteStudentCommand.puml @@ -30,4 +30,6 @@ 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 index 96796ad16e..4190c9e7af 100644 Binary files a/docs/diagrams/FindStudentCommand.png and b/docs/diagrams/FindStudentCommand.png differ diff --git a/docs/diagrams/FindStudentCommand.puml b/docs/diagrams/FindStudentCommand.puml index 70d8b09aae..140c97a80d 100644 --- a/docs/diagrams/FindStudentCommand.puml +++ b/docs/diagrams/FindStudentCommand.puml @@ -30,4 +30,6 @@ 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 index fd9571113f..df06a5455c 100644 Binary files a/docs/diagrams/GradeStorage.png and b/docs/diagrams/GradeStorage.png differ diff --git a/docs/diagrams/GradeStorage.puml b/docs/diagrams/GradeStorage.puml index d3772102d7..e24a8d0522 100644 --- a/docs/diagrams/GradeStorage.puml +++ b/docs/diagrams/GradeStorage.puml @@ -2,13 +2,22 @@ !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_6 -[->TL : setupAllLists() -activate TL +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(GRADE_FILE_PATH, initialComponentList, initialStudentList) +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 @@ -21,9 +30,7 @@ activate AL GS <-- AL: grades: Grade deactivate AL - - -loop fileScanner.hasNext() +loop file has next line GS -> GS: getGradeFromFileLine(currentLine: String) activate GS GS --> GS: newGrade: Grade @@ -35,14 +42,27 @@ loop fileScanner.hasNext() 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 -[<--TL -deactivate TL + +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 index 553055788a..eb41ba3f8c 100644 Binary files a/docs/diagrams/Setup.png and b/docs/diagrams/Setup.png differ diff --git a/docs/diagrams/Setup.puml b/docs/diagrams/Setup.puml index d66b1edbd4..8affaf4b31 100644 --- a/docs/diagrams/Setup.puml +++ b/docs/diagrams/Setup.puml @@ -8,49 +8,17 @@ participant "componentStorage:Storage" as CS LOGIC_COLOR_4 participant "gradeStorage:Storage" as GS LOGIC_COLOR_4 group sd setup -[->TL : main() -activate TL -TL->UI: displayWelcomeMessage() -activate UI -TL <-- UI -deactivate UI alt setup success - create SS - TL -> SS : new StudentStorage - activate SS - TL <-- SS : StudentStorage - deactivate SS - - create CS - TL -> CS : new ComponentStorage - activate CS - TL <-- CS :ComponentStorage - deactivate CS - - create GS - TL -> GS : new ComponentStorage - activate GS - TL <-- GS :ComponentStorage - deactivate GS - TL -> SS : loadStudentList() - activate SS - TL <-- SS : ArrayList - deactivate SS + ref over TL, UI, SS: load student data - TL -> CS : loadComponentList() - activate CS - TL <-- CS : ArrayList - deactivate CS + ref over TL, UI, CS: load component data - TL -> GS : loadGradeList() - activate GS - TL <-- GS : ArrayList - deactivate GS + ref over TL, UI, GS: load grade data create A - TL -> A : new AppState(ArrayList, ArrayList, ArrayList) + TL -> A : new AppState(students, grades, components) activate A TL <-- A : appState deactivate A @@ -62,7 +30,11 @@ else TutorLinkException end deactivate UI -[<-- TL -deactivate TL + +TL->UI: displayWelcomeMessage() +activate UI +TL <-- UI +deactivate UI + end -@enduml \ No newline at end of file +@enduml diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index b74887daa8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# Bui The Trung - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/trungbui32.md b/docs/team/trungbui32.md new file mode 100644 index 0000000000..11c742b18f --- /dev/null +++ b/docs/team/trungbui32.md @@ -0,0 +1,73 @@ +# 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 +- **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) and + ListComponentCommand [#83](https://github.com/AY2425S1-CS2113-W13-4/tp/pull/83) + - 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/src/main/java/tutorlink/TutorLink.java b/src/main/java/tutorlink/TutorLink.java index cec0abce5c..ce30f4da38 100644 --- a/src/main/java/tutorlink/TutorLink.java +++ b/src/main/java/tutorlink/TutorLink.java @@ -94,20 +94,21 @@ private static void setupAllLists() throws IOException, StorageOperationExceptio 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(discardedStudents, "Discarded student data:"); - ui.displayDiscardedEntries(discardedComponents, "Discarded component data:"); ui.displayDiscardedEntries(discardedGrades, "Discarded grade data:"); appState = new AppState(initialStudentList, initialGradeList, initialComponentList); + appState.updateAllStudentPercentageScores(); + } private static void saveAllLists() throws IOException { diff --git a/src/main/java/tutorlink/appstate/AppState.java b/src/main/java/tutorlink/appstate/AppState.java index 57bc28ca3e..788eb30e91 100644 --- a/src/main/java/tutorlink/appstate/AppState.java +++ b/src/main/java/tutorlink/appstate/AppState.java @@ -27,4 +27,12 @@ public AppState(ArrayList 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/command/AddComponentCommand.java b/src/main/java/tutorlink/command/AddComponentCommand.java index 42eaa8a422..94c08fc884 100644 --- a/src/main/java/tutorlink/command/AddComponentCommand.java +++ b/src/main/java/tutorlink/command/AddComponentCommand.java @@ -10,65 +10,135 @@ 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 int MAX_WEIGHT = 100; + /** + * 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) { throw new IllegalValueException(Commons.ERROR_NULL); } + } - int weightage = convertWeightageToValidInt(weightageNumber); - - if((weightage + Component.totalWeight) > MAX_WEIGHT) { - throw new InvalidWeightingException(String.format(Commons.ERROR_INVALID_TOTAL_WEIGHTING, - Component.totalWeight + weightage)); - } else { - Component.totalWeight += weightage; + /** + * 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)); } - double maxScore = convertMaxScoreToValidDouble(maxScoreNumber); - appState.components.addComponent(new Component(componentName, maxScore, weightage)); - return new CommandResult(String.format(Commons.ADD_COMPONENT_SUCCESS, - componentName, weightageNumber, maxScoreNumber)); + return weightage; } - private static int convertWeightageToValidInt(String weightageNumber) { + /** + * 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); + 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); } } - private static double convertMaxScoreToValidDouble(String maxScoreNumber) { + /** + * 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); } - 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 index d23b57555e..40eccdee3d 100644 --- a/src/main/java/tutorlink/command/AddGradeCommand.java +++ b/src/main/java/tutorlink/command/AddGradeCommand.java @@ -21,102 +21,166 @@ 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 { + 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]); - if (matricNumber == null || componentDescription == null || scoreNumber == null) { - throw new IllegalValueException(Commons.ERROR_NULL); - } - 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); - } - Component component = findComponentFromComponents(appstate, componentDescription); + validateArguments(matricNumber, componentDescription, scoreNumber); - assert component != null : "Component object should not be null after this point"; + matricNumber = formatMatricNumber(matricNumber); - Student student = findStudentFromStudents(appstate, matricNumber); + Component component = findComponent(appState, componentDescription); + Student student = findStudent(appState, matricNumber); - assert student != null : "Student object should not be null after this point"; - - //Convert scoreNumber to double - try { - double score = convertScoreToValidDouble(scoreNumber, component); + double score = parseScore(scoreNumber, component); - //create a new grade object - Grade grade = new Grade(component, student, score); + addGradeAndCalculateGPA(appState, component, student, score); - appstate.grades.addGrade(grade); - - double newGPA = appstate.grades.calculateStudentGPA( - student.getMatricNumber(), - appstate.components - ); - - student.setGpa(newGPA); - - } catch (NumberFormatException e) { - throw new IllegalValueException(Commons.ERROR_INVALID_SCORE); - } - - return new CommandResult(String.format(Commons.ADD_GRADE_SUCCESS, scoreNumber, componentDescription, - matricNumber)); + return new CommandResult( + String.format(Commons.ADD_GRADE_SUCCESS, scoreNumber, componentDescription, matricNumber)); } - private static double convertScoreToValidDouble(String scoreNumber, Component component) + /** + * 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 { - double score = Double.parseDouble(scoreNumber); + if (matricNumber == null || componentDescription == null || scoreNumber == null) { + throw new IllegalValueException(Commons.ERROR_NULL); + } + } - if (score < 0.0 || score > component.getMaxScore()) { - throw new IllegalValueException(Commons.ERROR_INVALID_SCORE); + /** + * 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 score; + return matricNumber; } - private static Student findStudentFromStudents(AppState appstate, String matricNumber) - throws StudentNotFoundException { - //Get Student student object using String matricNumber - StudentList studentFilteredList = appstate.students.findStudentByMatricNumber(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)); + } + } - Student student; + /** + * 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) { - student = studentFilteredList.getStudentArrayList().get(0); + return studentFilteredList.getStudentArrayList().get(0); } else if (studentFilteredList.size() == 0) { throw new StudentNotFoundException(String.format(STUDENT_NOT_FOUND, matricNumber)); } else { - String errorMessage = String.format(Commons.ERROR_DUPLICATE_STUDENT, matricNumber); - throw new DuplicateMatricNumberException(errorMessage); + throw new DuplicateMatricNumberException(String.format(Commons.ERROR_DUPLICATE_STUDENT, matricNumber)); } - return student; } - private static Component findComponentFromComponents(AppState appstate, String componentDescription) - throws DuplicateComponentException { - //Get component object using String componentDescription - ComponentList componentFilteredList = appstate.components.findComponent(componentDescription); - Component component; - if (componentFilteredList.size() == 1) { - component = componentFilteredList.getComponentArrayList().get(0); - } else if (componentFilteredList.size() == 0) { - throw new ComponentNotFoundException(String.format(Commons.ERROR_COMPONENT_NOT_FOUND, - componentDescription)); - } else { - String errorMessage = String.format(Commons.ERROR_DUPLICATE_COMPONENT, componentDescription); - throw new DuplicateComponentException(errorMessage); + /** + * 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); } - return component; } + /** + * 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/DeleteComponentCommand.java b/src/main/java/tutorlink/command/DeleteComponentCommand.java index 23c32b0f3f..254dbce409 100644 --- a/src/main/java/tutorlink/command/DeleteComponentCommand.java +++ b/src/main/java/tutorlink/command/DeleteComponentCommand.java @@ -4,7 +4,6 @@ import tutorlink.appstate.AppState; import tutorlink.commons.Commons; import tutorlink.exceptions.ComponentNotFoundException; -import tutorlink.exceptions.DuplicateComponentException; import tutorlink.exceptions.IllegalValueException; import tutorlink.exceptions.TutorLinkException; import tutorlink.lists.ComponentList; @@ -23,10 +22,10 @@ public CommandResult execute(AppState appState, HashMap hashmap) ComponentList componentsToDelete = appState.components.findComponent(componentName); if(componentsToDelete.size() == 0) { throw new ComponentNotFoundException(String.format(Commons.ERROR_COMPONENT_NOT_FOUND, componentName)); - } else if (componentsToDelete.size() > 1) { - throw new DuplicateComponentException(Commons.ERROR_DUPLICATE_COMPONENT); } appState.components.deleteComponent(componentsToDelete.getComponentArrayList().get(0)); + appState.grades.deleteGradesByComponent(componentName); + appState.updateAllStudentPercentageScores(); return new CommandResult(String.format(Commons.DELETE_COMPONENT_SUCCESS, componentName)); } diff --git a/src/main/java/tutorlink/command/DeleteGradeCommand.java b/src/main/java/tutorlink/command/DeleteGradeCommand.java index 926fb7bb66..656552a91e 100644 --- a/src/main/java/tutorlink/command/DeleteGradeCommand.java +++ b/src/main/java/tutorlink/command/DeleteGradeCommand.java @@ -16,25 +16,80 @@ 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) { throw new IllegalValueException(Commons.ERROR_NULL); } + } + + /** + * 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); } - //Check number of students with matricNumber + 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) { @@ -44,17 +99,37 @@ public CommandResult execute(AppState appState, HashMap hashmap) throw new DuplicateMatricNumberException(errorMessage); } - //Attempt to delete grade - appState.grades.deleteGrade(matricNumber, componentDescription); + return filteredList.getStudentArrayList().get(0); + } - // Update student GPA - Student student = filteredList.getStudentArrayList().get(0); - double newGPA = appState.grades.calculateStudentGPA(matricNumber, appState.components); - student.setGpa(newGPA); + /** + * 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); + } - return new CommandResult(String.format(Commons.DELETE_GRADE_SUCCESS, componentDescription, matricNumber)); + /** + * 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/InvalidCommand.java b/src/main/java/tutorlink/command/InvalidCommand.java index 1d182c5cf9..130dc0ded8 100644 --- a/src/main/java/tutorlink/command/InvalidCommand.java +++ b/src/main/java/tutorlink/command/InvalidCommand.java @@ -8,13 +8,32 @@ 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 { + 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 index 2197459680..f9594e8eb9 100644 --- a/src/main/java/tutorlink/command/ListComponentCommand.java +++ b/src/main/java/tutorlink/command/ListComponentCommand.java @@ -5,15 +5,36 @@ 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 index 0b77b78d13..92e1a7d2ff 100644 --- a/src/main/java/tutorlink/command/ListGradeCommand.java +++ b/src/main/java/tutorlink/command/ListGradeCommand.java @@ -13,6 +13,10 @@ 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"; @@ -20,6 +24,15 @@ public class ListGradeCommand extends Command { 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(); @@ -30,7 +43,7 @@ public CommandResult execute(AppState appState, HashMap hashMap) String matricNumber = hashMap.get("i/"); - // If a specific student is requested + // If a specific student's grades are requested if (matricNumber != null) { matricNumber = matricNumber.toUpperCase(); String finalMatricNumber = matricNumber; @@ -54,27 +67,46 @@ public CommandResult execute(AppState appState, HashMap hashMap) 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())); - // Sort grades by component name and display each with numbering 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\n", gradeIndex++, grade.getComponent().getName(), grade.getScore())); + String.format("%d: %-15s: %.2f / %.2f\n", + gradeIndex++, + grade.getComponent().getName(), + grade.getScore(), + grade.getComponent().getMaxScore() + )); } - // Calculate and display the GPA (final grade) - double gpa = appState.grades.calculateStudentGPA(student.getMatricNumber(), appState.components); - output.append(String.format("\nFinal GPA: %.2f\n", gpa)); + 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"); @@ -89,9 +121,8 @@ private CommandResult generateAllGradesReport(ArrayList grades, AppState 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())); + String.format("%d: %s (%s):\n", studentIndex++, student.getName(), student.getMatricNumber())); - // Grade numbering for each student's grades int gradeIndex = 1; for (Grade grade : entry.getValue().stream() .sorted(Comparator.comparing(g -> g.getComponent().getName())) @@ -101,17 +132,21 @@ private CommandResult generateAllGradesReport(ArrayList grades, AppState grade.getComponent().getName(), grade.getScore())); } - // Calculate and display the GPA for the student - double gpa = appState.grades.calculateStudentGPA(student.getMatricNumber(), appState.components); - output.append(String.format(" Final GPA: %.2f\n\n", gpa)); + 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 index eb0b8bae61..e528dbd00b 100644 --- a/src/main/java/tutorlink/command/ListStudentCommand.java +++ b/src/main/java/tutorlink/command/ListStudentCommand.java @@ -1,20 +1,39 @@ 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..841b58157a --- /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(" n/ 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 index 3bde9e315c..7fe562d0a1 100644 --- a/src/main/java/tutorlink/commons/Commons.java +++ b/src/main/java/tutorlink/commons/Commons.java @@ -8,8 +8,8 @@ public class Commons { //Input Validation public static final String MATRIC_NUMBER_REGEX = "A\\d{7}[A-Z]"; - public static final String ERROR_ILLEGAL_MATRIC_NUMBER = "Error! Ensure matric " + - "number is of the form A\\d{7}[A-Z] (case insensitive)"; + 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 = @@ -23,20 +23,23 @@ public class Commons { 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 double that is more than or equal to 0, and not exceed the max 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 more than or equal to 0!"; //@@author RCPilot1604 - public static final String ERROR_INVALID_TOTAL_WEIGHTING = "Error! Total weighting must add up to 100%%.\n" + + public static final String ERROR_INVALID_TOTAL_WEIGHTING = "Error! Total weighting must not exceed 100%%.\n" + "Current weighting (after addition): %s%%"; //Invalid diff --git a/src/main/java/tutorlink/component/Component.java b/src/main/java/tutorlink/component/Component.java index eb2b5dfe4f..40236a1f12 100644 --- a/src/main/java/tutorlink/component/Component.java +++ b/src/main/java/tutorlink/component/Component.java @@ -2,7 +2,6 @@ //@@author TrungBui32 public class Component { - public static int totalWeight = 0; private String name; private double maxScore; private int weight; @@ -25,10 +24,18 @@ 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().equals(this.getName()); + return comp.getName().equalsIgnoreCase(this.getName()); } return false; } diff --git a/src/main/java/tutorlink/grade/Grade.java b/src/main/java/tutorlink/grade/Grade.java index 43a89fce3e..fab1c37f3e 100644 --- a/src/main/java/tutorlink/grade/Grade.java +++ b/src/main/java/tutorlink/grade/Grade.java @@ -3,29 +3,66 @@ 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) { @@ -35,14 +72,30 @@ public boolean equals(Object obj) { 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 diff --git a/src/main/java/tutorlink/lists/ComponentList.java b/src/main/java/tutorlink/lists/ComponentList.java index aeb6705a23..6df8a690aa 100644 --- a/src/main/java/tutorlink/lists/ComponentList.java +++ b/src/main/java/tutorlink/lists/ComponentList.java @@ -9,7 +9,8 @@ import java.util.stream.IntStream; /** - * Represents a list of components. + * 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!"; @@ -17,14 +18,29 @@ public class ComponentList { 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 @@ -37,6 +53,12 @@ public ComponentList findComponent(String name) throws ComponentNotFoundExceptio 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)) { @@ -46,6 +68,12 @@ public void addComponent(Component component) throws DuplicateComponentException 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)) { @@ -56,22 +84,51 @@ public void deleteComponent(Component component) throws ComponentNotFoundExcepti 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")); + .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 index 77badcd654..334e7a3727 100644 --- a/src/main/java/tutorlink/lists/GradeList.java +++ b/src/main/java/tutorlink/lists/GradeList.java @@ -55,10 +55,9 @@ public void deleteGradesByMatric(String matricNumber) { } public void deleteGradesByComponent(String componentDescription) { - componentDescription = componentDescription.toUpperCase(); ArrayList gradesToDelete = new ArrayList<>(); for (Grade grade : gradeArrayList) { - if (grade.getComponent().getName().toUpperCase().equals(componentDescription.toUpperCase())) { + if (grade.getComponent().getName().equalsIgnoreCase(componentDescription)) { gradesToDelete.add(grade); } } @@ -97,7 +96,7 @@ public GradeList findGrade(String matricNumber, String componentDescription) thr } //@@author - public double calculateStudentGPA(String matricNumber, ComponentList componentList) { + public double calculateStudentPercentageScore(String matricNumber, ComponentList componentList) { ArrayList studentGrades = gradeArrayList .stream() .filter(grade -> grade.getStudent().getMatricNumber().equals(matricNumber.toUpperCase())) @@ -107,11 +106,8 @@ public double calculateStudentGPA(String matricNumber, ComponentList componentLi return 0; } - double totalWeighting = componentList - .getComponentArrayList() - .stream() - .mapToDouble(c -> c.getWeight()) - .sum(); + int totalWeighting = componentList.getTotalWeighting(); + if(totalWeighting == 0) { return 0; @@ -124,7 +120,7 @@ public double calculateStudentGPA(String matricNumber, ComponentList componentLi / grade.getComponent().getMaxScore() * grade.getComponent().getWeight() ) - .sum() / totalWeighting; + .sum() / totalWeighting * 100; } public ArrayList getGradeArrayList() { diff --git a/src/main/java/tutorlink/parser/Parser.java b/src/main/java/tutorlink/parser/Parser.java index 44be2f74c8..6d4fa2cccd 100644 --- a/src/main/java/tutorlink/parser/Parser.java +++ b/src/main/java/tutorlink/parser/Parser.java @@ -13,37 +13,59 @@ import tutorlink.command.ListStudentCommand; import tutorlink.command.AddGradeCommand; import tutorlink.command.AddComponentCommand; - +import tutorlink.command.UpdateComponentCommand; +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.split("\\s+"); - return words[0]; //return the first word + 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(); // Calls delete command handling method + return new AddStudentCommand(); case DeleteStudentCommand.COMMAND_WORD: - return new DeleteStudentCommand(); // Calls add command handling method + return new DeleteStudentCommand(); case FindStudentCommand.COMMAND_WORD: - return new FindStudentCommand(); // Lists all students + return new FindStudentCommand(); case ListStudentCommand.COMMAND_WORD: - return new ListStudentCommand(); // Lists all students + return new ListStudentCommand(); case AddGradeCommand.COMMAND_WORD: return new AddGradeCommand(); @@ -63,24 +85,27 @@ public Command getCommand(String line) { case ListGradeCommand.COMMAND_WORD: return new ListGradeCommand(); + case UpdateComponentCommand.COMMAND_WORD: + return new UpdateComponentCommand(); + case ExitCommand.COMMAND_WORD: - return new ExitCommand(); // Lists all students + return new ExitCommand(); 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 HashMap where keys are the prefixes (e.g., "n/", "i/") and the values are the corresponding arguments + * @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) { + public HashMap getArguments(String[] argumentPrefixes, String line) throws IllegalValueException { HashMap arguments = new HashMap<>(); if (argumentPrefixes == null) { @@ -99,11 +124,16 @@ public HashMap getArguments(String[] argumentPrefixes, String li 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 } diff --git a/src/main/java/tutorlink/result/CommandResult.java b/src/main/java/tutorlink/result/CommandResult.java index 40f5b8c4c9..ff103762c4 100644 --- a/src/main/java/tutorlink/result/CommandResult.java +++ b/src/main/java/tutorlink/result/CommandResult.java @@ -1,5 +1,8 @@ package tutorlink.result; +/** + * Represents the result of a command execution. + */ public class CommandResult { private static final String LINE_SEPARATOR = "\n\t"; @@ -10,6 +13,11 @@ 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 index 03c12782a7..5c80e1825d 100644 --- a/src/main/java/tutorlink/storage/ComponentStorage.java +++ b/src/main/java/tutorlink/storage/ComponentStorage.java @@ -8,17 +8,36 @@ 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 { + + /** + * 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); + Component newComponent = getComponentFromFileLine(fileScanner.nextLine(), components, totalWeight); + totalWeight += newComponent.getWeight(); components.add(newComponent); } catch (InvalidDataFileLineException e) { discardedEntries.add(e.getMessage()); @@ -27,6 +46,14 @@ public ArrayList loadComponentList() throws IOException { 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) { @@ -35,23 +62,46 @@ public void saveComponentList(ArrayList components) throws IOExceptio fileWriter.close(); } - private Component getComponentFromFileLine(String fileLine, ArrayList components) + /** + * 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 { - String name = stringParts[0]; - double maxScore = Double.parseDouble(stringParts[1]); - int weight = Integer.parseInt(stringParts[2]); - Component newComponent = new Component(name, maxScore, weight); - if (components.contains(newComponent)) { - throw new InvalidDataFileLineException(fileLine); - } - return newComponent; + 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); + 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(); diff --git a/src/main/java/tutorlink/storage/GradeStorage.java b/src/main/java/tutorlink/storage/GradeStorage.java index a44540bf05..0365820a65 100644 --- a/src/main/java/tutorlink/storage/GradeStorage.java +++ b/src/main/java/tutorlink/storage/GradeStorage.java @@ -10,23 +10,40 @@ 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; + /** + * 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. + */ public GradeStorage(String filePath, ArrayList componentList, ArrayList studentList) { super(filePath); this.componentList = componentList; this.studentList = studentList; } - public ArrayList loadGradeList() - throws IOException { + /** + * 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. + */ + public ArrayList loadGradeList() throws IOException { ArrayList grades = new ArrayList<>(); Scanner fileScanner = new Scanner(path); while (fileScanner.hasNext()) { try { - grades.add(getGradeFromFileLine(fileScanner.nextLine())); + Grade newGrade = getGradeFromFileLine(fileScanner.nextLine(), grades); + grades.add(newGrade); } catch (InvalidDataFileLineException e) { discardedEntries.add(e.getMessage()); } @@ -34,6 +51,12 @@ public ArrayList loadGradeList() return grades; } + /** + * 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. + */ public void saveGradeList(ArrayList grades) throws IOException { FileWriter fileWriter = new FileWriter(path.toFile()); for (Grade grade : grades) { @@ -42,14 +65,28 @@ public void saveGradeList(ArrayList grades) throws IOException { fileWriter.close(); } - private Grade getGradeFromFileLine(String fileLine) throws InvalidDataFileLineException { + /** + * 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. + */ + private Grade getGradeFromFileLine(String fileLine, ArrayList grades) + throws InvalidDataFileLineException { + String componentName; + String matricNumber; + double score; String[] stringParts = fileLine.split(READ_DELIMITER); - if (stringParts.length != 3) { + + try { + componentName = stringParts[0].strip(); + matricNumber = stringParts[1].strip(); + score = Double.parseDouble(stringParts[2].strip()); + } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) { throw new InvalidDataFileLineException(fileLine); } - String componentName = stringParts[0]; - String matricNumber = stringParts[1]; - double score = Double.parseDouble(stringParts[2]); Component selectedComp = null; for (Component comp : componentList) { @@ -67,13 +104,24 @@ private Grade getGradeFromFileLine(String fileLine) throws InvalidDataFileLineEx } } - if (selectedComp != null && selectedStudent != null) { - return new Grade(selectedComp, selectedStudent, score); - } else { + 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; } + /** + * 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. + */ private String getFileInputForGrade(Grade grade) { String componentName = grade.getComponent().getName(); String matricNumber = grade.getStudent().getMatricNumber(); diff --git a/src/main/java/tutorlink/storage/Storage.java b/src/main/java/tutorlink/storage/Storage.java index 87c9888d5e..7f1882bf4a 100644 --- a/src/main/java/tutorlink/storage/Storage.java +++ b/src/main/java/tutorlink/storage/Storage.java @@ -15,6 +15,13 @@ public class Storage { 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<>(); diff --git a/src/main/java/tutorlink/storage/StudentStorage.java b/src/main/java/tutorlink/storage/StudentStorage.java index 8576d51230..08e4cd9631 100644 --- a/src/main/java/tutorlink/storage/StudentStorage.java +++ b/src/main/java/tutorlink/storage/StudentStorage.java @@ -1,5 +1,6 @@ package tutorlink.storage; +import tutorlink.commons.Commons; import tutorlink.exceptions.InvalidDataFileLineException; import tutorlink.student.Student; @@ -7,7 +8,13 @@ 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); @@ -39,8 +46,14 @@ private Student getStudentFromFileLine(String fileLine, ArrayList stude throws InvalidDataFileLineException { String[] stringParts = fileLine.split(READ_DELIMITER); try { - String matricNumber = stringParts[0]; - String name = stringParts[1]; + 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); diff --git a/src/main/java/tutorlink/student/Student.java b/src/main/java/tutorlink/student/Student.java index 17b3c89ff8..33bf3e4d06 100644 --- a/src/main/java/tutorlink/student/Student.java +++ b/src/main/java/tutorlink/student/Student.java @@ -3,12 +3,25 @@ public class Student { private String matricNumber; private String name; - private double gpa; + private double percentageScore; public Student(String matricNumber, String name) { this.name = name; this.matricNumber = matricNumber.toUpperCase(); - this.gpa = 0.0; + 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() { @@ -19,17 +32,18 @@ public String getMatricNumber() { return matricNumber; } - public double getGpa() { - return gpa; + public double getPercentageScore() { + return percentageScore; } - public void setGpa(double gpa) { - this.gpa = gpa; + public void setPercentageScore(double percentageScore) { + this.percentageScore = percentageScore; } @Override public String toString() { - return this.name + " (matric no: " + this.matricNumber + ", GPA: " + this.gpa + ")"; + return this.name + " (matric no: " + this.matricNumber + ", percentage score: " + + String.format("%.2f", this.percentageScore) + ")"; } @Override @@ -38,6 +52,6 @@ public boolean equals(Object obj) { return false; } Student s = (Student) obj; - return this.matricNumber.equals(s.getMatricNumber()); + return this.matricNumber.equalsIgnoreCase(s.getMatricNumber()); } } diff --git a/src/main/java/tutorlink/ui/Ui.java b/src/main/java/tutorlink/ui/Ui.java index 3afddac24c..fef3191c6f 100644 --- a/src/main/java/tutorlink/ui/Ui.java +++ b/src/main/java/tutorlink/ui/Ui.java @@ -9,17 +9,59 @@ public class Ui { private Scanner in = new Scanner(System.in); - private final String logo = "___________ __ .____ .__ __\n" + private final String LOGO = "___________ __ .____ .__ __\n" + "\\__ ___/_ ___/ |_ ___________| | |__| ____ | | __\n" + " | | | | \\ __\\/ _ \\_ __ \\ | | |/ \\| |/ /\n" + " | | | | /| | ( <_> ) | \\/ |___| | | \\ <\n" + " |____| |____/ |__| \\____/|__| |_______ \\__|___| /__|_ \\\n" + " \\/ \\/ \\/\n"; - private final String halfBreakLine = + private final String HALF_BREAK_LINE = "-------------------------"; - private final String fullBreakLine = + private final String FULL_BREAK_LINE = "-------------------------------------------------------------"; + private 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 + -------------------------------------------------------------"""; + public Ui() { } @@ -28,30 +70,34 @@ public String getUserInput() { } public void displayWelcomeMessage() { - System.out.println(fullBreakLine); - System.out.println(logo); - System.out.println(fullBreakLine); + 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(fullBreakLine); + System.out.println(FULL_BREAK_LINE); + } + + public void displayHelpMessage() { + System.out.println(HELP_MESSAGE); } public void displayResult(CommandResult result) { - System.out.println(halfBreakLine + " Result " + halfBreakLine); + System.out.println(HALF_BREAK_LINE + " Result " + HALF_BREAK_LINE); System.out.println(result.toString()); - System.out.println(fullBreakLine); + System.out.println(FULL_BREAK_LINE); } public void displayException(TutorLinkException error) { - System.out.println(halfBreakLine + " Error " + halfBreakLine); + System.out.println(HALF_BREAK_LINE + " Error " + HALF_BREAK_LINE); System.out.println(error.getMessage()); - System.out.println(fullBreakLine); + System.out.println(FULL_BREAK_LINE); } public void displayDiscardedEntries(ArrayList discardedEntries, String header) { if (discardedEntries.isEmpty()) { return; } - System.out.println(fullBreakLine); + System.out.println(FULL_BREAK_LINE); System.out.println(header); for (String entry : discardedEntries) { System.out.println(" " + entry); diff --git a/src/test/java/tutorlink/command/AddComponentCommandTest.java b/src/test/java/tutorlink/command/AddComponentCommandTest.java index 5618121420..8bcb677890 100644 --- a/src/test/java/tutorlink/command/AddComponentCommandTest.java +++ b/src/test/java/tutorlink/command/AddComponentCommandTest.java @@ -5,8 +5,13 @@ 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; @@ -32,29 +37,34 @@ 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 parameter 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 parameter passed!", exception.getMessage()); + assertEquals(initialWeighting, appState.components.getTotalWeighting()); } @Test @@ -63,11 +73,12 @@ void execute_extraArgument_componentAddedSuccessfully() { 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 @@ -75,11 +86,25 @@ 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 @@ -87,23 +112,25 @@ 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_weightageNotDouble_throwsIllegalValueException() { + 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 @@ -111,11 +138,95 @@ 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); } } //@@author diff --git a/src/test/java/tutorlink/command/DeleteComponentCommandTest.java b/src/test/java/tutorlink/command/DeleteComponentCommandTest.java index 136d2cee08..4024730e20 100644 --- a/src/test/java/tutorlink/command/DeleteComponentCommandTest.java +++ b/src/test/java/tutorlink/command/DeleteComponentCommandTest.java @@ -8,7 +8,9 @@ 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; @@ -25,46 +27,66 @@ void setup() { 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 = new HashMap<>(); arguments.put("c/", "finals"); + int initialWeighting = appState.components.getTotalWeighting(); result = command.execute(appState, arguments); assertNotNull(result); assertEquals(appState.components.size(), 2); - try { - appState.components.findComponent("finals"); - } catch (ComponentNotFoundException e) { - assertEquals(e.getMessage(), "Error! Component finals does not exist in the list!"); - } catch (Exception e) { - fail("Expected: ComponentNotFoundException, actual: " + e.getMessage()); - } + assertEquals(appState.components.getTotalWeighting(), initialWeighting - 40); } @Test void deleteComponent_notFound_fail() { - arguments = new HashMap<>(); 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() { - arguments = new HashMap<>(); + int initialWeighting = appState.components.getTotalWeighting(); try { command.execute(appState, arguments); } catch (IllegalValueException e) { assertEquals(e.getMessage(), Commons.ERROR_NULL); } 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/FindStudentCommandTest.java b/src/test/java/tutorlink/command/FindStudentCommandTest.java index a24509404e..2568f195bc 100644 --- a/src/test/java/tutorlink/command/FindStudentCommandTest.java +++ b/src/test/java/tutorlink/command/FindStudentCommandTest.java @@ -40,7 +40,7 @@ void execute_matric_one() { arguments.clear(); arguments.put("i/","A1234567X"); CommandResult result = findCommand.execute(appState,arguments); - assertEquals(result.toString(), "\t1: John (matric no: A1234567X, GPA: 0.0)"); + assertEquals(result.toString(), "\t1: John (matric no: A1234567X, percentage score: 0.00)"); } @Test @@ -61,8 +61,8 @@ 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, GPA: 0.0)" + "\n\t" - + "2: Jon (matric no: A2234567X, GPA: 0.0)"); + assertEquals(result.toString(), "\t1: John (matric no: A1234567X, percentage score: 0.00)" + "\n\t" + + "2: Jon (matric no: A2234567X, percentage score: 0.00)"); } @Test 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/lists/ComponentListTest.java b/src/test/java/tutorlink/lists/ComponentListTest.java index 8b9b9dc441..1e2e67463f 100644 --- a/src/test/java/tutorlink/lists/ComponentListTest.java +++ b/src/test/java/tutorlink/lists/ComponentListTest.java @@ -71,7 +71,7 @@ void findComponent_findNotInList_exceptionThrown() { } @Test - void toString_test() { + void componentList_toString_success() { componentList.addComponent(comp1); componentList.addComponent(comp3); String expectedResult = "\t1: Homework 1 (maxScore: 30.0, weight: 10%)\n" + diff --git a/src/test/java/tutorlink/lists/StudentListTest.java b/src/test/java/tutorlink/lists/StudentListTest.java index 4eef845d2a..36e9f63946 100644 --- a/src/test/java/tutorlink/lists/StudentListTest.java +++ b/src/test/java/tutorlink/lists/StudentListTest.java @@ -86,8 +86,8 @@ void find_name_notFound() { void testToString() throws DuplicateMatricNumberException { studentList.addStudent("A1234567B", "John Doe"); studentList.addStudent("A7654321B", "Jane Smith"); - String expectedString = "\t1: John Doe (matric no: A1234567B, GPA: 0.0)" - + "\n\t2: Jane Smith (matric no: A7654321B, GPA: 0.0)"; + 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()); } } diff --git a/src/test/java/tutorlink/parser/ParserTest.java b/src/test/java/tutorlink/parser/ParserTest.java index c49b1aaf22..a1027baecd 100644 --- a/src/test/java/tutorlink/parser/ParserTest.java +++ b/src/test/java/tutorlink/parser/ParserTest.java @@ -9,20 +9,22 @@ 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() { + 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/"}; + String[] args = new String[]{"i/, n/"}; var out1 = parser.getArguments(args, input); var out2 = parser.getArguments(args, input2); @@ -31,7 +33,7 @@ void parserTestInterchangingArgument() { } @Test - void getCommand_addStudentCommand_addStudentCommandReturned() { + void getCommand_addStudentCommand_addStudentCommandReturned() { Parser parser = new Parser(); String input = "add_student i/A1234567X n/John"; @@ -41,7 +43,7 @@ void getCommand_addStudentCommand_addStudentCommandReturned() { } @Test - void getCommand_deleteStudentCommand_deleteStudentCommandReturned() { + void getCommand_deleteStudentCommand_deleteStudentCommandReturned() { Parser parser = new Parser(); String input = "delete_student i/A1234567X"; Command actualCommand = parser.getCommand(input); @@ -50,7 +52,7 @@ void getCommand_deleteStudentCommand_deleteStudentCommandReturned() { } @Test - void getCommand_exitCommand_exitCommandReturned() { + void getCommand_exitCommand_exitCommandReturned() { Parser parser = new Parser(); String input = "bye"; Command actualCommand = parser.getCommand(input); @@ -59,7 +61,7 @@ void getCommand_exitCommand_exitCommandReturned() { } @Test - void getCommand_listStudentCommand_listStudentCommandReturned() { + void getCommand_listStudentCommand_listStudentCommandReturned() { Parser parser = new Parser(); String input = "list_student"; Command actualCommand = parser.getCommand(input); @@ -68,7 +70,7 @@ void getCommand_listStudentCommand_listStudentCommandReturned() { } @Test - void getCommand_findStudentCommand_findStudentCommandReturned() { + void getCommand_findStudentCommand_findStudentCommandReturned() { Parser parser = new Parser(); String input = "find_student i/A1234567X n/John Doe"; Command actualCommand = parser.getCommand(input); @@ -77,7 +79,7 @@ void getCommand_findStudentCommand_findStudentCommandReturned() { } @Test - void getCommand_invalidCommand_invalidCommandReturned() { + void getCommand_invalidCommand_invalidCommandReturned() { Parser parser = new Parser(); String input = "test_input"; Command actualCommand = parser.getCommand(input); @@ -86,14 +88,14 @@ void getCommand_invalidCommand_invalidCommandReturned() { } @Test - void getArguments_addStudentCommand_addStudentCommandHashMapReturned() { + 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); + HashMap arguments = parser.getArguments(argumentPrefixes, line); assertEquals(2, arguments.size()); assertEquals("A1234567X", arguments.get("i/")); // Check matriculation number @@ -101,27 +103,27 @@ void getArguments_addStudentCommand_addStudentCommandHashMapReturned() { } @Test - void getArguments_exitCommandNoArgumentPrefix_exitCommandHashMapReturned() { + 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); + HashMap arguments = parser.getArguments(argumentPrefixes, line); assertEquals(0, arguments.size()); } @Test - void getArguments_addStudentCommandExtraArguments_ignoreExtraTag() { + 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); + HashMap arguments = parser.getArguments(argumentPrefixes, line); assertEquals(2, arguments.size()); assertEquals("A1234567X", arguments.get("i/")); // Check matriculation number @@ -129,14 +131,14 @@ void getArguments_addStudentCommandExtraArguments_ignoreExtraTag() { } @Test - void getArguments_addStudentCommandSwappedArguments_addStudentCommandHashMapReturned() { + 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); + HashMap arguments = parser.getArguments(argumentPrefixes, line); assertEquals(2, arguments.size()); assertEquals("A1234567X", arguments.get("i/")); // Check matriculation number @@ -144,30 +146,48 @@ void getArguments_addStudentCommandSwappedArguments_addStudentCommandHashMapRet } @Test - void getArguments_addStudentCommandMissingArguments_hashMapWithOnlyGivenArgumentsReturned() { + 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); + HashMap arguments = parser.getArguments(argumentPrefixes, line); assertEquals(1, arguments.size()); assertEquals("John Doe", arguments.get("n/")); // Check } @Test - void getArguments_addStudentCommandInvalidArguments_emptyHashMapReturned() { + 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); + 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 index 0a17ce6cf2..26a11f696d 100644 --- a/src/test/java/tutorlink/student/StudentTest.java +++ b/src/test/java/tutorlink/student/StudentTest.java @@ -26,8 +26,8 @@ void testConstructor() { @Test void testToString() { - assertEquals("John Doe (matric no: A1234567B, GPA: 0.0)", student1.toString()); - assertEquals("Jane Smith (matric no: A7654321B, GPA: 0.0)", student2.toString()); + 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 diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 40c4713d7d..37b241b97f 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -16,152 +16,127 @@ Student Ethan Chua (A0276007H) added successfully! ------------------------- Result ------------------------- Student John Doe (A9999999Z) added successfully! ------------------------------------------------------------- -------------------------- Result ------------------------- -Student John Smith (A1111111B) added successfully! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Ensure matric number is of the form A\d{7}[A-Z] (case insensitive) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! -------------------------------------------------------------- ------------------------- Error ------------------------- -Error! Null parameter passed! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Null parameter passed! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +Error! Student with Matric Number, A1111111B, already exists in the list! ------------------------------------------------------------- ------------------------- Result ------------------------- - 1: Ethan Chua (matric no: A0276007H, GPA: 0.0) - 2: John Doe (matric no: A9999999Z, GPA: 0.0) - 3: John Smith (matric no: A1111111B, GPA: 0.0) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! + 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 ------------------------- -Student A0276007H successfully deleted -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Student (Matric Number A1234567X) not found -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Ensure matric number is of the form A\d{7}[A-Z] (case insensitive) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +Component Quiz 1 of weight 20%, with max score 10 added successfully! ------------------------------------------------------------- ------------------------- Result ------------------------- - 1: John Doe (matric no: A9999999Z, GPA: 0.0) - 2: John Smith (matric no: A1111111B, GPA: 0.0) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +Component Quiz 2 of weight 20%, with max score 20 added successfully! ------------------------------------------------------------- ------------------------- Result ------------------------- -Component Finals of weight 50%, with max score 100 added successfully! +Component Quiz 3 of weight 20%, with max score 20 added successfully! ------------------------------------------------------------- ------------------------- Result ------------------------- -Component Quiz of weight 5%, with max score 20 added successfully! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Component already exists in the list! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Component already exists in the list! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Weightage must be integer that is between 0 and 100! +Component Quiz 4 of weight 20%, with max score 20 added successfully! ------------------------------------------------------------- ------------------------- Result ------------------------- -Component Fail of weight 0%, with max score 2 added successfully! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Total weighting must add up to 100%. -Current weighting (after addition): 169% -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Weightage must be integer that is between 0 and 100! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Weightage must be integer that is between 0 and 100! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Weightage must be integer that is between 0 and 100! +Score of 10 added successfully to Quiz 1 for A0276007H! ------------------------------------------------------------- ------------------------- Result ------------------------- - 1: Finals (maxScore: 100.0, weight: 50%) - 2: Quiz (maxScore: 20.0, weight: 5%) - 3: Fail (maxScore: 2.0, weight: 0%) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +Score of 20 added successfully to Quiz 2 for A0276007H! ------------------------------------------------------------- ------------------------- Result ------------------------- -Component Quiz successfully deleted +Score of 20 added successfully to Quiz 3 for A0276007H! ------------------------------------------------------------- ------------------------- Result ------------------------- - 1: Finals (maxScore: 100.0, weight: 50%) - 2: Fail (maxScore: 2.0, weight: 0%) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Component Quiz does not exist in the list! +Score of 20 added successfully to Quiz 4 for A0276007H! ------------------------------------------------------------- ------------------------- Result ------------------------- -Student Ethan Chua (A0276007H) added successfully! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +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 ------------------------- - 1: John Doe (matric no: A9999999Z, GPA: 0.0) - 2: John Smith (matric no: A1111111B, GPA: 0.0) - 3: Ethan Chua (matric no: A0276007H, GPA: 0.0) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Component Quiz does not exist in the list! +Score of 5 added successfully to Quiz 1 for A9999999Z! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Component Quiz does not exist in the list! -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Ensure matric number is of the form A\d{7}[A-Z] (case insensitive) -------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Component Wuiz does not exist in the list! +------------------------- Result ------------------------- +Score of 7.5 added successfully to Quiz 2 for A9999999Z! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +------------------------- Result ------------------------- +Score of 10 added successfully to Quiz 3 for A9999999Z! ------------------------------------------------------------- ------------------------- Result ------------------------- -No grades have been recorded yet. +Score of 10 added successfully to Quiz 4 for A9999999Z! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +------------------------- 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% + + ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Ensure matric number is of the form A\d{7}[A-Z] (case insensitive) +------------------------- Result ------------------------- +Score of 0 added successfully to Quiz 1 for A1111111B! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Grade for component 123 for student A0276007H does not exist in the list! +------------------------- Result ------------------------- +Score of 0 added successfully to Quiz 2 for A1111111B! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Grade for component Quiz for student A0276007H does not exist in the list! +------------------------- Result ------------------------- +Score of 0 added successfully to Quiz 3 for A1111111B! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Grade for component Quiz for student A0276007H does not exist in the list! +------------------------- Result ------------------------- +Score of 0 added successfully to Quiz 4 for A1111111B! ------------------------------------------------------------- -------------------------- Error ------------------------- -Error! Invalid command given! +------------------------- 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..16efe0f58e --- /dev/null +++ b/text-ui-test/data_init/componentlist.txt @@ -0,0 +1 @@ +Attendance | 5 | 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..779f504ca5 --- /dev/null +++ b/text-ui-test/data_init/gradelist.txt @@ -0,0 +1 @@ +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..12c2ec5bbd --- /dev/null +++ b/text-ui-test/data_init/studentlist.txt @@ -0,0 +1 @@ +A1111111B | John Smith diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 8a9d7ee16e..dba3a72b19 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1,52 +1,24 @@ add_student i/A0276007H n/Ethan Chua add_student i/A9999999Z n/John Doe add_student i/a1111111b n/John Smith -add_student i/A n/failure - -add_student i/A0276007H -add_student n/Ethan Chua - list_student - -delete_student i/A0276007H -delete_student i/A1234567X - -delete_student i/4567890987654567890 - -list_student - -add_component c/Finals w/50 m/100 -add_component c/Quiz w/5 m/20 -add_component c/Quiz w/5 m/15 -add_component c/Quiz w/10 m/20 -add_component c/Fail w/101 m/1 -add_component c/Fail w/0 m/2 -add_component c/Fail w/99 m/20 -add_component c/123 w/hello m/byebye -add_component c/FailMax w/2147483647 m/2147483647 -add_component c/FailMax w/-1 m/-1 -list_component - -delete_component c/Quiz -list_component - -add_grade i/A0276007H c/Quiz s/19 -add_student i/A0276007H n/Ethan Chua - -list_student -add_grade i/A0276007H c/Quiz s/19 -add_grade i/A0276007H c/Quiz s/100 -add_grade i/A1 c/Quiz s/19 -add_grade i/A0276007H c/Wuiz s/19 - +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 - -delete_grade i/A2 c/Quiz -delete_grade i/A0276007H c/123 -delete_grade i/A0276007H c/Quiz -delete_grade i/A0276007H c/Quiz - bye - - - diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat old mode 100644 new mode 100755 index 66283a9095..4e664378b9 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -13,12 +13,15 @@ for /f "tokens=*" %%a in ( ) :: delete data directory from previous run if it exists -if exist "..\text-ui-test\data" ( - rmdir /s /q "..\text-ui-test\data" +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 83b6c187d1..f288746493 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -14,6 +14,9 @@ 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