diff --git a/.gitignore b/.gitignore index 2873e189e1..78170c854f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +/data/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..eedeb48756 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "java.configuration.updateBuildConfiguration": "disabled", + "java.debug.settings.onBuildFailureProceed": true, + "java.project.referencedLibraries": [ + { + "scope": "java", + "path": "src/main/resources" + } + ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index d5e548e85f..4a1d6c0dc3 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,8 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + // https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple + implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1' } test { @@ -28,12 +30,20 @@ test { } } +sourceSets { + main { + resources { + srcDirs = ['src/main/resources'] + } + } +} + application { - mainClass = "seedu.duke.Duke" + mainClass = "seedu.penus.Penus" } shadowJar { - archiveBaseName = "duke" + archiveBaseName = "penus" archiveClassifier = null } @@ -41,6 +51,10 @@ checkstyle { toolVersion = '10.2' } -run{ +run { standardInput = System.in + enableAssertions = true } +jar { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) +} \ No newline at end of file diff --git a/docs/.idea/.gitignore b/docs/.idea/.gitignore new file mode 100644 index 0000000000..26d33521af --- /dev/null +++ b/docs/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/docs/.idea/checkstyle-idea.xml b/docs/.idea/checkstyle-idea.xml new file mode 100644 index 0000000000..fc77fd2c9d --- /dev/null +++ b/docs/.idea/checkstyle-idea.xml @@ -0,0 +1,16 @@ + + + + 10.8.1 + JavaOnly + true + + + \ No newline at end of file diff --git a/docs/.idea/vcs.xml b/docs/.idea/vcs.xml new file mode 100644 index 0000000000..6c0b863585 --- /dev/null +++ b/docs/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..85cc3cef04 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,11 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:-------------------:|:---------------------------------------------:|:---------: +![](AboutUsmedia/jiunyuan.jpg) | Tay Jiun Yuan | [Github](https://github.com/tayjiunyuan) | [Portfolio](team/tayjiunyuan.md) +![](https://via.placeholder.com/100.png?text=Photo) | Chia Yu Xuan | [Github](https://github.com/chiayuxuan) | [Portfolio](team/chiayuxuan.md) +![](AboutUsmedia/ansenn.png) | Lee Jun Hui Ansenn | [Github](https://github.com/ansenn) | [Portfolio](team/ansenn.md) +![](AboutUsmedia/sriram.JPG) | Senthilkumar Sriram | [Github](https://github.com/sriram-senthilkr) | [Portfolio](team/sriram-senthilkr.md) +![](AboutUsmedia/benpic.jpg) | Benjamin Toh | [Github](https://github.com/bentohset) | [Portfolio](team/bentohset.md) + + \ No newline at end of file diff --git a/docs/AboutUsmedia/ansenn.png b/docs/AboutUsmedia/ansenn.png new file mode 100644 index 0000000000..43abba2613 Binary files /dev/null and b/docs/AboutUsmedia/ansenn.png differ diff --git a/docs/AboutUsmedia/benpic.jpg b/docs/AboutUsmedia/benpic.jpg new file mode 100644 index 0000000000..08b11aa98c Binary files /dev/null and b/docs/AboutUsmedia/benpic.jpg differ diff --git a/docs/AboutUsmedia/jiunyuan.jpg b/docs/AboutUsmedia/jiunyuan.jpg new file mode 100644 index 0000000000..7e23517f20 Binary files /dev/null and b/docs/AboutUsmedia/jiunyuan.jpg differ diff --git a/docs/AboutUsmedia/sriram.JPG b/docs/AboutUsmedia/sriram.JPG new file mode 100644 index 0000000000..4b3fd1aab0 Binary files /dev/null and b/docs/AboutUsmedia/sriram.JPG differ diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..4d9ff2c3bf 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,820 @@ # Developer Guide + +- [Acknowledgements](#acknowledgements) +- [Design](#design) + - [Architecture](#architecture) + - [UI Component](#ui-component) + - [Logic Component](#logic-component) + - [Model Component](#model-component) + - [Storage Component](#storage-component) + - [Common classes](#common-classes) +- [Implementation](#implementation) + - [Add Module](#add-module) + - [Remove Module](#remove-module) + - [Mark Module As Taken](#mark-module-as-taken) + - [List Modules](#list-modules) + - [Display Status](#display-status) + - [Get Module Details](#get-module-details) + - [Initialise User](#initialise-user) + - [Clear Modules](#clear-modules) + - [Save To Local Drive](#save-planner-to-local-drive) + - [[Proposed] Handle CS/CU Grade](#proposed-handle-cscu-modules) +- [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) + - [Launch](#launch) + - [Plan Command](#plan-command) + - [Remove Command](#remove-command) + - [Mark Command](#mark-command) + - [Loading Data](#loading-data) + ## Acknowledgements +References made from [AddressBook 2](https://github.com/se-edu/addressbook-level2) and [AddressBook 3](https://github.com/se-edu/addressbook-level3). -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} -## Design & implementation + +## Design -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Architecture +![ArchitectureDiagram](uml/diagrams/Architecture.png) -## Product scope -### Target user profile +The **Architecture Diagram** given above explains the high-level design of the App. + +Given below is a quick overview of main components and how they interact with each other. +#### Main components of the architecture +`Main` is responsible for: +- At app launch: Initializes the components in the correct sequence, and connects them up with each other with the correct methods. +- At shut down: Shuts down the components and invokes cleanup methods where necessary. + +`Commons` represents a collection of classes used by multiple other components and includes: +- `Exceptions`: handles error and extends PenusException +- `Utils`: utility static methods like NUSMods API and other string manipulation +- `Messages`: Logo, goodbye and welcome messages of the App + +The rest of the App consists of four components. + +- `UI`: The UI of the App. +- `Logic`: The command executor. +- `Model`: Holds the data of the App in memory. +- `Storage`: Reads data from, and writes data to, the hard disk. + + +### UI Component +The component is specified in `Ui.java` + +![UIClassDiagram](uml/diagrams/UiClass.png) + +The `UI` component: +- displays messages to the user by printing to the CLI. +- displays results from commands executed by the `LogicManager` class. + +### Logic Component +The component is specified in `logic` package and facilitated by `LogicManager.java` + +![LogicObjDiagram](uml/diagrams/LogicClass.png) + +How the `Logic` component works: +1. When `LogicManager` is called upon to execute a command, it uses the `Parser` class to parse the user command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `ModelManager` when it is executed (e.g. to add a module). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from Logic and printed into the CLI. + + +### Model Component +The component is specified in `model` package and facilitated by `ModelManager.java` + +![ModelObjDiagram](uml/diagrams/ModelClass.png) + +The `Model` component: +- stores the module list data i.e., all `Module` objects (which are contained in a `ModuleList` object). +- stores a `User` object that represents the user’s preferences (name and course). +- stores the core module details as a `HashMap` object, initialised by a .txt file with core modules for each course. +- does not depend on any of the other three components (as the Model represents data entities of the domain, they should make sense on their own without depending on other components) + + +### Storage Component +The component is specified in `storage` package and facilitated by `StorageManager.java` + +![StorageObjDiagram](uml/diagrams/StorageClass.png) + +The `Storage` component: +- can save both module list data and user data in .txt format, and read them back into corresponding objects. +- retrieves core module details from a prefilled .txt file formatted as a resource. +- depends on some classes in the `Model` component (because the `Storage` component’s job is to save/retrieve objects that belong to the `Model`) + + +### Common classes +Classes used by multiple components are in the `seedu.penus.commons` package. + + +## Implementation +### Add module +The Add Module feature allows users to add two types of modules (taken or planning) to the ModuleList using the commands `plan` or `taken`. The two types of modules are differentiated by its `Module()` overloaded constructor, accepting different type signatures. It is facilitated by `LogicManager` and extends the abstract class `Command`. + +Given below is an example usage scenario of the 2 types of modules and how the add module mechanism behaves at each step. + +**When a planned module is added:** + +**Step 1.** The user launches the application for the first time. The ModuleList will be initialised with the initial module list state if provided in `penus.txt`. + +**Step 2.** The user executes the `plan CS2113 y/1 s/2` command to plan the module CS2113 for year 1 and semester 2 to be added into the list. The `plan` command is parsed through `Parser` which returns a `PlanCommand()` object if a valid command is provided. The `PlanCommand()` constructs a `Module()` object with the overloaded constructor `Module()`. It is instantiated with the respective arguments which sets the `isTaken` parameter `false`, `moduleCode`, `year` and `semester`. + +**Step 3.** This is then executed by the `LogicManager` calling `execute()` and passes it to the `ModelManager` through the `addModule()` method. In `ModelManager`, the `Module()` object is passed to the `ModuleList()` where the module is added to the list. + +**Step 4.** Upon successful execution of all of the above, it is then passed back to `PlanCommand` where `CommandResult()` is constructed with the message to be printed to the user. + +**Step 5.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. + +The following sequence diagram shows how the `plan` command works: +![AddModuleSequenceDiagram](uml/diagrams/AddModSequence.png) + +**Similarly, for when a taken module is added:** + +**Step 1.** The user launches the application for the first time. The ModuleList will be initialised with the initial module list state if provided in `penus.txt`. + +**Step 2.** The user executes the `taken CS2113 y/1 s/2 g/A+` command to plan the module CS2113 for year 1 and semester 2 to be added into the list. The `taken` command is parsed through `Parser` which returns a `TakenCommand()` object if a valid command is provided. The `TakenCommand()` constructs a `Module()` object with the overloaded constructor `Module()`. It is instantiated with the respective arguments which sets the `isTaken` parameter `true`, `moduleCode`, `year`, `semester` and `grade`. + +**Step 3 - 6.** Identical to that of a `plan` command as mentioned above. + +_Design considerations:_ +**Aspect: How plan/taken executes** +- **Alternative 1 (current choice):** Have an overloaded Module() constructor which accepts both types of modules. The difference being the isTaken parameter. + - Pros: Easy to implement + - Cons: Less readability and harder to differentiate the two. +- **Alternative 2:** Have two classes, TakenModule and PlanModule, for the 2 types. + - Pros: More readability and easier differentiation. + - Cons: More complex. 2 classes inherited + checking the class type in queries + +
+ +### Remove module +The Remove Module feature allows users to remove a module from the ModuleList using the command `remove`. It is facilitated by `LogicManager` and extends the abstract class `Command`. The module code is given as the argument to remove that specific module. + +Given below is an example scenario of the remove command and how it behaves at each step. + +**Step 1.** The user executes the `remove CS2113` command, given that a module `CS2113` exists within the `ModuleList`, to remove the module. The `remove` command is parsed through `Parser` which returns a `RemoveCommand()` object if a valid command is provided. The `RemoveCommand()` stores the string `moduleCode` as its attribute. + +**Step 2.** The `RemoveCommand` is then executed by the `LogicManager` calling `execute()`. Then, the `ModuleList`, which is retrieved from the `ModelManager` through `getModuleList()`, is iterated through to find the index of a `Module` with the same corresponding `moduleCode` through `getModule().getCode()`. This index is assigned to a variable. + +**Step 3.** It is then passed to the `ModelManager`, along with the index, and executes the `removeModule()` method on the `ModuleList` object. The `ModuleList` subsequently executes the `remove(index)` method to remove the module from the list. + +**Step 4.** Upon successful execution of the above, it is then passed back to `RemoveCommand` where `CommandResult()` is constructed with the message to be printed to the user. + +**Step 5.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. + +The following sequence diagram shows how the `remove` command works: +![RemoveModuleSequenceDiagram](uml/diagrams/RemoveModSequence.png) + +
+ +### Mark module as taken +The Mark Module feature allows user to mark a plan module as a taken module, adding the grade using the command `mark`. It is facilitated by `LogicManager` and extends the abstract class `Command`. The module code and grade is given as the argument to convert that specific planned module to a taken module and add a grade. + +Given below is an example scenario of the mark command and how it behaves at each step. + +**Step 1.** The user executes the `mark CS2113 g/A+` command, given that a plan module `CS2113` exists within the `ModuleList`, to mark the module. The `mark` command is parsed through `Parser` which returns a `MarkCommand()` object if a valid command is provided. The `MarkCommand()` stores the string `moduleCode` and `grade` as its attributes. + +**Step 2.** The `MarkCommand` is then executed by the `LogicManager` calling `execute()`. Then, the `ModuleList`, which is retrieved from the `ModelManager` through `getModuleList()`, is iterated through to find the index of a `Module` with the same corresponding `moduleCode` through `getModule().getCode()`. This index is assigned to a variable. + +**Step 3.** It is then passed to the `ModelManager`, along with the index and grade, and executes the `markModule()` method. It retrieves the `Module` object from the `ModuleList` through the `getModule(index)` method. The `markTaken()` method is then called on this `Module` which sets the `isTaken` attribute as true and saves the grade. + +**Step 4.** Upon successful execution of the above, it is then passed back to `MarkCommand` where `CommandResult()` is constructed with the message to be printed to the user. + +**Step 5.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. + +The following sequence diagram shows how the `mark` command works: +![MarkModuleSequenceDiagram](uml/diagrams/MarkModSequence.png) + +
+ +### List modules +The List modules feature allows users to view their added modules, in a specified range using the command `list [FILTER]`. There are 3 ways of modules listing : + 1. List all modules in the planner + 2. List all modules in the planner for a specific year + 3. List all modules in the planner for a specific year and semester + +Given below is an example usage scenario for each type, and how the list modules mechanism behaves at each step. + +**When the year and semester are not specified:** + +**Step 1.** The user types in `list`, which will be taken in by the `getUserCommand()` method of the `Ui` class. +The input command will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` in the `Parser` class +which returns the main command, `ListCommand`, if a valid command is entered. +The `LogicManager` then calls `execute()` on `ListCommand`, which runs the main logic behind this command. +In this example, `list means that the user wants to list all modules in the planner for all semesters from Year 1 to 4. + +**Step 2.** `Parser` returns a `ListCommand()` object with no arguments. + +**Step 3.** In `CommandResult` of `ListCommand`, a `List messageArray` list, `Map>>` Hashmap and `List moduleList` ArrayList +are initialised. The `List` ArrayList is a ModuleList object, which contains all modules in the planner. +Then, all the modules in `moduleList` are copied to the Hashmap, along with all their details such as their Year, Semester and Grade. + +**Step 4.** In the `ListCommand` class, the `year` and `semester` have default values of 0. When `ListCommand` is called without any year and semester specifications, the year and semester values of 0 will be passed to `CommandResult`. +Then, a `List allModules` list will be initialised, and the `getAllMods` method is called to retrieve all modules to be stored in `allModules`. + +**Step 5.** In the `getAllMods` method, a `List messageList` is initialised. `messageList` is used to store the string inputs to be returned to `CommandResult`. +A `List modulesInYearSem` is initialised, which copies all module details from the Hashmap. + +**Step 6.** If `modulesInYearSem` is not empty, then `modulesInYearSem` will be iterated through to be appended to `messageList` as a String, and the `messageList` list will be returned to `CommandResult`. + +**Step 7.** In `CommandResult`, the returned `messageList` list will have its contents copied into an `allModules` list. This `allModules` list will have its contents appended to the `messageArray` of `CommandResult`. +Then, `getOverallCap` of `Grade` is called to retrieve the overall CAP for the user, and this string is appended to `msessageArray`. +Lastly, a new `CommandResult()` is constructed with the message to be printed to the user. + +**Step 8.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. + +Below is a sequence diagram of how the `list` command works: +![ListSequenceDiagram](uml/diagrams/ListSequence.png) + +**When the year is specified:** + +**Step 1.** The user types in `list y/1`, which will be taken in by the `getUserCommand()` method of the `Ui` class. +The input command will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` in the `Parser` class which returns the main command, `ListCommand`, if a valid command is entered. +The `LogicManager` then calls `execute()` on `ListCommand`, which runs the main logic behind this command. +In this example, `list y/1` means that the user wants to list all modules in the planner for Year 1, i.e. both semesters in Year 1. + +**Step 2.** `Parser` returns a `ListCommand()` object with the year that the user entered, as the argument. + +**Step 3.** In `CommandResult` of `ListCommand`, a `List messageArray` list, `Map>>` Hashmap and `List` ArrayList named `moduleList` +are initialised. The `List` ArrayList is a ModuleList object, which contains all modules in the planner. +Then, all the modules in `moduleList` are copied to the Hashmap, along with all their details such as their Year, Semester and Grade. + +**Step 4.** In the `ListCommand` class, the `year` and `semester` have default values of 0. When `ListCommand` is called with only the year specified, the year value that the user entered and the default semester value of 0 will be passed to `CommandResult`. +Then, a `List yearModules` list will be initialised, and the `getYearMods` method is called to retrieve all modules to be stored in `yearModules`. + +**Step 5.** In the `getYearMods` method, a `List messageList` is initialised. `messageList` is used to store the string inputs to be returned to `CommandResult`. +A `List modulesInYear` is initialised, which copies all module details from modules of the specified year, from the Hashmap. +For this example, `year` is *1* and thus all modules with `year` as *1* will be copied to `modulesInYear`. + +**Step 6.** If `modulesInYear` is not empty, then `modulesInYear` will be iterated through to be appended to `messageList` as a String, and the `messageList` list will be returned to `CommandResult`. + +**Step 7.** In `CommandResult`, the returned `messageList` list will have its contents copied into an `yearModules` list. This `yearModules` list will have its contents appended to the `messageArray` of `CommandResult`. +For each semester in the year, the CAP for that semester will be retrieved through `getSemCAP` of `Grade`. This information is appended to `messageArray` as a String. +Then, `getOverallCap` of `Grade` is called to retrieve the overall CAP for the user, and this string is appended to `msessageArray`. +Lastly, a new `CommandResult()` is constructed with the message to be printed to the user. + +**Step 8.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. + +**When the year and semester are specified:** + +**Step 1.** The user types in `list y/2 s/1`, which will be taken in by the `getUserCommand()` method of the `Ui` class. +The input command will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` in the `Parser` class which returns the main command, `ListCommand`, if a valid command is entered. +The `LogicManager` then calls `execute()` on `ListCommand`, which runs the main logic behind this command. +In this example, `list y/2 s/1` means that the user wants to list all modules in the planner for Year 2 Semester 1. + +**Step 2.** `Parser` returns a `ListCommand()` object with the year and semester that the user entered, as the arguments. + +**Step 3.** In `CommandResult` of `ListCommand`, a `List messageArray` list, `Map>>` Hashmap and `List` ArrayList named `moduleList` +are initialised. The `List` ArrayList is a ModuleList object, which contains all modules in the planner. +Then, all the modules in `moduleList` are copied to the Hashmap, along with all their details such as their Year, Semester and Grade. + +**Step 4.** In the `ListCommand` class, the `year` and `semester` have default values of 0. When `ListCommand` is called with both the year and semester specified, the year and semester values that the user entered will be passed to `CommandResult`. +Then, a `List semModules` list will be initialised, and the `getSemMods` method is called to retrieve all modules to be stored in `semModules`. + +**Step 5.** In the `getSemMods` method, a `List messageList` is initialised. `messageList` is used to store the string inputs to be returned to `CommandResult`. +A `List modulesInYearAndSem` is initialised, which copies all module details from modules of the specified year, from the Hashmap. +For this example, `year` is *2*, `semester` is *1*, and thus all modules with `year` as *2* and `semester` as *1* will be copied to `modulesInYearAndSem`. + +**Step 6.** If `modulesInYearAndSem` is not empty, then `modulesInYearAndSem` will be iterated through to be appended to `messageList` as a String, and the `messageList` list will be returned to `CommandResult`. + +**Step 7.** In `CommandResult`, the returned `messageList` list will have its contents copied into an `semModules` list. This `semModules` list will have its contents appended to the `messageArray` of `CommandResult`. +The CAP for this semester will be retrieved through `getSemCAP` of `Grade`. This information is appended to `messageArray` as a String. +Then, `getOverallCap` of `Grade` is called to retrieve the overall CAP for the user, and this string is appended to `msessageArray`. +Lastly, a new `CommandResult()` is constructed with the message to be printed to the user. + +**Step 8.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. + +
+ +### Display status +The Display Status feature `status` lists all the core modules in the user's course, and indicates which ones the user has or has +not taken. The feature also displays the total number of MCs the user has taken. + +Given below is an example of how `status` is called at each step. + +**Step 1.** The user types in `status`, which will be taken in by the `getUserCommand()` method of the `Ui` class. The input command +will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` which returns the main command, `StatusCommand`. +The `LogicManager` then calls `execute()` on `StatusCommand`, which runs the main logic behind this command. + +**Step 2.** When `execute()` is first called, `getUserName()` and `getUserCourse()` of the `ModelManager` class are called to return +the user's `name` and `course` and added to the message. + + +**Step 3.** The methods then calls `getTakenCoreModsList()`. `getTakenCoreModsList()` first calls the `ModelManager`'s `getCoreModsList()` which returns a hashmap with the course as the key and list of string of core module codes. In order to get +the core module codes of the user's course, the user's course is retrieved from by `ModelManager`'s `getUserCourse()`, at which the +attribute `course` is given by the user on initialization. An exception is thus triggered if the user calls `status()` +without initialising. By giving the key as the user's course, the list of core modules is retrieved. The list of core modules is then compared against +the user's `ModuleList`, retrieved by `ModelManager`'s `getModuleList()` and taken core module codes will be added into `takenCoreMods` list. Lastly, it calls +`getTakenGESS()`, `getTakenGEC()` and `getTakenGEN()` to check for the status of GE modules and adds their codes into `takenCoreMods` if taken. `takenCoreMods` is then returned. + +**Step 4.** `getUntakenCoreModsList()` is called, which undergoes a same process as `getTakenCoreModsList()` but with untaken core modules codes added into `untakenCoreMods` list. + +**Step 5.** The static method `getNumberOfMCsTaken()` is called, which retrieves each module's MC through the `Module Retriever`'s `getModuleCredit2223()` +and adds them together, returning the `totalMCsTaken` taken by the user. + +**Step 6.** The static method `getNumberOfCoreMcsTaken()` is called, which retrieves the list of taken core modules through `getTakenCoreModsList()`. It then retrieves each module's MC through the `Module Retriever`'s `getModuleCredit2223()` +and adds them together, returning the `coreMCsTaken` taken by the user. It then calculates `electiveMCsTaken` by subtracting `totalMCsTaken` with `coreMCsTaken`. + +**Step 7.** Each module code in `takenCoreMods` list and `untakenCoreMods` list is then passed into a method `moduleDetailsString()`, which returns +in the format of "`moduleCode` `moduleTitle` Mcs: `moduleMCs`" and added into the `message` string, formatted using `Stringbuilder`. +The `moduleTitle` and `moduleMCs` of each module is retrieved by calling static methods `getModuleCredit2223()` and `getTitle2223()` of util's `ModuleRetriever` class. + +**Step 8.** The `totalMCsTaken` , `coreMCsTaken` and `electiveMCsTaken` were then added into the message. + +**Step 9.** The message is then passed to the `LogicManager` as a `CommandResult`. The `LogicalManager` then calls for `UI`'s `printResult()` to display the status message to +the user. + +The following sequence diagram shows how the `status` command works: +![Status Sequence Diagram](uml/diagrams/StatusSequenceDiagram.png) + + +
+ +### Get module details +The details feature is facilitated by `ModuleRetriever`. It retrieves the module’s title, description, pre-requisites, modular credits, and if the module is SU-able. Additionally, it implements the following operations: +- `retrieveTitle()` - Retrieves the module’s Title. +- `retrieveDescription()` - Retrieves the module’s Description. +- `retrievePrerequisite()` - Retrieves the module’s Pre-Requisites. +- `retrieveModuleCredits()` - Retrieves the module’s total Modular Credits. +- `retrieveSUstatus()` - Retrieves if the module can be SU-ed. + +These operations are exposed in the `ModuleRetriever` class as `retrieveTitle()`, `retrieveDescription()`, `retrievePrerequisite()`, `retrieveModuleCredits()` and `retrieveSUstatus()` respectively. -{Describe the target user profile} +**Step 1.** The user launches the application for the first time. The API will not be called. + +**Step 2.** The user executes `details cs2113` command to retrieve the details about CS2113 module. The details +command calls `getDetails(moduleCode)`, passing the module code as the parameter. + +**Step 3.** The `getDetails` method first checks if the module code passed in is valid by calling the `isValidMod(module)` method. If the module code is valid and can be found on NUSMods, the rest of the steps will be followed. +Else, it will return an exception to the user that the module code is invalid, and will let them re-enter another +module code. + +**Step 4.** The `getDetails` method then calls a few methods – `retrieveTitle(module)`, `retrieveDescription(module)`, +`retrievePrerequisite(module)`, `retrieveModuleCredit(module)`, and `retrieveSUstatus(module)`. +Each of these methods (e.g. `retrieveTitle(module)`) will make call the `getData()` method, passing the module code as the parameter. + +**Step 5a.** The `getData()` method will create a `HttpURLConnection` to NUS API MODS website, redirecting to the +particular module’s data. It will then create a “GET” request, and parse the JSON in the API into a `JSONObject` and `JSONArray`. +The retrieved information will be stored as `moduleInfo` `JSONObject` in the `ModuleRetriever` class. + +- _Note:_ If a “GET” request fails, it will stop the Connection, and return the HTTP Response Code. + +- _Note:_ If an incorrect module was entered, the program will return an error, and ask the user to input the correct module code. + +**Step 5b.** Each retrieve method (e.g. `retrieveTitle()`) will retrieve the respective information from the +retrieved `JSONObject`, and return it as a `String`. + +**Step 6.** The `getDetails()` method will then store each retrieved information in a `String`, and format them for +display standards. It will then store each separated `String` into a `messagePacket` array, and pass it into the `Ui.printMessage()` function to be printed in the CLI. + +The following sequence diagram shows how the mechanism works: +![Details Sequence Diagram](uml/diagrams/DetailsSequence.png) + +_Design considerations:_ +**Aspect: How detail executes** +- **Alternative 1 (current choice):** Retrieves module details from the NUS API. + - Pros: Access to all the modules’ information + - Cons: Not usable when the device is offline. +- **Alternative 2:** Store all the module details on an offline .txt file, then retrieve it from there. + - Pros: Can be used offline. + - Cons: It is a tedious task to store all the modules’ details on a .txt file, and the file size will be very big. + + +
+ +### Initialise user +The initialisation feature `init` prompts the user to provide his/her `name` and `course`. +Given below is an example of how `init` is called at each step. + +**Step 1.** The user types in `init n/John c/4`, which will be taken in by the `getUserCommand()` method of the `Ui` class. The input command +will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` which returns the main command, `InitCommand` with John as the `name` and 4 as the `courseCode`. +The `LogicManager` then calls `execute()` on `InitCommand`, which runs the main logic behind this command. + +**Step 2.** When `execute()` is first called, the method calls `setUserName()` of the `ModelManager` class, which calls `setName()` of +the `User` class and assigns "John" to the `name` attribute of `User`. + +**Step 3.** Next, the `courseCode` is then put through a switch case to retrieve the `courseName` of the corresponding course. *Eg. `courseCode`: 4 -> `courseName`: Computer Engineering.* +`setUserCourse()` of the `ModelManager` class is then called, which calls the `setCourse()` method of `User` and assigns "Computer Engineering" to the `course` attribute of `User`. + +**Step 4.** The return message is then passed to the `LogicManager` as a `CommandResult`. The `LogicalManager` then calls for `UI`'s `printResult()` to display the return message to +the user. + +The following sequence diagram shows how the `init` command works: +![Init Sequence Diagram](uml/diagrams/InitSequenceDiagram.png) + + +
+ +### Calculate CAP +The CAP calculator feature is facilitated by `Grade`. It calculates the User's +CAP by Semester and by Overall for all `taken` modules. +This is implemented by the following methods: +- `calculateSemCAP()` +- `calculateOverallCAP()` +- `getSemCAP()` +- `getOverallCAP()` + + +Below is the sequence of steps which utilises the CAP mechanism: + +**Step 1.** You already initialised yourself as a new user. +The user adds 3 module as taken `CG2023 y/1 s/2 g/A+` and `CG2111A y/1 s/2 g/B+` +and `CG1111A y/1 s/1 g/A+`. + +**Step 2.** User enters a list command which comprises all three ways of module listing as stated in +the List Modules feature. `ListCommand` refers to `ModelManager` to obtain the list of +modules planned or taken by the User. This is facilitated by `getModuleListObj()` method +under `ModelManager` class. + +**Step 3.** As such, when the `execute` method under `ListCommand` is called, it obtains the parameter +provided by `ModelManager`. The methods `getSemCAP()` and `getOverallCAP()` is executed to calculate CAP by Overall and by Semester from all modules. the CAP which is returned as a String is added into +a messageArray for printing to Command Line Interface. + + +The following is the Class Diagram describing when the CAP calculator is used. + +Class Diagram (with unnecessary details ommitted): +![Grade Class Diagram](uml/diagrams/GradeClass.png) + +
+ +### Clear modules +The Clear modules feature allows users to remove all of their added modules, in a specified range using the command `clear [FILTER]`. There are 3 ways of clearing modules : +1. Clear all modules in the planner +2. Clear all modules in the planner for a specific year +3. Clear all modules in the planner for a specific year and semester + +Given below is an example usage scenario for each type, and how the clear modules mechanism behaves at each step. + +**When the year and semester are not specified:** + +**Step 1.** The user types in `clear`, which will be taken in by the `getUserCommand()` method of the `Ui` class. +The input command will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` in the `Parser` class +which returns the main command, `ClearCommand`, if a valid command is entered. +The `LogicManager` then calls `execute()` on `ClearCommand`, which runs the main logic behind this command. +For this example, `clear` means that the user wants to delete **all** modules in the planner. + +**Step 2.** `Parser` returns a `ClearCommand()` object with no arguments. + +**Step 3.** In `CommandResult` of `ClearCommand`, a `List messageArray` list and `List moduleList` ArrayList +are initialised. `moduleList` is the list that contains all the modules added to the planner. + +**Step 4.** In the `ClearCommand` class, the `year` and `semester` have default values of 0. When `ClearCommand` is called without any year and semester specifications, the year and semester values of 0 will be passed to `CommandResult`. +Then, a `List clearAllModules` list is initialised, and the `clearAllMods` method is called to clear all modules that are stored in `moduleList`. + +**Step 5.** In the `clearAllMods` method, a `List messageList` is initialised. `messageList` is used to store the string inputs to be returned to `CommandResult`. + +**Step 6.** If `modulesList` is not empty, `clear()` of the `List` class is called to clear all modules in `moduleList`, which deletes all contents of `moduleList`. + +**Step 7.** A new `CommandResult()` is constructed with the message to be printed to the user. +The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. +This prints the String **"Cleared!"** to indicate that the `clear` command has been executed successfully. + +**When only the year is specified:** + +**Step 1.** The user types in `clear y/2`, which will be taken in by the `getUserCommand()` method of the `Ui` class. +The input command will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` in the `Parser` class +which returns the main command, `ClearCommand`, if a valid command is entered. +The `LogicManager` then calls `execute()` on `ClearCommand`, which runs the main logic behind this command. +For this example, `clear y/2` means that the user wants to delete **all** modules for Year 2. + +**Step 2.** `Parser` returns a `ClearCommand()` object with the user specified year as the argument. + +**Step 3.** In `CommandResult` of `ClearCommand`, a `List messageArray` list and `List` ArrayList named `moduleList` +are initialised. `moduleList` is the list that contains all the modules added to the planner. + +**Step 4.** In the `ClearCommand` class, the `year` and `semester` have default values of 0. When `ClearCommand` is called with the year as `2` but no specific semester, the year value of `2` and semester value of `0` will be passed to `CommandResult`. +Then, a `List clearYearModules` list is initialised, and the `clearYearMods` method is called to clear all modules that are stored in `moduleList`. + +**Step 5.** In the `clearYearMods` method, a `List messageList` is initialised. `messageList` is used to store the string inputs to be returned to `CommandResult`. + +**Step 6.** If `modulesList` is not empty, `moduleList` is iterated to search for the index of modules of the specified year. +When the year of the module in `moduleList` matches the specified year, its index is used to call `removeModule` of `ModelManager`. +`removeModule(index)` is called to delete that module from `moduleList`. + +**Step 7.** `clearYearMods` returns `messageList` to `CommandResult`, and a new `CommandResult()` is constructed with the message to be printed to the user. + +**Step 8.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. +This prints the String **"Cleared!"** to indicate that the `clear y/2` command has been executed successfully. + +**When both the year and semester are specified:** + +**Step 1.** The user types in `clear y/1 s/1`, which will be taken in by the `getUserCommand()` method of the `Ui` class. +The input command will then be passed to the `LogicManager` class by `getCommand()` and calls `parseCommand()` in the `Parser` class +which returns the main command, `ClearCommand`, if a valid command is entered. +The `LogicManager` then calls `execute()` on `ClearCommand`, which runs the main logic behind this command. +For this example, `clear y/1 s/1` means that the user wants to delete **all** modules for Year 1 Semester 1. + +**Step 2.** `Parser` returns a `ClearCommand()` object with the user specified year and semester as the arguments. + +**Step 3.** In `CommandResult` of `ClearCommand`, a `List messageArray` list and `List` ArrayList named `moduleList` +are initialised. `moduleList` is the list that contains all the modules added to the planner. + +**Step 4.** In the `ClearCommand` class, the `year` and `semester` have default values of 0. When `ClearCommand` is called with the year as `1` and semester as `1`, so the year value of `1` and semester value of `1` will be passed to `CommandResult`. +Then, a `List clearYearAndSemModules` list is initialised, and the `clearYearAndSemMods` method is called to clear all modules that are stored in `moduleList`. + +**Step 5.** In the `clearYearAndSemMods` method, a `List messageList` is initialised. `messageList` is used to store the string inputs to be returned to `CommandResult`. + +**Step 6.** If `modulesList` is not empty, `moduleList` is iterated to search for the index of modules of the specified year and semester. +When the year and semester of the module in `moduleList` matches the specified year and semester, its index is used to call `removeModule` of `ModelManager`. +`removeModule(index)` is called to delete that module from `moduleList`. + +**Step 7.** `clearYearAndSemMods` returns `messageList` to `CommandResult`, and a new `CommandResult()` is constructed with the message to be printed to the user. + +**Step 8.** The `CommandResult` object is passed to the `Ui` component with a `printMessage()` method which prints the formatted message to the Command Line Interface. +This prints the String **"Cleared!"** to indicate that the `clear y/1 s/1` command has been executed successfully. + +The following sequence diagram shows how the `clear y/1 s/1` command works: +![Clear Command Sequence Diagram](uml/diagrams/ClearCommandSequence.png) + +### Save planner to local drive +The Save to local drive feature allows user to save the `ModuleList` and `User` details to a `penus.txt` file. It is facilitated by FileStorage and is executed after the `execute()` of a command by `LogicManager`. + +Given below is an example of how the saving mechanism behaves at each step. + +**Step 1.** The application is started for the first time which creates a `/data` directory and an empty text file `penus.txt` in that directory for file saving. The user inputs a valid command and is successfully executed, returning a `CommandResult` to `LogicManager`. When this is returned, the `StorageManager` executes the method `saveStorage()` acccepting the `ModuleList` and `User` objects as its arguments. + +**Step 2.** In `StorageManager` the `saveStorage()` method calls the `save()` method and passes the `ModuleList` and `User` objects to the `FileStorage`. + +**Step 3.** A `FileWriter` object is instantiated with the filepath `/data/penus.txt` and writes to the text file. If the `User` object has valid attributes of `name` and `course`, the first line written would be the User's name and course in the format `User ### [NAME] ### [COURSE]`. + +_Example:_ +``` +User ### Albert ### Computer Engineering +``` + +**Step 4.** The next few lines of the file would then be written with the modules in the `ModuleList`. The list is iterated through and the `FileWriter` writes an encoded format of the module to the file with the `encode()` method of a `Module`. The `encode()` method formats the attributes of a `Module` into the format of : `[STATUS] ### [MODULECODE] ### [YEAR] ### [SEMESTER]` (`### [GRADE]` if module is `Taken`) + +_Example:_ +``` +Taken ### CS2113 ### 2 ### 1 ### A+ +Plan ### CS2105 ### 3 ### 1 +``` + +**Step 5.** The `FileWriter` is closed and the command flow continues as usual. + +The next time a user starts the program with a saved `penus.txt`: + +**Step 6.** Upon starting the program, the `start()` method of `Penus` is executed where the `StorageManager` would execute `loadStorage()` and `loadUser()` respectively into the `ModelManager` constructor. These methods call the `retrieveMods()` and `retrieveUser()` methods of the `FileStorage` respectively. + +**Step 7a.** In `retrieveMods()`, a `Scanner` and a `ArrayList` is instantiated. It takes in the lines of the `penus.txt` file which does not contain the keyword `User` and decodes it with `decodeModule()` method to return a `Module`. This `Module` is then added to the list. This process loops until there is no next line (aka the end of the file). This `ArrayList` is then passed back to the `ModelManager` constructor. + +**Step 7b.** In `retrieveUser()`, a `Scanner` and a `User` object is instantiated. The `Scanner` takes in the first line of the `penus.txt` file. If the keyword `User` is found, the line is decoded and sets the attribute `name` and `course` of the `User` object. This `User` is then passed back to the `ModelManager` constructor. + +**Step 8.** The `ModelManager` constructs the `ModuleList` and `User` object with the received objects respectively from the `StorageManager`. The user's saved state can then be continued on. + +Below is a class diagram of the classes pertaining to the save feature (some details omitted for simplicity): +![SaveFeatureClassDiagram](uml/diagrams/SaveFeatureClass.png) + +
+ +### [Proposed] Handle CS/CU modules +The proposed mechanism is facilitated by `ModuleRetriever` and would allow users to identify CS/CU modules and add only CS or CU grade to them. +>Note: In the current release, any grade can be assigned to a CS/CU module. It is up to users to verify if the module is CS/CU-able. Otherwise, CAP calculated will be wrong. + +**Step 1**. Implement a new method `isCSCU()` in `ModuleRetriever`. This method should communicate with the NUSMods API and check the `gradingBasisDescription` key of the JSON object. A boolean would be returned, indicating if the module is CS or CU graded. + +**Step 2**. Implement the `isCSCU()` method in the `TakenCommand` and `MarkCommand` commands. Check whether the indicated module only accepts CS or CU grade. An exception `InvalidGradeException` is thrown if the input grade is not CS/CU. Else the module is added. + + + +## Appendix A: Product scope + +### Target user profile +- NUS engineering students +- prefers desktop CLI over other available planner application(s) +- prefers typing to mouse interactions +- does not want to keep referring to NUSMods website ### Value proposition +Manage a planner faster and more efficiently than a typical mouse/GUI driven application. As NUS engineering students have an extensive number of modules to cover in their 4 years of study in addition to their busy schedules, this app aims to help them plan modules more efficiently without the need to keep referring to different websites and GUIs (example NUSMods and a scheduler site). + +## Appendix B: User Stories +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` + +| Priority | Version | As a ... | I want to ... | So that I can ... | +|:--------:|:-------:|:------------------------:|:---------------------------------------|:--------------------------------------------------------------------| +| *** | v1.0 | student | add modules to the planner | keep track of my planned and taken modules for my study term | +| *** | v1.0 | student | remove modules | remove modules that I have accidentally added | +| *** | v1.0 | student | list all the modules in my planner | see all modules I am planning for and have taken for the study term | +| *** | v1.0 | student | mark a planned module as taken | update my planner accordingly | +| *** | v1.0 | penultimate year student | display graduation status | graduate on time | +| ** | v2.0 | student | see realtime module details | understand module details without referring to other websites | +| ** | v2.0 | student | check whether I meet the prerequisites | plan my timetable with less worries | +| *** | v2.0 | student | keep track of my CAP | I do not need to calculate my CAP after every semester | +| *** | v2.0 | new user | see usage instructions | refer to them in case I forget the application commands | +| ** | v2.0 | returning user | save my planner | I do not need to re-add my modules | +| *** | v2.0 | new user | include my course | plan for course-specific core modules | +| * | v2.1 | undergraduate student | include S/U for S/U-able modules | get an accurate CAP displayed | + + +## Appendix C: Non-Functional Requirements +- PENUS should work on any mainstream OS as long as it has Java 11 or above installed. +- PENUS requires a **stable internet connection** as NUSMods API is used. +- PENUS should be able to hold up to 1000 modules without a noticeable sluggishness in performance for typical usage. +- A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +- Module data is limited to what is available on NUSMods, i.e. PENUS has module data for all modules offered by NUS in AY22/23 Semester 1 and 2. It is recommended to input updated modules offered in AY22/23 onwards. + +## Appendix D: Glossary + +* *Mainstream OS* - Windows, Linux, Unix, OS-X +* *CLI* - Command Line Interface +* *API* - Application Programming Interface + + + +## Appendix E: Instructions for manual testing +Given below are instructions to test the app manually. +> Note: These instructions only provide a starting point for testers to work on; +> testers are expected to do more *exploratory* testing. +### Launch +1. Initial launch +2. Download the jar file and copy into an empty folder +3. Open a command terminal, change your directory to the folder you put the `penus.jar` file in, and use the `java -jar penus.jar` command to run the application. A CLI should appear in a few seconds. + +### Plan command +1. Test case: `plan CS2113 y/2 s/2`
+ Expected output: A planned module is successfully added
+ Example: + ``` + ___________________________________________________________ + Module has been added: + Plan CS2113 year 2 semester 2 + You have 1 module(s) in your planner + ___________________________________________________________ + ``` + +2. Test case: `plan CS0000 y/2 s/2`
+ Expected output: Error as invalid module is added
+ Example: + ``` + ___________________________________________________________ + Error: Invalid module. Please try again + ___________________________________________________________ + ``` + +3. Test case: `plan CS2113 y/0 s/2`
+ Expected output: Error as year is invalid
+ Example: + ``` + ___________________________________________________________ + Error: Year must be 1 to 4. Please try again. + ___________________________________________________________ + ``` + +4. Other incorrect plan commands to try: + - `plan CS2113 y/a s/2` or `plan CS2113 y/2 s/a` + - `plan CS2113 y/1 s/2 23` + - `plan CS2113 y/1 s/2 g/A`
+ Expected output: Similar to previous + +### Remove command +1. Prerequisites: Add a few valid modules using `plan` or `taken` + +2. Test case: `remove CS2113` (where CS2113 is a module that exists in the list)
+Expected output: Module successfully removed
+Example: +``` + ___________________________________________________________ + Module has been removed: + Plan CS2113 year 1 semester 2 + You have 0 module(s) in your planner + ___________________________________________________________ +``` + +3. Test case: `remove CS3244` (where CS3244 does not exist in the list)
+Expected output: Error as module does not exist within the listt
+Example: +``` + ___________________________________________________________ + Error: No such module exists! + ___________________________________________________________ +``` + +### Mark command +1. Prerequisites: Add a few valid modules using `plan` + +2. Test case: `mark CS2113 g/A+` (where CS2113 is a planned module that exists in the list)
+Expected output: Successfully marked CS2113, converting it to a taken module
+Example: +``` + ___________________________________________________________ + Module has been taken: + Taken CS2113 year 1 semester 1 A+ + ___________________________________________________________ +``` + +3. Test case: `mark CS2113`
+Expected output: Error as `g/GRADE` is not provided +Example: +``` + ___________________________________________________________ + Error: Try again in the format: mark MODULE_CODE g/GRADE + ___________________________________________________________ +``` + +4. Test case: `mark CS2001 g/A`
+Expected output: Error as the module does not exist in the list
+Example: +``` + ___________________________________________________________ + Error: No such module exists! + ___________________________________________________________ +``` + +5. Test case: `mark CS2113 g/N`
+Expected output: Error as the grade is invalid
+Example: +``` + ___________________________________________________________ + Error: Grade is not valid + ___________________________________________________________ +``` + +6. Other incorrect mark commands +- `mark CS2113 g/9` or `mark CS2113 g/9 1` +- `mark g/9`
+Expected output: Similar as previous -{Describe the value proposition: what problem does it solve?} +### Loading data +1. Edit the `/data/penus.txt` file which appears within the same folder as `penus.jar` before launching the program
+> Note: If a user is to be initialised, User MUST be at the TOP of the .txt file -## User Stories +2. Test case: +``` +User ### John ### Computer Engineering +Taken ### CS2113 ### 1 ### 1 ### A+ +``` +Expected: Program launches successfully with: +- User: John (course: computer engineering) +- CS2113 as a taken module in year 1 semester 1 with grade A+ -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +3. Test case: +``` +User ### John1 ### Computer Engineering +``` +Expected: Error as Name has an integer -## Non-Functional Requirements +4. Test case: +``` +User ### John ### Computer Hacking +``` +Expected: Error as Course is invalid -{Give non-functional requirements} +5. Test case: +``` +Plan ### CS0000 ### 1 ### 1 +``` +Expected: Error as module code is an invalid module -## Glossary +6. Test case: +``` +Taken ### CS2113 ### 1 ### 1 +``` +Expected: Error as grade must be included -* *glossary item* - Definition +7. Test case: +``` +Plan ### CS2113 ### 1 ### 1 ### A+ +``` +Expected: Grade is ignored, program launches successfully -## Instructions for manual testing +8. Other incorrect inputs: +``` +Use ### John ### Computer Engineering +Plan ### CS2113 ### s ### 1 +Plan ### CS2103 ### 1 ### 0 +Taken ### CS2100 ### 1 ### 1 ### n +Plan #### CS2107 #### 1 #### 1 +``` +Expected: Similar to previous -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +### Details command +1. Test case: `details CS2040c`
+ Expected output: Relevant details for CS2040C will be displayed
+ Example: + ``` + ___________________________________________________________ + CS2040C Data Structures and Algorithms + This module introduces students to the design and implementation of fundamental + data structures and algorithms. The module covers basic data structures + (linked lists, stacks, queues, hashtables, binary heaps, trees, and graphs), + searching and sorting algorithms, basic analysis of algorithms, and basic + object-oriented programming concepts. + Pre-Requisites: CS1010 or its equivalent + MCs: 4 + Module cannot be SU-ed. + ___________________________________________________________ + ``` +2. Test case: `details ABCDE`
+ Expected output: Error message will be shown
+ Example: + ``` + ___________________________________________________________ + ABCDE This module code is invalid. Try again. + ___________________________________________________________ + ``` \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..98ba4f2b4e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# PENUS 👾 -{Give product intro here} +Planning Engineering with NUS (PENUS) is a desktop app that helps engineering students oversee and plan their modules in their university life! Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..67b9a4a45c 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,340 @@ -# User Guide +# 📜 PENUS User Guide + +
+ ___  _____        ______    ___  ___     ___   _________
+/   \/      \     /      \  /   \/   \   /   \ /         \ 
+|      __    \    |       \/    ||   |  |     ||    _     |
+|     |__|    |   |        |    ||   |  |     ||     \    |
+\            /    |    |   |    ||   |  |     |\      \_ /
+/        ___/___  |    |   |    ||   |  |     | \_      \
+|       | /     \ |    |   |    ||   |  |     |/  \      \
+|_______|/   <> _\|    |   |    ||   \__/     ||   \_     |
+\       /|   \____|    /\       |\            /|          |
+ \__|__/  \______/\___/  \_____/  \__________/  \_________/
+
## Introduction -{Give a product intro} -## Quick Start +**Planning Engineering with NUS (PENUS)** is a desktop app for managing and planning your modules in your university life! +Users are able to add modules that they have taken, plan for future modules and access useful features such as checking their graduation criteria and calculating their CAP. By using NUSmods API, users can retrieve important module details with a stroke of the keyboard. +It is optimised for use via a Command Line Interface (CLI). +For students that can type fast, PENUS can help them plan and track their modules for all four years of their time in university more efficiently. + + +## Table of Contents 📔 +- [Quick Start](#quick-start-⚙️) +- [NUSMods API](#NUSMods-API) +- [Features](#features-👾) + + [Initialisation: `init`](#initialisation-init) + + [Help: `help`](#help-help) + + [Add taken modules: `taken`](#add-taken-modules-taken) + + [Plan untaken modules: `plan`](#plan-untaken-modules-plan) + + [Remove a module: `remove`](#remove-a-module-remove) + + [Mark module as taken: `mark`](#mark-module-as-taken-mark) + + [View modules: `list`](#view-modules-list) + + [View graduation status: `status`](#view-graduation-status-status) + + [View module details: `details`](#view-module-details-details) + + [Clear modules: `clear`](#clear-modules-clear) + + [Exit: `exit`](#exit-the-program-exit) + + [Saving the data](#saving-the-data) + + [Editing the data file](#editing-the-data-file) +- [FAQ](#faq-💻) +- [Command Summary](#command-summary-🔑) -{Give steps to get started quickly} +## Quick Start ⚙️ 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +2. Download the latest version of PENUS from [here](https://github.com/AY2223S2-CS2113-T11-2/tp/releases/download/v2.0/penus.jar). +3. Copy the file to the folder you want to use as the home folder for your PENUS. +4. Open a command terminal, cd into the folder you put the jar file in, and use the `java -jar penus.jar` command to run the application. +5. Type the command in the Command Line Interface and press Enter to execute it. +_Some example commands you can try:_ + - `init n/John c/4`: sets user name as John and course as Computer Engineering + - `taken CS2113 y/2 s/2 g/A+`: Adds CS2113 to Year 2 Semester 2 with grade A+. + - `plan CS2040C y/1 s/2`: Adds CS2040C to Year 1 Semester 2 as untaken + - `list`: list all modules in the planner with overall CAP + - `remove CS2113`: Deletes the module CS2113 + - `clear`: Deletes all modules in the planner + - `status`: gets the status of core modules and MCs taken. + - `exit`: exits the application. +6. Refer to features below for details of each command + +## NUSMods API +Several of our features access the NUSMods API to retrieve data for the modules. +These features include `taken`, `plan`, `list`, `status` and `details`. + +
+ +Here are some points to note: +- Please ensure you have a **stable internet connection** when using PENUS. +- Retrieving data from the NUSMods API may take a while, please expect some loading time when using the features listed above. + +## Features 👾 + +### Initialisation: `init` +On startup, there will be a prompt for first time users to type `init` to start the initialisation process. + +
+ +Format:`init n/[NAME] c/[COURSE NUMBER]` + +
+ +| Course Number | Course | +|---------------|-------------------------------------| +| 1 | Biomedical Engineering | +| 2 | Chemical Engineering | +| 3 | Civil Engineering | +| 4 | Computer Engineering | +| 5 | Electrical Engineering | +| 6 | Environmental Engineering | +| 7 | Industrial and Systems Engineering | +| 8 | Mechanical Engineering | + +Example: +- `init n/John Doe c/1` Initiates a user with the name `John` and course `Biomedical Engineering`. + +Note: +- Each program is limited to 1 user, ie. Initialisation more than once will overwrite the current user `name` and `course` and not create a +separate profile + +
+ +### Help: `help` +Shows a message with the format and functionality of all features. + +
+ +Format:`help` + +
+ +### Add taken modules: `taken` +Adds a module to the planner as a taken module. + +
+ +Format:`taken [MODULE CODE] y/[YEAR] s/[SEMESTER] g/[GRADE]` + +
+ +Example: +- `taken CG1111A y/1 s/1 g/A+` means that you have `taken` and completed the `CG1111A` module in `Year 1` `Semester 1` with `A+` grade + + +
+ +### Plan untaken modules: `plan` +Adds a module to the planner as a module that has not been taken or completed. + +
+ +Format:`plan [MODULE CODE] y/[YEAR] s/[SEMESTER]` + +
+ +Example: +- `plan CG2111A y/1 s/2` means that you `plan` on taking `CG2111A` in `Year 1` `Semester 2`. + +
+ +### Remove a module: `remove` + +Removes a module from the planner. + +
+ +Format:`remove [MODULE CODE]` + +
+ +Example: +- `remove CS2113` + +
+ +### Mark module as taken: `mark` +Marks the module that has been taken and update its grade. +
Module must already have been added to the planner using the `plan` command. + +
+ +Format:`mark [MODULE CODE] g/[GRADE]` + +
+ +Example: +- `mark CG2111A g/A+` + +
+ +### View modules: `list` +Displays a list of all modules taken or planned in a specified Year and/or Semester. +- If Year/Semester is not specified, then all modules will be listed. +- Filter is optional +- The difference between a `plan` and `taken` module is indicated by the presence of a grade. (only `taken` module has a grade) + +
+ +Format:`list [FILTER]` + +
+ +| Filter | Action | Example | +|-----------------------|----------------------------------------|-----------------| +| [empty] | Lists all modules in the planner | `list` | +| y/[YEAR] | Lists modules in the specific year | `list y/1` | +| y/[YEAR] s/[SEMESTER] | Lists modules in the specific semester | `list y/1 s/1` | + +Example: +- `list` Display all modules taken. + +![list example](./ugmedia/list1.png) + +- `list y/2` Displays modules taken in Year 2. + +![list example](./ugmedia/list2.png) + +- `list y/2 s/2` Displays modules taken in Year 2 Semester 2. + +![list example](./ugmedia/list3.png) + +
+ +### View graduation status: `status` +Displays the status of core modules* and MCs taken. + +**Core modules are based on AY22/23* + +
+ +Format:`status` + +
+ +Example: `status` *(course: Computer Engineering)* + +![status example 1](./ugmedia/status1.png) +![status example 2](./ugmedia/status2.png) + +Note: +- The status command may take a while to load. Please ensure a stable internet connection. + +
+ +### View module details: `details` +Display the module title, description, pre-requisites, MCs and SU option. +
+Please ensure you have a stable internet connection when using this command. + +
+ +Format: `details [MODULECODE]` + +
+ +Example: +- `details CS1010` + +![details example](./ugmedia/details1.png) + +
+ +### Clear modules: `clear` +Clears all modules in a specified Year and/or Semester. +- If neither Year nor Semester are specified, then all modules in the planner will be cleared. +- Filter is optional. + +
+ +Format:`clear [FILTER]` + +
+ +| Filter | Action | Example | +|-----------------------|----------------------------------------|-----------------| +| `[empty]` | Clears all modules in the planner | `clear` | +| `y/[YEAR]` | Clears modules in the specific year | `clear y/1` | +| `y/[YEAR] s/[SEMESTER]` | Clears modules in the specific semester | `clear y/1 s/1` | + +Example: +- `clear` Clears all modules in the planner. + +
+ +![list example](./ugmedia/clear1.png) +- `clear y/1` Clears all modules planned/taken in Year 1. + +![list example](./ugmedia/clear2.png) + +- `clear y/2 s/1` Clears all modules planned/taken in Year 2 Semester 1. + +![list example](./ugmedia/clear3.png) + +
+ +### Exit the program: `exit` +Exits the program. + +
+ +Format: `exit` + +
+ + +### Saving the data +PENUS's data are saved in the hard disk automatically after exiting the program. +There is no need to save manually. + +
-## Features +### Editing the data file +PENUS's data are saved as a .txt file in `[JAR file location]/data/penus.txt`. +Edits must be made according to the formatting of the data. +- User MUST be at the **top of the file** +- If 2 Users are declared, only the first declared User will be initialised +- Line breaks/spacings are allowed +- Inclusive of valid formatting of parameters as mentioned in features above -{Give detailed description of each feature} +
-### Adding a todo: `todo` -Adds a new item to the list of todo items. +Format: -Format: `todo n/TODO_NAME d/DEADLINE` +| Line | Description | Format | +|------------|--------------------------|---------------------------------------------------------------------------------------------| +| 1 | User's Name and Course | User ### NAME ### COURSE_NAME | +| 2 to n | Taken or Planned Modules | Taken ### MODULECODE ### YEAR ### SEM ### GRADE
Plan ### MODULECODE ### YEAR ### SEM | -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +
-Example of usage: +Example: -`todo n/Write the rest of the User Guide d/next week` +![penus.txt example](./ugmedia/penustxt.png) -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +
-## FAQ +## FAQ 💻 **Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file in `/data/penus.txt` that contains the data of your previous PENUS application. -## Command Summary +
-{Give a 'cheat sheet' of commands here} +## Command Summary 🔑 -* Add todo `todo n/TODO_NAME d/DEADLINE` +| Command | Format | +|--------------|-----------------------------------------------| +| **init** | `init n/NAME c/COURSE NUMBER` | +| **help** | `help` | +| **taken** | `taken MODULE_CODE y/YEAR s/SEMESTER g/GRADE` | +| **plan** | `plan MODULE_CODE y/YEAR s/SEMESTER` | +| **remove** | `remove MODULE_CODE` | +| **mark** | `mark MODULE_CODE` | +| **list** | `list (FILTER)` | +| **status** | `status` | +| **details** | `details MODULE_CODE` | +| **clear** | `clear (FILTER)` | +| **exit** | `exit` | diff --git a/docs/team/ansenn.md b/docs/team/ansenn.md new file mode 100644 index 0000000000..9b6f65cfe1 --- /dev/null +++ b/docs/team/ansenn.md @@ -0,0 +1,54 @@ +# Ansenn - Project Portfolio Page + +### Project: PENUS +PENUS is a desktop app for NUS engineering students to manage and plan their modules in their university life. The user interacts with it using a CLI. It is written in Java, and has about 6 kLoC. + +Given below are my contributions to the project. + +- **Feature** : List command `list` + - What it does: Displays a list of all modules taken or planned in a specified Year and/or Semester. + - Highlights : Has a flexible format to its inputs with 3 different variations, which made it harder to parse the user's input and check for exceptions. + +- **Feature** : Help Command `help` + - What it does: Displays the list of accepted commands, and each command's purpose and use. + +- **Feature**: Clear Command `clear` + - What it does: Clears all modules added to the planner in a specified Year and/or Semester. + - Highlights : Similar to the `list` command, the `clear` command has a flexible format to its inputs with 3 different variations, which made it harder to parse the user's input and check for exceptions. + +- **Enhancement** : Changed the PENUS logo + - Improved the logo of PENUS + +- **Code contributed**: [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=ansenn&breakdown=true) + +- **Contributions to UG**: + - Edit formatting of various commands in the UG + - Improved clarity of the UG in response to PE-D comments + - Written Documentation for: + - List Command + - Help Command + - Clear Command + +- **Contributions to DG**: + - Written Documentation for: + - List Command + - Help Command + - Clear Command + - Sequence diagram for Clear Command (See extracts) + +- **Contributions to team-based tasks**: + - Assigned teammates for milestones and issues + - Organised and assigned PE-D issues to self and teammates + - Wrote and edited javadoc for some methods + +- **Review/mentoring contributions:**: + - PRs reviewed : [\#15](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/15), [\#43](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/43), [\#198](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/198) + +- **Contributions beyond the project team**: + - Reported 11 bugs for PE-D + +- **Contributions to the Developer Guide (Extracts)**: + +Clear Command Sequence diagram: + +![ClearSequenceDiagram](../uml/diagrams/ClearCommandSequence.png) \ No newline at end of file diff --git a/docs/team/bentohset.md b/docs/team/bentohset.md new file mode 100644 index 0000000000..33d3024aec --- /dev/null +++ b/docs/team/bentohset.md @@ -0,0 +1,88 @@ +### Project: PENUS +PENUS is a desktop app for NUS engineering students to manage and plan their modules in their university life. The user interacts with it using a CLI. It is written in Java, and has about 6 kLoC. + +Given below are my contributions to the project. + +- **Feature**: Plan and Taken command + - What it does: Adds a module (plan or taken) to the module list. + - Highlights: Decided on design and chose to implement by overloading a constructor. +- **Feature**: Mark Command +- **Feature**: Remove Command +- **Feature**: Exit Command +- **Feature**: Storage saving and loading + - What it does: enables saving data from the module list by encoding it and loads upon initialising the application + - Highlights: Required in-depth understanding of Scanner API as 2 different types of classes needed to be read (User and Module). Challenging as all errors that the Parser class handled will need to be implemented for the loading of the .txt file. + +- **Feature**: Resource retrieving from .txt file + - Justification: Allows user to read preset core module details for the status command. + - What it does: retrieves data from a .txt file with all core module details gathered. Saves it as a resource. + - Highlights: Used `getResourceAsStream()` java API. Challenging as it required in-depth understanding of folder architecture and gradle. The implementation was also challenging as it required a different way to scan input. Had to manually edit vscode and intellij settings to enable resources. + +- **Enhancement**: Overhaul architecture to adopt MVC design pattern + - Justification: Improves abstraction and OOP significantly and made it easier for teammates to work on features independently as working on previous codebase asynchronously made the code messy. + - Highlights: Affected all existing code. Required an in-depth understanding of design patterns. The implementation too was challenging as it required changes to existing commands on top of additional methods and classes. + - Credits: [AddressBook 2](https://github.com/se-edu/addressbook-level2) and [AddressBook 3](https://github.com/se-edu/addressbook-level3). + +- **Enchancement**: JUnit 100% branch coverage for PlanCommand, TakenCommand, RemoveCommand, MarkCommand + +- **Code contributed**: [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=bentohset&breakdown=true) + +- **Contributions to UG**: + - Table of contents and links + - Quick start, FAQ, Command summary + - Emojis to enhance look + +- **Contributions to DG**: + - Table of contents and overall structure + - Acknowledgements, Design, Appendix A, B, C, D and E sections + - Implementation section: Add module, Remove module, Mark module, Save planner, [Proposed] handle CS/CU + - Diagrams: (refer to extract) + +- **Contributions to team-based tasks**: + - Set up GitHub team org and repo + - Release management for v1.0 on GitHub + - Set up issue tracker and milestones + - Maintain issue tracker and milestones + - Add javadoc to most methods + +- **Review/mentoring contributions:**: + - PRs reviewed ([\#14](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/14), [\#30](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/30), [\#44](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/44), [\#48](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/48), [\#65](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/65), [\#67](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/67), [\#72](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/72), [\#186](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/186), [\#196](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/196)) + +- **Contributions beyond the project team**: + - Reported 16 bugs for PE-D + +- **Contributions to the Developer Guide (Extracts)**: + +**Architecture diagram:** + + + +Ui class diagram: | Logic object diagram: +:-------------------------:|:-------------------------: + | +**Model object diagram:** | **Storage object diagram:** + | + +**Add module sequence diagram:** + + + +**Remove module sequence diagram:** + + + +**Mark module sequence diagram:** + + + +**List sequence diagram:** + + + +**Grade class diagram:** + + + +**Save feature class diagram:** + + diff --git a/docs/team/chiayuxuan.md b/docs/team/chiayuxuan.md new file mode 100644 index 0000000000..5bade634fe --- /dev/null +++ b/docs/team/chiayuxuan.md @@ -0,0 +1,33 @@ +# Chia Yu Xuan - Project Portfolio Page + +## Overview +Project PENUS +PENUS is a desktop app for NUS engineering students to manage and plan their modules +in their university life. The user interacts with it using a CLI. +It is written in Java, and has about 6 kLoC. + +### Summary of Contributions + +- **New Functionality**: CAP calculator + - What it does: Provide a method to calculate CAP for use in List Command, which + displays CAP for Semester and Overall. +- **Improved Feature**: Help Command + - What it does: Provides user with a list of commands and its description in the CLI. +- **Code contributed**: [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=chiayuxuan&breakdown=true) + + +- **Contributions to DG**: + - Implementation of CAP calculator + + +- **Contributions to team-based tasks**: + - Written JUnit tests + - Added Javadocs + + +- **Contributions beyond the project team**: + - Reported 9 bugs for PE-D + + + + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/sriram-senthilkr.md b/docs/team/sriram-senthilkr.md new file mode 100644 index 0000000000..30e9a866cb --- /dev/null +++ b/docs/team/sriram-senthilkr.md @@ -0,0 +1,60 @@ +### Project: PENUS +PENUS is a desktop app for NUS engineering students to manage and plan their modules in their university life. The user interacts with it using a CLI. It is written in Java, and has about 6 kLoC. + +Given below are my contributions to the project. + +- **Feature**: Details command `details` + - What it does: Retrieves details of any module that is in NUSMods, such as its title, description, + Pre-requisites, Number of Modular Credits and whether it can be SU-ed. + - Highlights: Will be able to display each individual detail on its own, so even if one detail is not available, + it will not affect the command's functionality. + +- **Feature**: Connecting to external NUSMods API + - Justification: User will be able to retrieve details of a wide range of NUS modules. Hardcoding all the + modules will not be feasible, and will limit the functionality for the user. + - What it does: Creates a "GET" request to the NUSMods API for a particular module, and retrieves all the + information given from the API regarding the module. + - Highlights: Required in-depth understanding of `HttpURLConnection` in Java. Used "GET" requests to retrieve for + individual modules. + +- **Feature**: Working with JSON file to retrieve individual details from NUSMods API + - Justification: NUSMods API returns a JSON file of the all the details required, which is read as String in + Java. It is stored as JSON again to retrieve details easily. + - What it does: Parses retrieved String back as JSONObject, and stores in JSONArray. + - Highlights: Had to install external dependency `com.googlecode.json-simple`. Challenging to use `JSONParser`, + `JSONArray`, and `JSONObject` to retrieve data from the API, as the API had nested information that were hard to + parse and use using the parser. + +- **Tests Written**: + - Module Retriever Tests + - Details Compiler Tests + +- **Enhancement**: Made any type of information from NUSMods API retrievable, regardless of its JSON formatting. + +- **Code contributed**: [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=sriram-senthilkr&breakdown=true) + +- **Contributions to UG**: + - Written documentation for: + - Get Details feature + +- **Contributions to DG**: + - Implementation section: Get Module details + - Diagrams (refer to extract): get details sequence + +- **Contributions to team-based tasks**: + - Brainstormed for possible tP ideas + - Assigned teammates for milestone + - Add javadoc to most methods + - User Stories + +- **Review/mentoring contributions:**: + - PRs reviewed [#196](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/196), [#201](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/201) + +- **Contributions beyond the project team**: + - Reported 11 bugs for PE-D + +- **Contributions to the Developer Guide (Extracts)**: + +Get Details sequence diagram: + +![DetailsSequenceDiagram](../uml/diagrams/DetailsSequence.png) \ No newline at end of file diff --git a/docs/team/tayjiunyuan.md b/docs/team/tayjiunyuan.md new file mode 100644 index 0000000000..c7bd9ba83f --- /dev/null +++ b/docs/team/tayjiunyuan.md @@ -0,0 +1,64 @@ +### Tay Jiun Yuan's Project Portfolio Page +### Project: PENUS +PENUS is a desktop app for NUS engineering students to manage and plan their modules in their university life. The user interacts with it using a CLI. It is written in Java, and has about 6 kLoC. +Given below are my contributions to the project. + +- **New Feature**: Initialization Command + - What it does: Initialises the User's Name and Course + - Justification: Name allows users to personalize their PENUS experience and course allows the status command to retrieve the core modules of the user. + - Highlights: Improved usability as it is not feasible to have the user type out their entire course name as it would be prone to misspelling/ wrong format. Decided to map each course to a number instead. Created a new class User to store the information. + +- **New Feature**: Get core modules of user + - What it does: Retrieves a list of the core modules of the user. + - Justification: Allows the Status Command to retrieve the core module information of the user based on his/her course. + - Highlights: Hardcoded each course's core modules (found through each course's website) into a txt file and created a method getCoreMods() to read the txt file and add each core module code under the correct course in a hashmap. + +- **New Feature**: Status Command + - What it does: Displays the user's name, course, core module status and total module credits taken + - Justification: Provides a summary of the user's progress to graduation. + - Highlights: Very challenging to implement due to the number of components. Retrieving the status of each core module requires calling the methods to get the core module codes, checking if the user has taken it. Also required API calls to retrieve the module information and separate methods to get the MC progress of the user. Very difficult to achieve everything whilst maintaining strict OOP practices. + +- **New Feature**: Sample Data + - What it does: Creates an instance of the model, with sample data. + - Justification: When testing, the model can be instantiated with pre-added data, instead of manually adding the data in through commands. + +- **Tests Written**: + - Initialisation Command Tests + - Status Command Tests + - Parser Tests (for initialisation parser and status parser) + - ModuleList Tests (for getGEXX() methods) + - Sample Data Tests + - Clear Command Tests + +- **Enhancement**: Planned new architecture to adopt MVC design pattern + - Justification: Improves abstraction and OOP significantly and made it easier for teammates to work on features independently as working on previous codebase asynchronously made the code messy. Made architecture diagrams cleaner and simple. + - Highlights: Came up with the rough planning of refactored architecture adopting MVC pattern with Benjamin. Only responsible for planning and not implementation. + - Credits: [AddressBook 2](https://github.com/se-edu/addressbook-level2) and [AddressBook 3](https://github.com/se-edu/addressbook-level3). + +- **Code contributed**: [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=tayjiunyuan&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +- **Contributions to UG**: + - Reformatted the entire UG from Google Docs into md file + - Written Documentation for: + - Introduction + - NUSModsAPI + - Status Feature + - Initialisation Feature + - Editing Data File (partial) + +- **Contributions to DG**: + - Status Command + - Wrote documentation for Implementation and drew UML Sequence Diagram + - Initialisation Command + - Wrote documentation for Implementation and drew UML Sequence Diagram + +- **Contributions to team-based tasks**: + - Assigned teammates for milestones + - Opened and managed milestone v2.1 + - User Stories + +- **Review/mentoring contributions:**: + - PRs reviewed/merged: [#14](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/14), [#25](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/25), [#27](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/27), [#186](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/186), [#191](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/191), [#196](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/196), [#198](https://github.com/AY2223S2-CS2113-T11-2/tp/pull/198) + +- **Contributions beyond the project team**: + - Reported 5 bugs for PE-D diff --git a/docs/ugmedia/clear1.png b/docs/ugmedia/clear1.png new file mode 100644 index 0000000000..d6afcab22d Binary files /dev/null and b/docs/ugmedia/clear1.png differ diff --git a/docs/ugmedia/clear2.png b/docs/ugmedia/clear2.png new file mode 100644 index 0000000000..a7aac83e15 Binary files /dev/null and b/docs/ugmedia/clear2.png differ diff --git a/docs/ugmedia/clear3.png b/docs/ugmedia/clear3.png new file mode 100644 index 0000000000..8f43823bbb Binary files /dev/null and b/docs/ugmedia/clear3.png differ diff --git a/docs/ugmedia/details1.png b/docs/ugmedia/details1.png new file mode 100644 index 0000000000..1ad6dc6d13 Binary files /dev/null and b/docs/ugmedia/details1.png differ diff --git a/docs/ugmedia/list1.png b/docs/ugmedia/list1.png new file mode 100644 index 0000000000..c6e542629a Binary files /dev/null and b/docs/ugmedia/list1.png differ diff --git a/docs/ugmedia/list2.png b/docs/ugmedia/list2.png new file mode 100644 index 0000000000..4e9a799457 Binary files /dev/null and b/docs/ugmedia/list2.png differ diff --git a/docs/ugmedia/list3.png b/docs/ugmedia/list3.png new file mode 100644 index 0000000000..4c61a8fc2b Binary files /dev/null and b/docs/ugmedia/list3.png differ diff --git a/docs/ugmedia/penustxt.png b/docs/ugmedia/penustxt.png new file mode 100644 index 0000000000..0aa0a19ec1 Binary files /dev/null and b/docs/ugmedia/penustxt.png differ diff --git a/docs/ugmedia/status1.png b/docs/ugmedia/status1.png new file mode 100644 index 0000000000..231ef2133b Binary files /dev/null and b/docs/ugmedia/status1.png differ diff --git a/docs/ugmedia/status2.png b/docs/ugmedia/status2.png new file mode 100644 index 0000000000..cea9fc8f23 Binary files /dev/null and b/docs/ugmedia/status2.png differ diff --git a/docs/uml/CAP.png b/docs/uml/CAP.png new file mode 100644 index 0000000000..85c25e6df5 Binary files /dev/null and b/docs/uml/CAP.png differ diff --git a/docs/uml/diagrams/AddModSequence.png b/docs/uml/diagrams/AddModSequence.png new file mode 100644 index 0000000000..a2639d456b Binary files /dev/null and b/docs/uml/diagrams/AddModSequence.png differ diff --git a/docs/uml/diagrams/Architecture.png b/docs/uml/diagrams/Architecture.png new file mode 100644 index 0000000000..13e5b1c650 Binary files /dev/null and b/docs/uml/diagrams/Architecture.png differ diff --git a/docs/uml/diagrams/ClearCommandSequence.png b/docs/uml/diagrams/ClearCommandSequence.png new file mode 100644 index 0000000000..cc69ac47c7 Binary files /dev/null and b/docs/uml/diagrams/ClearCommandSequence.png differ diff --git a/docs/uml/diagrams/DetailsSequence.png b/docs/uml/diagrams/DetailsSequence.png new file mode 100644 index 0000000000..89a40249aa Binary files /dev/null and b/docs/uml/diagrams/DetailsSequence.png differ diff --git a/docs/uml/diagrams/GradeClass.png b/docs/uml/diagrams/GradeClass.png new file mode 100644 index 0000000000..a07251ddd5 Binary files /dev/null and b/docs/uml/diagrams/GradeClass.png differ diff --git a/docs/uml/diagrams/InitSequenceDiagram.png b/docs/uml/diagrams/InitSequenceDiagram.png new file mode 100644 index 0000000000..1a977de9a9 Binary files /dev/null and b/docs/uml/diagrams/InitSequenceDiagram.png differ diff --git a/docs/uml/diagrams/ListSequence.png b/docs/uml/diagrams/ListSequence.png new file mode 100644 index 0000000000..73001bcc47 Binary files /dev/null and b/docs/uml/diagrams/ListSequence.png differ diff --git a/docs/uml/diagrams/LogicClass.png b/docs/uml/diagrams/LogicClass.png new file mode 100644 index 0000000000..13925f8eb3 Binary files /dev/null and b/docs/uml/diagrams/LogicClass.png differ diff --git a/docs/uml/diagrams/MarkModSequence.png b/docs/uml/diagrams/MarkModSequence.png new file mode 100644 index 0000000000..177e337e2e Binary files /dev/null and b/docs/uml/diagrams/MarkModSequence.png differ diff --git a/docs/uml/diagrams/ModelClass.png b/docs/uml/diagrams/ModelClass.png new file mode 100644 index 0000000000..03b321d016 Binary files /dev/null and b/docs/uml/diagrams/ModelClass.png differ diff --git a/docs/uml/diagrams/RemoveModSequence.png b/docs/uml/diagrams/RemoveModSequence.png new file mode 100644 index 0000000000..0315d05cd6 Binary files /dev/null and b/docs/uml/diagrams/RemoveModSequence.png differ diff --git a/docs/uml/diagrams/SaveFeatureClass.png b/docs/uml/diagrams/SaveFeatureClass.png new file mode 100644 index 0000000000..096e62a4b0 Binary files /dev/null and b/docs/uml/diagrams/SaveFeatureClass.png differ diff --git a/docs/uml/diagrams/StatusSequenceDiagram.png b/docs/uml/diagrams/StatusSequenceDiagram.png new file mode 100644 index 0000000000..94973febdb Binary files /dev/null and b/docs/uml/diagrams/StatusSequenceDiagram.png differ diff --git a/docs/uml/diagrams/StorageClass.png b/docs/uml/diagrams/StorageClass.png new file mode 100644 index 0000000000..20eff549a2 Binary files /dev/null and b/docs/uml/diagrams/StorageClass.png differ diff --git a/docs/uml/diagrams/UiClass.png b/docs/uml/diagrams/UiClass.png new file mode 100644 index 0000000000..3eb800d769 Binary files /dev/null and b/docs/uml/diagrams/UiClass.png differ diff --git a/docs/uml/sourcecode/AddModSequence.puml b/docs/uml/sourcecode/AddModSequence.puml new file mode 100644 index 0000000000..3314af848e --- /dev/null +++ b/docs/uml/sourcecode/AddModSequence.puml @@ -0,0 +1,77 @@ +@startuml +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:PlanCommand" as PlanCommand +participant "result: CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +participant ":ModuleList" as ModuleList +end box + + +User -> Ui: getUserCommand() "plan CS2113 y/1 s/2" +activate Ui +Ui -> LogicManager: getCommand() +activate LogicManager + + +LogicManager -> Parser: parseCommand() +activate Parser + + +create PlanCommand +Parser -> PlanCommand +activate PlanCommand + +PlanCommand --> Parser: command +deactivate PlanCommand + + +Parser --> LogicManager: command +deactivate Parser + + +LogicManager -> PlanCommand: execute() +activate PlanCommand + + + +PlanCommand -> ModelManager: addModule() +activate ModelManager + +ModelManager -> ModuleList: addModule() +activate ModuleList + +ModuleList -> ModuleList: add() + +ModuleList --> ModelManager: +deactivate ModuleList + +ModelManager --> PlanCommand +deactivate ModelManager + +create CommandResult +PlanCommand -> CommandResult +activate CommandResult +CommandResult --> PlanCommand +deactivate CommandResult +destroy CommandResult + + +PlanCommand --> LogicManager: result +deactivate PlanCommand +destroy PlanCommand + +LogicManager --> Ui: printResult() +deactivate LogicManager + +Ui --> User +deactivate Ui + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/Architecture.puml b/docs/uml/sourcecode/Architecture.puml new file mode 100644 index 0000000000..bd4836df02 --- /dev/null +++ b/docs/uml/sourcecode/Architecture.puml @@ -0,0 +1,53 @@ +@startuml +!include +!include +!include + +skinparam classAttributeIconSize 0 +hide empty members +hide circle + +class "<$user>" as User + +class "<$globe_internet>" as Web + +class "<$documents>" as Harddisk + +package PENUS <> { + class UI #LightGreen { + + } + class Logic #LightBlue { + + } + class Model #LightPink { + + } + class Main { + + } + class Storage #LightYellow { + + } + class Commons #LightCyan { + + } +} + +User ..> UI +UI --> Logic +UI --> Model +Logic --> Model +Logic --> Storage +Main -> Storage +Main -> UI +Main -> Logic +Main -> Model +Web --> Commons + +Storage ..> Model +Storage ..> Harddisk +Commons <.right. Logic + + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/ClearCommandSequence.puml b/docs/uml/sourcecode/ClearCommandSequence.puml new file mode 100644 index 0000000000..f1fb20d63a --- /dev/null +++ b/docs/uml/sourcecode/ClearCommandSequence.puml @@ -0,0 +1,108 @@ +@startuml +'https://plantuml.com/sequence-diagram + +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:ClearCommand" as ClearCommand +participant "result:CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +participant ":ModuleList" as ModuleList +participant ":Module" as Module +end box + +User -> Ui: getUserCommand() "clear y/1 s/1" + +activate Ui +Ui -> LogicManager: getCommand() +activate LogicManager + + +LogicManager -> Parser: parseCommand() +activate Parser + +create ClearCommand +Parser -> ClearCommand +activate ClearCommand + +ClearCommand --> Parser: command +deactivate ClearCommand + +Parser --> LogicManager: command +deactivate Parser + +LogicManager -> ClearCommand: execute() + +activate ClearCommand + +ClearCommand -> ModelManager: getModuleList().size() +activate ModelManager +ModelManager --> ClearCommand : size +deactivate ModelManager + +loop index >= 0 + +ClearCommand -> ModelManager : getModuleList() +activate ModelManager +ModelManager -> ModuleList : getModule() +activate ModuleList +ModuleList -> Module : getYear() +activate Module +Module --> ModuleList : year +deactivate Module +ModuleList --> ModelManager +deactivate ModuleList +ModelManager --> ClearCommand +deactivate ModelManager + +ClearCommand -> ModelManager : getModuleList() +activate ModelManager +ModelManager -> ModuleList : getModule() +activate ModuleList +ModuleList -> Module : getSem() +activate Module +Module --> ModuleList : sem +deactivate Module +ModuleList --> ModelManager +deactivate ModuleList +ModelManager --> ClearCommand +deactivate ModelManager + +ClearCommand -> ModelManager: removeModule(index) + +activate ModelManager + +ModelManager -> ModuleList: removeModule(index) +activate ModuleList + +ModuleList -> ModuleList: remove(index) + +ModuleList --> ModelManager: +deactivate ModuleList +deactivate ModelManager +end + +create CommandResult + +ClearCommand -> CommandResult +activate CommandResult +CommandResult --> ClearCommand +deactivate CommandResult +destroy CommandResult + +ClearCommand --> LogicManager: result +deactivate ClearCommand +destroy ClearCommand + +LogicManager --> Ui: printResult() +deactivate LogicManager + +Ui --> User +deactivate Ui +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/DetailsSequence.puml b/docs/uml/sourcecode/DetailsSequence.puml new file mode 100644 index 0000000000..36d9d013c9 --- /dev/null +++ b/docs/uml/sourcecode/DetailsSequence.puml @@ -0,0 +1,123 @@ +@startuml +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command: DetailsCommand" as DetailsCommand +box "Commons" +participant ":DetailsCompiler" as DetailsCompiler +participant ":ModuleRetriever" as ModuleRetriever +end box +participant "result: CommandResult" as CommandResult +end box + +box "Model" #LightPink + +end box + + +User -> Ui: getUserCommand() "details CS2113" +activate Ui + +Ui -> LogicManager: getCommand() +activate LogicManager + +LogicManager -> Parser: parseCommand() +activate Parser + +create DetailsCommand +Parser -> DetailsCommand +activate DetailsCommand + +DetailsCommand --> Parser: command +deactivate DetailsCommand + +Parser --> LogicManager: command +deactivate Parser + + +LogicManager -> DetailsCommand: execute() +activate DetailsCommand + + + + + + +DetailsCommand -> DetailsCompiler: getDetails('CS2113') +activate DetailsCompiler + +DetailsCompiler -> ModuleRetriever: isValidMod('CS2113') +activate ModuleRetriever +ModuleRetriever -> ModuleRetriever: getData2223('CS2113') +activate ModuleRetriever +deactivate ModuleRetriever +ModuleRetriever --> DetailsCompiler: boolean value +deactivate ModuleRetriever + +DetailsCompiler -> ModuleRetriever: getTitle2223('CS2113') +activate ModuleRetriever +ModuleRetriever -> ModuleRetriever: getData2223('CS2113') +activate ModuleRetriever +deactivate ModuleRetriever +ModuleRetriever --> DetailsCompiler: title +deactivate ModuleRetriever + +DetailsCompiler -> ModuleRetriever: getDescription('CS2113') +activate ModuleRetriever +ModuleRetriever -> ModuleRetriever: getData2223('CS2113') +activate ModuleRetriever +deactivate ModuleRetriever +ModuleRetriever --> DetailsCompiler: description +deactivate ModuleRetriever + + +DetailsCompiler -> ModuleRetriever: getPrerequisite('CS2113') +activate ModuleRetriever +ModuleRetriever -> ModuleRetriever: getData2223('CS2113') +activate ModuleRetriever +deactivate ModuleRetriever +ModuleRetriever --> DetailsCompiler: prereqs +deactivate ModuleRetriever + + +DetailsCompiler -> ModuleRetriever: getModuleCredit2223('CS2113') +activate ModuleRetriever +ModuleRetriever -> ModuleRetriever: getData2223('CS2113') +activate ModuleRetriever +deactivate ModuleRetriever +ModuleRetriever --> DetailsCompiler: credits +deactivate ModuleRetriever + +DetailsCompiler -> ModuleRetriever: getSUstatus('CS2113') +activate ModuleRetriever +ModuleRetriever -> ModuleRetriever: getData2223('CS2113') +activate ModuleRetriever +deactivate ModuleRetriever +ModuleRetriever --> DetailsCompiler: suStatus +deactivate ModuleRetriever + +DetailsCompiler -> DetailsCommand: result +deactivate DetailsCompiler + +create CommandResult +DetailsCommand -> CommandResult +activate CommandResult +CommandResult --> DetailsCommand +deactivate CommandResult +destroy CommandResult + +DetailsCommand --> LogicManager: result +deactivate DetailsCommand +destroy DetailsCommand + +LogicManager --> Ui: printResult() +deactivate LogicManager + +Ui --> User +deactivate Ui + + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/GradeClass.puml b/docs/uml/sourcecode/GradeClass.puml new file mode 100644 index 0000000000..0a2ec1fae0 --- /dev/null +++ b/docs/uml/sourcecode/GradeClass.puml @@ -0,0 +1,30 @@ +@startuml +hide empty members +hide circle +skinparam classAttributeIconSize 0 + +class Grade { + +getGradePoint(grade: String): double + +isValid(grade: String): Boolean + +calculateOverallCAP(moduleList: List): double + +calculateSemCAP(semArray: List): double + +getOverallCAP(moduleList: List): String + +getSemCAP(semArray: List): String +} + +class ListCommand { + +execute(model: ModelManager): CommandResult +} + +class ModelManager{ + #moduleList: ModuleList +} + +class ModuleRetriever { + +getModuleCredit(module: String): String +} + +Grade <-right- ListCommand: uses < +Grade -down-> ModuleRetriever: uses > +ListCommand -down-> ModelManager: refers to > +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/InitCommandSequenceDiagram.puml b/docs/uml/sourcecode/InitCommandSequenceDiagram.puml new file mode 100644 index 0000000000..30535b7a03 --- /dev/null +++ b/docs/uml/sourcecode/InitCommandSequenceDiagram.puml @@ -0,0 +1,70 @@ +@startuml +participant user +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:InitCommand" as InitCommand +participant "result:CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +participant ":User" as User +end box + + +user -> Ui: getUserCommand() "init n/John c/4" +activate Ui + +Ui -> LogicManager: getCommand() +activate LogicManager + +LogicManager -> Parser: parseCommand() +activate Parser + +create InitCommand +Parser -> InitCommand +activate InitCommand + +InitCommand --> Parser: command +deactivate InitCommand + + +Parser --> LogicManager: command +deactivate Parser + + +LogicManager -> InitCommand: execute() +activate InitCommand + +InitCommand -> ModelManager: setUserName() +activate ModelManager + +ModelManager -> User: setName() +activate User + +InitCommand -> ModelManager: setUserCourse() + +ModelManager -> User: setCourse() +deactivate User +deactivate ModelManager + +create CommandResult +"InitCommand" -> CommandResult +activate CommandResult +CommandResult --> "InitCommand" +deactivate CommandResult +destroy CommandResult + +"InitCommand" -> LogicManager: result +deactivate "InitCommand" +destroy "InitCommand" + +LogicManager -> Ui: printResult() +deactivate "LogicManager" + +Ui -> user + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/ListSequence.puml b/docs/uml/sourcecode/ListSequence.puml new file mode 100644 index 0000000000..7ba5116a49 --- /dev/null +++ b/docs/uml/sourcecode/ListSequence.puml @@ -0,0 +1,76 @@ +@startuml +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:ListCommand" as ListCommand +participant ":Grade" as Grade +participant "result:CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +end box + +box "Common" + +end box + + +User -> Ui: getUserCommand() "list" + +activate Ui +Ui -> LogicManager: getCommand() +activate LogicManager + + +LogicManager -> Parser: parseCommand() +activate Parser + +create ListCommand +Parser -> ListCommand +activate ListCommand + +ListCommand --> Parser: command +deactivate ListCommand + + +Parser --> LogicManager: command +deactivate Parser + +LogicManager -> ListCommand: execute() + +activate ListCommand + +ListCommand -> ModelManager: getModuleListObj() +activate ModelManager +ModelManager --> ListCommand: moduleList +deactivate ModelManager + +ListCommand -> Grade: getOverallCAP() +Grade --> ListCommand: gradePoint + +create CommandResult + +ListCommand -> CommandResult +activate CommandResult +CommandResult --> ListCommand +deactivate CommandResult +destroy CommandResult + +ListCommand --> LogicManager: result +deactivate ListCommand +destroy ListCommand + +LogicManager --> Ui: printResult() +deactivate LogicManager + +Ui --> User +deactivate Ui + + + + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/LogicClass.puml b/docs/uml/sourcecode/LogicClass.puml new file mode 100644 index 0000000000..d74d21dc40 --- /dev/null +++ b/docs/uml/sourcecode/LogicClass.puml @@ -0,0 +1,32 @@ +@startuml +hide empty members +hide circle +skinparam classAttributeIconSize 0 + +package Logic { + class LogicManager + class XYZCommand + class Parser + class "{abstract}\nCommand" as Command + class CommandResult +} + +package Storage {} + +package Model {} + +class Main #FFFFFF + +Main ..> LogicManager +LogicManager -right-> Model +LogicManager -right-> Storage + +LogicManager .right.> Command : executes > +LogicManager -left->"1" Parser +Parser ..> XYZCommand : creates > +Command .up.> CommandResult : produces > +LogicManager .down.> CommandResult +XYZCommand -up-|> Command +note right of XYZCommand: XYZCommand = PlanCommand, \nRemoveCommand, etc + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/MarkModSequence.puml b/docs/uml/sourcecode/MarkModSequence.puml new file mode 100644 index 0000000000..74a2a519b6 --- /dev/null +++ b/docs/uml/sourcecode/MarkModSequence.puml @@ -0,0 +1,87 @@ +@startuml +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:MarkCommand" as MarkCommand +participant "result:CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +participant ":ModuleList" as ModuleList +participant "module:Module" as Module +end box + + +User -> Ui: getUserCommand() "mark CS2113 g/A+" +activate Ui + +Ui -> LogicManager: getCommand() +activate LogicManager + +LogicManager -> Parser: parseCommand() +activate Parser + +create MarkCommand +Parser -> MarkCommand +activate MarkCommand + +MarkCommand --> Parser: command +deactivate MarkCommand + + +Parser --> LogicManager: command +deactivate Parser + + +LogicManager -> MarkCommand: execute() +activate MarkCommand + + +MarkCommand -> ModelManager: getModuleList() +activate ModelManager +ModelManager --> MarkCommand: index +deactivate ModelManager + +MarkCommand -> ModelManager: markModule(i) +activate ModelManager + +ModelManager -> ModuleList: getModule(i) +activate ModuleList + +ModuleList --> ModelManager: module +deactivate ModuleList + +ModelManager -> Module: markModule(i, grade) +activate Module +Module -> Module: Mark() + +Module --> ModelManager +deactivate Module + +ModelManager --> MarkCommand +deactivate ModelManager + +create CommandResult + + +MarkCommand -> CommandResult +activate CommandResult +CommandResult --> MarkCommand +deactivate CommandResult +destroy CommandResult + +MarkCommand --> LogicManager: result +deactivate MarkCommand +destroy MarkCommand + +LogicManager --> Ui: printResult() +deactivate LogicManager + +Ui --> User +deactivate Ui + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/ModelClass.puml b/docs/uml/sourcecode/ModelClass.puml new file mode 100644 index 0000000000..f2c165b5ea --- /dev/null +++ b/docs/uml/sourcecode/ModelClass.puml @@ -0,0 +1,21 @@ +@startuml +hide empty members +hide circle +skinparam classAttributeIconSize 0 + +package Model{ + class ModelManager + class ModuleList + class User + class Module +} + +Class Storage #FFFFFF +Storage ..> ModelManager: core module details > + +ModelManager -> "1" ModuleList +ModelManager --> "1" User +ModuleList --> "*" Module + + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/RemoveModSequence.puml b/docs/uml/sourcecode/RemoveModSequence.puml new file mode 100644 index 0000000000..2e1e7adb3b --- /dev/null +++ b/docs/uml/sourcecode/RemoveModSequence.puml @@ -0,0 +1,84 @@ +@startuml +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:RemoveCommand" as RemoveCommand +participant "result:CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +participant ":ModuleList" as ModuleList +end box + + +User -> Ui: getUserCommand() "remove CS2113" + +activate Ui +Ui -> LogicManager: getCommand() +activate LogicManager + + +LogicManager -> Parser: parseCommand() +activate Parser + +create RemoveCommand +Parser -> RemoveCommand +activate RemoveCommand + +RemoveCommand --> Parser: command +deactivate RemoveCommand + + +Parser --> LogicManager: command +deactivate Parser + +LogicManager -> RemoveCommand: execute() + +activate RemoveCommand + +RemoveCommand -> ModelManager: getModuleList() +activate ModelManager +ModelManager --> RemoveCommand: index +deactivate ModelManager + +RemoveCommand -> ModelManager: removeModule(i) + +activate ModelManager + +ModelManager -> ModuleList: removeModule(i) +activate ModuleList + +ModuleList -> ModuleList: remove(i) + +ModuleList --> ModelManager: +deactivate ModuleList + +ModelManager --> RemoveCommand +deactivate ModelManager + +create CommandResult + +RemoveCommand -> CommandResult +activate CommandResult +CommandResult --> RemoveCommand +deactivate CommandResult +destroy CommandResult + +RemoveCommand --> LogicManager: result +deactivate RemoveCommand +destroy RemoveCommand + +LogicManager --> Ui: printResult() +deactivate LogicManager + +Ui --> User +deactivate Ui + + + + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/SaveFeatureClass.puml b/docs/uml/sourcecode/SaveFeatureClass.puml new file mode 100644 index 0000000000..866176b5c3 --- /dev/null +++ b/docs/uml/sourcecode/SaveFeatureClass.puml @@ -0,0 +1,40 @@ +@startuml +hide empty members +hide circle +skinparam classAttributeIconSize 0 + +class FileStorage { + #file: File + #dataDirectory: String + #filePath: String + -decodeModule(module: String): Module + +save(moduleList: ModuleList, user: User) + +retrieveMods(): List + +retrieveUser(): User +} + +class StorageManager { + #storage: FileStorage + +loadStorage(): List + +loadUser(): User + +saveStorage(moduleList: ModuleList, user: User) +} + +class Penus { + #storage: StorageManager + #model: ModelManager + -start() + -runCommandLoopUntilExitCommand() + -executeCommand(command: Command): CommandResult + +main(args: String[]) + +run() +} + +class ModelManager {} + +StorageManager -right-> "1" FileStorage +Penus <-down-> "1" StorageManager: retrieves and saves data +Penus -right-> "1" ModelManager: updates model + + +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/StatusSequenceDiagram.puml b/docs/uml/sourcecode/StatusSequenceDiagram.puml new file mode 100644 index 0000000000..4893a4ed7f --- /dev/null +++ b/docs/uml/sourcecode/StatusSequenceDiagram.puml @@ -0,0 +1,82 @@ +@startuml +participant User +participant ":Ui" as Ui + +box "Logic" #LightBlue +participant ":LogicManager" as LogicManager +participant ":Parser" as Parser +participant "command:StatusCommand" as StatusCommand +participant "result:CommandResult" as CommandResult +end box + +box "Model" #LightPink +participant ":ModelManager" as ModelManager +end box + +box "Utils" #LightGrey +participant ":Module Retriever" as ModuleRetriever +participant ":MCsTaken" as MCsTaken +end box + +User -> Ui: getUserCommand() "status" +activate Ui + +Ui -> LogicManager: getCommand() +activate LogicManager + +LogicManager -> Parser: parseCommand() +activate Parser +Parser -> LogicManager: status command +deactivate Parser + +LogicManager -> "StatusCommand" : execute() +activate "StatusCommand" + +"StatusCommand" -> ModelManager: getUserName() +activate ModelManager +ModelManager -> "StatusCommand": name +"StatusCommand" -> ModelManager: getUserCourse() +ModelManager -> "StatusCommand": course +"StatusCommand" -> ModelManager: getTakenCoreMods() +ModelManager -> "StatusCommand": takenCoreMods +"StatusCommand" -> ModelManager: getUntakenCoreMods() +ModelManager -> "StatusCommand": untakenCoreMods +deactivate ModelManager + + +"StatusCommand" -> MCsTaken: getNumberOfMCsTaken() +activate MCsTaken +MCsTaken -> "StatusCommand": total MCs taken +deactivate MCsTaken + +"StatusCommand" -> ModelManager: getNumberOfCoreMCsTaken() +activate ModelManager +ModelManager -> "StatusCommand": number of core module MCs taken +deactivate ModelManager + +loop For each Module Code + "StatusCommand" -> ModuleRetriever: moduleDetailsString() + activate ModuleRetriever + ModuleRetriever -> "StatusCommand": moduleDetailsString + deactivate ModuleRetriever +end + + +create CommandResult +"StatusCommand" -> CommandResult +activate CommandResult +CommandResult --> "StatusCommand" +deactivate CommandResult +destroy CommandResult + +"StatusCommand" -> LogicManager: result +deactivate "StatusCommand" +destroy "StatusCommand" + +LogicManager -> Ui: printResult() +deactivate "LogicManager" + + +Ui -> User + +@enduml diff --git a/docs/uml/sourcecode/StorageClass.puml b/docs/uml/sourcecode/StorageClass.puml new file mode 100644 index 0000000000..f26ed3cdaa --- /dev/null +++ b/docs/uml/sourcecode/StorageClass.puml @@ -0,0 +1,17 @@ +@startuml +hide empty members +hide circle +skinparam classAttributeIconSize 0 + +package Storage { + class StorageManager + class FileStorage + class ResourceStorage + class StorageDecoder +} + +StorageManager -left-> "1" FileStorage +StorageManager --> "1" ResourceStorage + +FileStorage ..> StorageDecoder: uses > +@enduml \ No newline at end of file diff --git a/docs/uml/sourcecode/UiClass.puml b/docs/uml/sourcecode/UiClass.puml new file mode 100644 index 0000000000..3aa603bc2b --- /dev/null +++ b/docs/uml/sourcecode/UiClass.puml @@ -0,0 +1,42 @@ +@startuml +hide circle +skinparam classAttributeIconSize 0 +hide empty members + +class Ui { + #in: Scanner + #out: PrintStream + #DIVIDER: String + +getUserCommand(): String + +printMessage(message: String...) + +printResult(result: CommandResult) + +printWelcome() + +printExit() +} + +class Penus { + #ui: Ui + #storage: StorageManager + #model: ModelManager + #logic: LogicManager + -start() + -runCommandLoopUntilExitCommand() + -executeCommand(command: Command): CommandResult + -getCommand(commandText: String): Command + -exit() + +main(args: String[]) + +run() +} + +class Scanner { + +nextLine() +} +class PrintStream { + +println() +} + +Penus --> "1" Ui +Ui .. Scanner: gets input from > +Ui .. PrintStream: prints to > + +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/seedu/penus/Penus.java b/src/main/java/seedu/penus/Penus.java new file mode 100644 index 0000000000..9a2fe4927d --- /dev/null +++ b/src/main/java/seedu/penus/Penus.java @@ -0,0 +1,116 @@ +package seedu.penus; + +import seedu.penus.common.exceptions.NoInternetException; +import seedu.penus.logic.utils.ModuleRetriever; +import seedu.penus.ui.Ui; +import seedu.penus.logic.LogicManager; +import seedu.penus.model.ModelManager; +import seedu.penus.storage.StorageManager; +import seedu.penus.logic.commands.Command; +import seedu.penus.logic.commands.CommandResult; +import seedu.penus.logic.commands.ExitCommand; + +public class Penus { + + private Ui ui; + private StorageManager storage; + private ModelManager model; + private LogicManager logic; + + public static void main(String[] args) { + new Penus().run(); + } + + /** Run program till termination */ + public void run() { + start(); + runCommandLoopUntilExitCommand(); + exit(); + } + + /** + * Sets up required objects, loads data from file and prints welcome message + */ + private void start() { + this.ui = new Ui(); + this.storage = new StorageManager(); + try { + ModuleRetriever.connectionChecker(); + } catch (NoInternetException e) { + ui.printMessage(e.getMessage()); + } + try { + this.model = new ModelManager( + storage.loadUser(), + storage.loadStorage(), + storage.loadCoreDetails(), + storage.loadCoreModList() + ); + } catch (Exception e) { + ui.printStorageError(e.getMessage()); + //unchecked exception to exit application upon initialising with error + throw new RuntimeException(e); + } + + this.logic = new LogicManager(model, storage); + ui.printWelcome(); + } + + /** + * Reads the user command and executes it until user issues exit command + */ + private void runCommandLoopUntilExitCommand() { + Command command; + do { + CommandResult result = null; + String userCommandText = ui.getUserCommand(); + command = getCommand(userCommandText); + if (command != null) { + result = executeCommand(command); + } + if (result != null && result.isArray) { + ui.printResultArray(result); + } + if (result != null && !result.isArray) { + ui.printResultString(result); + } + + } while (!ExitCommand.isExit(command)); + } + + /** + * Executes the command and returns the result + * @param command user command + * @return CommandResult + */ + private CommandResult executeCommand(Command command) { + CommandResult result = null; + try { + result = logic.execute(command); + + } catch (Exception e) { + ui.printMessage(e.getMessage()); + } + return result; + } + + /** + * Directs the command input to the LogicManager to retrieve the Command object + * @param commandText + * @return Command object relating to the commandText + */ + private Command getCommand(String commandText) { + Command command = null; + try { + command = logic.getCommand(commandText); + } catch (Exception e) { + ui.printMessage(e.getMessage()); + } + return command; + } + + /** Exits */ + private void exit() { + System.exit(0); + } +} diff --git a/src/main/java/seedu/penus/common/Messages.java b/src/main/java/seedu/penus/common/Messages.java new file mode 100644 index 0000000000..5f15cee3d3 --- /dev/null +++ b/src/main/java/seedu/penus/common/Messages.java @@ -0,0 +1,18 @@ +package seedu.penus.common; + +public class Messages { + public static final String MESSAGE_GOODBYE = "Bye see you again!"; + public static final String MESSAGE_WELCOME = + "Welcome to PENUS!\n \tEnter help for a list of commands or init to start"; + public static final String LOGO = "\n" + + "\t ___ _____ ______ ___ ___ ___ _________\n" + + "\t/ \\/ \\ / \\ / \\/ \\ / \\ / \\ \n" + + "\t| __ \\ | \\/ || | | || _ |\n" + + "\t| |__| | | | || | | || \\ |\n" + + "\t\\ / | | | || | | |\\ \\_ /\n" + + "\t/ ___/___ | | | || | | | \\_ \\\n" + + "\t| | / \\ | | | || | | |/ \\ \\\n" + + "\t|_______|/ <> _\\| | | || \\__/ || \\_ |\n" + + "\t\\ /| \\____| / \\ |\\ /| |\n" + + "\t \\__|__/ \\______/\\___/ \\_____/ \\__________/ \\_________/\n"; +} diff --git a/src/main/java/seedu/penus/common/exceptions/DuplicateModuleException.java b/src/main/java/seedu/penus/common/exceptions/DuplicateModuleException.java new file mode 100644 index 0000000000..f10c2edc16 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/DuplicateModuleException.java @@ -0,0 +1,8 @@ +package seedu.penus.common.exceptions; + +public class DuplicateModuleException extends PenusException { + private static final String message = "This module has already been added to the list. Please try again."; + public DuplicateModuleException() { + super(message); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidCommandException.java b/src/main/java/seedu/penus/common/exceptions/InvalidCommandException.java new file mode 100644 index 0000000000..75426667e3 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidCommandException.java @@ -0,0 +1,13 @@ +package seedu.penus.common.exceptions; + +public class InvalidCommandException extends PenusException { + private static final String MESSAGE = "This command is unsupported. Please try again"; + + public InvalidCommandException(String message) { + super(message); + } + + public InvalidCommandException() { + super(MESSAGE); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidFormatException.java b/src/main/java/seedu/penus/common/exceptions/InvalidFormatException.java new file mode 100644 index 0000000000..dfbd165ddd --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidFormatException.java @@ -0,0 +1,11 @@ +package seedu.penus.common.exceptions; + +public class InvalidFormatException extends PenusException { + public InvalidFormatException(String... missing) { + super("Wrong Format! Please include " + String.join(" and ", missing)); + } + + public InvalidFormatException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidGradeException.java b/src/main/java/seedu/penus/common/exceptions/InvalidGradeException.java new file mode 100644 index 0000000000..c9e237e105 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidGradeException.java @@ -0,0 +1,7 @@ +package seedu.penus.common.exceptions; + +public class InvalidGradeException extends PenusException { + public InvalidGradeException() { + super("Grade is not valid"); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidIndexException.java b/src/main/java/seedu/penus/common/exceptions/InvalidIndexException.java new file mode 100644 index 0000000000..64426ac288 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidIndexException.java @@ -0,0 +1,13 @@ +package seedu.penus.common.exceptions; + +public class InvalidIndexException extends PenusException { + private static final String MESSAGE = "This command is unsupported. Please try again"; + + public InvalidIndexException(String message) { + super(message); + } + + public InvalidIndexException() { + super(MESSAGE); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidModuleAPIException.java b/src/main/java/seedu/penus/common/exceptions/InvalidModuleAPIException.java new file mode 100644 index 0000000000..64e4616a42 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidModuleAPIException.java @@ -0,0 +1,10 @@ +package seedu.penus.common.exceptions; + +public class InvalidModuleAPIException extends PenusException { + private static final String message = "The module's details were not able to be retrieved," + + " as it is not available in the API."; + public InvalidModuleAPIException() { + super(message); + } + +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidModuleException.java b/src/main/java/seedu/penus/common/exceptions/InvalidModuleException.java new file mode 100644 index 0000000000..b2bf542bb7 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidModuleException.java @@ -0,0 +1,11 @@ +package seedu.penus.common.exceptions; + +public class InvalidModuleException extends PenusException { + public InvalidModuleException(String module) { + super("The module code must be given."); + } + + public InvalidModuleException() { + super("Invalid module. Please try again"); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidSemesterException.java b/src/main/java/seedu/penus/common/exceptions/InvalidSemesterException.java new file mode 100644 index 0000000000..2176df3db7 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidSemesterException.java @@ -0,0 +1,8 @@ +package seedu.penus.common.exceptions; + +public class InvalidSemesterException extends PenusException { + private static final String message = "The semester number is invalid. Please try again"; + public InvalidSemesterException(String s) { + super(message); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/InvalidYearException.java b/src/main/java/seedu/penus/common/exceptions/InvalidYearException.java new file mode 100644 index 0000000000..18f509afde --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/InvalidYearException.java @@ -0,0 +1,8 @@ +package seedu.penus.common.exceptions; + +public class InvalidYearException extends PenusException { + + public InvalidYearException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/NoInternetException.java b/src/main/java/seedu/penus/common/exceptions/NoInternetException.java new file mode 100644 index 0000000000..d3342eee0c --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/NoInternetException.java @@ -0,0 +1,8 @@ +package seedu.penus.common.exceptions; + +public class NoInternetException extends PenusException{ + private static final String message = "You are not connected to the internet! Connect now to prevent any errors."; + public NoInternetException() { + super(message); + } +} diff --git a/src/main/java/seedu/penus/common/exceptions/PenusException.java b/src/main/java/seedu/penus/common/exceptions/PenusException.java new file mode 100644 index 0000000000..6dedb6add3 --- /dev/null +++ b/src/main/java/seedu/penus/common/exceptions/PenusException.java @@ -0,0 +1,7 @@ +package seedu.penus.common.exceptions; + +public class PenusException extends Exception { + PenusException(String message) { + super("Error: " + message); + } +} diff --git a/src/main/java/seedu/penus/logic/LogicManager.java b/src/main/java/seedu/penus/logic/LogicManager.java new file mode 100644 index 0000000000..da995916fe --- /dev/null +++ b/src/main/java/seedu/penus/logic/LogicManager.java @@ -0,0 +1,43 @@ +package seedu.penus.logic; + +import seedu.penus.model.ModelManager; +import seedu.penus.storage.StorageManager; +import seedu.penus.logic.parser.Parser; +import seedu.penus.logic.commands.CommandResult; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.commands.Command; + +public class LogicManager { + private final ModelManager model; + private final StorageManager storage; + private final Parser parser; + + public LogicManager(ModelManager model, StorageManager storage) { + this.model = model; + this.storage = storage; + this.parser = new Parser(); + } + + /** + * Executes the command of a Command object and saves the moduleList and user to the storage file + * @param command command + * @return CommandResult from the execution of the command + * @throws PenusException exception + */ + public CommandResult execute(Command command) throws PenusException { + CommandResult commandResult = command.execute(model); + storage.saveStorage(model.getModuleList(), model.getUser()); + + return commandResult; + } + + /** + * Directs the commandText string to the Parser object for parsing commands + * @param commandText string + * @return Command object + * @throws PenusException exception + */ + public Command getCommand(String commandText) throws PenusException { + return parser.parseCommand(commandText); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/ClearCommand.java b/src/main/java/seedu/penus/logic/commands/ClearCommand.java new file mode 100644 index 0000000000..0be19eb4e0 --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/ClearCommand.java @@ -0,0 +1,113 @@ +package seedu.penus.logic.commands; + +import java.util.ArrayList; +import java.util.List; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; + +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + + public static final String MESSAGE = "Cleared!"; + + public final int year; + public final int semester; + + public ClearCommand() { + this.year = 0; + this.semester = 0; + } + + //Overloaded constructor for filter + public ClearCommand(int year, int semester) { + this.year = year; + this.semester = semester; + } + + @Override + public CommandResult execute(ModelManager model) throws PenusException { + List messageArray = new ArrayList<>(); + List moduleList = model.getModuleListObj(); + + //clear all modules + if (this.year == 0 && this.semester == 0) { + List clearAllModules = clearAllMods(moduleList); + messageArray.addAll(clearAllModules); + } + + //clear specific year only + if (this.year != 0 && this.semester == 0) { + List clearYearModules = clearYearMods(model); + messageArray.addAll(clearYearModules); + } + + //list year and sem specific + if (this.year != 0 && this.semester != 0) { + List clearYearAndSemModules = clearYearAndSemMods(model); + messageArray.addAll(clearYearAndSemModules); + } + + return new CommandResult(messageArray, true); + } + + //clear all modules method + private List clearAllMods(List moduleList) { + List messageList = new ArrayList<>(); + if (moduleList.isEmpty()) { + messageList.add("No modules to clear."); + + } else { + moduleList.clear(); + messageList.add(MESSAGE); + } + return messageList; + } + + //clear specific year method + private List clearYearMods(ModelManager model) { + List messageList = new ArrayList<>(); + messageList.add("Clearing modules for Year " + this.year); + + int listSize = model.getModuleList().size(); + + if (listSize == 0) { + + messageList.add("No modules to clear."); + } else { + + for (int index = listSize - 1; index >= 0; index -= 1) { + if (model.getModuleList().getModule(index).getYear().equals(this.year)) { + model.removeModule(index); + } + } + messageList.add(MESSAGE); + } + return messageList; + } + + //list year and sem specific method + private List clearYearAndSemMods(ModelManager model) { + List messageList = new ArrayList<>(); + messageList.add("Clearing modules for Year " + this.year + " and Semester " + this.semester); + + int listSize = model.getModuleList().size(); + + if (listSize == 0) { + + messageList.add("No modules to clear."); + } else { + + for (int index = listSize - 1; index >= 0; index -= 1) { + if ((model.getModuleList().getModule(index).getYear() == this.year) && + (model.getModuleList().getModule(index).getSem() == this.semester)) { + model.removeModule(index); + } + } + messageList.add(MESSAGE); + } + return messageList; + } + +} diff --git a/src/main/java/seedu/penus/logic/commands/Command.java b/src/main/java/seedu/penus/logic/commands/Command.java new file mode 100644 index 0000000000..b3cc11c98e --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/Command.java @@ -0,0 +1,16 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; + +public abstract class Command { + + /** + * Executes the command and returns the result message. + * + * @param model {@code ModelManager} which the command operates on + * @return CommandResult with the feedback message for display + * @throws PenusException if an error occurs during the execution + */ + public abstract CommandResult execute(ModelManager model) throws PenusException; +} diff --git a/src/main/java/seedu/penus/logic/commands/CommandResult.java b/src/main/java/seedu/penus/logic/commands/CommandResult.java new file mode 100644 index 0000000000..ee5a504645 --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/CommandResult.java @@ -0,0 +1,34 @@ +package seedu.penus.logic.commands; + +import java.util.List; + +public class CommandResult { + public final String feedbackToUser; + + public final List feedbackArray; + + public final boolean isArray; + + /** + * Stores a string feedbackToUser to return the feedback message from an executed command + * @param feedbackToUser string + * @param isArray boolean + */ + public CommandResult(String feedbackToUser, boolean isArray) { + this.feedbackToUser = feedbackToUser; + this.feedbackArray = null; + this.isArray = isArray; + } + + /** + * Overloaded constructor for printing arrays + * Stores a List of string feedbackArray to return the feedback message from an executed command + * @param feedbackArray string + * @param isArray boolean + */ + public CommandResult(List feedbackArray, boolean isArray) { + this.feedbackToUser = null; + this.feedbackArray = feedbackArray; + this.isArray = isArray; + } +} diff --git a/src/main/java/seedu/penus/logic/commands/DetailsCommand.java b/src/main/java/seedu/penus/logic/commands/DetailsCommand.java new file mode 100644 index 0000000000..23aca1728d --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/DetailsCommand.java @@ -0,0 +1,24 @@ +package seedu.penus.logic.commands; + +import seedu.penus.model.ModelManager; +import seedu.penus.logic.utils.DetailsCompiler; + +public class DetailsCommand extends Command { + public static final String COMMAND_WORD = "details"; + public final String moduleCode; + + /** + * Creates a DetailsCommand with the moduleCode for querying during execution + * @param moduleCode string + */ + public DetailsCommand(String moduleCode) { + this.moduleCode = moduleCode; + } + + @Override + public CommandResult execute(ModelManager model) { + String result = DetailsCompiler.getDetails(this.moduleCode); + + return new CommandResult(moduleCode + " " + result, false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/ExitCommand.java b/src/main/java/seedu/penus/logic/commands/ExitCommand.java new file mode 100644 index 0000000000..f12604b75c --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/ExitCommand.java @@ -0,0 +1,22 @@ +package seedu.penus.logic.commands; + +import seedu.penus.model.ModelManager; + +public class ExitCommand extends Command { + public static final String COMMAND_WORD = "exit"; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting PENUS ...\n\tBye see you again!"; + + /** + * Static method which signals the end of the program, called in main running loop + * @param command Command object + * @return boolean true if the given Command is an instance of ExitCommand + */ + public static boolean isExit(Command command) { + return command instanceof ExitCommand; // instanceof returns false if it is null + } + + @Override + public CommandResult execute(ModelManager model) { + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/HelpCommand.java b/src/main/java/seedu/penus/logic/commands/HelpCommand.java new file mode 100644 index 0000000000..6729ed78fd --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/HelpCommand.java @@ -0,0 +1,54 @@ +package seedu.penus.logic.commands; + +import seedu.penus.model.ModelManager; + +public class HelpCommand extends Command { + public static final String COMMAND_WORD = "help"; + + @Override + public CommandResult execute(ModelManager model) { + return new CommandResult( + ("\n\t" + String.format("%-52s %s", "Command", "Description") + + "\n\t" + String.format("%-52s %s", "-------", "-----------") + + "\n\t" + String.format("%-52s %s", + "clear [FILTER]", "Clears modules in the specified Year or Semester.") + + "\n\t" + String.format("%-52s %s", "", + "If [FILTER] is not specified, then all modules will cleared.") + + "\n\n\t" + String.format("%-52s %s", "exit", "Exits the program.") + + "\n\n\t" + String.format("%-52s %s", "list [FILTER]", + "Displays a list of all modules taken or planned") + + "\n\t" + String.format("%-52s %s", "", "in the specified Year or Semester.") + + "\n\t" + String.format("%-52s %s", "", + "If [FILTER] is not specified, then all modules will shown.") + + "\n\n\t" + String.format("%-52s %s", "mark [MODULE CODE] g/[GRADE]", + "Marks the module that has been cleared, while updating its grades.") + + String.format("%n %n \t") + + String.format("%-52s %s", "plan [MODULE CODE] y/[YEAR] s/[SEMESTER]", + "Adds a module to the planner as an untaken module.") + + "\n\n\t" + String.format("%-52s %s", + "remove [MODULECODE]", "Removes a module from the planner.") + + "\n\n\t" + String.format("%-52s %s", + "status", "Displays the status of Core Modules and MCs taken.") + + "\n\n\t" + String.format("%-52s %s", + "taken [MODULE CODE] y/[YEAR] s/[SEMESTER] g/[GRADE]", + "Adds a module to the planner as a module you have already taken.") + + "\n\n\t" + String.format("%-52s %s", "details [MODULE CODE]", + "Displays the details of given module, including") + + "\n\t" + String.format("%-52s %s", "", + "Title, Description, Prerequisites, Module Credits") + + "\n\t" + String.format("%-52s %s", "", "and if it can be SU-ed.") + + "\n\n\t" + String.format("%-52s %s", + "init n/[NAME] c/[COURSE NUMBER]", "Initialize User.") + + "\n\n\t" + String.format("%-52s %s", "", "[COURSE NUMBER] -> [COURSE NAME]") + + "\n\t" + String.format("%-52s %s", "", "1 -> Biomedical Engineering") + + "\n\t" + String.format("%-52s %s", "", "2 -> Chemical Engineering") + + "\n\t" + String.format("%-52s %s", "", "3 -> Civil Engineering") + + "\n\t" + String.format("%-52s %s", "", "4 -> Computer Engineering") + + "\n\t" + String.format("%-52s %s", "", "5 -> Electrical Engineering") + + "\n\t" + String.format("%-52s %s", "", "6 -> Environmental Engineering") + + "\n\t" + String.format("%-52s %s", "", "7 -> Industrial and Systems Engineering") + + "\n\t" + String.format("%-52s %s", "", "8 -> Mechanical Engineering") + + "\n\t" + ), false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/InitCommand.java b/src/main/java/seedu/penus/logic/commands/InitCommand.java new file mode 100644 index 0000000000..f8b3983d4d --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/InitCommand.java @@ -0,0 +1,46 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.model.ModelManager; + +public class InitCommand extends Command { //set username and course, command: init n/Jiun Yuan c/1 + public static final String COMMAND_WORD = "init"; + + public static final String MESSAGE = "Initialization Complete"; + + public final String name; + + public final Integer courseCode; + + public InitCommand (String name, int courseCode ) { + this.name = name; + this.courseCode = courseCode; + } + + @Override + public CommandResult execute(ModelManager model) throws InvalidCommandException { + String course = ""; + model.setUserName(name); + switch(courseCode) { + case 1: course = "Biomedical Engineering"; + break; + case 2: course = "Chemical Engineering"; + break; + case 3: course = "Civil Engineering"; + break; + case 4: course = "Computer Engineering"; + break; + case 5: course = "Electrical Engineering"; + break; + case 6: course = "Environmental Engineering"; + break; + case 7: course = "Industrial and Systems Engineering"; + break; + case 8: course = "Mechanical Engineering"; + break; + default: throw new InvalidCommandException("Enter within the index. Please initialize again"); + } + model.setUserCourse(course); + return new CommandResult(MESSAGE, false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/ListCommand.java b/src/main/java/seedu/penus/logic/commands/ListCommand.java new file mode 100644 index 0000000000..44994fac4d --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/ListCommand.java @@ -0,0 +1,131 @@ +package seedu.penus.logic.commands; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.utils.Grade; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; + +public class ListCommand extends Command { + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE = "List of"; + + public final int year; + public final int semester; + + public ListCommand() { + this.year = 0; + this.semester = 0; + } + + //Overloaded constructor for filter + public ListCommand(int year, int semester) { + this.year = year; + this.semester = semester; + } + + @Override + public CommandResult execute(ModelManager model) throws PenusException { + List messageArray = new ArrayList<>(); + Map>> modules = new HashMap<>(); + List moduleList = model.getModuleListObj(); + for (Module currMod : moduleList) { + int currYear = currMod.getYear(); + int currSem = currMod.getSem(); + String[] arr = new String[] { currMod.getCode(), currMod.getGrade() }; + modules.computeIfAbsent(currYear, k -> new HashMap<>()) + .computeIfAbsent(currSem,k -> new ArrayList<>()) + .add(arr); + } + + //list all modules + if (this.year == 0 && this.semester == 0) { + List allModules = getAllMods(modules); + messageArray.addAll(allModules); + } + + //list for year only + if (this.year != 0 && this.semester == 0) { + List yearModules = getYearMods(modules); + messageArray.addAll(yearModules); + } + + //list year and sem specific + if (this.year != 0 && this.semester != 0) { + List semModules = getSemMods(modules); + messageArray.addAll(semModules); + } + + messageArray.add(""); + messageArray.add(Grade.getOverallCAP(moduleList)); + + return new CommandResult(messageArray, true); + } + + private List getAllMods(Map>> modules) { + List messageList = new ArrayList<>(); + for (int y = 1; y < 5; y++) { + for (int s = 1; s <= 2; s++) { + messageList.add("- Year " + y + " Semester " + s + " -"); + + List modulesInYearSem = modules.getOrDefault(y, new HashMap<>()) + .getOrDefault(s, new ArrayList<>()); + + if (modulesInYearSem.isEmpty()) { + messageList.add("No modules taken/added."); + } else { + for (String[] string : modulesInYearSem) { + messageList.add(string[0] + " " + string[1]); + } + } + messageList.add(""); + } + } + return messageList; + } + + private List getYearMods(Map>> modules) throws PenusException { + List messageList = new ArrayList<>(); + for (int s = 1; s <= 2; s++) { + messageList.add("- Year " + this.year + " Semester " + s + " -"); + + List modulesInYear = modules.getOrDefault(this.year, new HashMap<>()) + .getOrDefault(s, new ArrayList<>()); + + if (modulesInYear.isEmpty()) { + messageList.add("No modules taken/added"); + } else { + for (String[] string : modulesInYear) { + messageList.add(string[0] + " " + string[1]); + } + } + messageList.add(Grade.getSemCAP(modulesInYear)); + messageList.add(""); + } + + return messageList; + } + + private List getSemMods(Map>> modules) throws PenusException { + List messageList = new ArrayList<>(); + messageList.add("- Year " + this.year + " Semester " + this.semester + " -"); + + List modulesInYearAndSem = modules.getOrDefault(this.year, new HashMap<>()) + .getOrDefault(this.semester, new ArrayList<>()); + + if (modulesInYearAndSem.isEmpty()) { + messageList.add("No modules taken/added"); + } else { + for (String[] string : modulesInYearAndSem) { + messageList.add(string[0] + " " + string[1]); + } + } + messageList.add(Grade.getSemCAP(modulesInYearAndSem)); + + return messageList; + } +} diff --git a/src/main/java/seedu/penus/logic/commands/MarkCommand.java b/src/main/java/seedu/penus/logic/commands/MarkCommand.java new file mode 100644 index 0000000000..4ab632d93f --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/MarkCommand.java @@ -0,0 +1,52 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.logic.utils.ModuleRetriever; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; +import seedu.penus.model.ModuleList; + +public class MarkCommand extends Command { + public static final String COMMAND_WORD = "mark"; + public static final String MESSAGE = + "Module has been taken:\n" + + "\t %s"; + + public final String moduleCode; + public final String grade; + + + // Creates a PlanCommand with the moduleCode and grade + public MarkCommand(String moduleCode, String grade) { + this.moduleCode = moduleCode; + this.grade = grade; + } + + /* + * Searches for the index of the moduleCode in the list and passes it to ModelManager to mark the module by index + */ + @Override + public CommandResult execute(ModelManager model) throws InvalidCommandException { + int index = -1; + ModuleList list = model.getModuleList(); + for (int i = 0; i < list.size(); i++) { + if (list.getModule(i).getCode().equals(moduleCode)) { + index = i; + } + } + if (index == -1) { + throw new InvalidCommandException("No such module exists!"); + } + + if (this.grade.equals("S") || this.grade.equals("U")) { + if (!ModuleRetriever.getSUstatus(this.moduleCode)) { + throw new InvalidCommandException("The module cannot be Su-ed"); + } + } + + model.markModule(index, this.grade); + Module markedModule = model.getModule(index); + + return new CommandResult(String.format(MESSAGE, markedModule), false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/PlanCommand.java b/src/main/java/seedu/penus/logic/commands/PlanCommand.java new file mode 100644 index 0000000000..ce82343b2f --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/PlanCommand.java @@ -0,0 +1,46 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.DuplicateModuleException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.utils.ModuleRetriever; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; + +/** + * Adds a Plan module to the list + */ +public class PlanCommand extends Command { + public static final String COMMAND_WORD = "plan"; + public static final String MESSAGE = + "Module has been added:\n" + + "\t %s\n" + + "\tYou have %s module(s) in your planner"; + + public final Module plan; + + /** + * Creates a PlanCommand to add the specified {@code Module} + * by constructing a new Module object with the given parameters + * @param moduleCode string + * @param semester int + * @param year int + */ + public PlanCommand(String moduleCode, int year, int semester) { + this.plan = new Module(moduleCode, year, semester); + } + + @Override + public CommandResult execute(ModelManager model) throws PenusException { + if (model.hasModule(plan)) { + throw new DuplicateModuleException(); + } + if (!ModuleRetriever.isValidMod(this.plan.getCode())) { + throw new InvalidModuleException(); + } + model.addModule(plan); + + return new CommandResult(String.format(MESSAGE, plan, model.getSize()), false); + } +} + diff --git a/src/main/java/seedu/penus/logic/commands/RemoveCommand.java b/src/main/java/seedu/penus/logic/commands/RemoveCommand.java new file mode 100644 index 0000000000..dea68b248d --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/RemoveCommand.java @@ -0,0 +1,45 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.ModuleList; +import seedu.penus.model.Module; + +public class RemoveCommand extends Command { + public static final String COMMAND_WORD = "remove"; + public static final String MESSAGE = + "Module has been removed:\n" + + "\t %s\n" + + "\tYou have %s module(s) in your planner"; + + public final String moduleCode; + + /** + * Creates a PlanCommand to with the moduleCode attribute + * @param moduleCode string + */ + public RemoveCommand(String moduleCode) { + this.moduleCode = moduleCode; + } + + /** + * Searches for the index of the moduleCode in the list and passes it to ModelManager to remove the module by index + */ + @Override + public CommandResult execute(ModelManager model) throws InvalidCommandException { + int index = -1; + ModuleList list = model.getModuleList(); + for (int i = 0; i < list.size(); i++) { + if (list.getModule(i).getCode().equals(moduleCode)) { + index = i; + } + } + if (index == -1) { + throw new InvalidCommandException("No such module exists!"); + } + Module removedModule = model.getModule(index); + model.removeModule(index); + + return new CommandResult(String.format(MESSAGE, removedModule, model.getSize()), false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/StatusCommand.java b/src/main/java/seedu/penus/logic/commands/StatusCommand.java new file mode 100644 index 0000000000..e83214974c --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/StatusCommand.java @@ -0,0 +1,128 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.utils.MCsTaken; +import seedu.penus.logic.utils.ModuleRetriever; +import seedu.penus.model.ModelManager; +import seedu.penus.model.ModuleList; +import seedu.penus.ui.Ui; + +import java.util.ArrayList; +import java.util.List; + +public class StatusCommand extends Command { + public static final String COMMAND_WORD = "status"; + + public List getTakenCoreModsList(ModelManager model) { + List coreMods = model.getCoreModList().get(model.getUserCourse()); + List takenCoreMods = new ArrayList<>(); + ModuleList moduleList = model.getModuleList(); + if (!model.getGEC().equals("")){ + takenCoreMods.add(model.getGEC()); + } + if (!model.getGESS().equals("")){ + takenCoreMods.add(model.getGESS()); + } + if (!model.getGEN().equals("")){ + takenCoreMods.add(model.getGEN()); + } + for (String coreModCode : coreMods) { + for (int i = 0; i < moduleList.size(); i++) { + String moduleCode = moduleList.getModule(i).getCode(); + boolean isCurrentUserModuleTaken = moduleList.getModule(i).getStatus().equals("Taken"); + if (coreModCode.equals(moduleCode) && isCurrentUserModuleTaken) { + takenCoreMods.add(moduleCode); + break; + } + } + } + return takenCoreMods; + } + + public List getUntakenCoreModsList(ModelManager model) { + List coreMods = model.getCoreModList().get(model.getUserCourse()); + List untakenCoreMods = new ArrayList<>(); + ModuleList moduleList = model.getModuleList(); + for (String coreModCode : coreMods) { + boolean isCoreModTaken = false; + for (int i = 0; i < moduleList.size(); i++) { + String moduleCode = moduleList.getModule(i).getCode(); + boolean isCurrentUserModuleTaken = moduleList.getModule(i).getStatus().equals("Taken"); + if (coreModCode.equals(moduleCode) && isCurrentUserModuleTaken) { + isCoreModTaken = true; + break; + } + } + if (!isCoreModTaken) { + untakenCoreMods.add(coreModCode); + } + } + return untakenCoreMods; + } + + public int getNumberOfCoreMCsTaken (ModelManager model) { + int numberOfElectiveMCs = 0; + List takenCoreModsList = getTakenCoreModsList(model); + for (String moduleCode : takenCoreModsList) { + numberOfElectiveMCs = numberOfElectiveMCs + + Integer.parseInt(ModuleRetriever.getModuleCredit2223(moduleCode)); + } + return numberOfElectiveMCs; + } + + public String moduleCodeToString(String moduleCode) { + return moduleCode + " "+ ModuleRetriever.getTitle2223(moduleCode) + + " MCs: " + ModuleRetriever.getModuleCredit2223(moduleCode); + } + + + @Override + public CommandResult execute(ModelManager model) throws PenusException { + if (model.getUserName().equals("")) { + throw new InvalidCommandException( + "Please initialise first with the init command!" + ); + } + Ui.showLoadingAnimation(); + StringBuilder sb = new StringBuilder(); + List takenCoreModsList = getTakenCoreModsList(model); + List untakenCoreModsList = getUntakenCoreModsList(model); + int totalMCsTaken = MCsTaken.numberOfMcsTaken(model.getModuleList().modules); + int coreMCsTaken = getNumberOfCoreMCsTaken(model); + int electiveMCsTaken = totalMCsTaken - coreMCsTaken; + List messageArray = new ArrayList<>(); + messageArray.add("-------------------------- User --------------------------"); + messageArray.add("\tUser: " + model.getUserName()); + messageArray.add("\tCourse: " + model.getUserCourse()); + messageArray.add("\t------------------- Core Modules Taken --------------------"); + for (String s : takenCoreModsList){ + messageArray.add("\t" + moduleCodeToString(s)); + } + messageArray.add("\t----------------- Core Modules Not Taken ------------------"); + if (model.getGEC().equals("")){ + messageArray.add("\tGECXXXX"); + } + if (model.getGESS().equals("")){ + messageArray.add("\tGESSXXXX"); + } + if (model.getGEN().equals("")){ + messageArray.add("\tGENXXXX"); + } + for (String s : untakenCoreModsList){ + messageArray.add("\t" + moduleCodeToString(s)); + } + messageArray.add("\t------------------------ MCs Status -----------------------"); + messageArray.add("\tCore Modules MCs Taken: " + Integer.toString(coreMCsTaken)); + messageArray.add("\tElective MCs Taken: " + Integer.toString(electiveMCsTaken)); + messageArray.add("\tTotal MCs Taken: " + Integer.toString(totalMCsTaken) + "/160"); + + for (String s : messageArray){ + sb.append(s).append("\n"); + } + + String message = sb.toString(); + Ui.stopLoadingAnimation(); + return new CommandResult(message, false); + } +} diff --git a/src/main/java/seedu/penus/logic/commands/TakenCommand.java b/src/main/java/seedu/penus/logic/commands/TakenCommand.java new file mode 100644 index 0000000000..e57e04d352 --- /dev/null +++ b/src/main/java/seedu/penus/logic/commands/TakenCommand.java @@ -0,0 +1,52 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.DuplicateModuleException; +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.utils.ModuleRetriever; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; + +/** + * Adds a Taken module to the list + */ +public class TakenCommand extends Command { + public static final String COMMAND_WORD = "taken"; + public static final String MESSAGE = + "Module has been added:\n" + + "\t %s\n" + + "\tYou have %s module(s) in your planner"; + public final Module taken; + + /** + * Creates a TakenCommand to add the specified {@code Module} + * by constructing a new Module object with the given parameters + * @param moduleCode string + * @param year int + * @param semester int + * @param grade string + */ + public TakenCommand(String moduleCode, int year, int semester, String grade) { + this.taken = new Module(moduleCode, year, semester, grade); + } + + @Override + public CommandResult execute(ModelManager model) throws PenusException { + if (!ModuleRetriever.isValidMod(this.taken.getCode())) { + throw new InvalidModuleException(); + } + if (taken.getGrade().equals("U") || taken.getGrade().equals("S")) { + if (!ModuleRetriever.getSUstatus(taken.getCode())){ + throw new InvalidCommandException("The module cannot be SU-ed"); + } + } + if (model.hasModule(taken)) { + throw new DuplicateModuleException(); + } + + model.addModule(taken); + + return new CommandResult(String.format(MESSAGE, taken, model.getSize()), false); + } +} diff --git a/src/main/java/seedu/penus/logic/parser/CliSyntax.java b/src/main/java/seedu/penus/logic/parser/CliSyntax.java new file mode 100644 index 0000000000..ce8ec0d3b7 --- /dev/null +++ b/src/main/java/seedu/penus/logic/parser/CliSyntax.java @@ -0,0 +1,23 @@ +package seedu.penus.logic.parser; + +public class CliSyntax { + public static final String LIST = "list"; + public static final String STATUS = "status"; + public static final String EXIT = "exit"; + public static final String MARK = "mark"; + public static final String REMOVE = "remove"; + public static final String PLAN = "plan"; + public static final String TAKEN = "taken"; + public static final String PREREQ = "prerequisite"; + public static final String DESC = "description"; + public static final String TITLE = "title"; + public static final String MC = "modulecredit"; + public static final String DETAILS = "details"; + public static final String HELP = "help"; + public static final String INIT = "init"; + public static final String CLEAR = "clear"; + + public static final String PREFIX_YEAR = "y/"; + public static final String PREFIX_SEMESTER = "s/"; + public static final String PREFIX_GRADE = "g/"; +} diff --git a/src/main/java/seedu/penus/logic/parser/Parser.java b/src/main/java/seedu/penus/logic/parser/Parser.java new file mode 100644 index 0000000000..e229eac63b --- /dev/null +++ b/src/main/java/seedu/penus/logic/parser/Parser.java @@ -0,0 +1,370 @@ +package seedu.penus.logic.parser; + +import static seedu.penus.logic.parser.CliSyntax.LIST; +import static seedu.penus.logic.parser.CliSyntax.STATUS; +import static seedu.penus.logic.parser.CliSyntax.EXIT; +import static seedu.penus.logic.parser.CliSyntax.MARK; +import static seedu.penus.logic.parser.CliSyntax.REMOVE; +import static seedu.penus.logic.parser.CliSyntax.PLAN; +import static seedu.penus.logic.parser.CliSyntax.TAKEN; + +import static seedu.penus.logic.parser.CliSyntax.DETAILS; +import static seedu.penus.logic.parser.CliSyntax.HELP; +import static seedu.penus.logic.parser.CliSyntax.INIT; +import static seedu.penus.logic.parser.CliSyntax.CLEAR; + +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.common.exceptions.InvalidFormatException; +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.InvalidSemesterException; +import seedu.penus.common.exceptions.InvalidYearException; +import seedu.penus.common.exceptions.PenusException; + +import seedu.penus.logic.utils.Grade; +import seedu.penus.logic.commands.ClearCommand; +import seedu.penus.logic.commands.Command; +import seedu.penus.logic.commands.PlanCommand; +import seedu.penus.logic.commands.TakenCommand; +import seedu.penus.logic.commands.RemoveCommand; +import seedu.penus.logic.commands.DetailsCommand; +import seedu.penus.logic.commands.ExitCommand; +import seedu.penus.logic.commands.InitCommand; +import seedu.penus.logic.commands.ListCommand; +import seedu.penus.logic.commands.MarkCommand; +import seedu.penus.logic.commands.StatusCommand; +import seedu.penus.logic.commands.HelpCommand; + +public class Parser { + /** + * Parses user input into a Command object for execution. + * @param userInput input string from user + * @return Command object if no arguments needed, xYZParser(arguments) if arguments needed + * @throws PenusException invalid command + */ + public Command parseCommand(String userInput) throws PenusException { + String[] inputArray = userInput.split(" ", 2); + String command = inputArray[0]; + String arguments = ""; + if (inputArray.length > 1) { + arguments = inputArray[1]; + } + switch (command) { + case INIT: + return initParser(arguments); + + case HELP: + return new HelpCommand(); + + case PLAN: + return planParser(arguments); + + case TAKEN: + return takenParser(arguments); + + case MARK: + return markParser(arguments); + + case LIST: + return listParser(arguments); + + case STATUS: + return new StatusCommand(); + + case REMOVE: + return removeParser(arguments); + + case DETAILS: + return detailsParser(arguments); + + case CLEAR: + return clearParser(arguments); + + case EXIT: + return new ExitCommand(); + + default: + throw new InvalidCommandException(); + } + } + + /** + * Parses the given {@code String} of arguments in the context of the PlanCommand + * and returns an PlanCommand object for execution. + * @param args aguments + * @return Command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command planParser(String args) throws PenusException { + if (args.contains("g/")) { + throw new InvalidFormatException("Grade should not be included!"); + } + String[] planDetails = args.split(" y/| s/", 3); + if (planDetails.length != 3 || planDetails[1].length() == 0 || planDetails[2].length() == 0) { + throw new InvalidFormatException("Try again in the format: plan MODULE_CODE y/YEAR s/SEM"); + } + + String moduleCode = planDetails[0].toUpperCase().trim(); + int year; + int semester; + try { + year = Integer.parseInt(planDetails[1].trim()); + semester = Integer.parseInt(planDetails[2].trim()); + } catch (NumberFormatException e) { + throw new InvalidFormatException("y/ or s/ Must be specified as an integer!"); + } + + if (year < 1 || year > 4) { + throw new InvalidYearException("Year must be 1 to 4. Please try again."); + } + + if (semester != 1 && semester != 2) { + throw new InvalidSemesterException("Semester must be 1 or 2!"); + } + return new PlanCommand(moduleCode, year, semester); + } + + /** + * Parses the given {@code String} of arguments in the context of the TakenCommand + * and returns an TakenCommand object for execution. + * @param args arguments + * @return Command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command takenParser(String args) throws PenusException { + String[] takenDetails = args.split(" y/| s/| g/", 4); + if (takenDetails.length != 4) { + throw new InvalidFormatException("Try again in the format: taken MODULE_CODE y/YEAR s/SEM g/GRADE"); + } + if (takenDetails[1].length() == 0 || takenDetails[2].length() == 0 || takenDetails[3].length() == 0) { + throw new InvalidFormatException("Try again, y/ s/ g/ cannot be empty"); + } + String moduleCode = takenDetails[0].toUpperCase().trim(); + int year; + int semester; + try { + year = Integer.parseInt(takenDetails[1].trim()); + semester = Integer.parseInt(takenDetails[2].trim()); + } catch (NumberFormatException e) { + throw new InvalidFormatException("y/ or s/ Must be specified as an integer!"); + } + + String grade = takenDetails[3].toUpperCase().trim(); + if (!Grade.isValid(grade)) { + throw new InvalidGradeException(); + } + if (year < 1 || year > 4) { + throw new InvalidYearException("Year must be 1 to 4. Please try again."); + } + if (semester != 1 && semester != 2) { + throw new InvalidFormatException("Semester must be 1 or 2!"); + } + + return new TakenCommand(moduleCode, year, semester, grade); + } + + /** + * Parses the given {@code String} of arguments in the context of the + * MarkCommand and returns an MarkCommand object for execution. + * + * @param args string + * @return command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command markParser(String args) throws PenusException { + if (!args.contains("g/")) { + throw new InvalidFormatException("\tTry again in the format: mark MODULE_CODE g/GRADE"); + } + String[] details = args.split(" g/"); + if (!Grade.isValid(details[1].trim())) { + throw new InvalidGradeException(); + } + String moduleCode = details[0].toUpperCase().trim(); + String grade = details[1].toUpperCase().trim(); + + return new MarkCommand(moduleCode, grade); + } + + /** + * Parses the given {@code String} of arguments in the context of the + * ListCommand and returns an ListCommand object for execution. + * + * @param args string + * @return Command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command listParser(String args) throws PenusException { + if (args.equals("") || args.equals(" ")) { + // list command with all modules + return new ListCommand(); + } + + if (args.contains("s/") && !args.contains("y/")) { // Semester specified but year not specified + throw new InvalidFormatException( + "\tTry again, y/ must not be empty if s/ is not empty. " + + "To show modules for that semester, please specify the year of study."); + } + + String[] details = args.split("y/| s/", 3); + int year = 0; + int semester = 0; + try { + year = Integer.parseInt(details[1].trim()); + } catch (NumberFormatException nfe) { + throw new InvalidFormatException("\tYear must be specified as an integer!"); + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidFormatException("\tTry again in the format: list y/YEAR s/SEM or list y/YEAR or list"); + } + if (year < 1 || year > 4) { + throw new InvalidFormatException("\tYear must be 1 to 4. Please try again."); + } + + if (details.length == 2) { + return new ListCommand(year, 0); + } else if (details.length == 3) { + if (args.contains("s/")) { + try { + semester = Integer.parseInt(details[2].trim()); + } catch (NumberFormatException nfe) { + throw new InvalidFormatException("\tSemester must be specified as an integer!"); + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidFormatException( + "\tTry again in the format: list y/YEAR s/SEM or list y/YEAR or list"); + } + } + + if (semester != 1 && semester != 2) { + throw new InvalidFormatException("\tSemester must be 1 or 2!"); + } + return new ListCommand(year, semester); + } else { + throw new InvalidFormatException("\tTry again in the format: list y/YEAR s/SEM or list y/YEAR or list"); + } + } + + /** + * Parses the given {@code String} of arguments in the context of the RemoveCommand + * and returns an RemoveCommand object for execution. + * @param args arguments + * @return command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command removeParser(String args) throws PenusException { + String moduleCode = args.toUpperCase().trim(); + if (args.equals("")) { + throw new InvalidModuleException(args); + } + if (args.equals(" ")) { + throw new InvalidModuleException("\tPlease specify a module"); + } + + return new RemoveCommand(moduleCode); + } + + /** + * Parses the given {@code String} of arguments in the context of the DetailsCommand + * and returns an DetailsCommand object for execution. + * @param args arguments + * @return command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command detailsParser(String args) throws PenusException { + String[] details = args.split(" "); + if (args.equals("") || args.equals(" ") || details.length >= 2) { + throw new InvalidModuleException(args); + } + String moduleCode = args.toUpperCase().trim(); + + return new DetailsCommand(moduleCode); + } + + /** + * Parses the given {@code String} of arguments in the context of the InitCommand + * and returns an InitCommand object for execution. + * @param args arguments + * @return command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command initParser (String args) throws PenusException { + int courseCode; + String [] initDetails = args.split ("n/| c/"); + if (initDetails.length != 3) { + throw new InvalidFormatException("Try again in the format: init n/NAME c/COURSE CODE"); + } + if (initDetails[1].length() == 0 || initDetails[2].length() == 0) { + throw new InvalidFormatException("Try again, n/ c/ cannot be empty"); + } + String name = initDetails[1].trim(); + if (!name.matches("[a-zA-Z ]+")){ + throw new InvalidFormatException("Name must only include letters and spaces."); + } + try { + courseCode = Integer.parseInt(initDetails[2].trim()); + } catch (NumberFormatException e){ + throw new InvalidFormatException("c/ must be an integer"); + } + return new InitCommand(name, courseCode); + + } + + + /** + * Parses the given {@code String} of arguments in the context of the ClearCommand + * and returns an ClearCommand object for execution. + * + * @param args arguments + * @return command object + * @throws PenusException if the user input does not conform the expected format + */ + public Command clearParser(String args) throws PenusException{ + if (args.equals("") || args.equals(" ")) { + //clear all modules + return new ClearCommand(); + } + + // Semester specified but year not specified + if (args.contains("s/") && !args.contains("y/")) { + throw new InvalidFormatException( + "\tTry again, y/ must not be empty if s/ is not empty. " + + "To clear modules for that semester, please specify the year of study."); + } + + String[] details = args.split("y/| s/",3); + + //Default values + int year = 0; + int semester = 0; + + try { + year = Integer.parseInt(details[1].trim()); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + throw new InvalidFormatException("Year must be specified as an integer!"); + } + //Invalid year + if (year < 1 || year > 4) { + throw new InvalidFormatException("Year must be within 1 to 4"); + } + + //Only year specified, semester = 0 + if (details.length == 2) { + return new ClearCommand(year,semester); + + } else if (details.length == 3) { //Year and Semester specified + if (args.contains("s/")) { + try { + semester = Integer.parseInt(details[2].trim()); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + throw new InvalidFormatException("Semester must be specified as an integer!"); + } + } + + //Invalid semester + if (semester != 1 && semester != 2) { + throw new InvalidFormatException("Semester must be 1 or 2!"); + } + return new ClearCommand(year, semester); + } else { + throw new InvalidFormatException("Try again in the format: clear y/YEAR s/SEM or clear y/YEAR or clear"); + } + } +} diff --git a/src/main/java/seedu/penus/logic/utils/DetailsCompiler.java b/src/main/java/seedu/penus/logic/utils/DetailsCompiler.java new file mode 100644 index 0000000000..a42fbc42d2 --- /dev/null +++ b/src/main/java/seedu/penus/logic/utils/DetailsCompiler.java @@ -0,0 +1,58 @@ +package seedu.penus.logic.utils; + +public class DetailsCompiler extends ModuleRetriever{ + protected String moduleCode; + + /** + * Retrieves details of given module code, and displays error message if information is not available + * @param module string + */ + public static String getDetails(String module) { + String title; + String description; + String prereqs; + String credits; + String suStatusDescription; + + try { + if (!isValidMod(module)) { + return "This module code is invalid. Try again."; + }; + } catch (Exception e) { + return "This module code is invalid. Try again."; + } + + try { + title = getTitle2223(module); + } catch (Exception e) { + title = "\tTitle is not available"; + } + try { + description = "\t" + getDescription(module); + } catch (Exception e) { + description = "\tDescription is not available"; + } + try { + prereqs = "\tPre-Requisites: " + getPrerequisite(module); + } catch (Exception e) { + prereqs = "\tPre-Requisites information is not available"; + } + try { + credits = "\tMCs: " + getModuleCredit2223(module); + } catch (Exception e) { + credits = "\tModular Credits information is not available"; + } + try { + boolean suStatus = getSUstatus(module); + if (suStatus) { + suStatusDescription = "\tModule can be SU-ed."; + } else { + suStatusDescription = "\tModule cannot be SU-ed."; + } + } catch (Exception e) { + suStatusDescription = "\tSU information is not available"; + } + + return title + "\n" + description + "\n" + prereqs + "\n" + credits + "\n" + suStatusDescription; + } +} diff --git a/src/main/java/seedu/penus/logic/utils/Grade.java b/src/main/java/seedu/penus/logic/utils/Grade.java new file mode 100644 index 0000000000..bea38661aa --- /dev/null +++ b/src/main/java/seedu/penus/logic/utils/Grade.java @@ -0,0 +1,205 @@ +package seedu.penus.logic.utils; + +import java.util.List; +import java.util.Arrays; + +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.model.Module; + +public class Grade { + /** + * Converts a {@code String} grade into its associated gradepoint + * + * @param grade string + * @return {@code double} corresponding value of the grade + * @throws InvalidGradeException invalid grade + */ + public static double getGradePoint(String grade) throws InvalidGradeException { + double gradePoint; + switch (grade.toUpperCase()) { + case "A+": + case "A": + gradePoint = 5.0; + break; + + case "A-": + gradePoint = 4.5; + break; + + case "B+": + gradePoint = 4.0; + break; + + case "B": + gradePoint = 3.5; + break; + + case "B-": + gradePoint = 3.0; + break; + + case "C+": + gradePoint = 2.5; + break; + + case "C": + gradePoint = 2.0; + break; + + case "D+": + gradePoint = 1.5; + break; + + case "D": + gradePoint = 1.0; + break; + + case "F": + gradePoint = 0.0; + break; + + default: + throw new InvalidGradeException(); + + } + return gradePoint; + } + + /** + * Checks if a grade {@code String} is valid and within expected inputs + * + * @param grade string + * @return Boolean true if grade is valid + */ + public static Boolean isValid(String grade) { + List validGrades = Arrays.asList( + "A+", "A", "A-", + "B+", "B", "B-", + "C+", "C", + "D+", "D", + "F", "S", "U", + "CS", "CU" + ); + return validGrades.contains(grade.toUpperCase()); + } + + /** + * For every module taken, calculate weighted score = number of MC * grade + * Sum up weighted score for all mods and divide by total MCs taken thus far + * S/U grades are not calculated for in Overall CAP + * + * @param moduleList the list containing all modules taken + * @return total CAP for all mods taken thus far + * @throws InvalidGradeException if there exists an unidentified Grade type + */ + public static double calculateOverallCAP(List moduleList) throws InvalidGradeException { + double cap; + int numOfSUMods = 0; + int numOfModsTaken = 0; + int numOfModsPlanned = 0; + double totalScore = 0.0; + double totalMC = 0.0; + String numberOfMCs; + for (Module module : moduleList) { + if (module.getStatus().equals("Taken")) { + numOfModsTaken++; + if (module.getGrade().matches("S|U|CS|CU")) { + numOfSUMods++; + } else { + numberOfMCs = ModuleRetriever.getModuleCredit2223(module.getCode()); + double weightedScore = Double.parseDouble(numberOfMCs) * + module.getGradePoint(); + totalScore += weightedScore; + totalMC += Double.parseDouble(numberOfMCs); + } + } else { + numOfModsPlanned++; + } + } + if (numOfModsPlanned == moduleList.size()) {//all mods are planned + cap = 0.0; + } else if (numOfSUMods == numOfModsTaken) { //all mods are SU + cap = 5.0; + } else { + cap = totalScore / totalMC; + } + return cap; + } + + /** + * For every module taken in a semester, calculate weighted score = number of MC + * * grade + * Sum up weighted score for all mods and divide by total MCs taken in the + * semester + * S/U grades are not calculated for in Semester CAP + * + * @param semArray list of String array containing moduleCode and moduleGrade + * @return total CAP for a particular semester + * @throws InvalidGradeException if there exists an unidentified Grade type + */ + public static double calculateSemCAP(List semArray) throws InvalidGradeException { + double cap; + int numOfSUMods = 0; + double totalScore = 0.0; + double totalMC = 0.0; + String numberOfMCs; + for (String[] module : semArray) { + String moduleCode = module[0]; + String moduleGrade = module[1]; + if (moduleGrade.matches("S|U|CS|CU")) { + numOfSUMods++; + } + if (!moduleGrade.matches("S|U|CS|CU") && !moduleGrade.equals("")) { + numberOfMCs = ModuleRetriever.getModuleCredit2223(moduleCode); + double weightedScore = Double.parseDouble(numberOfMCs) * + Grade.getGradePoint(moduleGrade); + totalScore += weightedScore; + totalMC += Double.parseDouble(numberOfMCs); + } + } + if (numOfSUMods == semArray.size()) {//all mods in sem are SU-ed + cap = 5.0; + } else if ((totalScore == 0.0) || (totalMC == 0.0)) {//all mods are planned + cap = 0.0; + } else { + cap = totalScore / totalMC; + } + return cap; + } + + /** + * Calls calculateOverallCAP and prints the overall CAP to 2 decimal places + * + * @param moduleList the list containing all modules taken + * @return capMessage String + * @throws InvalidGradeException if there exists an unidentified Grade + */ + public static String getOverallCAP(List moduleList) throws InvalidGradeException { + String capMessage; + if (moduleList.isEmpty()) { + capMessage = "Overall CAP : 0.00\n"; + } else { + Double overallCAP = calculateOverallCAP(moduleList); + capMessage = String.format("Overall CAP : %.2f\n", overallCAP); + } + return capMessage; + } + + /** + * Calls calculateSemCAP and prints the semester CAP to 2 decimal places + * + * @param semArray list of String array containing moduleCode and moduleGrade + * @return semCapMessage String + * @throws InvalidGradeException if there exists an unidentified Grade + */ + public static String getSemCAP(List semArray) throws InvalidGradeException { + String semCapMessage; + if (semArray.isEmpty()) { + semCapMessage = "Semester CAP : 0.00\n"; + } else { + Double semCAP = calculateSemCAP(semArray); + semCapMessage = String.format("Semester CAP : %.2f\n", semCAP); + } + return semCapMessage; + } +} diff --git a/src/main/java/seedu/penus/logic/utils/MCsTaken.java b/src/main/java/seedu/penus/logic/utils/MCsTaken.java new file mode 100644 index 0000000000..71f7fa9b17 --- /dev/null +++ b/src/main/java/seedu/penus/logic/utils/MCsTaken.java @@ -0,0 +1,22 @@ +package seedu.penus.logic.utils; + +import java.util.List; +import seedu.penus.model.Module; + +public class MCsTaken { + /** + * Iterates throught he moduleList and adds the number of MCs of each taken module to the sum + * The MCs are retreieved from the ModuleRetriever API from NUSMods. + * @param moduleList list of modules + * @return int total number of MCs of the taken modules in the list + */ + public static int numberOfMcsTaken(List moduleList) { + int numberOfMcs = 0; + for (Module module : moduleList) { + if (module.getStatus().equals("Taken")) { + numberOfMcs = numberOfMcs + Integer.parseInt(ModuleRetriever.getModuleCredit2223(module.getCode())); + } + } + return numberOfMcs; + } +} diff --git a/src/main/java/seedu/penus/logic/utils/ModuleRetriever.java b/src/main/java/seedu/penus/logic/utils/ModuleRetriever.java new file mode 100644 index 0000000000..32015f06e1 --- /dev/null +++ b/src/main/java/seedu/penus/logic/utils/ModuleRetriever.java @@ -0,0 +1,205 @@ +package seedu.penus.logic.utils; + +import org.json.simple.JSONObject; +import org.json.simple.JSONArray; +import org.json.simple.parser.JSONParser; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Scanner; +import seedu.penus.common.exceptions.InvalidModuleAPIException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.NoInternetException; + + +// test comment +public class ModuleRetriever { + public static JSONObject moduleInfo2223; + public static JSONObject moduleInfo2122; + public static void connectionChecker() throws NoInternetException { + try { + // Public API: + // https://api.nusmods.com/v2/2022-2023/modules/.json + + + URL url = new URL("https://api.nusmods.com/v2/2022-2023/modules/CS1231.json"); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.connect(); + + // check if connect is made + int responseCode = conn.getResponseCode(); + + // 200 OK + if (responseCode != 200) { + throw new Exception(); + } + } catch (Exception e) { + throw new NoInternetException(); + } + } + + public static void getData2223(String module) throws InvalidModuleAPIException{ + try { + // Public API: + // https://api.nusmods.com/v2/2022-2023/modules/.json + + module = module.toUpperCase(); + + URL url = new URL("https://api.nusmods.com/v2/2022-2023/modules/" + module + ".json"); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.connect(); + + // check if connect is made + int responseCode = conn.getResponseCode(); + + // 200 OK + if (responseCode != 200) { + throw new InvalidModuleAPIException(); + } else { + Scanner scanner = new Scanner(url.openStream()); + String informationString = scanner.nextLine(); + + // Close scanner + scanner.close(); + + // JSON simple library Setup with Maven is used to convert strings to JSON + JSONParser parser = new JSONParser(); + Object obj = parser.parse(String.valueOf(informationString)); + JSONArray dataObject = new JSONArray(); + dataObject.add(obj); + + // Get the first JSON object in the JSON array + moduleInfo2223 = (JSONObject) dataObject.get(0); + } + } catch (Exception e) { + moduleInfo2122 = null; + } + } + + public static void getData2122(String module) throws InvalidModuleAPIException { + try { + // Public API: + // https://api.nusmods.com/v2/2021-2022/modules/.json + + module = module.toUpperCase(); + + URL url = new URL("https://api.nusmods.com/v2/2021-2022/modules/" + module + ".json"); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.connect(); + + // check if connect is made + int responseCode = conn.getResponseCode(); + + // 200 OK + if (responseCode != 200) { + throw new InvalidModuleAPIException(); + } else { + Scanner scanner = new Scanner(url.openStream()); + String informationString = scanner.nextLine(); + + // Close scanner + scanner.close(); + + // JSON simple library Setup with Maven is used to convert strings to JSON + JSONParser parser = new JSONParser(); + Object obj = parser.parse(String.valueOf(informationString)); + JSONArray dataObject = new JSONArray(); + dataObject.add(obj); + + // Get the first JSON object in the JSON array + moduleInfo2122 = (JSONObject) dataObject.get(0); + + //test code + } + } catch (Exception e) { + moduleInfo2122 = null; + } + } + + public static String getPrerequisite(String module) { + try { + getData2122(module); + return (String) moduleInfo2122.get("prerequisite"); + } catch (InvalidModuleAPIException e) { + return null; + } + } + + public static String getDescription(String module) { + try { + getData2223(module); + return (String) moduleInfo2223.get("description"); + } catch (InvalidModuleAPIException e) { + return null; + } + } + + public static String getTitle2223(String module) { + try { + getData2223(module); + return (String) moduleInfo2223.get("title"); + } catch (InvalidModuleAPIException e) { + return null; + } + + } + + public static String getModuleCredit2223(String module) { + try { + getData2223(module); + return (String) moduleInfo2223.get("moduleCredit"); + } catch (InvalidModuleAPIException e) { + return null; + } + } + + public static String getTitle2122(String module) { + try { + getData2122(module); + return (String) moduleInfo2122.get("title"); + } catch (InvalidModuleAPIException e) { + return null; + } + } + + public static String getModuleCredit2122(String module) { + //Temporary fix, will make code cleaner if I can think of a better solution + if (module.equals("ES2631")) { + return "4"; + } + try { + getData2122(module); + return (String) moduleInfo2122.get("moduleCredit"); + } catch (InvalidModuleAPIException e) { + return null; + } + } + + public static Boolean getSUstatus(String module) { + try { + getData2223(module); + JSONObject attributes = (JSONObject) moduleInfo2223.get("attributes"); + Boolean suStatus = (Boolean) attributes.get("su"); + + return suStatus != null; + } catch (InvalidModuleAPIException e) { + return null; + } + } + + public static Boolean isValidMod(String module) throws InvalidModuleException { + try { + moduleInfo2223 = null; + getData2223(module); + } catch (InvalidModuleAPIException e) { + throw new InvalidModuleException(); + } + return moduleInfo2223 != null; + + } +} diff --git a/src/main/java/seedu/penus/model/ModelManager.java b/src/main/java/seedu/penus/model/ModelManager.java new file mode 100644 index 0000000000..4d867d8163 --- /dev/null +++ b/src/main/java/seedu/penus/model/ModelManager.java @@ -0,0 +1,132 @@ +package seedu.penus.model; + +import static java.util.Objects.requireNonNull; +import java.util.HashMap; +import java.util.List; + +public class ModelManager { + private ModuleList moduleList; + private final User user; + private final List coreDetails; + private final HashMap> coreModList; + + + public ModelManager(User user, + List list, + List coreDetails, + HashMap> coreModList) { + this.user = user; + this.moduleList = new ModuleList(list); + this.coreDetails = coreDetails; + this.coreModList = coreModList; + } + + //=============================== Module List =================================== + public ModuleList getModuleList() { + return this.moduleList; + } + + public List getModuleListObj() { + return moduleList.getModuleList(); + } + + /** + * Checks if the moduleList contains the specified Module + * @param module module + * @return boolean true if module exists + */ + public boolean hasModule(Module module) { + requireNonNull(module); + return moduleList.hasModule(module); + } + + public boolean hasModuleCode(String moduleCode) { + requireNonNull(moduleCode); + return moduleList.hasModuleCode(moduleCode); + } + + /** + * Adds the specificed Module to the moduleList + * @param module Module + */ + public void addModule(Module module) { + requireNonNull(module); + moduleList.addModule(module); + } + + /** + * removes the specified module by its index from the moduleList + * @param index int + */ + public void removeModule(int index) { + moduleList.removeModule(index); + } + + /** + * Marks the specified module by getting the Module from the moduleList by its index + * @param index int + * @param grade string + */ + public void markModule(int index, String grade) { + Module module = moduleList.getModule(index); + module.markTaken(grade); + } + + public int getSize() { + return moduleList.size(); + } + + public Module getModule(int index) { + return moduleList.getModule(index); + } + + public Module getModuleByCode(String moduleCode) { + return moduleList.getModuleByCode(moduleCode); + } + + public String getGESS() { + return moduleList.getGESS(); + } + + public String getGEC() { + return moduleList.getGEC(); + } + + public String getGEN() { + return moduleList.getGEN(); + } + + public void setModuleList(List moduleList) { + this.moduleList = new ModuleList(moduleList); + } + + //=============================== User ========================================== + public User getUser() { + return user; + } + + public String getUserName() { + return user.getName(); + } + + public String getUserCourse() { + return user.getCourse(); + } + + public void setUserName(String name) { + user.setName(name); + } + + public void setUserCourse(String course) { + user.setCourse(course); + } + + //==============================Core mods ======================================= + public List getCoreDetails() { + return coreDetails; + } + + public HashMap> getCoreModList() { + return coreModList; + } +} diff --git a/src/main/java/seedu/penus/model/Module.java b/src/main/java/seedu/penus/model/Module.java new file mode 100644 index 0000000000..ddf01a382a --- /dev/null +++ b/src/main/java/seedu/penus/model/Module.java @@ -0,0 +1,81 @@ +package seedu.penus.model; + +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.logic.utils.Grade; + +public class Module { + protected String moduleCode; + protected Integer year; + protected Integer semester; + protected String grade; + protected boolean isTaken; + + public Module(String moduleCode, Integer year, Integer semester) { + this.moduleCode = moduleCode; + this.year = year; + this.semester = semester; + this.isTaken = false; + } + + //overloaded method for taken modules + public Module(String moduleCode, Integer year, Integer semester, String grade) { + this.moduleCode = moduleCode; + this.year = year; + this.semester = semester; + this.grade = grade; + this.isTaken = true; + } + + public String getCode() { + return this.moduleCode; + } + + public Integer getYear() { + return this.year; + } + + public Integer getSem() { + return this.semester; + } + + public void markTaken(String grade) { + this.isTaken = true; + this.grade = grade; + } + + public void markUntaken() { + this.isTaken = false; + this.grade = ""; + } + + public String getStatus() { + return (isTaken ? "Taken" : "Plan"); + } + + public String getGrade() { + return (isTaken ? grade : ""); + } + + public double getGradePoint() throws InvalidGradeException { + return Grade.getGradePoint(this.grade); + } + + @Override + public String toString() { + return this.getStatus() + " " + this.moduleCode + " year " + + this.year + " semester " + this.semester + " " + getGrade(); + } + + public String encode() { + String encoded = null; + if (getStatus() == "Taken") { + encoded = String.format("%s ### %s ### %s ### %s ### %s", + getStatus(), this.moduleCode, this.year, this.semester, this.grade); + } else if (getStatus() == "Plan") { + encoded = String.format("%s ### %s ### %s ### %s", + getStatus(), this.moduleCode, this.year, this.semester); + } + + return encoded; + } +} diff --git a/src/main/java/seedu/penus/model/ModuleList.java b/src/main/java/seedu/penus/model/ModuleList.java new file mode 100644 index 0000000000..327a80bc76 --- /dev/null +++ b/src/main/java/seedu/penus/model/ModuleList.java @@ -0,0 +1,141 @@ +package seedu.penus.model; + +import java.util.ArrayList; +import java.util.List; + +public class ModuleList { + public final List modules; + + /** + * Overloaded constructor for the creation of a ModuleList object. + */ + public ModuleList() { + this.modules = new ArrayList<>(); + } + + /** + * Constructor for the creation of a ModuleList object. + * + * @param mods List of mods to be included in ModuleLIst after creation. + */ + public ModuleList(List mods) { + this.modules = mods; + } + + + + /** + * Gets the list of modules. + * + * @return List of modules inline separated by commas + */ + public List getModuleList() { + return this.modules; + } + + /** + * Gets a module from the module list by index + * + * @param index the index of the module + * @return the module corresponding to the given index + */ + public Module getModule(int index) { + return modules.get(index); + } + + public Module getModuleByCode(String moduleCode) { + for (Module m : modules) { + if (m.getCode().equals(moduleCode)) { + return m; + } + } + return null; + } + + public boolean hasModule(Module module) { + for (Module m : modules) { + if (m.getCode().equals(module.getCode())) { + return true; + } + } + return false; + } + + public boolean hasModuleCode(String moduleCode) { + for (Module m : modules) { + if (m.getCode().equals(moduleCode)) { + return true; + } + } + return false; + } + + /** + * Adds a given module to the ModuleList object. + * + * @param module The module to be added. + */ + public void addModule(Module module) { + this.modules.add(module); + } + + /** + * Deletes the module identified by its code from the ModuleList + * + * @param index The index corresponding to the module to be deleted. + */ + public void removeModule(int index) { + this.modules.remove(index); + } + + /** + * Returns the number of modules in the module list + * + * @return the number of modules in the module list + */ + public int size() { + return modules.size(); + } + + /** + * Returns the module code of GESS module if exist and taken, "" if does not exist + * + * @return the module code of GESS module if exist and taken, "" if does not exist + */ + public String getGESS() { + for (Module module : modules) { + if (module.moduleCode.substring(0, 4).equals("GESS") && module.isTaken) { + return module.moduleCode; + } + } + return ""; + } + + /** + * Returns the module code of GEC module if exist and taken, "" if does not exist + * + * @return the module code of GEC module if exist and taken, "" if does not exist + */ + public String getGEC() { + for (Module module : modules) { + if (module.moduleCode.substring(0, 3).equals("GEC") && module.isTaken) { + return module.moduleCode; + } + } + return ""; + } + + /** + * Returns the module code of GEN module if exist and taken, "" if does not exist + * + * @return the module code of GEN module if exist and taken, "" if does not exist + */ + public String getGEN() { + for (Module module : modules) { + if (module.moduleCode.substring(0, 3).equals("GEN") && module.isTaken) { + return module.moduleCode; + } + } + return ""; + } +} diff --git a/src/main/java/seedu/penus/model/User.java b/src/main/java/seedu/penus/model/User.java new file mode 100644 index 0000000000..24b4dc016b --- /dev/null +++ b/src/main/java/seedu/penus/model/User.java @@ -0,0 +1,39 @@ +package seedu.penus.model; + +public class User { + public String name; + + public String course; + + public User() { + this.name = ""; + this.course = ""; + } + + //overloaded method for initialising prev user + public User(String name, String course) { + this.name = name; + this.course = course; + } + + public void setName(String inputName) { + this.name = inputName; + } + + public void setCourse(String inputCourse){ + this.course = inputCourse; + } + + public String getName(){ + return this.name; + } + + public String getCourse(){ + return this.course; + } + + public String encode() { + return String.format("User ### %s ### %s", this.name, this.course); + } +} + diff --git a/src/main/java/seedu/penus/storage/FileStorage.java b/src/main/java/seedu/penus/storage/FileStorage.java new file mode 100644 index 0000000000..84e33d6a13 --- /dev/null +++ b/src/main/java/seedu/penus/storage/FileStorage.java @@ -0,0 +1,139 @@ +package seedu.penus.storage; + +import java.util.List; +import java.util.ArrayList; +import java.util.Scanner; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.FileNotFoundException; +import seedu.penus.common.exceptions.DuplicateModuleException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.Module; +import seedu.penus.model.ModuleList; +import seedu.penus.model.User; + +public class FileStorage { + public File file; + public String dataDirectory; + public String filePath; + + /** + * Constructor for the File Manager object. + * Creates a File object according to the relative path /data/penus.txt to store the data + * + * Initializes a /data/ folder and penus.txt if it does not exist + */ + public FileStorage() { + this.dataDirectory = "./data/"; + this.filePath = this.dataDirectory + "penus.txt"; + this.file = new File(this.filePath); + File directory = new File(this.dataDirectory); + try { + if (!directory.exists()) { + directory.mkdirs(); + } + if (!this.file.exists()) { + this.file.createNewFile(); + } + } catch (IOException e) { + System.out.println(e); + } + } + + /** + * Saves the current moduleList accumulated over the program's life and stores it in /data/penus.txt + * + * @param moduleList the moduleList containing all the user's modules + * @param user the user containing the user preferences + */ + public void save(ModuleList moduleList, User user) { + try { + FileWriter writer = new FileWriter(this.filePath); + if (!user.getName().equals("") && !user.getCourse().equals("")) { + writer.write(user.encode() + "\n"); + } + for (Module module : moduleList.getModuleList()) { + writer.write(module.encode() + "\n"); + } + writer.close(); + } catch (IOException e) { + System.out.println(e); + } + } + + /** + * Retrieves any modules saved in /data/penus.txt if the directory exists. + * Decodes the contents of penus.txt into a moduleList object. + * + * @return moduleList the moduleList containing all the user's modules saved in storage + */ + public List retrieveMods() throws PenusException { + Scanner scanner = null; + List moduleList = new ArrayList<>(); + try { + scanner = new Scanner(this.file); + while (scanner.hasNextLine()) { + String encoded = scanner.nextLine(); + if (encoded.length() == 0) { + continue; + } + if (encoded.contains("User")) { + continue; + } + Module decodedModule = StorageDecoder.decodemodule(encoded); + if (hasModule(decodedModule, moduleList)) { + throw new DuplicateModuleException(); + } + moduleList.add(decodedModule); + } + } catch (FileNotFoundException e) { + System.out.println(e); + } finally { + if (scanner != null) { + scanner.close(); + } + } + + return moduleList; + } + + /** + * Retrieves any User saved in /data/penus.txt if the directory exists. + * Decodes the contents of penus.txt into a User object. + * + * @return user the User containing the user details saved in storage + */ + public User retrieveUser() throws PenusException { + Scanner scanner = null; + User user = new User(); + try { + scanner = new Scanner(this.file); + if (scanner.hasNextLine()) { + String userLine = scanner.nextLine(); + if (userLine.contains("User")) { + user = StorageDecoder.decodeUser(userLine); + } + } + } catch (FileNotFoundException e) { + System.out.println(e); + } finally { + if (scanner != null) { + scanner.close(); + } + } + + return user; + } + + private boolean hasModule(Module module, List moduleList) { + for (Module m : moduleList) { + if (m.getCode().equals(module.getCode())) { + return true; + } + } + return false; + } +} + + diff --git a/src/main/java/seedu/penus/storage/ResourceStorage.java b/src/main/java/seedu/penus/storage/ResourceStorage.java new file mode 100644 index 0000000000..135c649e32 --- /dev/null +++ b/src/main/java/seedu/penus/storage/ResourceStorage.java @@ -0,0 +1,117 @@ +package seedu.penus.storage; + +import java.util.List; +import java.util.ArrayList; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import java.util.HashMap; + +import java.util.Objects; + + +/* + * IMPORTANT NOTE: + * in IntelliJ: right click /resources folder -> Mark Directory as -> Resources root + * in VSCode: check /.vscode/settings.json -> should include: + * "java.project.referencedLibraries": [ + { + "scope": "java", + "path": "src/main/resources" + } + ] + * then in VSCode Explorer Tab -> Java Projects -> ... -> Clean Workspace + * + * returns "jar:file:/C:/path/to/project/build/resources/main/.txt" + * returns "file:/C:/path/to/project/src/main/resources/.txt" + */ + +public class ResourceStorage { + public String coreModFile; + public String modDetailsFile; + + public ResourceStorage() { + this.coreModFile = "core-modules.txt"; + this.modDetailsFile = "core-module-details.txt"; + } + + /** + * Retrieves all module details in /resource/core-modules.txt + * + * Parses the content of core-modules.txt into a Hashmap + * Key: courseName String + * Value: coreModulesList Array + * + * @return HashMap + */ + public HashMap> getCoreMods() { + HashMap > coreModHashMap = new HashMap<>(); + String courseName = ""; + BufferedReader reader; + List coreModulesList = new ArrayList<>(); + try { + InputStreamReader stream = new InputStreamReader( + getClass().getClassLoader().getResourceAsStream(coreModFile) + ); + reader = new BufferedReader(stream); + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("##")) { + courseName = line.substring(2); + } else if (line.equals("END")){ + List coreModulesListCopy = new ArrayList<>(coreModulesList); + coreModHashMap.put(courseName, coreModulesListCopy); + coreModulesList.clear(); + } else { + coreModulesList.add(line); + } + } + reader.close(); + } catch (IOException e) { + System.out.println(e); + } + return coreModHashMap; + } + + /** + * Retrieves all module details in /resource/module-details.txt + * + * Parses the content of module-details.txt into a List of decoded modules. + * @return the List containing all the decoded modules. + */ + public List getAllModuleDetails() { + BufferedReader reader; + List moduleDetailsList = new ArrayList<>(); + try { + InputStreamReader stream = new InputStreamReader( + Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(modDetailsFile)) + ); + reader = new BufferedReader(stream); + String line; + while ((line = reader.readLine()) != null) { + String[] decodedModule = decodeModule(line); + moduleDetailsList.add(decodedModule); + } + reader.close(); + } catch (IOException e) { + System.out.println(e); + } + + return moduleDetailsList; + } + + /** + * Decoder method to read a line of module-details.txt storage and splits the string + * into a string array + * + * Format: + * moduleCode ### moduleName ### numberOfMcs ### preRequisites ### coRequisites ### + * preclusions ### semOfferedIn ### canSU + * @param module the string corresponding to the lines of module-details.txt + * @return decoded String array + */ + public String[] decodeModule(String module) { + return module.split(" ### "); + } +} diff --git a/src/main/java/seedu/penus/storage/StorageDecoder.java b/src/main/java/seedu/penus/storage/StorageDecoder.java new file mode 100644 index 0000000000..f73af81301 --- /dev/null +++ b/src/main/java/seedu/penus/storage/StorageDecoder.java @@ -0,0 +1,97 @@ +package seedu.penus.storage; + +import java.util.Arrays; +import java.util.List; +import seedu.penus.common.exceptions.InvalidFormatException; +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.InvalidSemesterException; +import seedu.penus.common.exceptions.InvalidYearException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.utils.Grade; +import seedu.penus.logic.utils.ModuleRetriever; +import seedu.penus.model.User; +import seedu.penus.model.Module; + +public class StorageDecoder { + public static User decodeUser(String userLine) throws PenusException { + User user = new User(); + String[] components = userLine.split(" ### "); + if (components[1].length() == 0 || components[2].length() == 0) { + throw new InvalidFormatException("Try again, name and course cannot be empty"); + } + String name = components[1].trim(); + if (!name.matches("[a-zA-Z ]+")){ + throw new InvalidFormatException("Name must only include letters and spaces."); + } + + String course = components[2].trim(); + List validCourse = Arrays.asList( + "Biomedical Engineering", "Chemical Engineering", "Civil Engineering", + "Computer Engineering", "Electrical Engineering", "Environmental Engineering", + "Industrial and Systems Engineering", "Mechanical Engineering" + ); + if (!validCourse.contains(course)) { + throw new InvalidFormatException("Course is not valid!"); + } + user.setName(name); + user.setCourse(course); + + + return user; + } + + /** + * Decoder method to read a lines in storage and splits the string + * into a string array + * Format: Taken/Plan ### moduleCode ### year ### semester (### grade for taken) + * @param module String + * @return decoded Module object + */ + public static Module decodemodule(String module) throws PenusException { + String[] components = module.split(" ### "); + String status = components[0].trim(); + String moduleCode = components[1].trim().toUpperCase(); + if (!ModuleRetriever.isValidMod(moduleCode)) { + throw new InvalidModuleException(); + } + int year; + int semester; + try { + year = Integer.parseInt(components[2].trim()); + semester = Integer.parseInt(components[3].trim()); + } catch (NumberFormatException e) { + throw new InvalidFormatException("Year and semester must be integers."); + } + if (year < 1 || year > 4) { + throw new InvalidYearException("Year must be 1 to 4. Please try again."); + } + if (semester != 1 && semester != 2) { + throw new InvalidSemesterException("Semester must be 1 or 2!"); + } + Module decoded = null; + + switch (status) { + case "Taken": + if (components.length <= 4) { + throw new InvalidFormatException("Grade must be included for taken command"); + } + String grade = components[4].trim().toUpperCase(); + if (!Grade.isValid(grade)) { + throw new InvalidGradeException(); + } + decoded = new Module(moduleCode, year, semester, grade); + break; + + case "Plan": + decoded = new Module(moduleCode, year, semester); + break; + + default: + break; + } + return decoded; + } + + +} diff --git a/src/main/java/seedu/penus/storage/StorageManager.java b/src/main/java/seedu/penus/storage/StorageManager.java new file mode 100644 index 0000000000..a4cdf07ca4 --- /dev/null +++ b/src/main/java/seedu/penus/storage/StorageManager.java @@ -0,0 +1,40 @@ +package seedu.penus.storage; + +import java.util.HashMap; +import java.util.List; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.Module; +import seedu.penus.model.User; +import seedu.penus.model.ModuleList; + +public class StorageManager { + protected FileStorage storage; + protected ResourceStorage resource; + + public StorageManager() { + this.storage = new FileStorage(); + this.resource = new ResourceStorage(); + } + + //=======================file storage ========================== + public List loadStorage() throws PenusException { + return storage.retrieveMods(); + } + + public User loadUser() throws PenusException { + return storage.retrieveUser(); + } + + public void saveStorage(ModuleList list, User user) { + storage.save(list, user); + } + + //========================resource getter ============================= + public List loadCoreDetails() { + return resource.getAllModuleDetails(); + } + + public HashMap> loadCoreModList() { + return resource.getCoreMods(); + } +} diff --git a/src/main/java/seedu/penus/testutils/SampleData.java b/src/main/java/seedu/penus/testutils/SampleData.java new file mode 100644 index 0000000000..6c6cd6e17f --- /dev/null +++ b/src/main/java/seedu/penus/testutils/SampleData.java @@ -0,0 +1,44 @@ +package seedu.penus.testutils; + +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; +import seedu.penus.model.User; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class SampleData { + + public static Module sampleModule1 = new Module("CS2040C", 1, 1, "A"); + + public static Module sampleModule2 = new Module("CS1231", 1, 1, "A"); + + public static Module sampleModule3 = new Module("GESS1004", 1, 2, "A"); + + public static Module sampleModule4 = new Module("GEC1015", 1, 2); + + public static Module sampleModule5 = new Module("BN1111", 2, 1, "A"); + + public static Module sampleModule6 = new Module("BN2111", 2, 2, "A"); + + public static User user = new User("bentohset", "Computer Engineering"); + + public static HashMap> sampleCoreModList = new HashMap>(); + public static ModelManager getSampleModel() { + List dummyCoreMods = new ArrayList<>(); + dummyCoreMods.add("CS2040C"); + dummyCoreMods.add("CS1231"); + dummyCoreMods.add("EE2211"); + dummyCoreMods.add("EG1311"); + sampleCoreModList.put("Computer Engineering", dummyCoreMods); + ModelManager model = new ModelManager(user, new ArrayList<>(), new ArrayList<>(), sampleCoreModList); + model.addModule(sampleModule1); + model.addModule(sampleModule2); + model.addModule(sampleModule3); + model.addModule(sampleModule4); + model.addModule(sampleModule5); + model.addModule(sampleModule6); + return model; + } +} diff --git a/src/main/java/seedu/penus/ui/Ui.java b/src/main/java/seedu/penus/ui/Ui.java new file mode 100644 index 0000000000..8cf4da30c0 --- /dev/null +++ b/src/main/java/seedu/penus/ui/Ui.java @@ -0,0 +1,115 @@ +package seedu.penus.ui; + +import static seedu.penus.common.Messages.MESSAGE_GOODBYE; +import static seedu.penus.common.Messages.MESSAGE_WELCOME; +import static seedu.penus.common.Messages.LOGO; +import static java.util.Objects.requireNonNull; + +import seedu.penus.logic.commands.CommandResult; + +import java.util.List; +import java.util.Scanner; +import java.io.PrintStream; +import java.io.InputStream; + +public class Ui { + private static Thread loadingThread; + private static final String DIVIDER = "\t___________________________________________________________"; + + private final Scanner in; + private final PrintStream out; + + public Ui() { + this(System.in, System.out); + } + + public Ui(InputStream in, PrintStream out) { + this.in = new Scanner(in); + this.out = out; + } + + public String getUserCommand() { + out.print("Enter command: "); + + return in.nextLine(); + } + + public void printMessage(String... messages) { + out.println(); + out.println(DIVIDER); + for (String m : messages) { + out.println("\t" + m); + } + out.println(DIVIDER); + } + + public void printStorageError(String... messages) { + out.println(); + out.println(DIVIDER); + for (String m : messages) { + out.println("\tStorage " + m); + } + out.println("\tPlease check ./data/penus.txt again"); + out.println(DIVIDER); + } + + public void printResultString(CommandResult result) { + requireNonNull(result); + printMessage(result.feedbackToUser); + } + + public void printResultArray(CommandResult result) { + List feedbackArray = result.feedbackArray; + out.println(); + out.println(DIVIDER); + for (String s : feedbackArray) { + out.println("\t" + s); + } + out.println(DIVIDER); + + } + + public void printWelcome() { + printMessage( + LOGO, + MESSAGE_WELCOME + ); + } + + public void printExit() { + printMessage( + MESSAGE_GOODBYE + ); + } + + public static void printStatus(List statusList){ + for (String s : statusList){ + System.out.println(s); + System.out.println(); + } + } + + public static void showLoadingAnimation() { + char[] animationChars = {'|', '/', '-', '\\' }; + loadingThread = new Thread(() -> { + int i = 0; + while (!Thread.currentThread().isInterrupted()) { + System.out.print("\tLoading " + animationChars[i % 4] + "\r"); + i++; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + System.out.print("\n"); + }); + loadingThread.start(); + } + + public static void stopLoadingAnimation() { + if (loadingThread != null && loadingThread.isAlive()) { + loadingThread.interrupt(); + } + } +} diff --git a/src/main/resources/core-module-details.txt b/src/main/resources/core-module-details.txt new file mode 100644 index 0000000000..8fa9d6701e --- /dev/null +++ b/src/main/resources/core-module-details.txt @@ -0,0 +1,28 @@ +CG1111A ### Engineering Principles and Practice I ### 4 ### 0 ### 0 ### BN2111, EE1111A, EE1111B, EE2111A ### Sem 1 ### 1 +CG2111A ### Engineering Principles and Practice II ### 4 ### CS1010 ### 0 ### CG1112 ### Sem 2 ### 1 +CS1231 ### Discrete Structures ### 4 ### must have completed 1 of 06 MATHEMATICS/07 FURTHER MATHEMATICS/21 PURE MATHEMATICS/22 APPLIED MATHEMATICS at a grade of at least E AND must be H2 OR must have completed 1 of MA1301/MA1301X at a grade of at least D ### 0 ### must not have completed 1 of MA1100/MA1100T at a grade of at least D ### Sem 1 & 2 ### 1 +CG2023 ### Signals and Systems ### 4 ### must have completed 1 of CE2407B/MA1506/MA1512 at a grade of at least D ### 0 ### If undertaking an Undergraduate Degree THEN must not have completed 1 of EE2023/EE2023E/TEE2023 at a grade of at least D ### Sem 2 ### 0 +CG2027 ### Transistor-level Digital Circuits ### 2 ### must have completed 1 of BN1102/CG1111/CG1111A/EE1112/EG1111/EG1112 at a grade of at least D ### 0 ### 0 ### Sem 1 ### 0 +CG2028 ### Computer Organization ### 2 ### must have completed 1 of CS1010/CS1010E at a grade of at least D AND must have completed EE2026 at a grade of at least D ### 0 ### 0 ### Sem 2 ### 0 +CG2271 ### Real-Time Operating Systems ### 4 ### must have completed 1 of "CS1020"/"CS1020E"/"CS2020"/CS2040/CS2040C at a grade of at least D ### 0 ### if undertaking an Undergraduate Degree then must not have completed 1 of "CS2271"/CS2106 at a grade of at least D ### Sem 2 ### 0 +CS2040C ### Data Structures and Algorithms ### 4 ### must have completed 1 of CS1010/CS1010E/CS1010J/CS1010S/CS1010X/CS1101S at a grade of at least D ### 0 ### must not have completed 1 of "CS1020"/"CS1020E"/"CS2010"/"CS2020"/CS2040/CS2040S at a grade of at least D ### Sem 1 & 2 ### 0 +CS2113 ### Software Engineering & Object-Oriented Programming ### 4 ### must have completed CS2040C at a grade of at least D or ( must have completed CS2030 at a grade of at least D and must have completed 1 of CS2040/CS2040S at a grade of at least D ) ### 0 ### must not have completed 1 of CS2103/CS2103T/CS2113T at a grade of at least D ### Sem 1 & 2 ### 0 +EE2026 ### Digital Design ### 4 ### must have completed 1 of "EG1111"/CG1111/CG1111A/CG1111A/EE1111/EE1111A/EE1111B/EE1112 at a grade of at least D ### 0 ### must not have completed EE2020 at a grade of at least D ### Sem 1 & 2 ### 0 +EE4204 ### Computer Networks ### 4 ### must have completed 1 of EE2012A/ESP2107/ST2334 at a grade of at least D OR (must be undertaking 2001CEGHON Bachelor of Engineering (Computer Engineering) (Hons) AND must be Year 2,3 or 4) ### 0 ### must not have completed 1 of CEG5101/CS2105/EE3204/EE5310/EE6310/TEE3204/TEE4204 at a grade of at least D ### Sem 1 & 2 ### 0 +MA1511 ### Engineering Calculus ### 2 ### must have completed 1 of 0006 / 0007 / 06 MATHEMATICS / 07 FURTHER MATHEMATICS / 21 PURE MATHEMATICS / 22 APPLIED MATHEMATICS / 99 O-LEVEL ADDITIONAL MATHEMATICS at a grade of at least E ### 0 ### must not have completed 1 of "EE1461"/"MA1102R"/"MA1506"/"MA1507"/"MA2501"/"PC2174"/MA1312/MA1505/MA1521/MA2002/MA2311/PC2134/YSC1216 at a grade of at least D ### Sem 1 & 2 ### 1 +MA1512 ### Differential Equations for Engineering ### 2 ### must have completed 1 of 0006 / 0007 / 06 MATHEMATICS / 07 FURTHER MATHEMATICS / 21 PURE MATHEMATICS / 22 APPLIED MATHEMATICS / 99 O-LEVEL ADDITIONAL MATHEMATICS at a grade of at least E ) ### 0 ### must not have completed 1 of "EE1461"/"MA1506"/"MA1507"/"PC2174"/PC2134 at a grade of at least D ### Sem 1 & 2 ### 1 +MA1508E ### Linear Algebra for Engineering ### 4 ### must have completed 1 of 0006 / 0007 / 06 MATHEMATICS / 07 FURTHER MATHEMATICS / 21 PURE MATHEMATICS / 22 APPLIED MATHEMATICS / 99 O-LEVEL ADDITIONAL MATHEMATICS at a grade of at least E ### 0 ### must not have completed 1 of "MA1101R"/"MA1506"/"MA1508"/MA1311/MA1513/MA2001/YSC2232 at a grade of at least D ### Sem 2 ### 1 +EG2401A ### Engineering Professionalism ### 2 ### must be Year 2,3 or 4 and must be in one of the cohorts to 2021 inclusive ### 0 ### 0 ### Sem 1 & 2 ### 0 +CP3880 ### Advanced Technology Attachment Programme ### 12 ### must have completed 1 of CS2101/ES1601/ES2007D/IS2101/NTW2001/NTW2017/NTW2028/NTW2029/NTW2030/NTW2031/NTW2032/NTW2033/NTW2034/UTW1001%/UWC2101% at a grade of at least D AND must have completed 1 of BT3103/CS2103/CS2103T/CS2113/CS2113T/IS3106 at a grade of at least D) ### 0 ### must not have completed EG3601 at a grade of at least D ### Sem 1 & 2 ### 0 +EG3611A ### Industrial Attachment ### 10 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 0 +ES2631 ### Critique and Communication of Thinking and Design ### 4 ### must be undertaking 1 of 2001CEGHON Bachelor of Engineering (Computer Engineering) (Hons), 0607ISEHON Bachelor of Engineering (Industrial and Systems Engineering) (Hons), 0609MEHON Bachelor of Engineering (Mechanical Engineering) (Hons), 0613EVEHON Bachelor of Engineering (Environmental Engineering) (Hons), 0605ESPHON Bachelor of Engineering (Engineering Science) (Hons), 0601BMEHON Bachelor of Engineering (Biomedical Engineering) (Hons), 0604ELEHON Bachelor of Engineering (Electrical Engineering) (Hons), 0608MSEHON Bachelor of Engineering (Materials Science and Engineering) (Hons), 0613CEHON Bachelor of Engineering (Civil Engineering) (Hons), 0602CHEHON Bachelor of Engineering (Chemical Engineering) (Hons), 0616IPMHON BEng (Infrastructure and Project Management) (Hons), 0616PFMHON BSc (Project and Facilities Management) (Hons)) AND (must be in one of the cohorts prior to 2013 inclusive OR must be in one of the cohorts from 2016 inclusive ) AND must have completed EP ENGLISH LANGUAGE PROFICIENCY TEST at a grade of at least N) ### 0 ### If undertaking an Undergraduate Degree THEN (( must not have completed 1 of any Courses (Modules) beginning with UTW1001) AND (must not be undertaking 1 of 1501TMBSPL UTown College Programme - Tembusu, 1501TMRSPL UTown Resident - Tembusu, 1502ANRSPL UTown Resident - CAPT, 1502ANGSPL UTown College Programme - CAPT, 1503RC4SPL UTown College Programme - RC4, 1503R4RSPL UTown Resident - RC4, 1520RVCSPL Ridge View Residential College Programme)) ### Sem 1 & 2 ### 1 +CS1010 ### Programming Methodology ### 4 ### 0 ### 0 ### must not have completed 1 of CS1010E/CS1010J/CS1010S/CS1010X/CS1101S at a grade of at least D ### Sem 1 & 2 ### 1 +GEA1000 ### Quantitative Reasoning with Data ### 4 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 1 +DTK1234 ### Design Thinking ### 4 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 1 +EG1311 ### Design and Make ### 4 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 1 +IE2141 ### Systems Thinking and Dynamics ### 4 ### 0 ### 0 ### must not have completed 1 of GEM1915, GET1011, IE2101, any Courses (Modules) beginning with UTC1702 ### Sem 1 & 2 ### 1 +EE2211 ### Introduction to Machine Learning ### 4 ### must have completed 1 of any Courses (Modules) beginning with CS1010 at a grade of at least D AND must have completed MA1511 at a grade of at least D AND must have completed 1 of CE2407B/MA1508E/MA1513 at a grade of at least D ### 0 ### 0 ### Sem 1 & 2 ### 0 +EG2501 ### Liveable Cities ### 4 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 1 +CDE2000 ### Creating Narratives ### 4 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 1 +PF1101 ### Fundamentals of Project Management ### 4 ### 0 ### 0 ### 0 ### Sem 1 & 2 ### 1 +CG4002 ### Computer Engineering Capstone Project ### 8 ### either of CG2028/EE2024 at a grade of at least D and 1 of CS2103/CS2103T/CS2113/CS2113T at a grade of at least D and CG2271 at a grade of at least D ### 0 ### CG3002 at a grade of at least D ### Sem 1 & 2 ### 0 diff --git a/src/main/resources/core-modules.txt b/src/main/resources/core-modules.txt new file mode 100644 index 0000000000..6585f0af00 --- /dev/null +++ b/src/main/resources/core-modules.txt @@ -0,0 +1,227 @@ +##Computer Engineering +ES2631 +CS1010 +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +CG4002 +MA1511 +MA1512 +MA1508E +EG2401A +EG3611A +CG1111A +CG2111A +CS1231 +CG2023 +CG2027 +CG2028 +CG2271 +CS2040C +CS2113 +EE2026 +EE4204 +END +##Electrical Engineering +ES2631 +CS1010E +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +EE4002D +MA1511 +MA1512 +MA1508E +EG2401A +EG3611A +EE1111A +EE2111A +EE2012 +EE2023 +EE2026 +EE2027 +EE2022 +PC2020 +END +##Biomedical Engineering +ES2631 +CS1010E +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +BN4101 +MA1511 +CE2407A +CE2407B +MA1513 +EG2401A +EG3611A +BN1111 +BN2111 +BN2301 +BN2102 +BN2201 +BN2204 +BN2403 +BN3101A +END +##Chemical Engineering +ES2631 +CS1010E +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +MA1511 +CE2407A +CE2407B +MA1513 +EG2401A +EG3611A +CN1101A +CB2102 +CN2101 +CN2116 +CN2121 +CN2122A +CN2125 +CN3101A +CN3121 +CN3124A +CN3132 +CN3135 +CN3421A +CN4122 +CN4123R +END +##Civil Engineering +ES2631 +CS1010E +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +CE4103R +MA1511 +CE2407A +CE2407B +MA1513 +EG2401A +EG3611A +CE1103 +CE2155 +CE2134 +CE3115 +CE3116 +CE3121 +CE3132 +CE3155A +CE3155B +CE3165 +CE3166 +END +##Environmental Engineering +ES2631 +CS1010E +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +ESE4501R +MA1511 +CE2407A +CE2407B +MA1513 +EG2401A +EG3611A +ESE2101 +ESE2102 +ESE2000 +ESE3101 +ESE2001 +ESE3301 +ESE3201 +ESE3401 +END +##Industrial and Systems Engineering +ES2631 +CS1010 +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +IE3100R +MA1511 +MA1512 +MA1508E +EG2401A +EG3611A +IE1111R +IE2111 +IE2100 +IE2110 +IE3101 +IE3110R +CS2040 +ST2334 +END +##Mechanical Engineering +ES2631 +CS1010 +GEA1000 +DTK1234 +EG1311 +IE2141 +EE2211 +EG2501 +CDE2000 +PF1101 +ME4101A +MA1505 +MA1512 +MA1513 +EG2401A +EG3611A +ME1102 +ME2104 +ME2102 +ME2112 +ME2115 +ME2121 +ME2134 +ME2142 +ME2162 +ME4102 +ME4103 +END \ No newline at end of file diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/penus/PenusTest.java similarity index 81% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/seedu/penus/PenusTest.java index 2dda5fd651..be0ba80966 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/seedu/penus/PenusTest.java @@ -1,10 +1,10 @@ -package seedu.duke; +package seedu.penus; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -class DukeTest { +class PenusTest { @Test public void sampleTest() { assertTrue(true); diff --git a/src/test/java/seedu/penus/logic/LogicManagerTest.java b/src/test/java/seedu/penus/logic/LogicManagerTest.java new file mode 100644 index 0000000000..4508470eaf --- /dev/null +++ b/src/test/java/seedu/penus/logic/LogicManagerTest.java @@ -0,0 +1,28 @@ +package seedu.penus.logic; + +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.User; +import seedu.penus.storage.StorageManager; +import java.util.ArrayList; +import java.util.HashMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class LogicManagerTest { + private LogicManager logicManager; + private final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + private final StorageManager storage = new StorageManager(); + + @BeforeEach + public void setUp() { + logicManager = new LogicManager(model, storage); + } + + @Test + public void getCommand_invalidCommand_exceptionThrown() { + assertThrows(PenusException.class, () -> logicManager.getCommand("invalid command")); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/ClearCommandTest.java b/src/test/java/seedu/penus/logic/commands/ClearCommandTest.java new file mode 100644 index 0000000000..2685d618d2 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/ClearCommandTest.java @@ -0,0 +1,54 @@ +package seedu.penus.logic.commands; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.testutils.SampleData; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class ClearCommandTest { + private ModelManager model; + private ClearCommand clearCommand; + + @BeforeEach + public void setUp() { + model = SampleData.getSampleModel(); + } + + @Test + public void testClearYearAndSemModsSuccess() throws PenusException { + clearCommand = new ClearCommand(1, 1); + clearCommand.execute(model); + assertFalse(model.hasModuleCode("CS2040C")); + assertFalse(model.hasModuleCode("CS1231")); + assertTrue(model.hasModuleCode("GESS1004")); + assertTrue(model.hasModuleCode("GEC1015")); + assertTrue(model.hasModuleCode("BN1111")); + assertTrue(model.hasModuleCode("BN2111")); + assertEquals(4, model.getSize()); + } + + @Test + public void testClearYearModsSuccess() throws PenusException { + clearCommand = new ClearCommand(1, 0); + clearCommand.execute(model); + assertFalse(model.hasModuleCode("CS2040C")); + assertFalse(model.hasModuleCode("CS1231")); + assertFalse(model.hasModuleCode("GESS1004")); + assertFalse(model.hasModuleCode("GEC1015")); + assertTrue(model.hasModuleCode("BN1111")); + assertTrue(model.hasModuleCode("BN2111")); + assertEquals(2, model.getSize()); + } + + @Test + public void testClearAllModsSuccess() throws PenusException { + clearCommand = new ClearCommand(0, 0); + clearCommand.execute(model); + assertEquals(0, model.getSize()); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/DetailsCommandTest.java b/src/test/java/seedu/penus/logic/commands/DetailsCommandTest.java new file mode 100644 index 0000000000..1cbd1b42de --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/DetailsCommandTest.java @@ -0,0 +1,23 @@ +package seedu.penus.logic.commands; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.HashMap; +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.User; + +public class DetailsCommandTest { + private final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + private DetailsCommand command; + + @Test + public void testValidModuleCodeSuccess() throws InvalidCommandException { + command = new DetailsCommand("CS2113"); + CommandResult result = command.execute(model); + + assertTrue(!result.feedbackToUser.contains("This information is not available")); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/ExitCommandTest.java b/src/test/java/seedu/penus/logic/commands/ExitCommandTest.java new file mode 100644 index 0000000000..fab52bde24 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/ExitCommandTest.java @@ -0,0 +1,21 @@ +package seedu.penus.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + + + +public class ExitCommandTest { + @Test + public void isExit_returnsTrue() { + assertTrue(ExitCommand.isExit(new ExitCommand())); + } + + @Test + public void testExecuteReturnsCorrectResult() { + CommandResult expectedResult = new CommandResult(ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT, false); + CommandResult actualResult = new ExitCommand().execute(null); + assertEquals(expectedResult.feedbackToUser, actualResult.feedbackToUser); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/HelpCommandTest.java b/src/test/java/seedu/penus/logic/commands/HelpCommandTest.java new file mode 100644 index 0000000000..1621e6c94e --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/HelpCommandTest.java @@ -0,0 +1,65 @@ +package seedu.penus.logic.commands; + +import org.junit.jupiter.api.Test; +import seedu.penus.model.ModelManager; +import seedu.penus.model.User; + +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HelpCommandTest { + + @Test + public void testExecute() { + final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + + CommandResult expected = new CommandResult( + ("\n\t" + String.format("%-52s %s", "Command", "Description") + + "\n\t" + String.format("%-52s %s", "-------", "-----------") + + "\n\t" + String.format("%-52s %s", + "clear [FILTER]", "Clears modules in the specified Year or Semester.") + + "\n\t" + String.format("%-52s %s", "", + "If [FILTER] is not specified, then all modules will cleared.") + + "\n\n\t" + String.format("%-52s %s", "exit", "Exits the program.") + + "\n\n\t" + String.format("%-52s %s", "list [FILTER]", + "Displays a list of all modules taken or planned") + + "\n\t" + String.format("%-52s %s", "", "in the specified Year or Semester.") + + "\n\t" + String.format("%-52s %s", "", + "If [FILTER] is not specified, then all modules will shown.") + + "\n\n\t" + String.format("%-52s %s", "mark [MODULE CODE] g/[GRADE]", + "Marks the module that has been cleared, while updating its grades.") + + String.format("%n %n \t") + + String.format("%-52s %s", "plan [MODULE CODE] y/[YEAR] s/[SEMESTER]", + "Adds a module to the planner as an untaken module.") + + "\n\n\t" + String.format("%-52s %s", + "remove [MODULECODE]", "Removes a module from the planner.") + + "\n\n\t" + String.format("%-52s %s", + "status", "Displays the status of Core Modules and MCs taken.") + + "\n\n\t" + String.format("%-52s %s", + "taken [MODULE CODE] y/[YEAR] s/[SEMESTER] g/[GRADE]", + "Adds a module to the planner as a module you have already taken.") + + "\n\n\t" + String.format("%-52s %s", "details [MODULE CODE]", + "Displays the details of given module, including") + + "\n\t" + String.format("%-52s %s", "", + "Title, Description, Prerequisites, Module Credits") + + "\n\t" + String.format("%-52s %s", "", "and if it can be SU-ed.") + + "\n\n\t" + String.format("%-52s %s", + "init n/[NAME] c/[COURSE NUMBER]", "Initialize User.") + + "\n\n\t" + String.format("%-52s %s", "", "[COURSE NUMBER] -> [COURSE NAME]") + + "\n\t" + String.format("%-52s %s", "", "1 -> Biomedical Engineering") + + "\n\t" + String.format("%-52s %s", "", "2 -> Chemical Engineering") + + "\n\t" + String.format("%-52s %s", "", "3 -> Civil Engineering") + + "\n\t" + String.format("%-52s %s", "", "4 -> Computer Engineering") + + "\n\t" + String.format("%-52s %s", "", "5 -> Electrical Engineering") + + "\n\t" + String.format("%-52s %s", "", "6 -> Environmental Engineering") + + "\n\t" + String.format("%-52s %s", "", "7 -> Industrial and Systems Engineering") + + "\n\t" + String.format("%-52s %s", "", "8 -> Mechanical Engineering") + + "\n\t" + ), false); + CommandResult actual = new HelpCommand().execute(model); + assertEquals(expected.feedbackToUser, actual.feedbackToUser); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/InitCommandTest.java b/src/test/java/seedu/penus/logic/commands/InitCommandTest.java new file mode 100644 index 0000000000..020940b89b --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/InitCommandTest.java @@ -0,0 +1,84 @@ +package seedu.penus.logic.commands; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.User; +import java.util.ArrayList; +import java.util.HashMap; + + +public class InitCommandTest { + private final ModelManager modelManager = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + private InitCommand initCommand; + + @Test + public void testInitCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 1); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Biomedical Engineering", modelManager.getUserCourse()); + } + + @Test + public void testInitChemCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 2); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Chemical Engineering", modelManager.getUserCourse()); + } + @Test + public void testInitCivilCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 3); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Civil Engineering", modelManager.getUserCourse()); + } + @Test + public void testInitComputerCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 4); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Computer Engineering", modelManager.getUserCourse()); + } + @Test + public void testInitElectricalCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 5); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Electrical Engineering", modelManager.getUserCourse()); + } + @Test + public void testInitEnvCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 6); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Environmental Engineering", modelManager.getUserCourse()); + } + @Test + public void testInitISECommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 7); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Industrial and Systems Engineering", modelManager.getUserCourse()); + } + @Test + public void testInitMechCommandSuccess() throws Exception { + initCommand = new InitCommand("John Doe", 8); + CommandResult commandResult = initCommand.execute(modelManager); + assertEquals("John Doe", modelManager.getUserName()); + assertEquals("Mechanical Engineering", modelManager.getUserCourse()); + } + + @Test + public void testExecuteInvalidCourseCodeThrowsInvalidCommandException() { + initCommand = new InitCommand("John Doe", 10); + PenusException invalidCommandException = assertThrows(InvalidCommandException.class, + () -> initCommand.execute(modelManager)); + assertEquals("Error: Enter within the index. Please initialize again", invalidCommandException.getMessage()); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/ListCommandTest.java b/src/test/java/seedu/penus/logic/commands/ListCommandTest.java new file mode 100644 index 0000000000..ca6c4b6cc4 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/ListCommandTest.java @@ -0,0 +1,70 @@ +package seedu.penus.logic.commands; + +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; +import seedu.penus.model.User; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListCommandTest { + private final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + private ListCommand command; + + //add plan module, cap should be 0 + @Test + public void testSuccessListNoFilter() throws PenusException { + Module module = new Module("CS2113", 2, 1); + model.addModule(module); + command = new ListCommand(); + + CommandResult result = command.execute(model); + + assertEquals(result.feedbackArray.get(result.feedbackArray.size() - 1), "Overall CAP : 0.00\n"); + } + + //add taken module cap should be correct, filter list + @Test + public void testSuccessListFilterYearSem() throws PenusException { + Module module = new Module("CS2113", 2, 1, "B+"); + model.addModule(module); + command = new ListCommand(2, 1); + + CommandResult result = command.execute(model); + + assertEquals(result.feedbackArray.get(result.feedbackArray.size() - 1), "Overall CAP : 4.00\n"); + } + + @Test + public void testSuccessListFilterYear() throws PenusException { + Module module = new Module("CS2113", 2, 1, "B+"); + model.addModule(module); + command = new ListCommand(2,0); + + CommandResult result = command.execute(model); + + assertEquals(result.feedbackArray.get(result.feedbackArray.size() - 1), "Overall CAP : 4.00\n"); + } + + @Test + public void testSuccessEmptyListYearSem() throws PenusException { + command = new ListCommand(1,2); + + CommandResult result = command.execute(model); + + assertEquals(result.feedbackArray.get(result.feedbackArray.size() - 1), "Overall CAP : 0.00\n"); + } + + @Test + public void testSuccessEmptyListNoFilter() throws PenusException { + command = new ListCommand(); + + CommandResult result = command.execute(model); + + assertEquals(result.feedbackArray.get(result.feedbackArray.size() - 1), "Overall CAP : 0.00\n"); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/MarkCommandTest.java b/src/test/java/seedu/penus/logic/commands/MarkCommandTest.java new file mode 100644 index 0000000000..f716d03460 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/MarkCommandTest.java @@ -0,0 +1,104 @@ +package seedu.penus.logic.commands; + +import java.util.ArrayList; +import java.util.HashMap; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; +import seedu.penus.model.User; + +import org.junit.jupiter.api.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.common.exceptions.PenusException; + +public class MarkCommandTest { + private final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + + private MarkCommand command; + + @Test + public void testValidModuleCodeSuccess() throws InvalidCommandException { + Module module = new Module("CS2113", 2, 1); + model.addModule(module); + + command = new MarkCommand("CS2113", "A"); + CommandResult result = command.execute(model); + Module markedModule = model.getModuleByCode("CS2113"); + + + assertEquals(String.format(MarkCommand.MESSAGE, module), result.feedbackToUser); + assertEquals(markedModule.getStatus(), "Taken"); + } + + @Test + public void testInValidModuleCodeException() throws InvalidCommandException { + Module module = new Module("CS2113", 2, 1); + model.addModule(module); + command = new MarkCommand("CS2103", "A"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } + + // create and execute a MarkCommand on a non-existent module + @Test + public void testInvalidModuleCodeThrowsInvalidCommandException() { + MarkCommand markCommand = new MarkCommand("CS2113T", "A"); + assertThrows(InvalidCommandException.class, () -> markCommand.execute(model)); + } + + // create and execute a MarkCommand with grade "S" on the module, cannot be SU-ed + @Test + public void executeSuUnsuccessfulThrowsInvalidCommandException() { + Module module = new Module("CS2040C", 2, 1); + model.addModule(module); + + MarkCommand markCommand = new MarkCommand("CS2040C", "S"); + assertThrows(InvalidCommandException.class, () -> markCommand.execute(model)); + } + + @Test + public void testValidSSuccess() throws PenusException { + Module module = new Module("CS1231", 2, 1); + model.addModule(module); + + command = new MarkCommand("CS1231", "S"); + CommandResult result = command.execute(model); + Module markedModule = model.getModuleByCode("CS2113"); + + + assertEquals(String.format(MarkCommand.MESSAGE, module), result.feedbackToUser); + } + + @Test + public void testValidUSuccess() throws PenusException { + Module module = new Module("CS1231", 2, 1); + model.addModule(module); + + command = new MarkCommand("CS1231", "U"); + CommandResult result = command.execute(model); + Module markedModule = model.getModuleByCode("CS2113"); + + + assertEquals(String.format(MarkCommand.MESSAGE, module), result.feedbackToUser); + } + + @Test + public void testInvalidUThrowsInvalidCommandException() throws PenusException { + Module module = new Module("CS2113", 2, 1); + model.addModule(module); + MarkCommand command = new MarkCommand("CS2113", "U"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } + + @Test + public void testInvalidSThrowsInvalidCommandException() throws PenusException { + Module module = new Module("CS2113", 2, 1); + model.addModule(module); + MarkCommand command = new MarkCommand("CS2113", "S"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/PlanCommandTest.java b/src/test/java/seedu/penus/logic/commands/PlanCommandTest.java new file mode 100644 index 0000000000..874e1c27b8 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/PlanCommandTest.java @@ -0,0 +1,49 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.DuplicateModuleException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.User; + +import java.util.ArrayList; +import java.util.HashMap; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PlanCommandTest { + private ModelManager model; + private PlanCommand command; + + @BeforeEach + public void setup() { + model = new ModelManager(new User(), new ArrayList<>(), new ArrayList<>(), new HashMap<>()); + } + @Test + public void testExecuteValidModuleSuccess() throws PenusException { + command = new PlanCommand("CS2113", 2, 2); + CommandResult result = command.execute(model); + + assertEquals(String.format(PlanCommand.MESSAGE, command.plan, model.getSize()), result.feedbackToUser); + assertEquals(1, model.getSize()); + } + + @Test + public void testExecuteDuplicateModuleThrowsDuplicateModuleException() throws PenusException { + PlanCommand command1 = new PlanCommand("CS2113", 2, 2); + PlanCommand command2 = new PlanCommand("CS2113", 2, 2); + + command1.execute(model); + + assertThrows(DuplicateModuleException.class, () -> command2.execute(model)); + } + + @Test + public void testExecuteInvalidModuleThrowsInvalidModuleException() throws PenusException { + PlanCommand command = new PlanCommand("CS211300", 2, 2); + + assertThrows(InvalidModuleException.class, () -> command.execute(model)); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/RemoveCommandTest.java b/src/test/java/seedu/penus/logic/commands/RemoveCommandTest.java new file mode 100644 index 0000000000..d7c98da298 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/RemoveCommandTest.java @@ -0,0 +1,49 @@ +package seedu.penus.logic.commands; + +import java.util.ArrayList; +import java.util.HashMap; +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.Module; +import seedu.penus.model.User; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class RemoveCommandTest { + private final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + + private RemoveCommand command; + + @Test + public void testValidModuleSuccess() throws InvalidCommandException { + Module module = new Module("CS2113", 2, 1); + model.addModule(module); + + command = new RemoveCommand("CS2113"); + + CommandResult result = command.execute(model); + + assertEquals(String.format(RemoveCommand.MESSAGE, module, model.getSize()), result.feedbackToUser); + assertEquals(0, model.getSize()); + } + + //module does not exist + @Test + public void testInvalidModuleThrowsInvalidCommandException() { + command = new RemoveCommand("CS2113"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } + + @Test + public void testExecute_moduleNotFound_throwsException() { + + model.addModule(new Module("CS2113", 2, 2)); + RemoveCommand command = new RemoveCommand("CS2105"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/StatusCommandTest.java b/src/test/java/seedu/penus/logic/commands/StatusCommandTest.java new file mode 100644 index 0000000000..f38d42defb --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/StatusCommandTest.java @@ -0,0 +1,62 @@ +package seedu.penus.logic.commands; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.testutils.SampleData; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class StatusCommandTest { + private ModelManager model; + private StatusCommand statusCommand; + + @BeforeEach + public void setUp() { + model = SampleData.getSampleModel(); + statusCommand = new StatusCommand(); + } + @Test + public void testGetTakenCoreModsListSuccess() { + assertEquals("GESS1004", statusCommand.getTakenCoreModsList(model).get(0)); + assertEquals("CS2040C", statusCommand.getTakenCoreModsList(model).get(1)); + assertEquals(3 , statusCommand.getTakenCoreModsList(model).size()); + } + + @Test + public void testGetUntakenCoreModsListSuccess() { + assertEquals("EE2211", statusCommand.getUntakenCoreModsList(model).get(0)); + assertEquals(2 , statusCommand.getUntakenCoreModsList(model).size()); + } + + @Test + public void testModuleCodeToStringSuccess() { + assertEquals("DTK1234 Design Thinking MCs: 4", statusCommand.moduleCodeToString("DTK1234")); + assertEquals("EE2211 Introduction to Machine Learning MCs: 4", statusCommand.moduleCodeToString("EE2211")); + } + + @Test + public void testStatusCommandSuccess() throws PenusException { + CommandResult result = statusCommand.execute(model); + String expectedString = + "-------------------------- User --------------------------\n" + + "\tUser: bentohset\n" + + "\tCourse: Computer Engineering\n" + + "\t------------------- Core Modules Taken --------------------\n" + + "\tGESS1004 Singapore and India: Emerging Relations MCs: 4\n" + + "\tCS2040C Data Structures and Algorithms MCs: 4\n" + + "\tCS1231 Discrete Structures MCs: 4\n" + + "\t----------------- Core Modules Not Taken ------------------\n" + + "\tGECXXXX\n" + + "\tGENXXXX\n" + + "\tEE2211 Introduction to Machine Learning MCs: 4\n" + + "\tEG1311 Design and Make MCs: 4\n" + + "\t------------------------ MCs Status -----------------------\n" + + "\tCore Modules MCs Taken: 12\n" + + "\tElective MCs Taken: 8\n" + + "\tTotal MCs Taken: 20/160\n"; + assertEquals(expectedString, result.feedbackToUser); + } +} diff --git a/src/test/java/seedu/penus/logic/commands/TakenCommandTest.java b/src/test/java/seedu/penus/logic/commands/TakenCommandTest.java new file mode 100644 index 0000000000..c57145a4d2 --- /dev/null +++ b/src/test/java/seedu/penus/logic/commands/TakenCommandTest.java @@ -0,0 +1,81 @@ +package seedu.penus.logic.commands; + +import seedu.penus.common.exceptions.DuplicateModuleException; +import seedu.penus.common.exceptions.InvalidCommandException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModelManager; +import seedu.penus.model.User; + +import java.util.ArrayList; +import java.util.HashMap; +import org.junit.jupiter.api.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TakenCommandTest { + private final ModelManager model = new ModelManager(new User(), new ArrayList<>(), + new ArrayList<>(), new HashMap<>()); + + private TakenCommand command; + + @Test + public void testValidModuleSuccess() throws PenusException { + command = new TakenCommand("CS2113", 2, 2, "A+"); + CommandResult result = command.execute(model); + + assertEquals(String.format(TakenCommand.MESSAGE, command.taken, model.getSize()), result.feedbackToUser); + assertEquals(1, model.getSize()); + } + + @Test + public void testDuplicateModuleThrowsDuplicateModuleException() throws PenusException { + TakenCommand command1 = new TakenCommand("CS2113", 2, 2, "A+"); + TakenCommand command2 = new TakenCommand("CS2113", 2, 2, "B+"); + + command1.execute(model); + + assertThrows(DuplicateModuleException.class, () -> command2.execute(model)); + } + + @Test + public void testInvalidModuleThrowsInvalidModuleException() throws PenusException { + TakenCommand command = new TakenCommand("CS211300 ", 2, 2, "A+"); + + assertThrows(InvalidModuleException.class, () -> command.execute(model)); + } + + //su + @Test + public void testInvalidSThrowsInvalidCommandException() throws PenusException { + TakenCommand command = new TakenCommand("CS2113", 2, 2, "S"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } + + @Test + public void testInvalidUThrowsInvalidCommandException() throws PenusException { + TakenCommand command = new TakenCommand("CS2113", 2, 2, "U"); + + assertThrows(InvalidCommandException.class, () -> command.execute(model)); + } + + @Test + public void testValidUSuccess() throws PenusException { + TakenCommand command = new TakenCommand("CS1231", 2, 2, "S"); + CommandResult result = command.execute(model); + + assertEquals(String.format(TakenCommand.MESSAGE, command.taken, model.getSize()), result.feedbackToUser); + assertEquals(1, model.getSize()); + } + + @Test + public void testValidSSuccess() throws PenusException { + TakenCommand command = new TakenCommand("CS1231", 2, 2, "U"); + CommandResult result = command.execute(model); + + assertEquals(String.format(TakenCommand.MESSAGE, command.taken, model.getSize()), result.feedbackToUser); + assertEquals(1, model.getSize()); + } +} diff --git a/src/test/java/seedu/penus/logic/parser/ParserTest.java b/src/test/java/seedu/penus/logic/parser/ParserTest.java new file mode 100644 index 0000000000..2971ad23d1 --- /dev/null +++ b/src/test/java/seedu/penus/logic/parser/ParserTest.java @@ -0,0 +1,447 @@ +package seedu.penus.logic.parser; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.InvalidFormatException; +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.InvalidSemesterException; +import seedu.penus.common.exceptions.InvalidYearException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.logic.commands.ClearCommand; +import seedu.penus.logic.commands.Command; +import seedu.penus.logic.commands.DetailsCommand; +import seedu.penus.logic.commands.ExitCommand; +import seedu.penus.logic.commands.HelpCommand; +import seedu.penus.logic.commands.InitCommand; +import seedu.penus.logic.commands.ListCommand; +import seedu.penus.logic.commands.MarkCommand; +import seedu.penus.logic.commands.PlanCommand; +import seedu.penus.logic.commands.RemoveCommand; +import seedu.penus.logic.commands.StatusCommand; +import seedu.penus.logic.commands.TakenCommand; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ParserTest { + private Parser parser; + + @BeforeEach + public void setUp() { + parser = new Parser(); + } + + @Test + public void testInitParserValidInput() throws PenusException, PenusException { + String args = "init n/John Doe c/1"; + Command result = parser.initParser(args); + assertTrue(result instanceof InitCommand); + InitCommand initCommand = (InitCommand) result; + assertEquals(initCommand.name, "John Doe"); + assertEquals(initCommand.courseCode, 1); + } + @Test + public void testInitParserInvalidFormat() throws PenusException { + String args = "init n/John Doe"; + PenusException invalidFormatException = assertThrows(InvalidFormatException.class, + ()->parser.parseCommand(args)); + assertEquals("Error: Try again in the format: init n/NAME c/COURSE CODE", + invalidFormatException.getMessage()); + } + + @Test + public void testInitParserEmptyParameters() throws PenusException { + String args = "init n/ c/1"; + PenusException invalidFormatException = assertThrows(InvalidFormatException.class, + ()->parser.parseCommand(args)); + assertEquals("Error: Try again, n/ c/ cannot be empty", invalidFormatException.getMessage()); + } + + @Test + public void testInitParserCourseCodeNotInteger() throws PenusException { + String args = "init n/John Doe c/abc"; + Exception invalidFormatException = assertThrows(InvalidFormatException.class, ()->parser.parseCommand(args)); + assertEquals("Error: c/ must be an integer", invalidFormatException.getMessage()); + } + + @Test + public void testInitParserNameInteger() { + String args = "init n/John Doe2 c/3"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(args)); + } + + @Test + public void testInitParserEmptyField() { + String args = "init n/ c/"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(args)); + } + + + @Test + public void testPlanParser_validInput() throws PenusException { + String input = "plan CS2113 y/1 s/1"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof PlanCommand); + PlanCommand command = (PlanCommand) result; + assertEquals(command.plan.getCode(), "CS2113"); + assertEquals(command.plan.getYear(), 1); + assertEquals(command.plan.getSem(), 1); + } + + //plan contains g/ + @Test + public void testPlanParser_containsGrade() { + String input = "plan CS2113 y/1 s/1 g/A"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //plan empty fields + @Test + public void testPlanParser_emptyField() { + String input = "plan CS2113 y/ s/1"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //plan not valid integer + @Test + public void testPlanParser_invalidInteger() { + String input = "plan CS2113 y/g s/1"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //plan year invalid + @Test + public void testPlanParser_invalidYear() { + String input = "plan CS2113 y/5 s/1"; + assertThrows(InvalidYearException.class, () -> parser.parseCommand(input)); + } + + //plan sem invalid + @Test + public void testPlanParser_invalidSem() { + String input = "plan CS2113 y/2 s/0"; + assertThrows(InvalidSemesterException.class, () -> parser.parseCommand(input)); + } + + + @Test + public void testTakenParser_validInput() throws PenusException { + String input = "taken CS2113 y/1 s/1 g/A"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof TakenCommand); + TakenCommand command = (TakenCommand) result; + assertEquals(command.taken.getCode(), "CS2113"); + assertEquals(command.taken.getYear(), 1); + assertEquals(command.taken.getSem(), 1); + assertEquals(command.taken.getGrade(), "A"); + } + + //taken dont contain g/\@Test + @Test + public void testTakenParser_missingGrade() { + String input = "taken CS2113 y/2 s/1"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //taken empty fields + @Test + public void testTakenParser_emptyField() { + String input = "taken CS2113 y/ s/1 g/A"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + + //taken invalid integer + @Test + public void testTakenParser_invalidInteger() { + String input = "taken CS2113 y/g s/1 g/A"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //taken invalid grade + @Test + public void testTakenParser_invalidGrade() { + String input = "taken CS2113 y/1 s/1 g/G"; + assertThrows(InvalidGradeException.class, () -> parser.parseCommand(input)); + } + + //taken year invalid + @Test + public void testTakenParser_invalidYear() { + String input = "taken CS2113 y/999 s/1 g/A"; + assertThrows(InvalidYearException.class, () -> parser.parseCommand(input)); + } + + //taken invalid sem + @Test + public void testTakenParser_invalidSem() { + String input = "taken CS2113 y/1 s/0 g/A"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + + @Test + public void testMarkParser_validInput() throws PenusException { + String input = "mark CS2113 g/A"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof MarkCommand); + MarkCommand command = (MarkCommand) result; + assertEquals(command.moduleCode, "CS2113"); + assertEquals(command.grade, "A"); + } + + //mark missing g/ + @Test + public void testMarkParser_missingGrade() { + String input = "mark CS2113"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //mark invalid grade + @Test + public void testMarkParser_invalidGrade() { + String input = "mark CS2113 g/N"; + assertThrows(InvalidGradeException.class, () -> parser.parseCommand(input)); + } + + + @Test + public void testListParser_validInputNoFilter() throws PenusException { + String input = "list"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ListCommand); + ListCommand command = (ListCommand) result; + assertEquals(command.year, 0); + assertEquals(command.semester, 0); + } + + @Test + public void testListParser_validInput() throws PenusException { + String input = "list y/1"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ListCommand); + ListCommand command = (ListCommand) result; + assertEquals(command.year, 1); + assertEquals(command.semester, 0); + } + + @Test + public void testListParser_validInputYearSem() throws PenusException { + String input = "list y/1 s/1"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ListCommand); + ListCommand command = (ListCommand) result; + assertEquals(command.year, 1); + assertEquals(command.semester, 1); + } + + //list sem but not year specified + @Test + public void testListParser_yearUnspecified() { + String input = "list s/2"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //list invalid integer + @Test + public void testListParser_invalidInteger() { + String input = "list y/g s/2"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + @Test + public void testListParser_invalidIntegerSem() { + String input = "list y/1 s/5,5"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + + //list year invalid + @Test + public void testListParser_invalidYear() { + String input = "list y/6 s/2"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //list sem invalid + @Test + public void testListParser_invalidSemester() { + String input = "list y/1 s/0"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //list empty year + @Test + public void testListParser_emptyYear() { + String input = "list y/"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //list empty semester + @Test + public void testListParser_emptySem() { + String input = "list y/1 s/"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //list empty year and semester + @Test + public void testListParser_emptyYearAndSem() { + String input = "list y/ s/"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + @Test + public void testRemoveParser_validInput() throws PenusException { + String input = "remove CS2113"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof RemoveCommand); + RemoveCommand command = (RemoveCommand) result; + assertEquals(command.moduleCode, "CS2113"); + } + + //remove without code + @Test + public void testRemoveParser_noCode() { + String input = "remove "; + assertThrows(InvalidModuleException.class, () -> parser.parseCommand(input)); + } + + @Test + public void testDetailsParser_validInput() throws PenusException { + String input = "details CS2113"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof DetailsCommand); + DetailsCommand command = (DetailsCommand) result; + assertEquals(command.moduleCode, "CS2113"); + } + + //detials no module code + @Test + public void testDetailsParser_noCode() { + String input = "details "; + assertThrows(InvalidModuleException.class, () -> parser.parseCommand(input)); + } + + @Test + public void testDetailsParser_noCode2() { + String input = "details "; + assertThrows(InvalidModuleException.class, () -> parser.parseCommand(input)); + } + + //clear + @Test + public void testClearParser_validInputNoFilter() throws PenusException { + String input = "clear"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ClearCommand); + ClearCommand command = (ClearCommand) result; + assertEquals(command.year, 0); + assertEquals(command.semester, 0); + } + + @Test + public void testClearParser_validInput() throws PenusException { + String input = "clear y/1"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ClearCommand); + ClearCommand command = (ClearCommand) result; + assertEquals(command.year, 1); + assertEquals(command.semester, 0); + } + + @Test + public void testClearParser_validInputYearSem() throws PenusException { + String input = "clear y/1 s/2"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ClearCommand); + ClearCommand command = (ClearCommand) result; + assertEquals(command.year, 1); + assertEquals(command.semester, 2); + } + + //clear year not specified but sem is + @Test + public void testClearParser_noYearButSem() { + String input = "clear s/2"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //clear invalid integer + @Test + public void testClearParser_invalidInteger() { + String input = "clear y/2,2"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //clear invalid year (not integer) + @Test + public void testClearParser_invalidYearInteger() { + String input = "clear y/g"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //clear invalid year + @Test + public void testClearParser_invalidYear() { + String input = "clear y/1000"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //clear invalid sem + @Test + public void testClearParser_invalidSem() { + String input = "clear y/1 s/0"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + @Test + public void testClearParser_invalidSemInteger() { + String input = "clear y/1 s/g"; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + //clear empty fields + @Test + public void testClearParser_emptyField() { + String input = "clear y/ s/ "; + assertThrows(InvalidFormatException.class, () -> parser.parseCommand(input)); + } + + @Test + public void testHelpParser_validInput() throws PenusException { + String input = "help"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof HelpCommand); + } + + @Test + public void testExitParser_validInput() throws PenusException { + String input = "exit"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof ExitCommand); + } + + @Test + public void testStatusParser_validInput() throws PenusException { + String input = "status"; + + Command result = parser.parseCommand(input); + assertTrue(result instanceof StatusCommand); + } +} + diff --git a/src/test/java/seedu/penus/logic/utils/DetailsCompilerTest.java b/src/test/java/seedu/penus/logic/utils/DetailsCompilerTest.java new file mode 100644 index 0000000000..c85aec03f2 --- /dev/null +++ b/src/test/java/seedu/penus/logic/utils/DetailsCompilerTest.java @@ -0,0 +1,63 @@ +package seedu.penus.logic.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DetailsCompilerTest { + + private DetailsCompiler details; + + @Test + void testGetDetailsForCS2113() { + String moduleCode = "CS2113"; + String actualOutput = details.getDetails(moduleCode); + String expectedOutput = "Software Engineering & Object-Oriented Programming\n\tThis module introduces the " + + "necessary skills for systematic and rigorous development of software systems. It covers " + + "requirements, design, implementation, quality assurance, and project management aspects of " + + "small-to-medium size multi-person software projects. The module uses the Object Oriented Programming" + + " paradigm. Students of this module will receive hands-on practice of tools commonly used in the " + + "industry, such as test automation tools, build automation tools, and code revisioning tools will be " + + "covered.\n\tPre-Requisites: CS2040C or ((CS2030 or its equivalent) and CS2040/S)\n\tMCs: 4\n\tModule" + + " cannot be SU-ed."; + + assertEquals(expectedOutput, actualOutput); + } + + @Test + void testGetDetailsForInvalidModule() { + String moduleCode = "INVALID MODULE"; + String actualOutput = details.getDetails(moduleCode); + String expectedOutput = "This module code is invalid. Try again."; + + assertEquals(expectedOutput, actualOutput); + } + + @Test + void testGetDetailsForNull() { + String moduleCode = null; + String actualOutput = details.getDetails(moduleCode); + String expectedOutput = "This module code is invalid. Try again."; + assertEquals(expectedOutput, actualOutput); + } + + // Test ES2631, exists in 22/23 but not 21/22 + @Test + void testGetDetailsForNewModule() { + String moduleCode = "ES2631"; + String actualOutput = details.getDetails(moduleCode); + String expectedOutput = "Critique and Communication of Thinking and Design\n\tThis " + + "module equips students with competencies requiring students to analyze, critique, " + + "and communicate engineering ideas in a systematic and thoughtful manner. " + + "Students will be introduced to a reasoning in engineering framework " + + "(Paul et al., 2019), as well as key principles of effective communication " + + "in the field of engineering, such as being purpose- and context-conscious " + + "and audience-centric (Irish & Weiss, 2013). These will be applied to analyze " + + "engineering ideas in both written and oral communication. Students will also " + + "engage in a group engineering conceptual design project aimed at promoting " + + "critical analysis and communication within groups.\n\tPre-Requisites " + + "information is not available\n\tMCs: 4\n\tModule can be SU-ed."; + + assertEquals(expectedOutput, actualOutput); + } +} diff --git a/src/test/java/seedu/penus/logic/utils/GradeTest.java b/src/test/java/seedu/penus/logic/utils/GradeTest.java new file mode 100644 index 0000000000..7d8bcb5bdb --- /dev/null +++ b/src/test/java/seedu/penus/logic/utils/GradeTest.java @@ -0,0 +1,301 @@ +package seedu.penus.logic.utils; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.model.Module; +import seedu.penus.model.ModuleList; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class GradeTest { + static final String NO_OVERALL_CAP = "Overall CAP : 0.00\n"; + static final String PERFECT_OVERALL_CAP = "Overall CAP : 5.00\n"; + static final String PERFECT_SEM_CAP = "Semester CAP : 5.00\n"; + static final String NO_SEM_CAP = "Semester CAP : 0.00\n"; + private ModuleList moduleList; + private List semArray; + + @BeforeEach + public void setUp() { + moduleList = new ModuleList(); + semArray = new ArrayList<>(); + } + @Test + void getGradePoint_throwsInvalidGradeException_success() { + assertThrows(InvalidGradeException.class, () -> { + Grade.getGradePoint("SU"); + }); + } + @Test + void getGradePoint_grade_success() throws InvalidGradeException { + assertEquals(0.0, Grade.getGradePoint("F")); + } + + @Test + void getGradePoint_gradeAMinus_success() throws InvalidGradeException { + assertEquals(4.5, Grade.getGradePoint("A-")); + } + + @Test + void getGradePoint_gradeBMinus_success() throws InvalidGradeException { + assertEquals(3.0, Grade.getGradePoint("B-")); + } + + @Test + void getGradePoint_gradeCPlus_success() throws InvalidGradeException { + assertEquals(2.5, Grade.getGradePoint("C+")); + } + + @Test + void getGradePoint_gradeC_success() throws InvalidGradeException { + assertEquals(2.0, Grade.getGradePoint("C")); + } + + @Test + void getGradePoint_gradeDPlus_success() throws InvalidGradeException { + assertEquals(1.5, Grade.getGradePoint("D+")); + } + + @Test + void getGradePoint_gradeD_success() throws InvalidGradeException { + assertEquals(1.0, Grade.getGradePoint("D")); + } + + @Test + void isValid_lowercaseGrade_success() { + assertEquals(true, Grade.isValid("a+")); + } + @Test + void isValid_uppercaseGrade_success() { + assertEquals(true, Grade.isValid("S")); + } + + @Test + void calculateOverallCAP_noSU_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "B+")); + moduleList.addModule(new Module("CS2113", 2, 2, "A+")); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(4.50, cap); + } + @Test + void calculateOverallCAP_noSUGradeF_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "F")); + moduleList.addModule(new Module("CS2113", 2, 2, "F")); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(0.00, cap); + } + @Test + void calculateOverallCAP_allSU_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "S")); + moduleList.addModule(new Module("PF1101", 2, 2, "U")); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(5.00, cap); + } + @Test + void calculateOverallCAP_someSUGradeB_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "B")); + moduleList.addModule(new Module("PF1101", 2, 2, "U")); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(3.50, cap); + } + @Test + void calculateOverallCAP_someSUGradeF_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "F")); + moduleList.addModule(new Module("PF1101", 2, 2, "U")); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(0.00, cap); + } + @Test + void calculateOverallCAP_takenAndPlanGradeF_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "F")); + moduleList.addModule(new Module("PF1101", 2, 2)); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(0.00, cap); + } + @Test + void calculateOverallCAP_takenAndPlanGradeSU_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "CU")); + moduleList.addModule(new Module("PF1101", 2, 2)); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(5.00, cap); + } + @Test + void calculateOverallCAP_takenAndPlanGradeB_success() throws + InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "B")); + moduleList.addModule(new Module("PF1101", 2, 2)); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(3.50, cap); + } + @Test + void calculateOverallCAP_planOnly_success() throws + InvalidGradeException { + moduleList.addModule(new Module("EG2501", 2, 1)); + moduleList.addModule(new Module("CG2023", 2, 2)); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateOverallCAP(moduleList.getModuleList()))); + assertEquals(0.00, cap); + } + @Test + void calculateSemCAP_noSU_success() throws InvalidGradeException { + Module module1 = new Module("CS1010", 1, 1, "A+"); + Module module2 = new Module("CG1111A", 1, 1, "A+"); + Module module3 = new Module("CG2111A", 1, 1, "B+"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + semArray.add(new String[] { module3.getCode(), module3.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(4.67, cap); + } + @Test + void calculateSemCAP_noSUAndFGrade_success() throws InvalidGradeException { + Module module1 = new Module("CS1010", 1, 1, "F"); + Module module2 = new Module("CG1111A", 1, 1, "F"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(0.00, cap); + } + + @Test + void calculateSemCAP_someSU_success() throws InvalidGradeException { + Module module1 = new Module("CS1010", 1, 1, "U"); + Module module2 = new Module("PF1101", 1, 1, "A"); + Module module3 = new Module("PF1101", 1, 1, "B+"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + semArray.add(new String[] { module3.getCode(), module3.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(4.50, cap); + } + @Test + void calculateSemCAP_someSUGradeF_success() throws InvalidGradeException { + Module module1 = new Module("CS1010", 1, 1, "CU"); + Module module2 = new Module("CG1111A", 1, 1, "U"); + Module module3 = new Module("PF1101", 1, 1, "F"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + semArray.add(new String[] { module3.getCode(), module3.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(0.00, cap); + } + @Test + void calculateSemCAP_allSU_success() throws InvalidGradeException { + Module module1 = new Module("CS1010", 1, 1, "U"); + Module module2 = new Module("CG1111A", 1, 1, "U"); + Module module3 = new Module("PF1101", 1, 1, "U"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + semArray.add(new String[] { module3.getCode(), module3.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(5.00, cap); + } + @Test + void calculateSemCAP_takenOnly_success() throws + InvalidGradeException { + Module module1 = new Module("EG2501", 2, 1, "A"); + Module module2 = new Module("CG2111A", 2, 1, "B+"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(4.50, cap); + } + @Test + void calculateSemCAP_takenAndPlanGradeA_success() throws + InvalidGradeException { + Module module1 = new Module("EG2501", 2, 1, "A"); + Module module2 = new Module("CG2111A", 2, 1); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(5.00, cap); + } + @Test + void calculateSemCAP_takenAndPlanGradeF_success() throws + InvalidGradeException { + Module module1 = new Module("EG2501", 2, 1, "F"); + Module module2 = new Module("CG2111A", 2, 1); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(0.00, cap); + } + @Test + void calculateSemCAP_takenAndPlanSU_success() throws + InvalidGradeException { + Module module1 = new Module("EG2501", 2, 1, "S"); + Module module2 = new Module("CG2111A", 2, 1); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(0.00, cap); + } + @Test + void calculateSemCAP_planOnly_success() throws InvalidGradeException { + Module module1 = new Module("EG2501", 2, 1); + Module module2 = new Module("CG2111A", 2, 1); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + double cap = Double.parseDouble(new DecimalFormat("#.##"). + format(Grade.calculateSemCAP(semArray))); + assertEquals(0.00, cap); + } + @Test + void getOverallCAP_noModsTaken_success() throws InvalidGradeException { + assertEquals(NO_OVERALL_CAP, Grade.getOverallCAP(moduleList.getModuleList())); + } + @Test + void getOverallCAP_modsTaken_success() throws InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1, "A+")); + moduleList.addModule(new Module("PF1101", 2, 2, "U")); + assertEquals(PERFECT_OVERALL_CAP, Grade.getOverallCAP(moduleList.getModuleList())); + } + @Test + void getOverallCAP_planOnly_success() throws InvalidGradeException { + moduleList.addModule(new Module("CS1010", 1, 1)); + assertEquals(NO_OVERALL_CAP, Grade.getOverallCAP(moduleList.getModuleList())); + } + @Test + void getSemCAP_noModsTaken_success() throws InvalidGradeException { + assertEquals(NO_SEM_CAP, Grade.getSemCAP(semArray)); + } + @Test + void getSemCAP_modsTaken_success() throws InvalidGradeException { + Module module1 = new Module("CS1010", 1, 1, "U"); + Module module2 = new Module("CG1111A", 1, 1, "U"); + semArray.add(new String[] { module1.getCode(), module1.getGrade()}); + semArray.add(new String[] { module2.getCode(), module2.getGrade()}); + assertEquals(PERFECT_SEM_CAP, Grade.getSemCAP(semArray)); + } + +} diff --git a/src/test/java/seedu/penus/logic/utils/ModuleRetrieverTest.java b/src/test/java/seedu/penus/logic/utils/ModuleRetrieverTest.java new file mode 100644 index 0000000000..9a95de5cf1 --- /dev/null +++ b/src/test/java/seedu/penus/logic/utils/ModuleRetrieverTest.java @@ -0,0 +1,78 @@ +package seedu.penus.logic.utils; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ModuleRetrieverTest { + + @Test + void testGetDescription() { + String module = "CS2103T"; + String expectedDescription = + "This module introduces the necessary conceptual and analytical tools for " + + "systematic and rigorous development of software systems." + + " It covers four main areas of software development, " + + "namely object-oriented system analysis, object-oriented system" + + " modelling and design, implementation, and testing, " + + "with emphasis on system modelling and design and implementation of " + + "software modules that work cooperatively to " + + "fulfill the requirements of the system. Tools and techniques for software development," + + " such as Unified Modelling Language (UML)," + + " program specification, and testing methods, will be taught." + + " Major software engineering issues such as modularisation criteria, " + + "program correctness, and software quality will also be covered."; + + assertEquals(expectedDescription, ModuleRetriever.getDescription(module)); + } + + @Test + void testGetPrerequisite() { + String module = "CS2103T"; + String expectedPrerequisite = "For SoC students only. (CS1020 or its equivalent) or CS2020 or" + + " ((CS2030 or its equivalent) and (CS2040 or its equivalent))"; + + assertEquals(expectedPrerequisite, ModuleRetriever.getPrerequisite(module)); + } + + @Test + void testGetTitle2223() { + String module = "CS2103T"; + String expectedTitle = "Software Engineering"; + + assertEquals(expectedTitle, ModuleRetriever.getTitle2223(module)); + } + + @Test + void testGetModuleCredit2223() { + String module = "CS2103T"; + String expectedModuleCredit = "4"; + + assertEquals(expectedModuleCredit, ModuleRetriever.getModuleCredit2223(module)); + } + + @Test + void testGetTitle2122() { + String module = "CS2103T"; + String expectedTitle = "Software Engineering"; + + assertEquals(expectedTitle, ModuleRetriever.getTitle2122(module)); + } + + @Test + void testGetModuleCredit2122() { + String module = "CS2103T"; + String expectedModuleCredit = "4"; + + assertEquals(expectedModuleCredit, ModuleRetriever.getModuleCredit2122(module)); + } + + @Test + void testGetSUstatus() { + String module = "PF1101"; + Boolean expectedSUstatus = true; + + assertEquals(expectedSUstatus, ModuleRetriever.getSUstatus(module)); + } + +} + diff --git a/src/test/java/seedu/penus/model/ModelManagerTest.java b/src/test/java/seedu/penus/model/ModelManagerTest.java new file mode 100644 index 0000000000..913f5e5242 --- /dev/null +++ b/src/test/java/seedu/penus/model/ModelManagerTest.java @@ -0,0 +1,80 @@ +package seedu.penus.model; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ModelManagerTest { + private ModelManager modelManager; + private User user; + private List moduleList; + private List coreDetails; + private HashMap> coreModList; + + @BeforeEach + public void setUp() { + user = new User("John Doe", "Computer Engineering"); + moduleList = new ArrayList<>(); + moduleList.add(new Module("CS1010", 1, 1)); + moduleList.add(new Module("CS1231", 2, 1)); + coreDetails = new ArrayList<>(); + coreDetails.add(new String[]{"DTK1234", "Design Thinking", "4", "0"}); + coreModList = new HashMap<>(); + coreModList.put("Computer Engineering", new ArrayList<>()); + coreModList.get("Computer Engineering").add("CS1010"); + modelManager = new ModelManager(user, moduleList, coreDetails, coreModList); + } + + @Test + public void hasModule_moduleInList_returnsTrue() { + assertTrue(modelManager.hasModule(new Module("CS1010", 1, 1))); + } + + @Test + public void hasModule_moduleNotInList_returnsFalse() { + assertFalse(modelManager.hasModule(new Module("MA1508E", 1, 2))); + } + + @Test + public void hasModuleCode_success() { + assertFalse(modelManager.hasModuleCode("CS2040C")); + assertTrue(modelManager.hasModuleCode("CS1231")); + } + + @Test + public void addModule_validModule_success() { + modelManager.addModule(new Module("MA1508E", 1, 2)); + assertEquals(3, modelManager.getSize()); + } + + @Test + public void removeModule_validIndex_success() { + modelManager.removeModule(1); + assertEquals(1, modelManager.getSize()); + } + + @Test + public void markModule_validIndexAndGrade_success() { + modelManager.markModule(0, "A+"); + assertEquals("A+", modelManager.getModule(0).getGrade()); + } + + @Test + public void getUserName_returnsName() { + assertEquals("John Doe", modelManager.getUserName()); + } + + @Test + public void getUserCourse_returnsCourse() { + assertEquals("Computer Engineering", modelManager.getUserCourse()); + } + +} diff --git a/src/test/java/seedu/penus/model/ModuleListTest.java b/src/test/java/seedu/penus/model/ModuleListTest.java new file mode 100644 index 0000000000..31e9976f5d --- /dev/null +++ b/src/test/java/seedu/penus/model/ModuleListTest.java @@ -0,0 +1,59 @@ +package seedu.penus.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModuleListTest { + private ModuleList list; + + @BeforeEach + public void setUp() { + list = new ModuleList(); + } + + @Test + void addModuleTest() { + Module testModule = new Module("CS2113", 2, 2, "A+"); + list.addModule(testModule); + + assertEquals(1, list.size()); + } + + @Test + void removeModuleTest() { + Module unmarkedModule = new Module("CS2113", 2, 2); + list.addModule(unmarkedModule); + + list.removeModule(0); + assertEquals(0, list.size()); + } + + @Test + void getGECTest() { + Module gecModule = new Module("GEC1015", 2, 2, "A+"); + list.addModule(gecModule); + assertEquals("GEC1015", list.getGEC()); + list.removeModule(0); + assertEquals("", list.getGEC()); + } + + @Test + void getGENTest() { + Module genModule = new Module("GEN2000", 2, 2, "A+"); + list.addModule(genModule); + assertEquals("GEN2000", list.getGEN()); + list.removeModule(0); + assertEquals("", list.getGEN()); + } + + @Test + void getGESSTest() { + Module gessModule = new Module("GESS1004", 2, 2, "A+"); + list.addModule(gessModule); + assertEquals("GESS1004", list.getGESS()); + list.removeModule(0); + assertEquals("", list.getGESS()); + } +} diff --git a/src/test/java/seedu/penus/model/ModuleTest.java b/src/test/java/seedu/penus/model/ModuleTest.java new file mode 100644 index 0000000000..b9e38fe577 --- /dev/null +++ b/src/test/java/seedu/penus/model/ModuleTest.java @@ -0,0 +1,86 @@ +package seedu.penus.model; + + +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.InvalidGradeException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModuleTest { + static final int YEAR_2022 = 2022; + static final int SEMESTER_1 = 1; + static final String GRADE_A = "A"; + + @Test + public void testGetCode() { + Module module = new Module("CS1010", YEAR_2022, SEMESTER_1); + assertEquals("CS1010", module.getCode()); + } + + @Test + public void testGetYear() { + Module module = new Module("CS1010", YEAR_2022, SEMESTER_1); + assertEquals(2022, module.getYear().intValue()); + } + + @Test + public void testGetSem() { + Module module = new Module("CS1010", YEAR_2022, SEMESTER_1); + assertEquals(1, module.getSem().intValue()); + } + + @Test + public void testMarkTaken() { + Module module = new Module("CS1010", YEAR_2022, SEMESTER_1); + module.markTaken("A"); + assertEquals("Taken", module.getStatus()); + assertEquals("A", module.getGrade()); + } + + @Test + public void testMarkUntaken() { + Module module = new Module("CS1010", YEAR_2022, SEMESTER_1, GRADE_A); + module.markUntaken(); + assertEquals("Plan", module.getStatus()); + assertEquals("", module.getGrade()); + } + + @Test + public void testGetStatus() { + Module modulePlan = new Module("CS1010", YEAR_2022, SEMESTER_1); + Module moduleTaken = new Module("CS1010", YEAR_2022, SEMESTER_1, GRADE_A); + assertEquals("Plan", modulePlan.getStatus()); + assertEquals("Taken", moduleTaken.getStatus()); + } + + @Test + public void testGetGrade() { + Module modulePlan = new Module("CS1010", YEAR_2022, SEMESTER_1); + Module moduleTaken = new Module("CS1010", YEAR_2022, SEMESTER_1, GRADE_A); + assertEquals("", modulePlan.getGrade()); + assertEquals("A", moduleTaken.getGrade()); + } + + @Test + public void testGetGradePoint() throws InvalidGradeException { + Module module = new Module("CS1010", YEAR_2022, SEMESTER_1, GRADE_A); + assertEquals(5.0, module.getGradePoint(), YEAR_2022); + } + + @Test + public void testToString() { + Module modulePlan = new Module("CS1010", YEAR_2022, SEMESTER_1); + Module moduleTaken = new Module("CS1010", YEAR_2022, SEMESTER_1, GRADE_A); + assertEquals("Plan CS1010 year 2022 semester 1 ", modulePlan.toString()); + assertEquals("Taken CS1010 year 2022 semester 1 A", moduleTaken.toString()); + } + + @Test + public void testEncode() { + Module modulePlan = new Module("CS1010", YEAR_2022, SEMESTER_1); + Module moduleTaken = new Module("CS1010", YEAR_2022, SEMESTER_1, GRADE_A); + assertEquals("Plan ### CS1010 ### 2022 ### 1", modulePlan.encode()); + assertEquals("Taken ### CS1010 ### 2022 ### 1 ### A", moduleTaken.encode()); + } + +} diff --git a/src/test/java/seedu/penus/model/UserTest.java b/src/test/java/seedu/penus/model/UserTest.java new file mode 100644 index 0000000000..f3ac96f951 --- /dev/null +++ b/src/test/java/seedu/penus/model/UserTest.java @@ -0,0 +1,53 @@ +package seedu.penus.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UserTest { + static final String EMPTY_STRING = ""; + public User user; + + @BeforeEach + public void setUp() { + user = new User(); + } + @Test + public void testDefaultConstructor() { + assertEquals(EMPTY_STRING, user.getName()); + assertEquals(EMPTY_STRING, user.getCourse()); + } + + @Test + public void testOverloadedConstructor() { + User user = new User("John Doe", "Computer Engineering"); + String name = "John Doe"; + String course = "Computer Engineering"; + assertEquals(name, user.getName()); + assertEquals(course, user.getCourse()); + } + + @Test + public void testSetName() { + String name = "Jane Doe"; + user.setName(name); + assertEquals(name, user.getName()); + } + + @Test + public void testSetCourse() { + String course = "Computer Engineering"; + user.setCourse(course); + assertEquals(course, user.getCourse()); + } + + @Test + public void testEncode() { + String name = "John Doe"; + String course = "Computer Engineering"; + user = new User(name, course); + String expected = String.format("User ### %s ### %s", name, course); + assertEquals(expected, user.encode()); + } +} diff --git a/src/test/java/seedu/penus/storage/FileManagerTest.java b/src/test/java/seedu/penus/storage/FileManagerTest.java new file mode 100644 index 0000000000..c12f91e41c --- /dev/null +++ b/src/test/java/seedu/penus/storage/FileManagerTest.java @@ -0,0 +1,23 @@ +package seedu.penus.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FileManagerTest { + FileStorage fileManager; + + @BeforeEach + public void setUp() { + fileManager = new FileStorage(); + } + //test that file exists and path is correct + @Test + public void testConstructor() { + assertNotNull(fileManager.filePath); + assertEquals("./data/penus.txt", fileManager.filePath); + assert fileManager.filePath.equals("./data/penus.txt") : "saved file path error"; + } +} + diff --git a/src/test/java/seedu/penus/storage/FileStorageTest.java b/src/test/java/seedu/penus/storage/FileStorageTest.java new file mode 100644 index 0000000000..b36bef8edb --- /dev/null +++ b/src/test/java/seedu/penus/storage/FileStorageTest.java @@ -0,0 +1,171 @@ +package seedu.penus.storage; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.DuplicateModuleException; +import seedu.penus.common.exceptions.InvalidFormatException; +import seedu.penus.common.exceptions.InvalidGradeException; +import seedu.penus.common.exceptions.InvalidModuleException; +import seedu.penus.common.exceptions.InvalidSemesterException; +import seedu.penus.common.exceptions.InvalidYearException; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.Module; +import seedu.penus.model.ModuleList; +import seedu.penus.model.User; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import java.util.Scanner; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FileStorageTest { + static final int NUMBER_OF_MODULES = 2; + static final String DATA_DIRECTORY = "./data/"; + static final String FILE_PATH = "./data/penus.txt"; + + FileStorage fileStorage; + + @BeforeEach + public void setUp() { + fileStorage = new FileStorage(); + } + + + @Test + public void testConstructor() { + assertEquals(FILE_PATH, fileStorage.filePath); + assertEquals(DATA_DIRECTORY, fileStorage.dataDirectory); + assertEquals(new File("./data/penus.txt"), fileStorage.file); + } + + @Test + void testSave_inputModuleListAndUser_writeSuccess() throws IOException { + ModuleList moduleList = new ModuleList(); + moduleList.addModule(new Module("CS1010", 1, 1, "A+")); + User user = new User("John", "Electrical Engineering"); + fileStorage.save(moduleList, user); + Scanner scanner = new Scanner(fileStorage.file); + assertEquals("User ### John ### Electrical Engineering", scanner.nextLine()); + assertEquals("Taken ### CS1010 ### 1 ### 1 ### A+", scanner.nextLine()); + FileWriter writer = new FileWriter(fileStorage.file); + writer.write(""); + writer.close(); + } + + @Test + void testRetrieveMods_returnsModuleListSuccess() throws IOException, PenusException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("User ### John ### Electrical Engineering\n" + + "Taken ### CS1010 ### 1 ### 1 ### A+\n" + + "Plan ### CS2040C ### 2 ### 1\n"); + writer.close(); + List moduleList = fileStorage.retrieveMods(); + FileWriter toDelete = new FileWriter("./data/penus.txt"); + toDelete.write(""); + toDelete.close(); + assertEquals("CS1010", moduleList.get(0).getCode()); + assertEquals("CS2040C", moduleList.get(1).getCode()); + assertEquals(NUMBER_OF_MODULES, moduleList.size()); + } + + @Test + void testRetrieveUser_returnsUserSuccess() throws IOException, PenusException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("User ### John ### Electrical Engineering\n" + + "Taken ### CS1010 ### 1 ### 1 ### A+\n" + + "Plan ### CS2040C ### 2 ### 1\n"); + writer.close(); + User user = fileStorage.retrieveUser(); + FileWriter toDelete = new FileWriter("./data/penus.txt"); + toDelete.write(""); + toDelete.close(); + assertEquals("John", user.getName()); + assertEquals("Electrical Engineering", user.getCourse()); + } + + @Test + void testRetrieveUser_returnsUserSuccessEmptyLine() throws IOException, PenusException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("User ### John ### Electrical Engineering\n" + + "Taken ### CS1010 ### 1 ### 1 ### A+\n" + + " \n" + + "Plan ### CS2040C ### 2 ### 1\n"); + writer.close(); + User user = fileStorage.retrieveUser(); + FileWriter toDelete = new FileWriter("./data/penus.txt"); + toDelete.write(""); + toDelete.close(); + assertEquals("John", user.getName()); + assertEquals("Electrical Engineering", user.getCourse()); + } + + @Test + void testInvalidUser_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("User ### John1 ### Electrical Engineering\n"); + writer.close(); + assertThrows(InvalidFormatException.class, () -> fileStorage.retrieveUser()); + } + + @Test + void testInvalidCourse_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("User ### John ### Electrical Engineering sadasd\n"); + writer.close(); + assertThrows(InvalidFormatException.class, () -> fileStorage.retrieveUser()); + } + + @Test + void testInvalidModule_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("Taken ### CS10101 ### 1 ### 1 ### A+\n"); + writer.close(); + assertThrows(InvalidModuleException.class, () -> fileStorage.retrieveMods()); + } + + @Test + void testInvalidGrade_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("Taken ### CS1010 ### 1 ### 1 ### 12\n"); + writer.close(); + assertThrows(InvalidGradeException.class, () -> fileStorage.retrieveMods()); + } + + @Test + void testInvalidYear_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("Taken ### CS1010 ### 9999 ### 1 ### A+\n"); + writer.close(); + assertThrows(InvalidYearException.class, () -> fileStorage.retrieveMods()); + } + + @Test + void testInvalidSemester_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("Taken ### CS1010 ### 1 ### 1090 ### A+\n"); + writer.close(); + assertThrows(InvalidSemesterException.class, () -> fileStorage.retrieveMods()); + } + + @Test + void testInvalidInteger_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("Taken ### CS1010 ### 1 ### asdf ### A+\n"); + writer.close(); + assertThrows(InvalidFormatException.class, () -> fileStorage.retrieveMods()); + } + + @Test + void testDuplicateMod_throwsException() throws PenusException, IOException { + FileWriter writer = new FileWriter("./data/penus.txt"); + writer.write("Taken ### CS1010 ### 1 ### 1 ### A+\n" + + "Plan ### CS1010 ### 2 ### 1\n"); + writer.close(); + assertThrows(DuplicateModuleException.class, () -> fileStorage.retrieveMods()); + } +} diff --git a/src/test/java/seedu/penus/storage/ResourceManagerTest.java b/src/test/java/seedu/penus/storage/ResourceManagerTest.java new file mode 100644 index 0000000000..c9726fcf5e --- /dev/null +++ b/src/test/java/seedu/penus/storage/ResourceManagerTest.java @@ -0,0 +1,35 @@ +package seedu.penus.storage; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ResourceManagerTest { + ResourceStorage resource; + + @BeforeEach + public void setUp() { + resource = new ResourceStorage(); + } + + //test that file exists and path is correct + @Test + public void testConstructor() { + assertNotNull(resource.coreModFile); + assertNotNull(resource.modDetailsFile); + assertEquals("core-modules.txt", resource.coreModFile); + assert resource.coreModFile == "core-modules.txt" : "core modules file name error"; + assertEquals("core-module-details.txt", resource.modDetailsFile); + assert resource.modDetailsFile == "core-module-details.txt" : "core module details file name error"; + } + + //test if getAllModuleDetails successfully converts module-details.txt into List form + @Test + public void testGetAllModuleDetails() { + List moduleDetailsList = resource.getAllModuleDetails(); + assertEquals(28, moduleDetailsList.size()); + } +} diff --git a/src/test/java/seedu/penus/storage/ResourceStorageTest.java b/src/test/java/seedu/penus/storage/ResourceStorageTest.java new file mode 100644 index 0000000000..bc2614f314 --- /dev/null +++ b/src/test/java/seedu/penus/storage/ResourceStorageTest.java @@ -0,0 +1,81 @@ +package seedu.penus.storage; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +class ResourceStorageTest { + static final int NUMBER_OF_CORE_MODS_FOR_MECH_ENG = 27; + static final int NUMBER_OF_CORE_MODS_FOR_ENV_ENG = 25; + + public ResourceStorage storage; + @BeforeEach + public void setUp() { + storage = new ResourceStorage(); + } + + @Test + public void testGetCoreModsSuccess() { + HashMap> coreMods = storage.getCoreMods(); + assertNotNull(coreMods); + assertTrue(coreMods.containsKey("Electrical Engineering")); + assertEquals(NUMBER_OF_CORE_MODS_FOR_MECH_ENG, coreMods.get("Mechanical Engineering").size()); + assertTrue(coreMods.containsKey("Environmental Engineering")); + assertEquals(NUMBER_OF_CORE_MODS_FOR_ENV_ENG, coreMods.get("Environmental Engineering").size()); + assertEquals("EG2501", coreMods.get("Civil Engineering").get(7)); + } + + @Test + public void testConstructor() { + assertEquals("core-modules.txt", storage.coreModFile); + assertEquals("core-module-details.txt", storage.modDetailsFile); + } + + @Test + public void testGetCoreMods() { + HashMap> coreHashMap = storage.getCoreMods(); + List courseNameList = new ArrayList<>(Arrays.asList("Computer Engineering", + "Electrical Engineering", "Biomedical Engineering", "Chemical Engineering", + "Civil Engineering", "Environmental Engineering", + "Industrial and Systems Engineering", "Mechanical Engineering")); + for (String courseName : coreHashMap.keySet()) { + assertTrue(courseNameList.contains(courseName)); + } + List compareList = new ArrayList<>(Arrays.asList("ES2631", "CS1010", "GEA1000", + "DTK1234", "EG1311", "IE2141", "EE2211", "EG2501", "CDE2000", "PF1101", + "CG4002", "MA1511", "MA1512", "MA1508E", "EG2401A", "EG3611A", + "CG1111A", "CG2111A", "CS1231", "CG2023", "CG2027", "CG2028", + "CG2271", "CS2040C", "CS2113", "EE2026", "EE4204")); + List modCodeList = coreHashMap.get("Computer Engineering"); + for (String modCode : modCodeList) { + assertTrue(compareList.contains(modCode)); + } + } + + @Test + public void testGetAllModuleDetails() { + List moduleDetailsList = storage.getAllModuleDetails(); + String[] moduleDetails = moduleDetailsList.get(0); + List compareList = new ArrayList<>(Arrays.asList("CG1111A", + "Engineering Principles and Practice I", "4", "0", "0", + "BN2111, EE1111A, EE1111B, EE2111A", "Sem 1", "1")); + for (int i = 0; i < 8; ++i) { + assert (moduleDetails[i].equals(compareList.get(i))); + } + } + + + + +} diff --git a/src/test/java/seedu/penus/storage/StorageManagerTest.java b/src/test/java/seedu/penus/storage/StorageManagerTest.java new file mode 100644 index 0000000000..06963aefb9 --- /dev/null +++ b/src/test/java/seedu/penus/storage/StorageManagerTest.java @@ -0,0 +1,53 @@ +package seedu.penus.storage; + + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.penus.common.exceptions.PenusException; +import seedu.penus.model.ModuleList; +import seedu.penus.model.User; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class StorageManagerTest { + StorageManager storageManager; + @BeforeEach + public void setUp() { + storageManager = new StorageManager(); + } + + + @Test + public void testConstructor() { + assertNotNull(storageManager); + assertNotNull(storageManager.storage); + assertNotNull(storageManager.resource); + } + + @Test + public void testLoadStorage() throws PenusException { + storageManager.storage = new FileStorage(); + assertNotNull(storageManager.loadStorage()); + } + @Test + public void testLoadUser() throws PenusException { + storageManager.storage = new FileStorage(); + assertNotNull(storageManager.loadUser()); + } + @Test + public void testSaveStorage() { + storageManager.storage = new FileStorage(); + storageManager.saveStorage(new ModuleList(), new User()); + } + @Test + public void testLoadCoreDetails() { + storageManager.resource = new ResourceStorage(); + assertNotNull(storageManager.loadCoreDetails()); + } + + @Test + public void testLoadCoreModList() { + storageManager.resource = new ResourceStorage(); + assertNotNull(storageManager.loadCoreModList()); + } +} diff --git a/src/test/java/seedu/penus/testutil/SampleDataTest.java b/src/test/java/seedu/penus/testutil/SampleDataTest.java new file mode 100644 index 0000000000..e1004ff7ce --- /dev/null +++ b/src/test/java/seedu/penus/testutil/SampleDataTest.java @@ -0,0 +1,47 @@ +package seedu.penus.testutil; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.penus.model.ModelManager; +import seedu.penus.testutils.SampleData; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SampleDataTest { + private ModelManager model; + + @BeforeEach + public void setUp() { + model = SampleData.getSampleModel(); + } + + @Test + public void testSampleUserSuccess() { + assertEquals("bentohset", model.getUserName()); + assertEquals("Computer Engineering", model.getUserCourse()); + } + + @Test + public void testSampleCoreModsListSuccess() { + assertTrue(model.getCoreModList().containsKey("Computer Engineering")); + assertEquals("CS2040C", model.getCoreModList().get("Computer Engineering").get(0)); + } + + @Test + public void testSampleModulesSuccess() { + assertEquals("CS2040C", model.getModule(0).getCode()); + assertEquals("CS1231", model.getModule(1).getCode()); + assertEquals("GESS1004", model.getModule(2).getCode()); + assertEquals("Taken", model.getModule(2).getStatus()); + assertEquals("Plan", model.getModule(3).getStatus()); + assertEquals("CS2040C", model.getModuleList().getModule(0).getCode()); + } + + @Test + public void testGetCoreModsListFromUserCourseSuccess() { + assertEquals("CS2040C", model.getCoreModList().get(model.getUserCourse()).get(0)); + } +} + + diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..5df009a3dc 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,100 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling + + ___________________________________________________________ + + ___ _____ ______ ___ ___ ___ _________ + / \/ \ / \ / \/ \ / \ / \ + | __ \ | \/ || | | || _ | + | |__| | | | || | | || \ | + \ / | | | || | | |\ \_ / + / ___/___ | | | || | | | \_ \ + | | / \ | | | || | | |/ \ \ + |_______|/ <> _\| | | || \__/ || \_ | + \ /| \____| / \ |\ /| | + \__|__/ \______/\___/ \_____/ \__________/ \_________/ + + Welcome to PENUS! + Enter help for a list of commands or init to start + ___________________________________________________________ +Enter command: + ___________________________________________________________ + Module has been added: + Plan CS2113 year 1 semester 2 + You have 1 module(s) in your planner + ___________________________________________________________ +Enter command: + ___________________________________________________________ + Module has been added: + Taken CS2040C year 2 semester 1 A + You have 2 module(s) in your planner + ___________________________________________________________ +Enter command: + ___________________________________________________________ + Module has been taken: + Taken CS2113 year 1 semester 2 A+ + ___________________________________________________________ +Enter command: + ___________________________________________________________ + Module has been removed: + Taken CS2113 year 1 semester 2 A+ + You have 1 module(s) in your planner + ___________________________________________________________ +Enter command: + ___________________________________________________________ + Module has been removed: + Taken CS2040C year 2 semester 1 A + You have 0 module(s) in your planner + ___________________________________________________________ +Enter command: + ___________________________________________________________ + + Command Description + ------- ----------- + clear [FILTER] Clears modules in the specified Year or Semester. + If [FILTER] is not specified, then all modules will cleared. + + exit Exits the program. + + list [FILTER] Displays a list of all modules taken or planned + in the specified Year or Semester. + If [FILTER] is not specified, then all modules will shown. + + mark [MODULE CODE] g/[GRADE] Marks the module that has been cleared, while updating its grades. + + plan [MODULE CODE] y/[YEAR] s/[SEMESTER] Adds a module to the planner as an untaken module. + + remove [MODULECODE] Removes a module from the planner. + + status Displays the status of Core Modules and MCs taken. + + taken [MODULE CODE] y/[YEAR] s/[SEMESTER] g/[GRADE] Adds a module to the planner as a module you have already taken. + + details [MODULE CODE] Displays the details of given module, including + Title, Description, Prerequisites, Module Credits + and if it can be SU-ed. + + init n/[NAME] c/[COURSE NUMBER] Initialize User. + + [COURSE NUMBER] -> [COURSE NAME] + 1 -> Biomedical Engineering + 2 -> Chemical Engineering + 3 -> Civil Engineering + 4 -> Computer Engineering + 5 -> Electrical Engineering + 6 -> Environmental Engineering + 7 -> Industrial and Systems Engineering + 8 -> Mechanical Engineering + + ___________________________________________________________ +Enter command: + ___________________________________________________________ + CS2113 Software Engineering & Object-Oriented Programming + This module introduces the necessary skills for systematic and rigorous development of software systems. It covers requirements, design, implementation, quality assurance, and project management aspects of small-to-medium size multi-person software projects. The module uses the Object Oriented Programming paradigm. Students of this module will receive hands-on practice of tools commonly used in the industry, such as test automation tools, build automation tools, and code revisioning tools will be covered. + Pre-Requisites: CS2040C or ((CS2030 or its equivalent) and CS2040/S) + MCs: 4 + Module cannot be SU-ed. + ___________________________________________________________ +Enter command: + ___________________________________________________________ + Exiting PENUS ... + Bye see you again! + ___________________________________________________________ diff --git a/text-ui-test/data/penus.txt b/text-ui-test/data/penus.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..d248e35d15 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,8 @@ -James Gosling \ No newline at end of file +plan CS2113 y/1 s/2 +taken CS2040C y/2 s/1 g/A +mark cs2113 g/A+ +remove CS2113 +remove cs2040c +help +details cs2113 +exit \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 1dcbd12021..4f33ccf7fa 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -20,4 +20,4 @@ then else echo "Test failed!" exit 1 -fi +fi \ No newline at end of file