diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1ea972e389..2cf07e2d38 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -45,6 +45,8 @@ jobs: - name: Perform IO redirection test (Windows) if: always() && runner.os == 'Windows' - working-directory: ${{ github.workspace }}/text-ui-test + working-directory: ${{ github.workspace }}/text-ui-test shell: cmd - run: runtest.bat \ No newline at end of file + run: | + set JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF-8 + runtest.bat diff --git a/.gitignore b/.gitignore index 2873e189e1..5420cd6a15 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,17 @@ /build/ src/main/resources/docs/ +# VSCode config files +.vscode/ + # MacOS custom attributes files created by Finder .DS_Store *.iml bin/ +# IO output files /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +# Class files +*class diff --git a/README.md b/README.md index e243ece764..1dcd432013 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# FindOurSEP project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. diff --git a/build.gradle b/build.gradle index ea82051fab..46048d63ab 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,18 @@ repositories { } dependencies { + // JUnit dependencies for testing testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + + // AsciiTable dependencies for formatting tables + implementation group: 'de.vandermeer', name: 'asciitable', version: '0.3.2' + + // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.0' + + // https://mvnrepository.com/artifact/com.opencsv/opencsv + implementation group: 'com.opencsv', name: 'opencsv', version: '5.9' } test { @@ -29,11 +39,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("findoursep.FindOurSEP") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("FindOurSEP") archiveClassifier.set("") } @@ -41,6 +51,7 @@ checkstyle { toolVersion = '10.2' } -run{ +run { standardInput = System.in + enableAssertions = true } diff --git a/data/allocation_results.csv b/data/allocation_results.csv new file mode 100644 index 0000000000..53473f92c6 --- /dev/null +++ b/data/allocation_results.csv @@ -0,0 +1,22 @@ +"ID","GPA","Preference Rankings","Allocation Status" +"A1234567J","4.5","[1, 2, 3]","-1" +"A1234567I","5.0","[12, 61, 43]","-1" +"A7654321K","3.8","[2, 3, 1]","-1" +"A1357913L","4.0","[3, 1, 2]","-1" +"A9876543M","3.2","[12, 45, 67]","-1" +"A2468101N","4.7","[5, 23, 49]","-1" +"A1122334O","2.9","[78, 56, 23]","-1" +"A3344556P","4.0","[9, 34, 21]","-1" +"A5566778Q","3.5","[41, 12, 89]","-1" +"A6677889R","2.4","[30, 77, 18]","-1" +"A7788990S","3.9","[60, 15, 2]","-1" +"A9988776T","4.3","[88, 55, 13]","-1" +"A4455667U","3.1","[27, 44, 91]","-1" +"A2233445V","5.0","[20, 1, 50]","-1" +"A1123581W","4.8","[33, 9, 76]","-1" +"A3141592X","3.6","[65, 22, 48]","-1" +"A5454545C","3.0","[17, 58, 36]","-1" +"A4343434B","4.6","[6, 35, 40]","-1" +"A3232323A","3.4","[28, 19, 70]","-1" +"A2121212Z","4.2","[7, 50, 29]","-1" +"A0101010Y","2.7","[14, 63, 5]","-1" diff --git a/data/allocation_results.json b/data/allocation_results.json new file mode 100644 index 0000000000..faeaeb3689 --- /dev/null +++ b/data/allocation_results.json @@ -0,0 +1,129 @@ +{ + "students" : [ { + "id" : "A1234567J", + "gpa" : 4.5, + "uniPreferences" : [ 1, 2, 3 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A1234567I", + "gpa" : 5.0, + "uniPreferences" : [ 12, 61, 43 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A7654321K", + "gpa" : 3.8, + "uniPreferences" : [ 2, 3, 1 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A1357913L", + "gpa" : 4.0, + "uniPreferences" : [ 3, 1, 2 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A9876543M", + "gpa" : 3.2, + "uniPreferences" : [ 12, 45, 67 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A2468101N", + "gpa" : 4.7, + "uniPreferences" : [ 5, 23, 49 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A1122334O", + "gpa" : 2.9, + "uniPreferences" : [ 78, 56, 23 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A3344556P", + "gpa" : 4.0, + "uniPreferences" : [ 9, 34, 21 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A5566778Q", + "gpa" : 3.5, + "uniPreferences" : [ 41, 12, 89 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A6677889R", + "gpa" : 2.4, + "uniPreferences" : [ 30, 77, 18 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A7788990S", + "gpa" : 3.9, + "uniPreferences" : [ 60, 15, 2 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A9988776T", + "gpa" : 4.3, + "uniPreferences" : [ 88, 55, 13 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A4455667U", + "gpa" : 3.1, + "uniPreferences" : [ 27, 44, 91 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A2233445V", + "gpa" : 5.0, + "uniPreferences" : [ 20, 1, 50 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A1123581W", + "gpa" : 4.8, + "uniPreferences" : [ 33, 9, 76 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A3141592X", + "gpa" : 3.6, + "uniPreferences" : [ 65, 22, 48 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A5454545C", + "gpa" : 3.0, + "uniPreferences" : [ 17, 58, 36 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A4343434B", + "gpa" : 4.6, + "uniPreferences" : [ 6, 35, 40 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A3232323A", + "gpa" : 3.4, + "uniPreferences" : [ 28, 19, 70 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A2121212Z", + "gpa" : 4.2, + "uniPreferences" : [ 7, 50, 29 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + }, { + "id" : "A0101010Y", + "gpa" : 2.7, + "uniPreferences" : [ 14, 63, 5 ], + "allocatedUniversity" : -1, + "successfullyAllocated" : false + } ] +} \ No newline at end of file diff --git a/data/allocation_results.txt b/data/allocation_results.txt new file mode 100644 index 0000000000..9a409df5e1 --- /dev/null +++ b/data/allocation_results.txt @@ -0,0 +1,21 @@ +id/A1234567J, gpa/4.5, p/[1, 2, 3], alloc/-1 +id/A1234567I, gpa/5.0, p/[12, 61, 43], alloc/-1 +id/A7654321K, gpa/3.8, p/[2, 3, 1], alloc/-1 +id/A1357913L, gpa/4.0, p/[3, 1, 2], alloc/-1 +id/A9876543M, gpa/3.2, p/[12, 45, 67], alloc/-1 +id/A2468101N, gpa/4.7, p/[5, 23, 49], alloc/-1 +id/A1122334O, gpa/2.9, p/[78, 56, 23], alloc/-1 +id/A3344556P, gpa/4.0, p/[9, 34, 21], alloc/-1 +id/A5566778Q, gpa/3.5, p/[41, 12, 89], alloc/-1 +id/A6677889R, gpa/2.4, p/[30, 77, 18], alloc/-1 +id/A7788990S, gpa/3.9, p/[60, 15, 2], alloc/-1 +id/A9988776T, gpa/4.3, p/[88, 55, 13], alloc/-1 +id/A4455667U, gpa/3.1, p/[27, 44, 91], alloc/-1 +id/A2233445V, gpa/5.0, p/[20, 1, 50], alloc/-1 +id/A1123581W, gpa/4.8, p/[33, 9, 76], alloc/-1 +id/A3141592X, gpa/3.6, p/[65, 22, 48], alloc/-1 +id/A5454545C, gpa/3.0, p/[17, 58, 36], alloc/-1 +id/A4343434B, gpa/4.6, p/[6, 35, 40], alloc/-1 +id/A3232323A, gpa/3.4, p/[28, 19, 70], alloc/-1 +id/A2121212Z, gpa/4.2, p/[7, 50, 29], alloc/-1 +id/A0101010Y, gpa/2.7, p/[14, 63, 5], alloc/-1 diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..620a349fef 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ -# About us +# 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 +--------|:-----:|:--------------:|:---------: +![Ice-cold Catnaldo](images/Catnaldo.jpeg) | Anderson Lim | [Github](https://github.com/Holy-An)| [Anderson's Portfoio](https://ay2425s1-cs2113-w12-2.github.io/tp/team/holy-an.html) +![](https://avatars.githubusercontent.com/u/141603285?s=400&u=bf5b9eb5fde9c6bd5ab3f007f24ca6db6e24870b&v=4) | Isaac | [Github](https://github.com/isaacsaw25) | [Isaac's Portfolio](https://ay2425s1-cs2113-w12-2.github.io/tp/team/isaacsaw25.html) +![Thumbs-Up-Man](images/ehz0ah.png) | Lee Hao Zhe | [Github](https://github.com/ehz0ah) | [Hao Zhe's Portfolio](https://ay2425s1-cs2113-w12-2.github.io/tp/team/ehz0ah.html) +![](images/grincat.png) | Paul Tham | [Github](https://github.com/paulktham)| [Paul's Portfolio](https://ay2425s1-cs2113-w12-2.github.io/tp/team/paulktham.html) +![](images/thisisxxz.jpg) | Xiong Xinzhuang | [Github](https://github.com/ThisisXXZ)|[XXZ's Portfolio](https://ay2425s1-cs2113-w12-2.github.io/tp/team/thisisxxz.html) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..196ee9ecec 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,580 @@ # Developer Guide +## Table of Contents +- [Acknowledgements](#acknowledgements) +- [Installation](#installation) +- [Design & Implementation](#design--implementation) + - [Architecture](#architecture) + - [FindOurSEP](#findoursep) + - [Commands](#commands) + - [Add Command](#add-command) + - [Delete Command](#delete-command) + - [Criteria Command](#criteria-command) + - [Find Command](#find-command) + - [Filter Command](#filter-command) + - [List Command](#list-command) + - [Stats Command](#stats-command) + - [ViewQuota Command](#viewquota-command) + - [Allocate Command](#allocate-command) + - [Revert Command](#revert-command) + - [Generate Command](#generate-command) + - [Help Command](#help-command) + - [Exit Command](#exit-command) + - [Unknown Command](#unknown-command) + - [Components](#components) + - [Frontend / User Interface](#frontend--user-interface) + - [Parser](#parser) + - [Allocator](#allocator) + - [Student](#student) + - [StudentList](#studentlist) + - [University and UniversityRepository](#university-and-universityrepository) + - [FileHandler](#filehandler) +- [Product scope](#product-scope) + * [Target user profile](#target-user-profile) + * [Value proposition](#value-proposition) +- [User Stories](#user-stories) +- [Non-Functional Requirements](#non-functional-requirements) +- [Glossary](#glossary) +- [Instructions for Manual Testing](#instructions-for-manual-testing) + + ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +This project utilizes the following dependencies: + +- [JUnit 5.10.0](https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api/5.10.0) for testing. +- [AsciiTable 0.3.2](https://mvnrepository.com/artifact/de.vandermeer/asciitable/0.3.2) for formatting tables. +- [Jackson Databind 2.18.0](https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind/2.18.0) for JSON processing. +- [OpenCSV 5.9](https://mvnrepository.com/artifact/com.opencsv/opencsv/5.9) for CSV parsing. + +Many thanks to the developers and maintainers of these libraries for their incredible work. Their efforts have significantly contributed to the success of this project. + +## Installation + +### Prerequisites + +- Java 17 +- Download the latest `.jar` from [here](https://github.com/AY2425S1-CS2113-W12-2/tp/releases/tag/v1.0). + +### Steps + +1. **Copy the `.jar` file:** + - Move the downloaded `.jar` file into a designated folder on your computer. + +2. **Prepare your data file:** + - If you want to parse a file (.CSV, .JSON, .TXT) containing student data, ensure you have the absolute path ready for that file. + +3. **Run the `.jar` file:** + - Open a terminal. + - Navigate (`cd`) to the folder containing the `.jar` file. + - Execute the `.jar` file using the following command: + ```shell + java -jar FindOurSEP.jar + ``` + +πŸŽ‰ Congratulations! You’re all set to dive into the wonders of this project. Enjoy the ride! + +## Design & Implementation + +### Architecture + +![Architecture](UML_Diagrams/Architecture.drawio.svg) + +
+ +### FindOurSEP + +FindOurSEP is primarily a Command-Line Interface (CLI) based Java Application. The entry point to our application is +`findoursep.FindOurSEP`. How it works: +1. The user launches the application, which creates an instance of the `FindOurSEP` class. + +2. During initialisation, the FindOurSEP constructor instantiates `UI`, `StudentList`, and `Parser` components, + preparing them for managing user input, student data, and command parsing. + +3. After launching, the `start()` method calls `setUpFileHandler()`, where the user is prompted to provide a file path + (e.g., a `.csv`, `.json`, or `.txt`) containing student data, if the user selects 2, which is to upload student + data. + +4. The `FileHandler` class is then initialised with the file path and `Parser` instance. + +5. If the file loads successfully, a success message is displayed. If there is an error, such as an incorrect format or + missing data, an appropriate error message is shown. + +6. The program enters a loop where it waits for user commands, which will be processed by `Parser`. If an invalid + command or incorrect format is detected, a `SEPException` is raised, and an error message is displayed. + +7. When the user decides to exit, the program checks if the user wants to save their data. If the user chooses to save, + they can select the save format (e.g., `.csv`, `.json` or `.txt`), and `FileHandler` saves the current `StudentList` + data accordingly. + +8. A farewell message is displayed, and the application terminates. + +### Commands + +#### Add Command + +Add Command adds a new student into the StudentList. Users need to provide the following information of the newly-added student so that he/she could participate in the allocation process: + +* Student ID (two uppercase alphabets with 7 natural numbers between it, e.g., ``A1234567N``) +* GPA (a valid float with a maximum of 2 decimal places, greater than 0.0 but lesser than 5.0) +* Preferences (three integers ranging from 1 to 92, representing the partner universities, enclosed in curly brackets) + +![AddCommandSequence](./UML_Diagrams/AddCommand.drawio.svg) + +You can also refer to [Parser](#parser) section to check the detailed workflow of `AddCommand`. + +#### Delete Command + +Delete Command removes an existing Student in the StudentList. To do so, users must provide the ID of the student which they wish to delete. + +![DeleteCommandSequence](./UML_Diagrams/DeleteCommand.drawio.svg) + +#### Criteria Command + +Criteria Command sets a minimum GPA every student must achieve before they can be allocated to a university. This GPA value must be between 0 and 5 with a maximum of 2 decimal places. + +![CriteriaCommandSequence](./UML_Diagrams/CriteriaCommand.drawio.svg) + +#### Find Command + +The find mechanism is facilitated by `StudentList`. Every instance of `FindCommand` is created with a string `input`, +which contains the keywords for finding a student, and a `UI` instance for output handling. + +Below is a class diagram for the find command. + +![FindCommandClass](./UML_Diagrams/FindCommandClass.drawio.svg) + +The following sequence diagram shows how the find command works. + +![FindCommandSequence](./UML_Diagrams/FindSequence.drawio.svg) + +(*Sd frames from references will be shown in the `Ui` class.*) + +Given below is an example usage scenario and how the find mechanism behaves at each step. + +1. The user launches the application, initialising `StudentList`, which contains stored `Student` entries if any. + +2. The user executes a command such as `find list A12345` or `find report B1234567I` to search for a student + by their ID in the StudentList. (*Note: input is case-sensitive*) + +3. The command is parsed by `Parser`, which creates a `FindCommand` object with the keywords passed as + the input parameter. + +4. The `FindCommand` constructor stores the `input` string and initialises `StudentList` and `UI` for managing + search and output, respectively. + +5. When `run()` is called on `FindCommand`, it invokes the `findStudent()` method in `StudentList`, + passing `input` to find the students based on the given keywords. + +6. Within `findStudent()`, the input is validated and parsed. If the format is invalid, + a `SEPFormatException` is thrown. An error message will be displayed informing the user. + If the command includes `list`, the output will print a `list` format of the students found. + Alternatively, if the command includes `report`, the output will generate a `report` format of the students found. + +7. If students are found, a `list` or `report` will be displayed. + However, if no students are found, an `SEPEmptyException` is thrown. An error message will inform the user that + no students are found. + +#### Filter Command + +The filter mechanism is facilitated by `StudentList`. Every instance of `FilterCommand` is created +with a string `input`, which contains the filter criteria, and a `UI` instance for output handling. + +Below is a class diagram for the filter command. + +![FilterCommandClass](./UML_Diagrams/FilterCommandClass.drawio.svg) + +The following sequence diagram shows how the filter command works. + +![FilterCommandSequence](./UML_Diagrams/FilterSequence.drawio.svg) + +![FilterStudentId](./UML_Diagrams/FilterStudentIdsd.drawio.svg) + +![FilterStudentGPA](./UML_Diagrams/FilterStudentGpasd.drawio.svg) + +![FilterStudentAllocationList](./UML_Diagrams/FilterStudentAllocationListsd.drawio.svg) + +![FilterStudentReport](./UML_Diagrams/FilterStudentReportsd.drawio.svg) + +(*Remaining sd frames from references will be shown in the `Ui` class.*) + +Given below is an example usage scenario and how the find mechanism behaves at each step. + +1. The user launches the application, initialising `StudentList`, which contains stored `Student` entries if any. + +2. The user executes a command such as `filter list gpa ascending` or `filter report allocated` to filter students + by criteria based on GPA, ID, or allocation status. + +3. The command is parsed by `Parser`, which creates a `FilterCommand` object with the criteria passed as + the input parameter. + +4. The `FilterCommand` constructor stores the `input` string and initialises `StudentList` and `UI` for managing + filter and output, respectively. + +5. When `run()` is called on `FilterCommand`, it invokes the `filterStudent()` method in `StudentList`, + passing `input` to filter students based on the specified criteria. + +6. Within `filterStudent()`, the input is validated and parsed. If the format is invalid, + a `SEPFormatException` is thrown. An error message will be displayed informing the user. + If the command includes `list`, the output will print a `list` format of the students filtered. + Alternatively, if the command includes `report`, the output will generate a `report` format of the students filtered. + +7. Depending on the filter criteria (e.g., `gpa ascending`, `id descending`, `unallocated`), + the appropriate method (e.g., `filterStudentGpa()`, `filterStudentId()`, or `filterByAllocationStatus()`) is invoked + respectively, to filter students by the specified attribute and order. + +8. If students are found after the filter, a `list` or `report` will be displayed. + However, if no students remain, an `SEPEmptyException` is thrown. An error message will inform the user that + no students are found. + +#### List Command + +The `ListCommand` is used to print the current student list to the console. + +1. A `ListCommand` object is instantiated when a `list` command is parsed. +2. `ListCommand` calls `printStudentList()` in the `StudentList` object. +3. `StudentList` calls `printStudentList()` in the `UI` object, using the `students` array list. +4. `UI` iterates over **ALL** students in the `students` array list, getting their `Id`, `Gpa` and `uniPreferences`. +5. `UI` invokes its own `printResponse()` function, to print a templated message, and the info of all the students as a +nicely formatted ASCII table. + +![ListCommandSequenceDiagram](UML_Diagrams/ListCommand.drawio.svg) + +#### Stats Command + +The `StatCommand` class implements the `stats` command, which provides GPA-related statistics (average GPA or minimum GPA) for students who have been allocated to a specified university. The command syntax is `stats `, where `` can be `-avggpa` for average GPA or `-mingpa` for minimum GPA. + +![StatSequence](UML_Diagrams/StatSequence.drawio.svg) + +The above sequence diagram illustrates the execution of the `stats` command, specifically the `stats -avg` example, which calculates the average GPA for students who have been allocated to the specified university. + +* The `StatCommand` class initiates the command with the syntax `stats -avg `, where `-avg` indicates that the average GPA calculation is required, and `` specifies the target university by its index. +* `StatCommand` calls the `getUniversityByIndex()` method on `UniversityRepo`, passing the university index as an argument. +* `UniversityRepo` retrieves the `University` object corresponding to the specified index and returns it to `StatCommand`. +* After checking the `University` object is not null, `StatCommand` invokes the `calculateAverageGPAforUniversity()` method on `StudentList`, supplying the university index as an argument. +* `StudentList` calculates the average GPA for students associated with this university and returns the result as a `double` value (`avgGpa`). +* `StatCommand` then calls `printResponse()` on `UI`, passing a formatted string that includes the calculated average GPA. This response is displayed to the user with a message like "The average GPA for university is: ." + +#### ViewQuota Command + +The `ViewQuotaCommand` class handles the `viewQuota` command to display information about a university’s remaining quota (available spots) based on a specified university index. + +![ViewQuotaSequence](UML_Diagrams/ViewQuotaSequence.drawio.svg) + +The sequence diagram illustrates the execution flow of the `viewQuota` command. + +* The `ViewQuotaCommand` initiates the command using a syntax like `viewQuota `, where `` specifies the university's index in the system. +* `ViewQuotaCommand` calls the `getUniversityByIndex()` method on `UniversityRepo`, passing the university index as a parameter. +* `UniversityRepo` returns the corresponding `University` object. +* With the `University` object in hand, `ViewQuotaCommand` calls `getSpotsLeft()` on the `University` object to retrieve the number of available spots (quota) for the university. +* The `University` object returns the quota as an `int` value (`spotsLeft`), representing the remaining spots. +* Next, `ViewQuotaCommand` calls `getFullName()` on the `University` object to obtain the full name of the university. +* The `University` object returns the university name as a `String` (`name`). +* Finally, `ViewQuotaCommand` calls `printResponse()` on `UI`, passing a formatted string that includes the university's index, name, and remaining quota. +* The message displayed to the user is structured as "Index: , Name: , Quota: ," providing a summary of the requested information. + +#### Allocate Command + +The `AllocateCommand` class manages the allocation process of students using the `Allocator` class. This command sets up an allocation process for students in the specified `StudentList` and informs the user that allocation is underway. + +You could refer to [Allocator](#allocator) section to check the detailed workflow of `AllocateCommand`. + +#### Revert Command + +![RevertSequence](UML_Diagrams/RevertCommandSequence.drawio.svg) + +Upon parsing a `revert` command, a `RevertCommand` instance is created. `RevertCommand` then calls the `revertAllocation()` +method in `StudentList`, which loops through all the students in the `students` array list. The method `revertAllocation()` +within the `Student` objects resets the allocation status and allocated university. The operation is completed by calling +the `UI` to print the templated response from the `Messages` enum. + +#### Generate Command + +![GenerateSequence](UML_Diagrams/GenerateCommandSequence.drawio.svg) -## Design & implementation +The `generate` command is calls the `generateReport()` method in `StudentList`, which then calls the `generateReport()` +in the `UI` using the student array list, which prints an ASCII table representing the allocation outcome. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +#### Help Command + +- The help command provides users with a comprehensive guide to all the commands that the program can recognize and respond to. + +- When this command is executed, the program prints out a detailed list of available commands, each accompanied by a brief description of its functionality. + +- Additionally, the help command specifies the correct input format for each command, ensuring that users understand how to interact effectively with the program. + +- This feature is designed to enhance user experience by providing clear instructions and support, facilitating easier and more efficient use of the program's capabilities. + +#### Exit Command + +- The `ExitCommand` class is initialised whenever the parser extracts out the commands `exit`, `bye` and `quit`. The execution of `ExitCommand` prompts the user to choose whether to save the allocation results. + +- After execution, `parseInput()` method returns a `false` which sets `isRunning` to `false` and breaks out of the loop. + +- If the user inputted `yes` previously to choose to save their allocation results, they will be asked to choose their desired file format (.CSV, .JSON, .TXT) to save their allocation results. + +- Subsequently, the `saveAllocationResults` method is called on the `FileHandler` class to save the results before the `UI` class prints a lovely goodbye message to the user. + +Note: For further details on the `FileHandler` class / `setUpFileHandler()` method, please refer to [this](#filehandler). + +![ExitSequence](UML_Diagrams/ExitSequence.drawio.svg) + +#### Unknown Command + +- The `UnknownCommand` is triggered when the parser is unable to recognise the command inputted by the user. + +- The execution of this command notifies the user of their invalid command and prompts them to type `help` to display a list of all valid commands and their correct formats + + +## Components + +### Frontend / User Interface +The frontend currently consists of 2 main components: +1. `UI` class - Manages interactions with the user, including printing messages, tables, and capturing user inputs. +2. `Messages` enum - Stores standardized messages for consistent user prompts and feedback across the application. + +#### 1. `UI` Class + The `UI` class is designed to handle both input and output for the command-line interface. It manages user prompts, + input retrieval, and formatting for both regular messages and ASCII tables displaying lists. + +Here is the class diagram highlighting the structure of the `UI` class. +![UIClass](UML_Diagrams/UIClass.drawio.svg) + +How `UI` Works: +1. Whenever the program needs to interact with the user, it does so through the `UI` class, which serves as a **facade** + between the logical backend components and the user interface elements. + +2. The `UI` class is responsible for displaying messages, receiving user input, and printing data formatted as tables or +text responses. + +3. Upon receiving a message or command request, the UI class formats the message, incorporating relevant content +(e.g., student details or allocation results) before displaying it to the user. This approach maintains separation +between backend logic and the presentation layer. + +4. For each user action, such as displaying a list of students or allocating slots, the `UI` class uses helper methods +(e.g., `printStudentList`, `generateReport`) to format and render the output. The methods ensure the responses are +consistent and user-friendly. + +Below are sd frames for `printStudentList` and `generateReport` respectively. + +![printStudentList](./UML_Diagrams/PrintStudentListsd.drawio.svg) + +![generateReport](./UML_Diagrams/GenerateReportsd.drawio.svg) + +The `UI` class methods typically return `void`, but will print responses directly to the console or handle user input, +streamlining interactions and allowing the backend to focus solely on data processing. `UI` returns a type when there is +data that needs to be passed from the frontend to the program logic, e.g. `getUserInput` return a `String` + +#### 2. `Messages` Enum +The `Messages` enum centralizes common UI messages. For example, `Messages.ERROR` is passed to the UI for +display for default errors. This keeps responses uniform and allows for changes to user-facing text without modifying +backend logic. List of all `Messages`: + +`WELCOME`: Greeting message displayed at startup. + +`EXIT`: Farewell message when exiting. + +`ALLOCATE_COMPLETE`: Shown upon completion of the allocation process. + +`HELP`: Multi-line help message listing all available commands and their descriptions. + +`ERROR`: General error message for unexpected issues. + +`REVERT_COMPLETE`: Message displayed after a successful revert operation. + +Each message can be accessed and printed via `Messages.` in the `UI` class or any other class that +references it. + +#### Customizing and Extending the UI +Adding New Messages: +1. Open the `Messages` enum. +2. Add a new constant with the message text. Example: +```java +NEW_MESSAGE("Your custom message text here"); +``` +3. Reference the new message in the `UI` class or any other relevant part of the application using `Messages.NEW_MESSAGE` + + + +### Parser + +The `Parser` class is a crucial component instantiated as soon as FindOurSEP is initialised. Its responsibility is to process the user’s input into commands and invoking the correct command object for the rest of the program. + +Some of its core features include: +- Breaking down user input and extracting the relevant command and data for further processing. +- Provide robust error handling when unknown command is received. +- Validate data parsed in from external file (.CSV, .JSON, .TXT) sources. (Further details in the `FileHandler` class) + +Here is a class diagram highlighting the fundamental structure of the `Parser` class. + + +![ParserClass](UML_Diagrams/ParserClass.drawio.svg) + +How `Parser` works: +1. Whenever the user enters an input, the input will be directed to the `Parser` class's `parseInput()` method. +2. Within the method, the command will be extracted and the appropriate `XYZCommand` object (XYZCommand is a placeholder for various commands such as DeleteCommand, ListCommand, etc.) will be instantiated. +3. Upon instantiation, the `XYZCommand` is prepared for execution. Each `XYZCommand` class, inheriting from the abstract `Command` class, has a `run()` method that executes its specific instructions. + +The sequence diagram below demonstrates the interactions within the `Parser` component when a user inputs the command: `add id/A1234567I gpa/5.0 p/{13,61,43}`. + + +![ParserSequence](UML_Diagrams/ParserSequence.drawio.svg) + +The boolean return value of `parseInput()` indicates whether the user has chosen to continue or terminate the program. A `true` value keeps FindOurSEP running, while a `false` value ends the program. + +### Allocator + +The `Allocator` class is responsible for allocating students to universities based on their preferences and GPA. It interacts with the `StudentList`, `UniversityRepository`, and `Student` classes to perform the allocation. + +The allocation logic is designed as follows: + +* **Sorting by GPA**: The list of students is sorted in descending order of GPA, so higher-GPA students are prioritized. +* **Preference-Based Allocation**: For each student: + * Iterates through the student’s university preferences. + * Checks if the university has open spots and if the student’s GPA meets the `minimumGPA` requirement. + * If both conditions are met, the student is allocated to that university, and the university’s spot count is reduced. + * The allocation stops once a student is assigned to a university. +* **Sorting by ID**: After allocation, the list is re-sorted by student ID. + +Note that ``Allocator`` will copy the passed student list, therefore any modifications to the student list inside ``Allocator`` will not reflect in the original one. + +Here is a class diagram highlighting the fundamental structure of the `Allocator` class. + +![AllocatorClass](UML_Diagrams/AllocatorClass.drawio.svg) + +``Allocator`` mainly participates in the execution of ``allocate`` command. + +![AllocatorSequence](UML_Diagrams/AllocatorSequence.drawio.svg) + +The sequence diagram above showcases the program workflow when a user inputs the command ``allocate`` (assume before that several students have been added into the student list). + +* The `FindOurSEP` class initiates the command by calling `parseInput("allocate")` on `Parser`, passing the `"allocate"` command as input. +* `Parser` processes the command and creates an `AllocateCommand` object. +* ``Parser`` invokes the `run()` method on `AllocateCommand` to start the allocation process. +* Inside `run()`, `AllocateCommand` creates an instance of `Allocator`, which will handle the allocation of universities. +* `AllocateCommand` calls the `allocate()` method on `Allocator`, initiating the main allocation logic. `Allocator` performs the allocation and returns a populated `StudentList` (with allocated students) to `AllocateCommand`. +* `AllocateCommand` then calls `setStudentList()` on the `StudentList` object, updating it with the newly allocated data provided by `Allocator`. +* Finally, `AllocateCommand` calls `printAllocatingMessage()` on `UI` to display a message to the user, indicating that the allocation process has completed successfully. +* The command execution completes, returning a boolean result to indicate success or failure of the allocation. + +#### Student + +The `Student` class has a composition relationship with class StudentList. Its purpose is to store key information on the different students that have applied for the Student Exchange Program. Such information include their GPA and university preferences, which helps us allocate them to the various universities fairly, and also other information which helps the app track their allocation status. Please refer to diagrams in [StudentList](#studentlist) to see a detailed sequence diagram and class diagram. + +#### StudentList + +The ```StudentList``` is a fundamental component which is initiated as soon as FindOurSEP is initialised. Its purpose is to hold the necessary information of the different students that are applying for SEP. By having the list of students we are able to fairly allocate universities to the different students by comparing them to their cohort. + +The sequence above in [AddCommand](#add-command) illustrates the interactions between ```StudentList``` and ```Student``` when an addCommand is called with the appropriate inputs. + +This diagram below shows the class diagram of Student and StudentList. + +![StudentandStudentListClassDiagram](./UML_Diagrams/studentandstudentlist.drawio.svg) + +#### University and UniversityRepository + +These two classes have a composition relationship, where ```UniversityRepository``` is composed of ```University``` objects. The ```University``` object holds the various crucial information of any single university that is provided in the list of available universities. The ```UniversityRepository``` class then creates a static HashMap and statically inputs the list of universities into this HashMap. This HashMap is then easily accessible by other classes to get any information which may be necessary from the universities. + +The diagram below shows the class diagram of University and UniversityRepository. + +![UniversityAndUniversityRepositoryClassDiagram](./UML_Diagrams/University.drawio.svg) + +### FileHandler + +The `FileHandler` class is responsible for parsing file inputs from users as well as writing allocation results to an external file in which the file type is determined by the user. + +![FileHandlerSequence](UML_Diagrams/FileHandlerSequence.drawio.svg) + +After successfully processing the file, the program compiles the student data into a comprehensive student list. This list serves as the foundational data structure upon which the program performs allocation operations, ensuring efficient and accurate data handling. + +The program continues to run afterward, prompting the user for commands. + +For file outputs, `saveAllocationResults()` will be called. Below is a further sequence diagram + +![saveAllocationResultsSequence](UML_Diagrams/saveAllocationResultsSequence.drawio.svg) ## Product scope -### Target user profile -{Describe the target user profile} +### Target User Profile + +This application is designed for an administrator responsible for handling the allocation of Student Exchange Program (SEP) locations for Computer Engineering (CEG) students at the National University of Singapore (NUS). The admin overseeing the complex process of assigning students to various international exchange programs, ensuring that each student receives an appropriate placement based on their preferences and qualifications. + +### Value Proposition + +The application provides a comprehensive solution to streamline the SEP allocation process. By leveraging automated workflows and data-driven decision-making, it allows administrators to manage the allocation process with greater efficiency and accuracy. The key benefits include: -### Value proposition +- **Efficiency**: Automated workflows reduce the time and effort required to match students with available SEP locations, freeing up administrators to focus on more strategic tasks. +- **Accuracy**: Data-driven decision-making ensures that allocations are based on objective criteria, minimizing errors and bias. +- **Convenience**: The user-friendly interface and clear documentation make it easy for administrators to navigate the system and carry out their tasks effectively. +- **Scalability**: The application can handle a large number of student applications and SEP locations, making it suitable for both small and large cohorts. +- **Transparency**: Detailed tracking and reporting features provide visibility into the allocation process, allowing for better oversight and accountability. +- **Support**: Comprehensive support and maintenance services ensure that the application remains reliable and up-to-date, adapting to any changes in the SEP process or requirements. -{Describe the value proposition: what problem does it solve?} +By integrating these features, the application significantly enhances the convenience and effectiveness of managing SEP allocations, ultimately benefiting both administrators and students. + +
## User Stories -|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| +| Version | As a ... | I want to ... | So that I can ... | +|---------|---------------|--------------------------------------------------------------------------------|---------------------------------------------------------------| +| v1.0 | Administrator | Upload and reference student data | Easily access necessary information for each application | +| v1.0 | Administrator | Generate report on CLI for applications submitted, approved, and rejected | Conveniently release results to students. | +| v1.0 | Administrator | Upload data sources for university capacity reports and student records | Ensure data accuracy. | +| v1.0 | Administrator | View list of all student applications | See the current status of each application. | +| v1.0 | Administrator | Designate alternate universities for students without first-choice slots | Provide backup options in case of slot unavailability. | +| | | | | +| v2.0 | Administrator | Search for student applications by ID or destination | Quickly locate specific applications. | +| v2.0 | Administrator | Access average GPA and minimum GPA for students choosing a partner university | Evaluate university performance over time. | +| v2.0 | Administrator | Set criteria for SEP allocation | Ensure fair distribution of program slots. | +| v2.0 | Administrator | Filter applications by GPA, destination, or priority | View targeted subsets of student applications easily. | +| v2.0 | Administrator | View university spot availability | Make informed allocation decisions. | +| v2.0 | Administrator | Export allocation data in user-based format | Use data for offline analysis and reporting. | +| v2.0 | Administrator | Be able to undo allocations | Correct allocations if needed due to mistakes or updates. | +| v2.0 | Administrator | Upload student and allocation data in various formats (e.g., CSV, TXT) | Present data flexibly across different platforms or settings. | + +
## Non-Functional Requirements -{Give non-functional requirements} +**Performance**: The application should process allocation commands and generate reports within 2 seconds under typical load conditions (e.g., 500 students). + +**Reliability**: Ensure 99.9% uptime, with automatic error handling and recovery mechanisms to prevent application crashes during operations. + +**Usability**: Designed with a clear command-line interface (CLI), allowing new users to perform essential tasks with minimal learning effort. + +**Maintainability**: The codebase should be modular and follow standard coding conventions, with detailed inline documentation to facilitate future updates. + +**Scalability**: Capable of handling an increase in student and university data volumes by up to 50% (e.g., 750 students) without significant degradation in performance. + +**Portability**: The application should be compatible with any system running Java 17, ensuring wide accessibility across environments. + +**Comprehensive User Guide**: The User Guide must be thoroughly documented, including multiple examples of usage to facilitate understanding. + +**High Availability**: The application must be available at all times with minimal dependency on the internet or other APIs. + +
## Glossary -* *glossary item* - Definition +* *SEP* (Student Exchange Programme): NUS’s largest global exchange initiative, enabling students to study at over 300 partner universities in 40+ countries. *FindOurSEP* project is designed specifically to assist in the SEP allocation process for CEG (Computer Engineering) students. +* *GPA* (Grade Point Average): A numeric score ranging from 0.0 to 5.0, representing a student's academic performance, used for allocation. +* *CSV* (Comma-Separated Values): A file format used to store tabular data, such as student records. +* *JSON* (JavaScript Object Notation): A lightweight data-interchange format used for storing student and university data. +* *Allocator*: Class responsible for assigning students to universities based on GPA and preferences. +* *Command*: A specific action or function executed by the program, such as `add`, `delete`, or `allocate`. +* *Parser*: Class that interprets and processes user input commands. +* *StudentList*: Data structure containing records of all students in the SEP system. +* *UniversityRepository*: A repository containing information on partner universities available for SEP. +* *FindOurSEP*: The name of the project system designed to facilitate and optimize the SEP (Student Exchange Programme) allocation process specifically for Computer Engineering students at NUS. +* *Java 17*: A version of the Java programming language and runtime environment. It’s important to have this or a more recent version installed to run certain Java applications. +* *.jar file*: A Java ARchive file that contains Java classes and associated metadata and resources. It's used to distribute Java applications. +* *Terminal*: A text-based interface used to interact with the computer’s operating system, allowing you to execute commands. +* *Navigate (cd)*: A command used in the terminal to change the current directory to a different directory. cd stands for "change directory." + +## Instructions for Manual Testing -## Instructions for manual testing +- Do refer to our [User Guide](https://ay2425s1-cs2113-w12-2.github.io/tp/UserGuide.html) for quick start details. -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +- When running the app, you can key in the command `help` for a list of executable commands and their usage on the application. diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..66799bde15 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,8 @@ -# Duke +# FindOurSEP -{Give product intro here} +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently manage the allocation process using automated workflows and data-driven decision-making. Useful links: -* [User Guide](UserGuide.md) +* [FindOurSEP User Guide](UserGuide.md) * [Developer Guide](DeveloperGuide.md) * [About Us](AboutUs.md) diff --git a/docs/UML_Diagrams/AddCommand.drawio.svg b/docs/UML_Diagrams/AddCommand.drawio.svg new file mode 100644 index 0000000000..32fa220bc0 --- /dev/null +++ b/docs/UML_Diagrams/AddCommand.drawio.svg @@ -0,0 +1,4 @@ + + + +
:AddCommand
:Student
:StudentList
validateStudentID(studentID)
validateGPA(gpa)
validatePreferences(preferences)
preferences
makeStudent()
Student(id,gpa,preferences)
:Student
:Student
addStudent(newStudent)
loop
[until all students are checked]
getID()
:String
studentID
gpa
\ No newline at end of file diff --git a/docs/UML_Diagrams/AllocatorClass.drawio.svg b/docs/UML_Diagrams/AllocatorClass.drawio.svg new file mode 100644 index 0000000000..ba62e91c44 --- /dev/null +++ b/docs/UML_Diagrams/AllocatorClass.drawio.svg @@ -0,0 +1,4 @@ + + + +Allocator+ allocate(): StudentList+ Allocator(studentlist: StudentList)
1
1
AllocateCommand
AllocateCommand
{abstract}
Command
{abstract}...
1
1
1
1
StudentList
StudentList
92
92
UniversityRepository
UniversityRepository
University
University
searchesΒ β–²
searchesΒ β–²
UI
UI

Class Diagram Involving Allocator Class

Class Diagram Involving Allocator Class
1
1
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/AllocatorSequence.drawio.svg b/docs/UML_Diagrams/AllocatorSequence.drawio.svg new file mode 100644 index 0000000000..3691f426a2 --- /dev/null +++ b/docs/UML_Diagrams/AllocatorSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:FindOurSEP
:FindOurSEP
:Parser
:Parser
parseInput("allocate")
parseInput("allocate")
:boolean
:boolean
AllocateCommand()
AllocateCo...
:AllocateCommand
:AllocateCommand
:Allocator
:Allocator
Allocator()
Allocator()
run()
run()
allocate()
allocate()
:StudentList
:StudentList
β•³
β•³
β•³
β•³
:StudentList
:StudentList
setStudentList()
setStudentList()
:UI
:UI
printAllocatingMessage()
printAllocatingMessage()

Sequence diagram for the execution of allocate command

Sequence diagram for the execution of allocate command
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/Architecture.drawio.svg b/docs/UML_Diagrams/Architecture.drawio.svg new file mode 100644 index 0000000000..a09472054c --- /dev/null +++ b/docs/UML_Diagrams/Architecture.drawio.svg @@ -0,0 +1,4 @@ + + + +
Main
UI
Parser
StudentList
Command
UniversityRepository
Allocator
\ No newline at end of file diff --git a/docs/UML_Diagrams/CriteriaCommand.drawio.svg b/docs/UML_Diagrams/CriteriaCommand.drawio.svg new file mode 100644 index 0000000000..c87c189aee --- /dev/null +++ b/docs/UML_Diagrams/CriteriaCommand.drawio.svg @@ -0,0 +1,4 @@ + + + +
:CriteriaCommand
:StudentList
:Allocator
setMinimumGPA()
validateGpa()
:float
\ No newline at end of file diff --git a/docs/UML_Diagrams/DeleteCommand.drawio.svg b/docs/UML_Diagrams/DeleteCommand.drawio.svg new file mode 100644 index 0000000000..946e98b981 --- /dev/null +++ b/docs/UML_Diagrams/DeleteCommand.drawio.svg @@ -0,0 +1,4 @@ + + + +
:DeleteCommand
:StudentList
deleteStudent()
return
:Student
getId()
loop
:String
[Until a student's ID equals new studentID]
opt
[Student has been allocated a University previously]
getAllocatedUniversity()
:int
:UniversityRepository
:University
addASpot()
getUniversityByIndex()
:University
\ No newline at end of file diff --git a/docs/UML_Diagrams/ExitSequence.drawio.svg b/docs/UML_Diagrams/ExitSequence.drawio.svg new file mode 100644 index 0000000000..cc1695a40f --- /dev/null +++ b/docs/UML_Diagrams/ExitSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:FindOurSEP
setUpFileHandler()
start()
loop
[isRunning]
:UI
getUserInput()
:String
:Parser
parseInput("exit")
:boolean
:ExitCommand
ExitCommand()
run()
cleanupAndExit()
:FileHandler
FileHandler()
toSave()
:boolean
opt
[toSave]
getSaveFileChoice()
saveAllocationResults(results, fileChoice)
sayBye()
ExitCommand: Start to End Sequence Diagram
End of Program
\ No newline at end of file diff --git a/docs/UML_Diagrams/FileHandlerSequence.drawio.svg b/docs/UML_Diagrams/FileHandlerSequence.drawio.svg new file mode 100644 index 0000000000..b00d87155b --- /dev/null +++ b/docs/UML_Diagrams/FileHandlerSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:FindOurSEP
setUpFileHandler()
start()
:UI
printConfigMessage()
:Parser
parseInput()
parseInput()
parseInput()
:FileHandler
getFileExtension()
hasProcessJSON()
:boolean
hasProcessFileSuccessfully()
opt
[File do not exist]
[File is empty]
throw FileNotFoundΒ 
throw EmptyFileΒ 
alt
[CSV]
hasProcessCSV()
[JSON]
hasProcessTXT()
[TXT]
[else]
alt
[Process File Successfully]
printFileLoadSuccessMessage()
[else]
printProcessError()
FileHandler()
promptFilePath()
Sequence Diagram for FileHandler (File Input)
opt
[Empty FilePath]
Program continues running
If empty filePath,
return from setUpFileHandler()
throw UnknownFileΒ 
\ No newline at end of file diff --git a/docs/UML_Diagrams/FileHandlerSequenceOld.drawio.svg b/docs/UML_Diagrams/FileHandlerSequenceOld.drawio.svg new file mode 100644 index 0000000000..b0fdccc5d5 --- /dev/null +++ b/docs/UML_Diagrams/FileHandlerSequenceOld.drawio.svg @@ -0,0 +1,4 @@ + + + +
:FindOurSEP
setUpFileHandler()
start()
:UI
printConfigMessage()
:Parser
:FileHandler
getFileExtension()
hasProcessJSON()
FileHandler()
promptFilePath()
:boolean
hasProcessFileSuccessfully()
alt
[File do not exist]
[File is empty]
throw FileNotFoundΒ 
throw EmptyFileΒ 
alt
[CSV]
hasProcessCSV()
[JSON]
hasProcessTXT()
[TXT]
[else]
throw UnknownFileΒ 
alt
[Process File Successfully]
printFileLoadSuccessMessage()
[else]
printProcessError()
Program continues running
Sequence Diagram for FileHandler (File Input)
\ No newline at end of file diff --git a/docs/UML_Diagrams/FilterCommandClass.drawio.svg b/docs/UML_Diagrams/FilterCommandClass.drawio.svg new file mode 100644 index 0000000000..0ba737dea4 --- /dev/null +++ b/docs/UML_Diagrams/FilterCommandClass.drawio.svg @@ -0,0 +1,4 @@ + + + +
{abstract}
Command
UI
1

FilterCommand


- input: String

- ui: UI


+ run(): void

*
Student

StudentList


+ filterStudent(input: String) : void

1
\ No newline at end of file diff --git a/docs/UML_Diagrams/FilterSequence.drawio.svg b/docs/UML_Diagrams/FilterSequence.drawio.svg new file mode 100644 index 0000000000..0d3dcd6532 --- /dev/null +++ b/docs/UML_Diagrams/FilterSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
alt
:FilterCommand
:StudentList
filterStudent(input)
[command.equals("list")]
[command.equals("report")]
alt
[listCommand.equals("id")]
ref
filter student id
[listCommand.equals("gpa")]
ref
filter student gpa
[listCommand.equals("allocate")]
[listCommand.equals("unallocate")]
ref
filter student allocation list
validateFindFilterFormat()
filterStudentList()
filterStudentReport()
ref
filter student report
\ No newline at end of file diff --git a/docs/UML_Diagrams/FilterStudentAllocationListsd.drawio.svg b/docs/UML_Diagrams/FilterStudentAllocationListsd.drawio.svg new file mode 100644 index 0000000000..30a256819f --- /dev/null +++ b/docs/UML_Diagrams/FilterStudentAllocationListsd.drawio.svg @@ -0,0 +1,4 @@ + + + +
opt
loop
sd filter student allocation listΒ 
:StudentList
filterByAllocationStatus()
:Ui
ref
print student listΒ 
:Student
[student:students]
filteredStudents.add(student)
filteredStudents
[isAllocated]
\ No newline at end of file diff --git a/docs/UML_Diagrams/FilterStudentGpasd.drawio.svg b/docs/UML_Diagrams/FilterStudentGpasd.drawio.svg new file mode 100644 index 0000000000..9f4db8d21d --- /dev/null +++ b/docs/UML_Diagrams/FilterStudentGpasd.drawio.svg @@ -0,0 +1,4 @@ + + + +
sd filter student gpaΒ 
:StudentList
:Ui
alt
[command.equals("ascending")]
sortStudentsByAscendingGPA()
ref
print student list
[command.equals("descending")]
sortStudentsByDescendingGPA()
ref
print student list
\ No newline at end of file diff --git a/docs/UML_Diagrams/FilterStudentIdsd.drawio.svg b/docs/UML_Diagrams/FilterStudentIdsd.drawio.svg new file mode 100644 index 0000000000..0dddb03582 --- /dev/null +++ b/docs/UML_Diagrams/FilterStudentIdsd.drawio.svg @@ -0,0 +1,4 @@ + + + +
sd filter student idΒ 
:StudentList
:Ui
alt
[command.equals("ascending")]
sortStudentsByAscendingId()
ref
print student list
[command.equals("descending")]
sortStudentsByDescendingId()
ref
print student list
\ No newline at end of file diff --git a/docs/UML_Diagrams/FilterStudentReportsd.drawio.svg b/docs/UML_Diagrams/FilterStudentReportsd.drawio.svg new file mode 100644 index 0000000000..69e2cc2ed3 --- /dev/null +++ b/docs/UML_Diagrams/FilterStudentReportsd.drawio.svg @@ -0,0 +1,4 @@ + + + +
opt
loop
sd filter student reportΒ 
:StudentList
filterByAllocationStatus()
:Ui
ref
generate report
:Student
[student:students]
filteredStudents.add(student)
filteredStudents
[isAllocated]
\ No newline at end of file diff --git a/docs/UML_Diagrams/FindCommandClass.drawio.svg b/docs/UML_Diagrams/FindCommandClass.drawio.svg new file mode 100644 index 0000000000..3cab7ec5a5 --- /dev/null +++ b/docs/UML_Diagrams/FindCommandClass.drawio.svg @@ -0,0 +1,4 @@ + + + +
{abstract}
Command
UI
1

FindCommand


- input: String

- ui: UI


+ run(): void

*
Student

StudentList


+ findStudent(input: String) : void

1
\ No newline at end of file diff --git a/docs/UML_Diagrams/FindSequence.drawio.svg b/docs/UML_Diagrams/FindSequence.drawio.svg new file mode 100644 index 0000000000..be87a8976d --- /dev/null +++ b/docs/UML_Diagrams/FindSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
alt
:FindCommand
:StudentList
validateFindFilterFormat()
findStudent(input)
:Student
opt
loop
student.getId().contains(studentId)
foundStudents.add(student)
[contains(studentId)]
boolean
:Ui
[command.equals("list")]
[command.equals("report")]
[student : students]
ref
print student list
ref
generate report
\ No newline at end of file diff --git a/docs/UML_Diagrams/GenerateCommandSequence.drawio.svg b/docs/UML_Diagrams/GenerateCommandSequence.drawio.svg new file mode 100644 index 0000000000..227d24ab28 --- /dev/null +++ b/docs/UML_Diagrams/GenerateCommandSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:GenerateCommand
:GenerateCommand
:StudentList
:StudentList
:UI
:UI
generateReport()
generateReport()
Sequence diagram for the execution of Generate command
Sequence diagram for the execution of Generate command
generateReport()
generateReport()
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/GenerateReportsd.drawio.svg b/docs/UML_Diagrams/GenerateReportsd.drawio.svg new file mode 100644 index 0000000000..019a248c55 --- /dev/null +++ b/docs/UML_Diagrams/GenerateReportsd.drawio.svg @@ -0,0 +1,4 @@ + + + +
:UI
:UI
sdΒ generate report
sdΒ generate report
:StudentList
:StudentList
:Student
:Student
generateReport()
generateReport()
getAllocatedUniversity()
getAllocatedUniversity()
loop
loop
[Student s:studentList]
[Student s:studentList]
getFullName()
getFullName()
:UniversityRepository
:UniversityRepos...
:University
:University
getUniversityByIndex()
getUniversityByIndex()
get(index)
get(index)
:University
:University
:University
:University
alt
alt
[allocated != null]
[allocated != null]
getId()
getId()
getId()
getId()
[else]
[else]
printResponse()
printResponse()
:int
:int
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/ListCommand.drawio.svg b/docs/UML_Diagrams/ListCommand.drawio.svg new file mode 100644 index 0000000000..7b4967c12e --- /dev/null +++ b/docs/UML_Diagrams/ListCommand.drawio.svg @@ -0,0 +1,4 @@ + + + +
:ListCommand
:ListCommand
:StudentList
:StudentList
:UI
:UI
printStudentList()
printStudentList()
Sequence diagram for the execution of List command
Sequence diagram for the execution of List command
printStudentList()
printStudentList()
loop
loop
[until all students are printed]
[until all students are printe...
:Student
:Student
getId()
getId()
getGpa()
getGpa()
getUniPreferences()
getUniPreferences()
printResponse()
printResponse()
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/ParserClass.drawio.svg b/docs/UML_Diagrams/ParserClass.drawio.svg new file mode 100644 index 0000000000..fc22272e8b --- /dev/null +++ b/docs/UML_Diagrams/ParserClass.drawio.svg @@ -0,0 +1,4 @@ + + + +
FindOurSEP
UI
Parser
XYZCommand
StudentList
1
1
executes
1
1
1
{abstract}
Command
1
1
1
XYZCommand = AddCommand, ListCommand, ExitCommand, etc.
Class Diagram Involving Parser Class
\ No newline at end of file diff --git a/docs/UML_Diagrams/ParserSequence.drawio.svg b/docs/UML_Diagrams/ParserSequence.drawio.svg new file mode 100644 index 0000000000..a50c006ba9 --- /dev/null +++ b/docs/UML_Diagrams/ParserSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:Parser
:Parser
boolean
boolean
:AddCommand
:AddCommand
AddCommand()
AddCommand()
run()
run()
:StudentList
:StudentList
makeStudent()
makeStudent()
:Student
:Student
:UI
:UI
Sequence diagram for the execution of Add command
Sequence diagram for the execution of Add command
addStudent()
addStudent()
printResponse()
printResponse()
parseInput("add id/A1234567J gpa/5.0 p/{13,61,43}")
parseInput("add id/A1234567J gpa/5.0 p/{13,61,43}")
:FindOurSEP
:FindOurSEP
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/PrintStudentListsd.drawio.svg b/docs/UML_Diagrams/PrintStudentListsd.drawio.svg new file mode 100644 index 0000000000..dd8d8a8cc8 --- /dev/null +++ b/docs/UML_Diagrams/PrintStudentListsd.drawio.svg @@ -0,0 +1,4 @@ + + + +
loop
sd print student list
:StudentList
:Ui
:Student
printStudentList()
getId()
[Student s:studentList]
getGpa()
getUniPreferences()
printResponse()
\ No newline at end of file diff --git a/docs/UML_Diagrams/RevertCommandSequence.drawio.svg b/docs/UML_Diagrams/RevertCommandSequence.drawio.svg new file mode 100644 index 0000000000..73387ff1bf --- /dev/null +++ b/docs/UML_Diagrams/RevertCommandSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:RevertCommand
:RevertCommand
:StudentList
:StudentList
:UI
:UI
printResponse()
printResponse()
revertAllocation()
revertAllocation()
:Student
:Student
loop
loop
[until all students are covered]
[until all students are covere...
revertAllocation()
revertAllocation()
Sequence diagram for the execution of Revert command
Sequence diagram for the execution of Revert command
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/StatSequence.drawio.svg b/docs/UML_Diagrams/StatSequence.drawio.svg new file mode 100644 index 0000000000..ff8e1b48cd --- /dev/null +++ b/docs/UML_Diagrams/StatSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:StatCommand
:StatCommand
β•³
β•³
:StudentList
:StudentList
calculateAverageGPAforUniversity()
calculateAverageGPAforUniversity()
avgGpa:double
avgGpa:double
:UI
:UI
printResponse("The average GPA is: " + avgGpa)
printResponse("The average GPA is: " + avgGpa)
:UniversityRepo
:UniversityRepo

Sequence diagram for stats command (take stats -avg as an example)

Sequence diagram for stats command (take stats -avg as an example)
getUniversityByIndex()
getUniversityByIndex()
:University
:University
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/StudentList.drawio.svg b/docs/UML_Diagrams/StudentList.drawio.svg new file mode 100644 index 0000000000..4577055c31 --- /dev/null +++ b/docs/UML_Diagrams/StudentList.drawio.svg @@ -0,0 +1,4 @@ + + + +
:AddCommand
:Student
:StudentList
validateStudentID()
makeStudent()
Student()
:Student
:Student
addStudent()
validateGPA()
loop
[until all students are checked]
getID()
String
\ No newline at end of file diff --git a/docs/UML_Diagrams/UIClass.drawio.svg b/docs/UML_Diagrams/UIClass.drawio.svg new file mode 100644 index 0000000000..41cfd12e4a --- /dev/null +++ b/docs/UML_Diagrams/UIClass.drawio.svg @@ -0,0 +1,4 @@ + + + +
UI
UI
- LINE_LENGTH : int = 80
+ HORIZONTAL_LINE : String
- scanner : Scanner
- LINE_LENGTH : int = 80...
+ UI()
+ getUserInput() :String
+ printStudentList(studentList : ArrayList<Student>) : void
+ printAllocatingMessage() : void
+ generateReport(studentList : ArrayList<Student>) : void
+ UI()...
1
1
1
1
Parser
Parser
Command
Command
Student
Student
StudentList
StudentList
1
1
*
*
FindOurSEP
FindOurSEP
1
1
1
1
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/University.drawio.svg b/docs/UML_Diagrams/University.drawio.svg new file mode 100644 index 0000000000..68b30ce46b --- /dev/null +++ b/docs/UML_Diagrams/University.drawio.svg @@ -0,0 +1,4 @@ + + + +University- fullname: String- acronym: String- spotsLeft: intUniversityRepository- universityMap: HashMap<Integer, University>+ initialiseMap()+ getUniversityByIndex(index: int): University+ resetMap()
92
*
\ No newline at end of file diff --git a/docs/UML_Diagrams/ViewQuotaSequence.drawio.svg b/docs/UML_Diagrams/ViewQuotaSequence.drawio.svg new file mode 100644 index 0000000000..55921d0913 --- /dev/null +++ b/docs/UML_Diagrams/ViewQuotaSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:ViewQuotaCommand
:ViewQuotaCommand
β•³
β•³
:University
:University
getSpotsLeft()
getSpotsLeft()
:int
:int
:UI
:UI
printResponse("Index": index, "Name": name, "Quota": spotsLeft)
printResponse("Index": index, "Name": name, "Quota": spotsLeft)
:UniversityRepo
:UniversityRepo

Sequence diagram for viewQuota command

Sequence diagram for viewQuota command
getUniversityByIndex()
getUniversityByIndex()
:University
:University
getFullName()
getFullName()
:String
:String
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/UML_Diagrams/saveAllocationResultsSequence.drawio.svg b/docs/UML_Diagrams/saveAllocationResultsSequence.drawio.svg new file mode 100644 index 0000000000..dc1f2213c5 --- /dev/null +++ b/docs/UML_Diagrams/saveAllocationResultsSequence.drawio.svg @@ -0,0 +1,4 @@ + + + +
:FileHandler
alt
[fileChoice.equals("csv")]
loop
:Student
getId()
getGpa()
getUniPreferences()
getAllocatedUniversity()
loop
getId()
getGpa()
getUniPreferences()
getAllocatedUniversity()
[fileChoice.equals("txt")]
[fileChoice.equals("json")]
[student : ArrayList<Student> results]
saveToTXT(results, "data/allocation_results.txt")
[student : students]
saveToCSV(results, "data/allocation_results.csv")
mapper:ObjectMapper
saveToJSON(results, "data/allocation_results.json")
mapper.writeValue(jsonFile, studentsMap)
ObjectMapper()
\ No newline at end of file diff --git a/docs/UML_Diagrams/studentandstudentlist.drawio.svg b/docs/UML_Diagrams/studentandstudentlist.drawio.svg new file mode 100644 index 0000000000..2b8d0c5e68 --- /dev/null +++ b/docs/UML_Diagrams/studentandstudentlist.drawio.svg @@ -0,0 +1,4 @@ + + + +Student- id: Sting- gpa: float- uniPreferences : ArrayList<Integer>- isSuccessfullyAllocated: boolean- allocatedUniversity: intStudentList- ui: UI+ addStudent(student: Student)+ deleteStudent(input: String)+ calculateAverageGpaForUniversity(uniIndex): double+ findStudent(input: String)+ generateReport()+ filterStudentList(filter: String)
*
*
\ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index d6cf4c3b3a..a4d8676075 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,41 +2,524 @@ ## Introduction -{Give a product intro} +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently manage the allocation process using automated workflows and data-driven decision-making. + +## Table of Contents +- [Quick Start](#quick-start) +- [Pre-Usage Guidelines](#pre-usage-guidelines) +- [Features](#features) + - [Uploading Information](#uploading-information) + - [Manual Input](#manual-input) + - [File Input](#file-input) + - [Add Command](#add-student-application-add) + - [Delete Command](#delete-student-application-delete) + - [Help Command](#view-help-help) + - [List Command](#print-current-student-list-list) + - [Find Command](#find-student-find) + - [Minimum Command](#set-minimum-gpa-criteria-minimum) + - [Filter Command](#filter-student-filter) + - [Stats Command](#view-allocation-statistics-stats) + - [ViewQuota Command](#view-remaining-quota-viewquota) + - [Allocate Command](#run-allocation-algorithm-allocate) + - [Revert Command](#revert-allocation-outcome-revert) + - [Generate Command](#generate-report-of-allocation-generate) + - [Exit Command](#exit-program-bye-exit-quit) +- [FAQ](#faq) +- [Glossary](#glossary) +- [Command Summary](#command-summary) +- [Accepted File Format](#accepted-file-format) ## Quick Start -{Give steps to get started quickly} +1. Ensure that you have Java 17 or above installed. ([Installation Guide](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)) +2. Download the latest version v2.1 of `FindOurSEP` and the 3 test files from [here](https://github.com/AY2425S1-CS2113-W12-2/tp/releases). +3. **Copy the files:** + - Move the downloaded `.jar` file into a designated folder on your computer. + - Move the 3 test files (test.csv, test.json, test.txt) to the same folder as the `.jar` file. +4. **Run the `.jar` file:** + - Open a terminal. + - Navigate (`cd`) to the folder containing the `.jar` file. + - Execute the `.jar` file using the following command: + ```shell + java -jar FindOurSEP.jar + ``` + +## Pre-Usage Guidelines +Before using the program, please take note of the following IMPORTANT GUIDELINES: + +### Language Recognition +- **Only English language is recognized** in the program. Please ensure that all data inputs use English characters exclusively. + +### Data Upload +- Users can **ONLY upload data at the START of the program**. Please ensure that all necessary data is prepared and ready for upload before initiating the program. +- For further support, please take a look at [Accepted File Format](#accepted-file-format). + +### Output Data +- The `allocation_results` file located under the `data` folder is **NOT meant to be re-inputted into the program**. It is intended solely for external usage by the users. + +### Proper Exit Procedure +- Please exit the program using the `exit` command. Avoid using other methods to terminate the program to ensure a clean and safe shutdown. + +Please adhere to these guidelines to ensure smooth and proper functionality of the program. + + +
+ +## Features + +### Uploading Information +Upon start-up, the user will see this interface: +```bash +-------------------------------------------------------------------------------- +Good day to you! +Before we begin, would you like to: +1. Manually input students data +2. Upload a file (.csv, .txt, .json) +Please choose 1 or 2 or exit :) +-------------------------------------------------------------------------------- +``` +The program provides two methods for uploading student data, allowing flexibility in how you input and manage information. Below are the options: + +It is **HIGHLY RECOMMENDED** to use file input for large datasets and manual input for minor adjustments. + +#### Manual Input +By inputting `1`, users can directly enter student data into the program. This option is useful for adding individual records or performing quick updates without needing to upload an entire file. + +Subsequently, the user will be met with the following: +```shell +-------------------------------------------------------------------------------- +Hi! Welcome to FindOurSEP! Enter help for the list of commands. +-------------------------------------------------------------------------------- +``` +The user can then begin using the program by inputting [commands](#notes-about-the-command-format). + +
+ +#### File Input +If `2` is chosen instead, the users will be asked to upload files in CSV, JSON, or TXT format, containing multiple student records at once. This method is ideal for batch processing, allowing efficient loading of extensive datasets into the program. Each file type follows a specific format for student ID, GPA, and university preferences, ensuring consistent and structured data handling. + +Then, the user will be prompted to input the file path to the file. +```shell +-------------------------------------------------------------------------------- +Example: C:\Users\bob\OneDrive\Documents\tp\test.csv +Please enter the ABSOLUTE path to the file: +-------------------------------------------------------------------------------- +``` + +**NOTE:** If the test files are in the same folder as the `.jar` file, users can just enter the name of the test file. E.g, `test.json`. + +If the file is uploaded successfully, the following will be displayed. +```shell +-------------------------------------------------------------------------------- +File loaded successfully! Let's begin! Enter help for available commands. +-------------------------------------------------------------------------------- +``` +However, if the file has any abnormality, an error message will be displayed to the user. The user will still be allowed to continue using the program but with no data uploaded. + +For e.g, the following is the error message shown when the user's CSV file contains data with wrong formatting. +```shell +A1234567I,5, {12,61,43}, ]" is not in correct format! +Please ensure that you only have 3 columns representing the ID, GPA and PREFERENCES. +-------------------------------------------------------------------------------- +Process error! To re-upload a file, please restart the program by entering 'exit' +followed by 'no' and ensure file is formatted correctly before retrying. +Otherwise, you can continue to use the program. Enter help for available commands. +-------------------------------------------------------------------------------- +``` +Upon file upload failure, users can continue using the program without any data uploaded. +To attempt to reload the data, exit and restart the program to try the upload again. + +For further support, please take a look at [Accepted File Format](#accepted-file-format). + +
+ +### *Notes about the command format:* +- Words in UPPER_CASE are the parameters to be supplied by the user. +- Extraneous parameters for commands that do not take in parameters (such as help, list and exit) will be ignored. + - e.g. if the command specifies `help 123`, it will be interpreted as help. +- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple +lines as space characters surrounding line-breaks may be omitted when copied over to the application. + +
+ +### View help: `help` +Shows a message explaining how to use the program. (Commands, etc.) +Format: `help` +Expected output: + +```shell +> help +-------------------------------------------------------------------------------- +Here is the list of possible commands: + add Adds a student with the specified ID, GPA, and preference rankings. + Example: add id/A1234567I gpa/5.0 p/{13,61,43} + Note: PREFERENCE_RANKINGS should be enclosed in curly braces + + delete Deletes the student with the specified ID from the list. + Example: delete A1234567I + + help Displays this help message. + + list Lists all students currently in the system. + + find Finds the student with the keyword, returning either a list or report. + Example: find list A1234567I + + minimum Sets the minimum GPA to be considered for exchange. + + filter Filters student data with a keyword, returning either a list or report. + User can choose between ascending/descending id/gpa and allocation status. + The format is: 'filter ', + or 'filter ' + Example: filter list gpa ascending + + stats Displays GPA stats for students who have been allocated a uni. + Usage: + stats -avggpa Displays the average GPA for the specified uni. + stats -mingpa Displays the minimum GPA for the specified uni. + + viewQuota Displays the index, name, and remaining quota for the specified uni. + Usage: viewQuota + + allocate Allocates students to available slots based on their preferences. + + revert Reverts the student list to original, pre-allocation state. + + generate Generates a report of allocations and student data. + + exit Exits the application. + +-------------------------------------------------------------------------------- + +``` + +
+ +### Add Student Application: `add` + +Adds a student to the student list. `PREFERENCE_RANKINGS` should be enclosed in curly braces. We allow students to have a preference of up to 3 universities, and they are ranked left to right, with the HIGHEST priority starting on the left. +Duplicate preference rankings would imply the same preferences for following rounds of allocation. + +Format: `add id/STUDENT_ID gpa/GPA p/{PREFERENCE_RANKINGS}` +e.g. +`add id/A1234567I gpa/5.0 p/{13,61,43}` + +### Delete Student Application: `delete` + +Deletes a student from the student list. + +Format: `delete STUDENT_ID` +Deletes the student with the specified id. +The id to be deleted must match the id in the student records. +e.g. `delete A1234567I` +Example output: +```shell +> delete A1234567I +-------------------------------------------------------------------------------- +Removed student, 1 student(s) left +-------------------------------------------------------------------------------- +``` + +### Print current student list: `list` +Outputs a list of all current students in the student list. +Format: `list` +Example output: -1. Ensure that you have Java 17 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +```shell +> list +-------------------------------------------------------------------------------- +Here is the list: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Student β”‚ GPA β”‚ Preference Rankings β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ A1234567I β”‚ 5.0 β”‚ 13,61,43 β”‚ +β”‚ A2113113X β”‚ 4.99 β”‚ 61 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +-------------------------------------------------------------------------------- +``` -## Features +
-{Give detailed description of each feature} +### Find Student: `find` +Finds the student with the keyword, returning either a list or report. Input is case-sensitive and +full IDs are not required, but merely keyword. -### Adding a todo: `todo` -Adds a new item to the list of todo items. +Format: `find STUDENT_ID` +e.g. +`find list A123` -Format: `todo n/TODO_NAME d/DEADLINE` +Example output: +```shell +-------------------------------------------------------------------------------- +Finding for students... student(s) found. +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Here is the list: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Student β”‚ GPA β”‚ Preference Rankings β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ A1234567I β”‚ 5.0 β”‚ 13,61,43 β”‚ +β”‚ A2113113X β”‚ 4.99 β”‚ 61 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +-------------------------------------------------------------------------------- +``` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +### Set minimum GPA criteria: `minimum` -Example of usage: +This sets the minimum GPA for the cohort. -`todo n/Write the rest of the User Guide d/next week` +Format: `minimum GPA` +The student must achieve the same or a higher GPA to be considered for exchange. Any student below this GPA will not be allocated to any universities. +e.g. +`minimum 4.0` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` + +
+ +### Filter Student: `filter` + +Filters student data with a keyword, returning either a list or report. +User can choose between ascending/descending id/gpa and allocation status. + +Format: `filter `, + or `filter ` +e.g. +`filter list gpa ascending` + +Example output: + +```shell +-------------------------------------------------------------------------------- +Here is the list: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Student β”‚ GPA β”‚ Preference Rankings β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ A1237154B β”‚ 4.50 β”‚ 64 β”‚ +β”‚ A1234567I β”‚ 5.0 β”‚ 13,61,43 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +-------------------------------------------------------------------------------- +``` + +### View allocation statistics: ``stats`` + +Displays the average or minimum GPA for the students who have been allocated to the specified partner university. +Format: `stats -avggpa UNI_INDEX` or `stats -mingpa UNI_INDEX` +Example output: + +```bash +> stats -avggpa 36 +-------------------------------------------------------------------------------- +The average GPA for university index 36 (The University of Hong Kong) is: 3.80 +-------------------------------------------------------------------------------- +``` + +
+ +### View remaining quota: ``viewQuota`` + +Displays the index, name, and remaining quota for the specified partner university. +Format: `viewQuota UNI_INDEX` +Example output: + +```bash +> viewQuota 58 +-------------------------------------------------------------------------------- + Index: 58 + Name: ETH Zurich + Quota: 1 +-------------------------------------------------------------------------------- +``` + +### Run allocation algorithm: ``allocate`` + +Start the allocation algorithm. It will allocate universities to the current student list based on each student's preferences and GPA. +Format: `allocate` +Upon successful completion, the console should output: +```shell +-------------------------------------------------------------------------------- +Allocation algorithm complete! Run generate command to view. +-------------------------------------------------------------------------------- +``` +After which, the user can run the `generate` command to view the results, or `revert` command to undo the allocation. + +### Revert allocation outcome: `revert` + +Reverses the allocation algorithm. To be used when changes need to be made to the student list. +Format: `revert` +Upon successful completion, the console should output: +```shell +-------------------------------------------------------------------------------- +Revert complete! Run the allocate command whenever you are ready. +-------------------------------------------------------------------------------- +``` + +
+ +### Generate report of allocation: `generate` +Outputs all students' ID and allocated university in an ASCII table. +Used for viewing the results of the allocation process. + +Format: `generate` + +Example output: +```shell +Here is the allocation report: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Student β”‚ University Granted β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ A1234567I β”‚ Chongqing University β”‚ +β”‚ A2113113X β”‚ National Tsing Hua University β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Exit Program: `bye`, `exit`, `quit` +When any of the exit commands (bye, exit, quit) are entered, the program will prompt the user (`Do you want to save your results?`) to save their allocation results. If the user choose `yes`, they will be prompted to enter the desired file format (CSV, JSON, TXT). +Based on their selection, the program will save the results in the specified format. + +File Formats: +- CSV: Your data will be saved in a comma-separated values format, making it easy to open in spreadsheet applications. +- JSON: The data will be structured in a JSON format, suitable for data interchange and storage. +- TXT: Your results will be saved in a plain text format, which can be opened in any text editor. + +Once the save operation is complete, the program will exit gracefully. + +If the user choose not to save the results, the program will end immediately without saving any data. + +
+ +Below is an example of an `exit` scenario. +```shell +> exit +-------------------------------------------------------------------------------- +Do you want to save your results? +-------------------------------------------------------------------------------- +> yes +-------------------------------------------------------------------------------- +Please choose a file type (CSV, JSON, TXT) to save your results. +-------------------------------------------------------------------------------- +> json +Allocation results saved to JSON file at data/allocation_results.json +-------------------------------------------------------------------------------- +Adios, amigo! +-------------------------------------------------------------------------------- +``` +In the above case, the data will be stored in the relative path: `.../data/allocation_results.json`. + +**IMPORTANT**: Please note that if the file already exists, its contents will be overwritten. + +
## FAQ -**Q**: How do I transfer my data to another computer? +## Frequently Asked Questions (FAQs) + +> **Q: I am facing trouble starting the application. Do you know what might be the issue?** +> +> A: Please ensure that you have Java 17 or above installed on your machine. You may find more instructions in the [Quick Start](#quick-start) section. + +> **Q: How do I know whether the data entered is saved?** +> +> A: Your data is safely stored in the `data` folder located in the folder where your jar file is. The file will be named `allocation_results` with the extension you selected before the application closed. + +> **Q: My application crashed. How do I report the problem to the developers?** +> +> A: We are sorry for the unpleasant experience with FindOurSEP, and we would be more than happy to solve the issue. You may file an issue on our [GitHub](https://github.com/AY2425S1-CS2113-W12-2/tp/issues) stating how you arrived at the problem, so that our developers can assist you with the issue. + +> **Q: I am a developer. How can I find the source code and contribute to FindOurSEP?** +> +> A: FindOurSEP is an open-source application, and we welcome developers to share their ideas. You may find the source code on [GitHub](https://github.com/AY2425S1-CS2113-W12-2/tp). + +> **Q: How does FindOurSEP decide who should be allocated first?** +> +> A: In FindOurSEP, students are prioritized based on their GPA, with higher GPAs sorted first. When two students share the same GPA, priority is given to the one listed earlier, following a first-come-first-serve basis. + +
+ +## Glossary -**A**: {your answer here} +* *SEP* (Student Exchange Programme): NUS’s largest global exchange initiative, enabling students to study at over 300 partner universities in 40+ countries. *FindOurSEP* project is designed specifically to assist in the SEP allocation process for CEG (Computer Engineering) students. +* *GPA* (Grade Point Average): A numeric score ranging from 0.0 to 5.0, representing a student's academic performance, used for allocation. +* *CSV* (Comma-Separated Values): A file format used to store tabular data, such as student records. +* *JSON* (JavaScript Object Notation): A lightweight data-interchange format used for storing student and university data. +* *Allocator*: Class responsible for assigning students to universities based on GPA and preferences. +* *Command*: A specific action or function executed by the program, such as `add`, `delete`, or `allocate`. +* *Parser*: Class that interprets and processes user input commands. +* *StudentList*: Data structure containing records of all students in the SEP system. +* *UniversityRepository*: A repository containing information on partner universities available for SEP. +* *FindOurSEP*: The name of the project system designed to facilitate and optimize the SEP (Student Exchange Programme) allocation process specifically for Computer Engineering students at NUS. +* *Java 17*: A version of the Java programming language and runtime environment. It’s important to have this or a more recent version installed to run certain Java applications. +* *.jar file*: A Java ARchive file that contains Java classes and associated metadata and resources. It's used to distribute Java applications. +* *Terminal*: A text-based interface used to interact with the computer’s operating system, allowing you to execute commands. +* *Navigate (cd)*: A command used in the terminal to change the current directory to a different directory. cd stands for "change directory." + +
## Command Summary -{Give a 'cheat sheet' of commands here} +| Action | Description | Format/Example | +| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| Add | Adds new student to list, if not already present | `add id/STUDENT_ID gpa/GPA p/{PREFERENCE_RANKINGS}`
e.g. `add id/A1234567I gpa/5.0 p/{13,61,43}` | +| Delete | Removes student with specified ID | `delete STUDENT_ID`
e.g. `delete A1234567I` | +| Find | Searches the student list for all matches to query | `find STUDENT_ID`
e.g. `find list A1234567I` | +| Filter | Prints a sorted student list | `filter ` or `filter `
e.g. `filter list gpa ascending` | +| List | Prints the current student list | `list` | +| Statistics | Shows statistics for a university | ``stats -avggpa UNI_INDEX`` or ``stats -mingpa UNI_INDEX``
e.g. ``stats -mingpa 42`` | +| ViewQuota | Shows spots available in a university | ``viewQuota UNI_INDEX``
e.g. ``viewQuota 42`` | +| Allocate | Runs the allocation algorithm | `allocate` | +| Minimum | Sets the minimum GPA for student to be considered for allocation | `minimum GPA_CRITERIA`.
e.g. ``minimum 4.0`` | +| Revert | Resets the allocation algorithm | `revert` | +| GetReport | Returns the outcome of the allocation | `generate` | +| Exit | Ends the current session. User will be prompted about the save file | `bye`, `exit`, `quit` | +| Help | Prints a message containing all available commands | `help` | + + +## Accepted File Format +The program supports files input in `.JSON`, `.CSV`, and `.TXT` formats. + +Please make sure your file matches one of these formats: +### JSON: +```json +{ + "students": [ + { + "ID": "A1234567J", + "GPA": "4.5", + "PREFERENCES": "{1,2,3}" + }, + { + "ID": "A7654321K", + "GPA": "3.8", + "PREFERENCES": "{2,3,1}" + }, + { + "ID": "A1357913L", + "GPA": "5.0", + "PREFERENCES": "{3,1,2}" + } + ] +} +``` + +
+ +### CSV: + +| ID | GPA | PREFERENCES | +|-----------|-----|-------------| +| A1234567J | 4.5 | {1,2,3} | +| A1234567I | 5 | {12,61,43} | +| A7654321K | 3.8 | {2,3,1} | +| A1357913L | 4 | {3,1,2} | + + + +**IMPORTANT**: Please take note that the `.CSV` file is **ONLY** opened, created, and edited using Microsoft Excel. Do **NOT** use an IDE or any other application to modify this file. + +### TXT: +```text +id/A1234567J, gpa/4.5, p/{1,2,3} +id/A7654321K, gpa/3.8, p/{2,3,1} +id/A1357913L, gpa/5.0, p/{3,1,2} +``` -* Add todo `todo n/TODO_NAME d/DEADLINE` +These examples are for viewing purposes only. Feel free to use the test.csv, test.json and test.txt files available in [v2.0](https://github.com/AY2425S1-CS2113-W12-2/tp/releases) for testing. diff --git a/docs/images/CSVFormat.png b/docs/images/CSVFormat.png new file mode 100644 index 0000000000..85b7e8285e Binary files /dev/null and b/docs/images/CSVFormat.png differ diff --git a/docs/images/Catnaldo.jpeg b/docs/images/Catnaldo.jpeg new file mode 100644 index 0000000000..a22b86399c Binary files /dev/null and b/docs/images/Catnaldo.jpeg differ diff --git a/docs/images/JSONFormat.png b/docs/images/JSONFormat.png new file mode 100644 index 0000000000..b215eab60c Binary files /dev/null and b/docs/images/JSONFormat.png differ diff --git a/docs/images/TXTFormat.png b/docs/images/TXTFormat.png new file mode 100644 index 0000000000..96e0a5f459 Binary files /dev/null and b/docs/images/TXTFormat.png differ diff --git a/docs/images/ehz0ah.png b/docs/images/ehz0ah.png new file mode 100644 index 0000000000..44c11fb5ed Binary files /dev/null and b/docs/images/ehz0ah.png differ diff --git a/docs/images/grincat.png b/docs/images/grincat.png new file mode 100644 index 0000000000..0b34b3e5b6 Binary files /dev/null and b/docs/images/grincat.png differ diff --git a/docs/images/thisisxxz.jpg b/docs/images/thisisxxz.jpg new file mode 100644 index 0000000000..3af63483c3 Binary files /dev/null and b/docs/images/thisisxxz.jpg differ diff --git a/docs/team/ehz0ah.md b/docs/team/ehz0ah.md new file mode 100644 index 0000000000..12bee86f26 --- /dev/null +++ b/docs/team/ehz0ah.md @@ -0,0 +1,86 @@ +# Lee Hao Zhe - Project Portfolio Page + +## Overview +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange +Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently +manage the allocation process using automated workflows and data-driven decision-making. + + +## Summary of contributions +Code Contributed: [RepoSense (tP Code Dashboard)](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=ehz0ah&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-09-20&tabOpen=true&tabType=authorship&tabAuthor=ehz0ah&tabRepo=AY2425S1-CS2113-W12-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### v1.0 Enhancements: + +1. Parser Class ([#22](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/22)) + - **Purpose**: To analyze and interpret user commands, ensuring that each input is accurately understood by the system. + - **Justification**: Streamlines the command processing workflow by providing a unified approach to parsing user inputs, thus enhancing overall efficiency and reliability. + - **Highlights**: + - Robust error handling for unrecognized commands + - Modular design, allowing easy updates and maintenance. + +2. Abstract Command Class ([#23](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/23)) + - **Purpose**: To serve as a foundational template for all specific command classes, encapsulating shared functionalities and promoting code reusability. + - **Justification**: Reduces code redundancy and facilitates the implementation of new commands by providing a consistent framework for command execution. + - **Highlights**: + - Defined structure for command initialization and execution. + - Standardized error handling and validation mechanisms. + - Supports a wide range of command types with minimal code duplication. + +3. XYZCommands Class ([#23](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/23), [#37](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/37)) + - **Purpose**: Implements specific commands that interact directly with core functionalities, driving the primary operations of the application. + - **Justification**: Adds concrete functionality to the abstract command framework, ensuring that the application can execute essential operations efficiently. + - **Highlights**: + - Clear separation of command logic from user interface considerations. + - Optimized execution paths for common operations. + +### v2.0 Enhancements: + +1. FileHandler Class ([#87](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/87), [#83](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/83), [#79](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/79), [#61](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/61)) + - **Purpose**: To handle the processing and management of file operations for different file types (.CSV, .JSON, .TXT). + - **Justification**: Provides a unified approach to read and write data from various file formats, ensuring consistency and reliability in file handling. + - **Highlights**: + - Processes .CSV, .JSON, and .TXT files from user inputs. + - Saves allocation results and writes them to external files in .JSON and .TXT formats (.CSV is handled by my teammate, Anderson). + - Ensures seamless integration and management of file operations across the system. + + +### User Guide Contributions: + +Added documentation for the following: ([#90](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/90), [#122](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/122), [#174](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/174)) +1. Quick Start +2. Pre-Usage Guidelines +3. Uploading Information + - Manual Input + - File Input +4. Exit Program +5. Frequently Asked Questions (FAQs) +6. Accepted File Format + +### Developer Guide Contributions: + +PR for 1-3: [#61](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/61) + +1. Table of Contents +2. Acknowledgements +3. Installation + - Prerequisites + - Steps to run program +4. Commands [#99](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/99) + - Help Command + - Exit Command + - Unknown Command +5. Components + - Parser [#67](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/67) + - FileHandler [#99](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/99) +6. Product Scope [#122](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/122) + - Non-Functional Requirements + - Instructions for Manual Testing + - Target User Profile & Value Proposition + +### Contributions to Team-Based Tasks +1. Set up team GitHub Organisation/Repository +2. Managed release `v1.0` & `v2.1` on GitHub ([v1.0](https://github.com/AY2425S1-CS2113-W12-2/tp/releases/tag/v1.0)) +3. Maintained issue and project tracker +4. Incorporated third-party libraries (OpenCSV & Jackson) +5. Updated `gradle.yml` to ensure smooth running of GitHub Continuous Integration Actions ([#38](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/38)) +6. PRs reviewed: [#97](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/97), [#70](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/70), [#49](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/49), [#40](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/40), [#30](https://github.com/AY2425S1-CS2113-W12-2/tp/pull/30) diff --git a/docs/team/holy-an.md b/docs/team/holy-an.md new file mode 100644 index 0000000000..086f6c7761 --- /dev/null +++ b/docs/team/holy-an.md @@ -0,0 +1,45 @@ +# Anderson Lim - Project Portfolio Page + +## Overview +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange +Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently +manage the allocation process using automated workflows and data-driven decision-making. + +### Summary of Contributions +[RepoSense (tP Code Dashboard)](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=holy-an&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-09-20&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +### v1.0 Enhancements: `Exception` class, `Exception` helper methods +- `SEPException` Development + - Developed the Exception classes and methods for the team project to throw whenever needed. + - Also helped to test, as well as implement edge cases needed to be handled in methods for v1.0 + - Added helper methods to validate user inputs, such as `validateStudentId()` and others. + +### v2.0 Enhancements: `saveCSV`,`find` and `filter` command +- `saveToCSV` Implementation + - Designed and implemented `saveToCSV()` method to allow users to save their results via a CSV file. + +- `find` Command Implementation + - Designed and implemented the FindCommand class to allow users to search for student IDs, + outputting either a list or a report. + +- `filter` Command Implementation + - Designed and implemented the FilterCommand class to allow users to filter for student IDs, GPAs, + or allocation status, outputting either a list or a report. + - Created more validation helper methods such as `validateFindFilterFormat()`. + +### User Guide Contributions: +- Along with the other group members, we discussed the structure of our User Guide. + +- Added `find` and `filter` command. + +### Developer Guide Contributions: +- Along with the other group members, we drew the main high-level diagrams needed in our Developer guide. + +- Added `find` and `filter` sequence and class diagrams, as well as any methods inside it, such as + `printStudentList()` and `generateReport`. + +- Explained the `find` and `filter` commands. + +- Added `saveAllocationResults()` sequence diagram. + +- Added `FindOurSEP` main class component. \ No newline at end of file diff --git a/docs/team/isaacsaw25.md b/docs/team/isaacsaw25.md new file mode 100644 index 0000000000..6cef0a81ee --- /dev/null +++ b/docs/team/isaacsaw25.md @@ -0,0 +1,30 @@ +# Isaac Saw - Project Portfolio Page + +## Overview +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange +Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently +manage the allocation process using automated workflows and data-driven decision-making. + + +## Summary of contributions +[RepoSense (tP Code Dashboard)](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=isaacsaw25&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-09-20&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +### v1.0 Enhancements: `UI` Class, `PrintStudentList` Method Design Improvement +- User Interface (`UI`) Development + - Developed the UI class to obtain user inputs and output responses from the program. Adopt a facade pattern + between the logical backend elements (commands, student allocation logic, etc.) and the user interface. Created a + enum `Messages`. + +- `PrintStudentList()` Method Design Improvement + - Identified and fixed a design flaw in the `printStudentList()` method that caused strange order of method calls + between classes. + +### v2.0 Enhancements: `Revert` command +- Revert Command Implementation + - Designed and implemented the RevertCommand class to allow for undoing allocations, + resetting each student’s allocation status and university index. Integrated logging and + assertions to ensure system reliability. + +### User Guide Contributions: + +### Developer Guide Contributions: 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/paulktham.md b/docs/team/paulktham.md new file mode 100644 index 0000000000..8e0ba71f3a --- /dev/null +++ b/docs/team/paulktham.md @@ -0,0 +1,42 @@ +# Paul Tham - Project Portfolio Page + +## Overview +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange +Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently +manage the allocation process using automated workflows and data-driven decision-making. + + +## Summary of contributions +[RepoSense (tP Code Dashboard)](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=paulktham&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-09-20) + +### v1.0 Enhancements: `Student` Class, `University` Class and `UniversityRepository` Class `PrintStudentList` Method Design Improvement +- Student (`Student`) Development + - Created the class to represent one student applying for SEP, ensuring that it has the right information for the allocator to fairly decide which student is allocated to which University. I also had to think ahead with my team members to decide what helper functions they require so that we can implement other features seamlessly. + +- University (`University`) Development + - Created the University Class which holds the relevant information for each University available to NUS CEG students for exchange. Using this, I populated a class UniversityRepository which has a static HashMap which holds the information of all the Universities. + +### v2.0 Enhancements: `Minimum` command +- Minimum Command Implementation + - Designed and implemented the CriteriaCommand class to allow the admin handling these allocations to set a minimum GPA every student should attain before they can be considered to be eligible to be allocated to a university. This was done by changing the necessary code in `UI` and `allocator` class. + +### User Guide Contributions: +- Along with the other group members we discuss the structure of our User Guide. These were the areas which I was in charge of: (#85) + 1. Content Table. + 2. Explained how to run our application through Quick Start. + 3. Add Command + 4. Minimum Command. + +### Developer Guide Contributions: +- Along with the other group members we drew the different diagrams needed in our Developer guide. These were the areas which I was in charge of: + 1. Architecture diagram (#70) + 2. Add Command (#175) + 3. University and UniversityRepository (#119) + 4. Student and StudentList (#119) + 5. Criteria Command (#88) + 6. Delete Command (#179) + +### Contribution to Team-Based Tasks +1. Maintained Issue Tracker +2. Constant feedback given through comments of issues and PRs +3. [PRs reviewed](https://github.com/AY2425S1-CS2113-W12-2/tp/pulls?q=is%3Apr+reviewed-by%3A%40me+is%3Aclosed) \ No newline at end of file diff --git a/docs/team/thisisxxz.md b/docs/team/thisisxxz.md new file mode 100644 index 0000000000..9ad2e443d6 --- /dev/null +++ b/docs/team/thisisxxz.md @@ -0,0 +1,65 @@ +# Xiong Xinzhuang - Project Portfolio Page + +## Overview +FindOurSEP is a Command Line Interface (CLI) tool designed for an admin handling the allocation of Student Exchange +Program (SEP) locations for Computer Engineering (CEG) students at NUS. The app allows the administrator to efficiently +manage the allocation process using automated workflows and data-driven decision-making. + +### Summary of Contributions + +[RepoSense (tP Code Dashboard)](https://nus-cs2113-ay2425s1.github.io/tp-dashboard/?search=thisisxxz&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-09-20&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +### v1.0 Enhancements: `Allocator` Class + +- Allocation Logic Development + + - Developed the backbone of the ``Allocator`` class. Designed an $O(n^2)$ allocation algorithm in FindOurSEP 1.0 version. Wrote JUnit tests for the allocator. + - Added corresponding methods (copy constructors, helper methods) in ``Student`` and ``StudentList`` classes to serve the needs of the allocator. + +- Allocate Command Bug Fix (run ``allocate`` multiple times lead to a stuck in program) + + + +### v2.0 Enhancements: `Stats` and ``viewQuota`` commands + +- Stats Command Implementation + * Designed and implemented the ``StatCommand`` class to view statistics of the specified partner universities. In FindOurSEP 2.0 version, this refers to checking the minimum and average GPA of the university. Wrote JUnit tests for this command. +- ViewQuota Command Implementation + * Designed and implemented the ``viewQuotaCommand`` class to check the remaining quota of the specified partner universities. Wrote JUnit tests for this command. +- Criteria Command Bug Fix (program crashes if nothing is supplied to ``minimum`` command) +- Allocator Bug Fix (run ``allocate`` multiple times will change the results) +- ``DeleteCommand`` Bug Fix (quota is not released) +- Enable Assertions in ``build.gradle`` File + + + +### Documentation Contributions + +Developer Guide: + +* Class Diagram for ``Allocator``, with explanation +* Sequence Diagram for ``AllocateCommand``, with explanation +* Sequence Diagram for ``StatCommand``, with explanation +* Sequence Diagram for ``ViewQuotaCommand``, with explanation +* Contributions to Glossary Section +* Contributions to Non-Functional Requirements Section +* Some Minor Fixes + +User Guide: + +* Description of ``allocate`` Command +* Description of ``stats`` Command +* Description of ``viewQuota`` Command +* Contributions to FAQ Section +* Some Minor Fixes + +JavaDoc: + +* Self-Implemented Codes (Mostly focus on ``Allocator``, ``StatCommand``, ``ViewQuotaCommand`` and helper functions in ``StudentList``) + + + + + + + diff --git a/src/main/java/allocator/Allocator.java b/src/main/java/allocator/Allocator.java new file mode 100644 index 0000000000..04d515d3e9 --- /dev/null +++ b/src/main/java/allocator/Allocator.java @@ -0,0 +1,122 @@ +package allocator; + +import exceptions.SEPEmptyException; +import student.Student; +import studentlist.StudentList; +import university.University; +import university.UniversityRepository; + +import java.util.Set; + +import exceptions.SEPException; +import exceptions.SEPFormatException; + +import java.util.HashSet; + +/** + * The Allocator class manages the allocation of students to universities based on their GPA + * and university preferences. It provides functionality to set a minimum GPA requirement and + * to perform the allocation process. + */ + +public class Allocator { + + private static double minimumGPA = 0; + private StudentList studentList; + + + /** + * Constructs an Allocator with a deep copy of the provided StudentList. + * This ensures the original list is not modified during allocation. + * + * @param studentList The list of students to be allocated. + */ + public Allocator(StudentList studentList) { + this.studentList = new StudentList(studentList); // don't want to modify the original list + } + + /** + * Sets the minimum GPA by processing the user's input. + * This method uses a helper function found in studentList to validate the GPA. + * The helper function validateGPA checks if the GPA is valid, meaning it falls within + * the acceptable range of 0.0 to 5.0. If valid, the GPA is returned and used to set + * the minimumGPA variable. + * + * @param input The input written by the user to be processed. + * @throws SEPException if there are any errors in the input. + */ + public void setMinimumGPA(String input) throws SEPException { + assert input != null : "Input cannot be null"; + assert !input.isEmpty() : "Input cannot be empty"; + + Set errorMessages = new HashSet<>(); + String[] parts = input.split(" "); + assert parts.length >= 2 : "Input must contain at least two parts separated by spaces"; + + if (parts.length < 2) { + throw SEPFormatException.rejectCriteriaFormat(); + } + + String stringGpa = parts[1]; + float gpa = studentList.validateGpa(stringGpa, errorMessages); + + if (!errorMessages.isEmpty()) { + throw new SEPException(String.join("\n", errorMessages)); + } + + assert gpa >= 0.0 && gpa <= 5.0 : "GPA must be between 0.0 and 5.0"; // Adjust range if necessary + + // Setting the static minimum GPA criteria to the input + minimumGPA = gpa; + } + + /** + * Returns the current minimum GPA requirement for allocation. + * + * @return The minimum GPA as a double. + */ + + public double getMinimumGPA(){ + return minimumGPA; + } + + /** + * Allocates students to universities based on their GPA and preferences. + * This method sorts students by GPA in descending order and iterates through each student's + * university preferences. For each student, it checks if a university has available spots + * and if the student meets the minimum GPA requirement. If both conditions are met, the student + * is allocated to that university, and the allocation process continues with the next student. + * Finally, students are re-sorted by ID before the list is returned. + * + * @return the updated StudentList after allocation with each student's assigned university. + */ + + public StudentList allocate() throws SEPEmptyException { + if (studentList.getNumStudents() <= 0) { + throw SEPEmptyException.rejectAllocateEmpty(); + } + + boolean isChanged = false; + studentList.sortStudentsByDescendingGPA(studentList.getList()); + for (Student student : studentList.getList()) { + if (student.getSuccessfullyAllocated()) { + continue; + } + for (int uni : student.getUniPreferences()) { + University university = UniversityRepository.getUniversityByIndex(uni); + if (university.getSpotsLeft() > 0 && student.getGpa() >= minimumGPA) { + isChanged = true; + university.removeASpot(); + student.setAllocatedUniversity(uni); + student.setSuccessfullyAllocated(true); + break; + } + } + } + if (!isChanged) { + throw SEPEmptyException.rejectAllocateNoChange(); + } + studentList.sortStudentsByAscendingId(studentList.getList()); + return studentList; + } +} diff --git a/src/main/java/command/AddCommand.java b/src/main/java/command/AddCommand.java new file mode 100644 index 0000000000..8457d3e977 --- /dev/null +++ b/src/main/java/command/AddCommand.java @@ -0,0 +1,36 @@ +package command; + +import exceptions.SEPException; + +import student.Student; +import studentlist.StudentList; +import ui.UI; + +public class AddCommand extends Command { + private String input; + private UI ui; + + public AddCommand(StudentList studentList, String input, UI ui) { + super(studentList); + this.input = input; + this.ui = ui; + } + + /** + * Executes the AddCommand, which creates a new student based on the input data + * and adds it to the student list. If successful, displays a confirmation message. + * If an error occurs, an error message is displayed instead. + */ + + @Override + public void run() { + try { + Student newStudent = super.studentList.makeStudent(this.input); + super.studentList.addStudent(newStudent); + ui.printResponse("Added " + newStudent.getId() + " successfully.\n" + + "There are " + studentList.getNumStudents() + " student(s) in the list."); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/AllocateCommand.java b/src/main/java/command/AllocateCommand.java new file mode 100644 index 0000000000..4cb0292ff5 --- /dev/null +++ b/src/main/java/command/AllocateCommand.java @@ -0,0 +1,49 @@ +/** + * The AllocateCommand class is responsible for performing the student allocation process. + * This command utilizes the {@link Allocator} to assign students and updates the + * {@link StudentList} with the allocation results. Additionally, it interacts with the + * {@link UI} to display a message indicating that allocation is in progress. + */ +package command; + +import allocator.Allocator; +import exceptions.SEPException; +import studentlist.StudentList; +import ui.UI; + +/** + * Represents a command to allocate students to resources or groups. + * Initializes and uses an {@link Allocator} to manage the allocation process + * for the given {@link StudentList}. + */ +public class AllocateCommand extends Command { + private Allocator allocator; + private UI ui; + + /** + * Constructs an AllocateCommand with the specified student list and UI. + * + * @param studentList The list of students to be allocated. + * @param ui The user interface used to display allocation messages. + */ + public AllocateCommand(StudentList studentList, UI ui) { + super(studentList); + this.ui = ui; + this.allocator = new Allocator(studentList); + } + + /** + * Executes the allocation process by setting the allocated student list in + * {@link StudentList} and printing an allocation message in the UI. + */ + @Override + public void run() { + try { + this.studentList.setStudentList(allocator.allocate()); + this.studentList.setAllocationStatus(true); + this.ui.printAllocatingMessage(); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..055ee5b0b8 --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,22 @@ +package command; + +import exceptions.SEPEmptyException; +import studentlist.StudentList; + +/** + * An abstract class representing a command that can be executed by the application. + * Subclasses should override the run() method to provide the implementation of the + * command. + */ +public abstract class Command { + protected StudentList studentList; + + public Command(StudentList studentList) { + this.studentList = studentList; + } + + /** + * Executes the command. + */ + public abstract void run() throws SEPEmptyException; +} diff --git a/src/main/java/command/Commands.java b/src/main/java/command/Commands.java new file mode 100644 index 0000000000..4124ee0d69 --- /dev/null +++ b/src/main/java/command/Commands.java @@ -0,0 +1,5 @@ +package command; + +public enum Commands { + ADD, DELETE, LIST, FIND, FILTER, ALLOCATE, EXIT, QUIT, BYE, HELP, GENERATE, MINIMUM, STATS, VIEWQUOTA, REVERT, VOID +} diff --git a/src/main/java/command/CriteriaCommand.java b/src/main/java/command/CriteriaCommand.java new file mode 100644 index 0000000000..bec3449425 --- /dev/null +++ b/src/main/java/command/CriteriaCommand.java @@ -0,0 +1,43 @@ +package command; + +import allocator.Allocator; +import exceptions.SEPException; +import studentlist.StudentList; +import ui.UI; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class CriteriaCommand extends Command { + private String input; + private UI ui; + private Allocator allocator; + + public CriteriaCommand (StudentList studentList,String input, UI ui) { + super(studentList); + this.input = input; + this.ui = ui; + this.allocator = new Allocator(studentList); + } + + /** + * Processes the user input to set a minimum GPA requirement. + * This method takes the user-provided input and extracts the necessary part + * to set the minimum GPA through the allocator's setMinimumGPA method. + * If the GPA is valid, a confirmation message is printed; otherwise, an error + * message is displayed. + * + * @throws SEPException if there are issues with GPA validation. + */ + @Override + public void run() { + try { + allocator.setMinimumGPA(input); + BigDecimal roundedGPA = BigDecimal.valueOf(allocator.getMinimumGPA()).setScale(2, RoundingMode.HALF_UP); + ui.printResponse("Minimum requirement of GPA set to " + roundedGPA + " successfully."); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } + +} diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java new file mode 100644 index 0000000000..5c9f42d836 --- /dev/null +++ b/src/main/java/command/DeleteCommand.java @@ -0,0 +1,33 @@ +package command; + +import exceptions.SEPException; +import studentlist.StudentList; +import ui.UI; + +public class DeleteCommand extends Command { + private String input; + private UI ui; + + public DeleteCommand(StudentList studentList, String input, UI ui) { + super(studentList); + this.input = input; + this.ui = ui; + } + + /** + * Executes the DeleteCommand, which removes a student based on the input data + * from the student list. If successful, displays a confirmation message. + * If an error occurs, an error message is displayed instead. + */ + + @Override + public void run() { + try { + super.studentList.deleteStudent(this.input); + ui.printResponse("Removed student, " + studentList.getNumStudents() + " student(s) left"); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + + } +} diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java new file mode 100644 index 0000000000..57857173e9 --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,18 @@ +package command; + +import studentlist.StudentList; +import ui.UI; + +public class ExitCommand extends Command { + private UI ui; + + public ExitCommand(StudentList studentList, UI ui) { + super(studentList); + this.ui = ui; + } + + @Override + public void run() { + this.ui.cleanupAndExit(); + } +} diff --git a/src/main/java/command/FilterCommand.java b/src/main/java/command/FilterCommand.java new file mode 100644 index 0000000000..7f214f519b --- /dev/null +++ b/src/main/java/command/FilterCommand.java @@ -0,0 +1,25 @@ +package command; + +import exceptions.SEPException; +import studentlist.StudentList; +import ui.UI; + +public class FilterCommand extends Command{ + private String input; + private UI ui; + + public FilterCommand(StudentList studentList, String input, UI ui) { + super(studentList); + this.input = input; + this.ui = ui; + } + + @Override + public void run() { + try { + super.studentList.filterStudent(this.input);; + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java new file mode 100644 index 0000000000..0906eedb21 --- /dev/null +++ b/src/main/java/command/FindCommand.java @@ -0,0 +1,26 @@ +package command; + +import exceptions.SEPException; + +import studentlist.StudentList; +import ui.UI; + +public class FindCommand extends Command { + private String input; + private UI ui; + + public FindCommand(StudentList studentList, String input, UI ui) { + super(studentList); + this.input = input; + this.ui = ui; + } + + @Override + public void run() { + try { + super.studentList.findStudent(this.input);; + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/GenerateCommand.java b/src/main/java/command/GenerateCommand.java new file mode 100644 index 0000000000..36373ed6a8 --- /dev/null +++ b/src/main/java/command/GenerateCommand.java @@ -0,0 +1,18 @@ +package command; + +import studentlist.StudentList; +import ui.UI; + +public class GenerateCommand extends Command { + private UI ui; + + public GenerateCommand(StudentList studentList, UI ui) { + super(studentList); + this.ui = ui; + } + + @Override + public void run() { + super.studentList.generateReport(); + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..d357bb8c8e --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,19 @@ +package command; + +import studentlist.StudentList; +import ui.Messages; +import ui.UI; + +public class HelpCommand extends Command { + private UI ui; + + public HelpCommand(StudentList studentList, UI ui) { + super(studentList); + this.ui = ui; + } + + @Override + public void run() { + this.ui.printResponse(Messages.HELP); + } +} diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java new file mode 100644 index 0000000000..e91b08ca49 --- /dev/null +++ b/src/main/java/command/ListCommand.java @@ -0,0 +1,23 @@ +package command; + +import exceptions.SEPException; +import studentlist.StudentList; +import ui.UI; + +public class ListCommand extends Command { + private final UI ui; + + public ListCommand(StudentList studentList, UI ui) { + super(studentList); + this.ui = ui; + } + + @Override + public void run() { + try { + ui.printStudentList(studentList.getList()); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/RevertCommand.java b/src/main/java/command/RevertCommand.java new file mode 100644 index 0000000000..8af73eb415 --- /dev/null +++ b/src/main/java/command/RevertCommand.java @@ -0,0 +1,61 @@ +package command; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import exceptions.SEPEmptyException; +import studentlist.StudentList; +import ui.Messages; +import ui.UI; +import university.UniversityRepository; + +/** + * Command to revert the allocation status of all students in the StudentList. + * Resets each student's allocation status and associated university index + * to indicate no allocation, allowing for a fresh allocation process. + */ +public class RevertCommand extends Command { + private static final Logger logger = Logger.getLogger(RevertCommand.class.getName()); + /** + * Reference to the UI object used for displaying messages to the user. + */ + private UI ui; + + + /** + * Constructs a RevertCommand with the specified student list and user interface. + * + * @param studentList The reference to the StudentList object. + * @param ui The reference to the UI object. + */ + public RevertCommand(StudentList studentList, UI ui) { + super(studentList); + assert studentList != null : "StudentList cannot be null"; + this.ui = ui; + } + + /** + * Executes the revert command, resetting the allocation status of all students + * in the list to their unallocated state. Upon completion, a confirmation message + * is displayed to the user. Also logs the start and completion of the revert operation. + */ + @Override + public void run() { + logger.log(Level.FINE, "Starting revert operation."); + + // Ensure studentList is initialized + assert studentList != null : "StudentList cannot be null during revert operation"; + + try { + // Perform the revert action on student list and uni repo + studentList.revertAllocation(); + UniversityRepository.resetMap(); + + // Log completion and notify the user + logger.log(Level.FINE, "Revert operation completed."); + ui.printResponse(Messages.REVERT_COMPLETE); + } catch (SEPEmptyException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/StatCommand.java b/src/main/java/command/StatCommand.java new file mode 100644 index 0000000000..0872be8ecb --- /dev/null +++ b/src/main/java/command/StatCommand.java @@ -0,0 +1,103 @@ +package command; + +import exceptions.SEPEmptyException; +import exceptions.SEPException; +import exceptions.SEPFormatException; + +import studentlist.StudentList; +import ui.UI; +import university.UniversityRepository; + +/** + * The StatCommand class handles the "stats" command, which displays statistical information + * about the GPAs of students who have chosen a specified partner university. The command supports + * viewing the average GPA (-avggpa) and minimum GPA (-mingpa) of students for a given university index. + */ +public class StatCommand extends Command { + private static final String INTEGER_REGEX = "-?\\d+"; // Regular expression for validating integer input + private String input; + private UI ui; + + /** + * Constructs a new StatCommand with the given student list, user input, and UI instance. + * + * @param studentList The StudentList containing all student information. + * @param input The input string provided by the user. + * @param ui The UI instance for interacting with the user. + */ + public StatCommand(StudentList studentList, String input, UI ui) { + super(studentList); + assert studentList != null : "StudentList must not be null"; + assert input != null && !input.isEmpty() : "Input must not be null or empty"; + assert ui != null : "UI instance must not be null"; + + this.input = input; + this.ui = ui; + } + + /** + * Executes the stat command, validating the input format and displaying + * the appropriate statistical information (average GPA or minimum GPA) based on + * the specified university index. + * + * @throws SEPException if there is any issue with the command input or if the university + * index is not found. + */ + @Override + public void run() { + try { + // Split the input and validate the parts + String[] parts = input.split(" "); + assert parts.length > 0 : "Input split should produce parts"; + + if (parts.length != 3) { + throw SEPFormatException.rejectStatFormat(); + } + + // Validate the stat type (either -avggpa or -mingpa) + String statType = parts[1].trim().toLowerCase(); + assert statType != null : "Stat type should not be null"; + if (!statType.equals("-avggpa") && !statType.equals("-mingpa")) { + throw SEPFormatException.rejectStatFormat(); + } + + // Validate the university index format + String uniIndexString = parts[2].trim(); + assert uniIndexString != null : "University index string should not be null"; + if (!uniIndexString.matches(INTEGER_REGEX)) { + throw SEPFormatException.rejectStatFormat(); + } + + int uniIndex = Integer.parseInt(uniIndexString); + assert uniIndex >= 0 : "University index must be non-negative"; + + // Check if the university exists in the repository + if (UniversityRepository.getUniversityByIndex(uniIndex) == null) { + throw SEPEmptyException.rejectUniversityNotFound(); + } + + // Process the command based on the stat type + switch (statType) { + case "-avggpa": + double avgGpa = super.studentList.calculateAverageGpaForUniversity(uniIndex); + assert avgGpa >= 0.0 : "Average GPA must be non-negative"; + ui.printResponse("The average GPA for university index " + uniIndex + + " (" + UniversityRepository.getUniversityByIndex(uniIndex).getFullName() + ")" + + " is: " + String.format("%.2f", avgGpa)); + break; + case "-mingpa": + double minGpa = super.studentList.calculateMinGpaForUniversity(uniIndex); + assert minGpa >= 0.0 : "Minimum GPA must be non-negative"; + ui.printResponse("The minimum GPA for university index " + uniIndex + + " (" + UniversityRepository.getUniversityByIndex(uniIndex).getFullName() + ")" + + " is: " + String.format("%.2f", minGpa)); + break; + default: + throw SEPFormatException.rejectStatFormat(); + } + + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/command/UnknownCommand.java b/src/main/java/command/UnknownCommand.java new file mode 100644 index 0000000000..26db3ecdeb --- /dev/null +++ b/src/main/java/command/UnknownCommand.java @@ -0,0 +1,18 @@ +package command; + +import studentlist.StudentList; +import ui.UI; + +public class UnknownCommand extends Command { + private UI ui; + + public UnknownCommand(StudentList studentList, UI ui) { + super(studentList); + this.ui = ui; + } + + @Override + public void run() { + this.ui.printResponse("Invalid command"); + } +} diff --git a/src/main/java/command/ViewQuotaCommand.java b/src/main/java/command/ViewQuotaCommand.java new file mode 100644 index 0000000000..59d67ee1b5 --- /dev/null +++ b/src/main/java/command/ViewQuotaCommand.java @@ -0,0 +1,87 @@ +package command; + +import exceptions.SEPEmptyException; +import exceptions.SEPException; +import exceptions.SEPFormatException; + +import studentlist.StudentList; +import ui.UI; +import university.University; +import university.UniversityRepository; + +/** + * The ViewQuotaCommand class handles the "viewQuota" command, which displays information + * about a university's quota based on the specified university index. The information includes + * the university's name and the number of spots left for allocation. + */ +public class ViewQuotaCommand extends Command { + private static final String INTEGER_REGEX = "-?\\d+"; + private String input; + private UI ui; + + /** + * Constructs a new ViewQuotaCommand with the given student list, user input, and UI instance. + * + * @param studentList The StudentList containing all student information. + * @param input The input string provided by the user. + * @param ui The UI instance for interacting with the user. + */ + public ViewQuotaCommand(StudentList studentList, String input, UI ui) { + super(studentList); + assert studentList != null : "StudentList must not be null"; + assert input != null && !input.isEmpty() : "Input must not be null or empty"; + assert ui != null : "UI instance must not be null"; + + this.input = input; + this.ui = ui; + } + + /** + * Executes the viewQuota command, validating the input format and displaying + * the university's information, including its index, name, and the remaining quota (spots left). + * + * @throws SEPException if there is any issue with the command input or if the university + * index is not found. + */ + @Override + public void run() { + try { + // Split the input and validate the parts + String[] parts = input.split(" "); + assert parts.length > 0 : "Input split should produce parts"; + + if (parts.length != 2) { + throw SEPFormatException.rejectViewQuotaFormat(); + } + + // Validate the university index format + String uniIndexString = parts[1].trim(); + assert uniIndexString != null : "University index string should not be null"; + if (!uniIndexString.matches(INTEGER_REGEX)) { + throw SEPFormatException.rejectViewQuotaFormat(); + } + + int uniIndex = Integer.parseInt(uniIndexString); + assert uniIndex >= 0 : "University index must be non-negative"; + + // Retrieve the university from the repository and check if it exists + University university = UniversityRepository.getUniversityByIndex(uniIndex); + assert university != null : "University should not be null if found"; + + if (university == null) { + throw SEPEmptyException.rejectUniversityNotFound(); + } + + // Assert that spots left are non-negative before displaying + int spotsLeft = university.getSpotsLeft(); + assert spotsLeft >= 0 : "Spots left must be non-negative"; + + // Display the university's information, including its quota + ui.printResponse(" Index: " + uniIndex + "\n" + + " Name: " + university.getFullName() + "\n" + + " Quota: " + spotsLeft); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } +} diff --git a/src/main/java/exceptions/SEPDuplicateException.java b/src/main/java/exceptions/SEPDuplicateException.java new file mode 100644 index 0000000000..3d6c155d9d --- /dev/null +++ b/src/main/java/exceptions/SEPDuplicateException.java @@ -0,0 +1,20 @@ +package exceptions; + +/** + * This class handles duplicate student entries. + */ +public class SEPDuplicateException extends SEPException { + public SEPDuplicateException(String message) { + super(message); + } + + /** + * Throws an exception when attempting to add a student that already exists + * in the student list. + * + * @return A SEPDuplicateException indicating the student is a duplicate. + */ + public static SEPDuplicateException rejectDuplicateStudent() { + return new SEPDuplicateException("Student is already inside the student list."); + } +} diff --git a/src/main/java/exceptions/SEPEmptyException.java b/src/main/java/exceptions/SEPEmptyException.java new file mode 100644 index 0000000000..1fe2a2a916 --- /dev/null +++ b/src/main/java/exceptions/SEPEmptyException.java @@ -0,0 +1,50 @@ +package exceptions; + +/** + * This class handles exceptions when output is empty. + */ +public class SEPEmptyException extends SEPException { + public SEPEmptyException(String message) { + super(message); + } + + public static SEPEmptyException rejectEmptyStudentList() { + return new SEPEmptyException("No students available for generation."); + } + + public static SEPEmptyException rejectStudentNotFound() { + return new SEPEmptyException("Student not found."); + } + + public static SEPEmptyException rejectUniversityNotFound() { + return new SEPEmptyException("The specified university was not found. Please check the university index."); + } + + /** + * Returns a SEPEmptyException with a message indicating that the file was not found. + * + * @return A SEPEmptyException with the appropriate message. + */ + public static SEPEmptyException rejectFileNotFound() { + return new SEPEmptyException("This file don't exist...Don't make me go " + + "through your folder for nothing leh :(.\nProcess Outcome: No data is loaded. " + + "You can continue using the program."); + } + + public static SEPEmptyException rejectEmptyFile() { + return new SEPEmptyException("Nothing to read from the file. Please ensure there is content next time."); + } + + public static SEPEmptyException rejectAllocationIncomplete() { + return new SEPEmptyException("Allocation incomplete, try running allocate before reverting! :>"); + } + + public static SEPEmptyException rejectAllocateEmpty() { + return new SEPEmptyException("Unable to allocate with empty student list, try adding students! :>"); + } + + public static SEPEmptyException rejectAllocateNoChange() { + return new SEPEmptyException("No changes made in the allocation. \n" + + "If you've edited the original student list, try run `revert` first then `allocate`! :>"); + } +} diff --git a/src/main/java/exceptions/SEPException.java b/src/main/java/exceptions/SEPException.java new file mode 100644 index 0000000000..0caa510e5f --- /dev/null +++ b/src/main/java/exceptions/SEPException.java @@ -0,0 +1,11 @@ +package exceptions; + +/** + * This class represents the custom base exception for this program. + * Custom exceptions are stored for easy access. + */ +public class SEPException extends Exception { + public SEPException(String message) { + super(message); + } +} diff --git a/src/main/java/exceptions/SEPFormatException.java b/src/main/java/exceptions/SEPFormatException.java new file mode 100644 index 0000000000..1316e66f65 --- /dev/null +++ b/src/main/java/exceptions/SEPFormatException.java @@ -0,0 +1,111 @@ +package exceptions; + +/** + * This class handles format exceptions for operations + * such as adding and deleting a student. + */ +public class SEPFormatException extends SEPException { + public SEPFormatException(String message) { + super(message); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the add student command. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectAddStudentFormat() { + return new SEPFormatException("Invalid add student format. Please use: " + + "add id/ gpa/ p/{}"); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the GPA. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectGpaFormat() { + return new SEPFormatException("Invalid gpa format. Please enter a valid float " + + "with a maximum of 2 decimal places."); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the preference rankings. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectPreferenceFormat() { + return new SEPFormatException("Invalid preference format. " + + "Please enter a valid integer with curly braces enclosing it."); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the student ID. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectIdFormat() { + return new SEPFormatException("Invalid ID format. Please enter two uppercase alphabets " + + "with 7 natural numbers between it."); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the stats command. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectStatFormat() { + return new SEPFormatException("Invalid format for stats command. " + + "Usage: ``stats -avggpa `` or ``stats -mingpa ``."); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the ViewQuota command. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectViewQuotaFormat() { + return new SEPFormatException("Invalid format for viewQuota command. " + + "Usage: `viewQuota `."); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the Find command. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectFindFormat() { + return new SEPFormatException("Invalid find format. Please enter: " + + "'find ' "); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the filter command. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectFilterFormat() { + return new SEPFormatException("Invalid filter format. Please enter: " + + "'filter ',\n" + + "or 'filter '."); + } + + /** + * Returns a SEPFormatException with a message indicating the + * invalid format for the criteria command. + * + * @return A SEPFormatException with the appropriate message. + */ + public static SEPFormatException rejectCriteriaFormat() { + return new SEPFormatException("Invalid criteria format. Please enter: " + + "'minimum '"); + } +} diff --git a/src/main/java/exceptions/SEPIOException.java b/src/main/java/exceptions/SEPIOException.java new file mode 100644 index 0000000000..6463b8ec56 --- /dev/null +++ b/src/main/java/exceptions/SEPIOException.java @@ -0,0 +1,74 @@ +package exceptions; + +public class SEPIOException extends SEPException { + public SEPIOException(String message) { + super(message); + } + + // Exception for missing "students" node or invalid array + public static SEPIOException missingStudentsJSONArray() { + return new SEPIOException("Invalid JSON format! The 'students' array is missing or not an array."); + } + + // Exception for missing required fields in the student entry + public static SEPIOException missingRequiredJSONFields() { + return new SEPIOException("Invalid JSON format! Each student entry must contain 'ID', 'GPA', " + + "and 'PREFERENCES'."); + } + + // Exception for invalid data format after checking the values + public static SEPIOException invalidDataJSONFormat() { + return new SEPIOException("Invalid data! One or more fields contain invalid data. Please check ID, GPA, " + + "and PREFERENCES. \nPreferences goes only from 1-92, GPA from 0 to 5."); + } + + + /** + * Exception for wrong CSV format. The first row must contain the header "ID", "GPA", and "PREFERENCES". + * Subsequent rows must contain the data separated by a comma. For example, "A1234567J, 4.5, \"{1,2,3}\"" + * + * @return A SEPIOException with a message indicating the wrong CSV format. + */ + public static SEPIOException rejectCSVFile() { + return new SEPIOException("Wrong CSV format! Please ensure first row has column headers ID, GPA, PREFERENCES" + + " and subsequent rows to be , , \"\"\n" + + "For e.g. A1234567J | 4.5 | {1,2,3}"); + } + + /** + * Exception for incorrect CSV data format. This method constructs an error message + * indicating that the provided line is not in the correct format. Each element in + * the line should be separated by a comma. + * + * @param line The CSV line that is not in the correct format. + * @return A SEPIOException with a message indicating the incorrect CSV data format. + */ + public static SEPIOException rejectCSVDataFormat(String[] line) { + return new SEPIOException(String.join(",", line) + "\" is not in correct format! " + + "\nPlease ensure that you only have 3 columns representing the ID, GPA and PREFERENCES."); + } + + /** + * Exception for missing required fields in the student entry in the TXT file. + * Each line must contain 'id/[ID]', 'gpa/[GPA]', and 'p/[PREFERENCES]'. + * + * @return A SEPIOException with a message indicating the missing required fields. + */ + public static SEPIOException missingTXTRequiredFields() { + return new SEPIOException("Invalid TXT format! Each line must follow this format: 'id/', 'gpa/', " + + " 'p/{}'. \nFor e.g. id/A1234567J, gpa/4.5, p/{1,2,3}."); + } + + /** + * Exception for invalid data in the student entry in the TXT file. This method + * constructs an error message indicating that one or more fields contain invalid + * data. The user is asked to check the ID, GPA, and PREFERENCES. + * + * @return A SEPIOException with a message indicating the invalid data in the TXT file. + */ + public static SEPIOException invalidTXTDataFormat() { + return new SEPIOException("Invalid data in TXT file! One or more fields contain invalid data. " + + "Please check ID, GPA, and PREFERENCES. \nPreferences goes only from 1-92, GPA from 0 to 5." + + "\nFor e.g. id/A1234567J gpa/4.5 p/{1,2,3}"); + } +} diff --git a/src/main/java/exceptions/SEPRangeException.java b/src/main/java/exceptions/SEPRangeException.java new file mode 100644 index 0000000000..12ca17fb7f --- /dev/null +++ b/src/main/java/exceptions/SEPRangeException.java @@ -0,0 +1,35 @@ +package exceptions; + +/** + * This class handles exceptions for range boundaries when + * dealing with add operations. + */ +public class SEPRangeException extends SEPException { + public SEPRangeException(String message) { + super(message); + } + + public static SEPRangeException rejectGpaRange() { + return new SEPRangeException("GPA should be between 0 and 5."); + } + + /** + * Throws an exception if the number of preference rankings exceeds the allowed limit. + * As of now, only up to 3 preference rankings are accepted. + * + * @return A SEPRangeException with a message indicating the preference rankings limit. + */ + public static SEPRangeException rejectRankingsRange() { + return new SEPRangeException("Preferences should not contain more than three rankings."); + } + + /** + * This method ensures that the preferences would only be able to go up to 92, + * as there are up to 92 universities to be allocated to students. + * + * @return A SEPRangeException if preference rankings are less than 1 or more than 92. + */ + public static SEPRangeException rejectPreferenceRange() { + return new SEPRangeException("Preferences should be between 1 and 92."); + } +} diff --git a/src/main/java/exceptions/SEPUnknownException.java b/src/main/java/exceptions/SEPUnknownException.java new file mode 100644 index 0000000000..b8f5d7d28e --- /dev/null +++ b/src/main/java/exceptions/SEPUnknownException.java @@ -0,0 +1,18 @@ +package exceptions; + +public class SEPUnknownException extends SEPException { + public SEPUnknownException(String message) { + super(message); + } + + /** + * Returns a SEPUnknownException with a message indicating that the file type + * is unknown or unsupported. + * + * @return A SEPUnknownException with the appropriate message. + */ + public static SEPUnknownException rejectUnknownFileType() { + return new SEPUnknownException("Unknown/Unsupported file type detected! " + + "\nYou can continue to use the program."); + } +} diff --git a/src/main/java/filehandler/FileHandler.java b/src/main/java/filehandler/FileHandler.java new file mode 100644 index 0000000000..c292a414a0 --- /dev/null +++ b/src/main/java/filehandler/FileHandler.java @@ -0,0 +1,462 @@ +package filehandler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.opencsv.CSVParser; +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import com.opencsv.CSVWriter; +import com.opencsv.exceptions.CsvValidationException; + +import exceptions.SEPEmptyException; +import exceptions.SEPException; +import exceptions.SEPIOException; +import exceptions.SEPUnknownException; +import parser.Parser; +import student.Student; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static studentlist.StudentList.ADD_STUDENT_REGEX; + +public class FileHandler { + private String filePath; + private Parser parser; + + public FileHandler(String filePath, Parser parser) { + this.filePath = filePath.isEmpty() ? "data" : filePath; + this.parser = parser; + } + + public static boolean isValidPath(String path) { + try { + Path filePath = Paths.get(path); + return true; + } catch (InvalidPathException e) { + return false; + } + } + + /** + * Returns the file extension of the given file, or an empty string + * if the file does not have an extension. + * + * @param file the file to get the extension of + * @return the file extension + */ + private String getFileExtension(File file) { + String name = file.getName(); + int lastDotIndex = name.lastIndexOf('.'); + return (lastDotIndex == -1) ? "" : name.substring(lastDotIndex + 1); + } + + /** + * Processes the file located at the path given during construction of + * this object, and returns a boolean indicating whether the file was + * processed successfully or not. The file type is determined by its + * extension, and the appropriate private method is called to process + * the file. If the file is missing or has an unknown extension, an + * exception is thrown. + * + * @return true if the file was processed successfully, false otherwise + * @throws SEPException if there is an issue with the file, such as it + * being missing, or having an unknown extension + */ + public boolean hasProcessFileSuccessfully() throws SEPException, IOException { + Path path = Paths.get(this.filePath); + boolean result; + + if (!Files.exists(path)) { + throw SEPEmptyException.rejectFileNotFound(); + } + + if (Files.size(path) == 0) { + throw SEPEmptyException.rejectEmptyFile(); + } + + String extension = getFileExtension(path.toFile()); + + switch (extension.toLowerCase()) { + case "csv": + result = hasProcessCSV(path.toFile()); + break; + case "json": + result = hasProcessJSON(path.toFile()); + break; + case "txt": + result = hasProcessTXT(path); + break; + default: + throw SEPUnknownException.rejectUnknownFileType(); + } + + return result; + } + + + + /** + * Processes a CSV file containing student data, validates each row, + * and adds the student information to the system. The CSV file is + * expected to have three columns: ID, GPA, and Preferences. If any + * row is not in the correct format or contains invalid data, an + * exception is thrown. The output is temporarily suppressed during + * processing. + * + * @param file the CSV file to be processed + * @return true if the file was processed successfully, false otherwise + * @throws SEPIOException if there is an I/O error or invalid CSV format + * @throws CsvValidationException if there is an issue with CSV parsing + * @throws SEPException if there is an issue with student data validation + */ + private boolean hasProcessCSV(File file) { + // Save the current System.out + PrintStream originalOut = System.out; + + // Suppress output + System.setOut(new PrintStream(new ByteArrayOutputStream())); + try { + // Use default CSVParser to split on commas + CSVParser parser = new CSVParserBuilder().withSeparator(',').build(); + + // Initialize CSVReader with the parser + CSVReader reader = new CSVReaderBuilder(new java.io.FileReader(file)).withCSVParser(parser).build(); + + String[] line; + reader.readNext(); // Skip the header + while ((line = reader.readNext()) != null) { + if (line.length != 3) { + throw SEPIOException.rejectCSVDataFormat(line); + } + assert line.length == 3; + + // Extract and format the command + String id = line[0].trim(); + String gpa = line[1].trim(); + String preferences = line[2].trim(); + String command = String.format("add id/%s gpa/%s p/%s", id, gpa, preferences); + + // Validate the data format + if ((!this.parser.isValidData(id, gpa,preferences)) || (!command.matches(ADD_STUDENT_REGEX))) { + throw SEPIOException.rejectCSVFile(); + } + + this.parser.parseInput(command); + } + System.setOut(originalOut); + } catch (IOException | CsvValidationException | SEPException e) { + System.setOut(originalOut); + System.out.println(e.getMessage()); + return false; + } + return true; + } + + /** + * Prints an example of a correct JSON file containing three students. + * The example is formatted with indentation for readability. + */ + public void printExampleJson() { + try { + // Create ObjectMapper to build the JSON structure + ObjectMapper objectMapper = new ObjectMapper(); + + // Create root node "students" + ObjectNode rootNode = objectMapper.createObjectNode(); + ArrayNode studentsArray = objectMapper.createArrayNode(); + + // Example student 1 + ObjectNode student1 = objectMapper.createObjectNode(); + student1.put("ID", "A1234567J"); + student1.put("GPA", "4.5"); + student1.put("PREFERENCES", "{1,2,3}"); + + // Example student 2 + ObjectNode student2 = objectMapper.createObjectNode(); + student2.put("ID", "A7654321K"); + student2.put("GPA", "3.8"); + student2.put("PREFERENCES", "{2,3,1}"); + + // Example student 3 + ObjectNode student3 = objectMapper.createObjectNode(); + student3.put("ID", "A1357913L"); + student3.put("GPA", "4.0"); + student3.put("PREFERENCES", "{3,1,2}"); + + // Add students to the array + studentsArray.add(student1); + studentsArray.add(student2); + studentsArray.add(student3); + + // Add the array to the root node + rootNode.set("students", studentsArray); + + // Convert root node to pretty-printed JSON string + String exampleJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode); + + // Print the example JSON + System.out.println("Example of a correct JSON file:"); + System.out.println(exampleJson); + + } catch (JsonProcessingException e) { + System.out.println(e.getMessage()); + } + } + + + /** + * Processes a JSON file and adds students to the student list if the file is valid. + * If the file is invalid, prints an error message and an example of a correct JSON file. + * + * @param file The JSON file to be processed. + * @return true if the file is valid, false otherwise. + */ + private boolean hasProcessJSON(File file) { + ObjectMapper objectMapper = new ObjectMapper(); + // Save the current System.out + PrintStream originalOut = System.out; + + // Suppress output + System.setOut(new PrintStream(new ByteArrayOutputStream())); + try { + JsonNode rootNode = objectMapper.readTree(file); + JsonNode students = rootNode.get("students"); + + // Check if "students" node exists and is an array + if (students == null || !students.isArray()) { + throw SEPIOException.missingStudentsJSONArray(); + } + + for (JsonNode student : students) { + // Check if required fields are present + if (!student.has("ID") || !student.has("GPA") || !student.has("PREFERENCES")) { + throw SEPIOException.missingRequiredJSONFields(); + } + + // Extract and format the command + String id = student.get("ID").asText(); + String gpa = student.get("GPA").asText(); + String preferences = student.get("PREFERENCES").asText(); + String command = String.format("add id/%s gpa/%s p/%s", id, gpa, preferences); + + // Validate the data format + if ((!this.parser.isValidData(id, gpa,preferences)) || (!command.matches(ADD_STUDENT_REGEX))) { + throw SEPIOException.invalidDataJSONFormat(); + } + + this.parser.parseInput(command); + } + System.setOut(originalOut); + } catch (IOException | SEPException e) { + System.setOut(originalOut); + System.out.println(e.getMessage()); + printExampleJson(); + return false; + } + return true; + } + + + /** + * Processes a text file and adds students to the student list if the file is valid. + * + * @param path The path to the text file. + * @return true if the file is processed successfully, false otherwise. + * @throws SEPIOException If the file is invalid or any other exception occurs. + */ + private boolean hasProcessTXT(Path path) { + // Save the current System.out to restore later + PrintStream originalOut = System.out; + + // Suppress output + System.setOut(new PrintStream(new ByteArrayOutputStream())); + try { + // Read all lines from the file + List lines = Files.readAllLines(path); + + for (String line : lines) { + // Split the line by commas and spaces + String[] parts = line.split(", "); + + // Ensure the line has 3 parts: id, gpa, p + if (parts.length != 3) { + throw SEPIOException.missingTXTRequiredFields(); + } + + String idPart = parts[0]; + String gpaPart = parts[1]; + String preferencesPart = parts[2]; + + String command = "add " + String.format("%s %s %s", idPart, gpaPart, preferencesPart); + + // Validate the format of each part + if (!command.matches(ADD_STUDENT_REGEX)) { + throw SEPIOException.missingTXTRequiredFields(); + } + + String id = idPart.substring(3); // Extract the actual ID value + String gpa = gpaPart.substring(4); // Extract the actual GPA value + String preferences = preferencesPart.substring(2); // Extract the actual Preferences value + + // Validate the data using your existing parser + if (!this.parser.isValidData(id, gpa, preferences)) { + throw SEPIOException.invalidTXTDataFormat(); + } + + this.parser.parseInput(command); + } + System.setOut(originalOut); + } catch (IOException | SEPException e) { + System.setOut(originalOut); + System.out.println(e.getMessage()); + return false; + } + return true; + } + + /** + * Saves the allocation results to a file in the specified format. + * + * @param results The list of students with their allocation results. + * @param fileChoice The format of the file to be saved, either "csv", "json", or + * "txt". If choice is "csv", the allocation results will be + * saved to a CSV file. If choice is "json", the allocation + * results will be saved to a JSON file. If choice is "txt", the + * allocation results will be saved to a text file. + */ + public void saveAllocationResults(ArrayList results, String fileChoice) { + switch (fileChoice) { + case "csv": + saveToCSV(results, "data/allocation_results.csv"); + break; + case "json": + saveToJSON(results, "data/allocation_results.json"); + break; + case "txt": + saveToTXT(results, "data/allocation_results.txt"); + break; + default: + break; + } + } + + /** + * Saves the allocation results to a JSON file at the specified file path. + * The JSON file will contain an array of students, each with their respective + * fields. The output is formatted with indentation for readability. + * + * @param results The list of students with their allocation results to be saved. + * @param filePath The file path where the JSON file will be saved. + */ + public void saveToJSON(ArrayList results, String filePath) { + // Create an ObjectMapper instance + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + + // Create a map to hold the students array + Map> studentsMap = new HashMap<>(); + studentsMap.put("students", results); + + Path path = Paths.get(filePath); + + try { + Files.createDirectories(path.getParent()); + + // Write the map to a JSON file + File jsonFile = new File(path.toString()); + mapper.writeValue(jsonFile, studentsMap); + System.out.println("Allocation results saved to JSON file at " + filePath); + } catch (IOException e) { + System.err.println("Error saving allocation results to JSON: " + e.getMessage()); + } + } + + /** + * Saves the allocation results to a .txt file at the specified file path. + * The .txt file will contain each student's details, with each line formatted + * as follows: id/[id], gpa/[gpa], p/[rank1, rank2, ...], alloc/[allocatedUni] + * + * @param results The list of students with their allocation results to be saved. + * @param filePath The file path where the .txt file will be saved. + */ + public void saveToTXT(ArrayList results, String filePath) { + Path path = Paths.get(filePath); + + try { + // Ensure parent directories exist + Files.createDirectories(path.getParent()); + + // Create a BufferedWriter to write to the .txt file + try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) { + for (Student student : results) { + String line = String.format("id/%s, gpa/%.1f, p/%s, alloc/%d", + student.getId(), + student.getGpa(), + student.getUniPreferences().toString(), + student.getAllocatedUniversity()); + writer.write(line); + writer.newLine(); + } + System.out.println("Allocation results saved to TXT file at " + filePath); + } + } catch (IOException e) { + System.err.println("Error saving allocation results to TXT: " + e.getMessage()); + } + } + + /** + * Saves a list of students to a CSV file at the specified file path. + * The file includes columns for student ID, GPA, university preferences, + * and allocation status. + * + * @param students The list of Student objects to be saved in the CSV file. + * @param filePath The file path where the CSV file will be saved. + */ + public void saveToCSV(ArrayList students, String filePath) { + Path path = Paths.get(filePath); + try { + // Ensure directories exist before creating the file + Files.createDirectories(path.getParent()); + + // Use try-with-resources to automatically close the writer + try (CSVWriter writer = new CSVWriter(new FileWriter(path.toString()))) { + // Add header row + String[] header = { "ID", "GPA", "Preference Rankings", "Allocation Status" }; + writer.writeNext(header); + + for (Student student : students) { + String[] data = { + student.getId(), + String.valueOf(student.getGpa()), + String.valueOf(student.getUniPreferences()), + String.valueOf(student.getAllocatedUniversity()) + }; + writer.writeNext(data); + } + + System.out.println("CSV file saved successfully to " + path); + } + + } catch (IOException e) { + System.out.println("Error while saving CSV file: " + e.getMessage()); + } + } +} diff --git a/src/main/java/findoursep/FindOurSEP.java b/src/main/java/findoursep/FindOurSEP.java new file mode 100644 index 0000000000..56da2212a1 --- /dev/null +++ b/src/main/java/findoursep/FindOurSEP.java @@ -0,0 +1,78 @@ +package findoursep; + +import exceptions.SEPException; +import filehandler.FileHandler; +import studentlist.StudentList; +import ui.UI; +import parser.Parser; + +import java.io.IOException; + +public class FindOurSEP { + private UI ui; + private Parser parser; + private StudentList studentList; + private FileHandler fileHandler; + + /** + * Constructs a new FindOurSEP object. + * This constructor initializes the UI and parser. + */ + public FindOurSEP() { + this.ui = new UI(); + this.studentList = new StudentList(this.ui); + this.parser = new Parser(this.studentList,this.ui); + } + + /** + * Initializes the program using the user's .csv, .json or .txt file. + * Alternatively by the user's manual input. Calls FileHandler to parse + * the text in the file and create the corresponding Student object. + */ + public void setUpFileHandler() { + this.ui.printConfigMessage(); + String filePath = this.ui.promptFilePath(); + this.fileHandler = new FileHandler(filePath,this.parser); + if (filePath.isEmpty()) { + return; + } + try { + if (this.fileHandler.hasProcessFileSuccessfully()) { + this.ui.printFileLoadSuccessMessage(); + } else { + this.ui.printProcessError(); + } + } catch (SEPException | IOException e) { + this.ui.printResponse(e.getMessage()); + } + } + + /** + * The main loop of the application. + * This method will continue to loop until the user chooses to exit the + * application. It will read the user's input, process it. + */ + public void start() { + this.setUpFileHandler(); + String line; + boolean isRunning = true; + while (isRunning) { + line = this.ui.getUserInput(); + isRunning = this.parser.parseInput(line); + } + if (this.ui.toSave()) { + String choice = this.ui.getSaveFileChoice(); + assert choice != null; + this.fileHandler.saveAllocationResults(this.studentList.getList(),choice); + } + this.ui.sayBye(); + } + + /** + * Main entry-point for the findoursep.FindOurSEP application. + */ + public static void main(String[] args) { + FindOurSEP bob = new FindOurSEP(); + bob.start(); + } +} diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java new file mode 100644 index 0000000000..e77e7aa589 --- /dev/null +++ b/src/main/java/parser/Parser.java @@ -0,0 +1,116 @@ +package parser; + +import command.AddCommand; +import command.AllocateCommand; +import command.Commands; +import command.CriteriaCommand; +import command.DeleteCommand; +import command.ExitCommand; +import command.FindCommand; +import command.FilterCommand; +import command.GenerateCommand; +import command.HelpCommand; +import command.ListCommand; +import command.StatCommand; +import command.RevertCommand; +import command.UnknownCommand; +import command.ViewQuotaCommand; + +import studentlist.StudentList; +import ui.UI; + +import java.util.HashSet; +import java.util.Set; + +/** + * The Parser class is the main class responsible for parsing the user's input and executing the corresponding command. + */ +public class Parser { + private StudentList studentList; + private UI ui; + + public Parser(StudentList studentList, UI ui) { + this.studentList = studentList; + this.ui = ui; + } + + /** + * Parses the user's input and execute the corresponding command. + * + * @return false if the user wants to exit the application, true otherwise. + */ + public boolean parseInput(String input) { + String[] parts = input.split(" "); + Commands command; + try { + command = Commands.valueOf(parts[0].toUpperCase()); + } catch (IllegalArgumentException e) { + command = Commands.VOID; + } + switch (command) { + case ADD: + new AddCommand(this.studentList, input, this.ui).run(); + break; + case DELETE: + new DeleteCommand(this.studentList, input, this.ui).run(); + break; + case FIND: + new FindCommand(this.studentList, input, this.ui).run(); + break; + case FILTER: + new FilterCommand(this.studentList, input, this.ui).run(); + break; + case LIST: + new ListCommand(this.studentList, this.ui).run(); + break; + case ALLOCATE: + new AllocateCommand(this.studentList, this.ui).run(); + break; + case EXIT: + case QUIT: + case BYE: + new ExitCommand(this.studentList, this.ui).run(); + return false; + case HELP: + new HelpCommand(this.studentList, this.ui).run(); + break; + case GENERATE: + new GenerateCommand(this.studentList, this.ui).run(); + break; + case MINIMUM: + new CriteriaCommand(this.studentList, input, this.ui).run(); + break; + case STATS: + new StatCommand(this.studentList, input, this.ui).run(); + break; + case VIEWQUOTA: + new ViewQuotaCommand(this.studentList, input, this.ui).run(); + break; + case REVERT: + new RevertCommand(this.studentList, this.ui).run(); + break; + case VOID: + // fall through + default: + new UnknownCommand(this.studentList, this.ui).run(); + break; + } + return true; + } + + /** + * Validates the student data including ID, GPA, and preferences. + * + * @param id The student ID to validate. + * @param gpa The GPA to validate. + * @param preferences The preferences to validate. + * @return true if all data is valid, false if there are validation errors. + */ + public boolean isValidData(String id, String gpa, String preferences) { + Set errorMessages = new HashSet<>(); + this.studentList.validateStudentId(id, errorMessages); + this.studentList.validateGpa(gpa, errorMessages); + this.studentList.validatePreferences(preferences, errorMessages); + return errorMessages.isEmpty(); + } +} 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/student/Student.java b/src/main/java/student/Student.java new file mode 100644 index 0000000000..d4dc12e48d --- /dev/null +++ b/src/main/java/student/Student.java @@ -0,0 +1,81 @@ +package student; + +import java.util.ArrayList; + +/** + * This class represents a student with a unique ID, GPA, university preferences + * and allocation status. + */ +public class Student { + private String id; + private float gpa; + private ArrayList uniPreferences; + private boolean isSuccessfullyAllocated = false; + private int allocatedUniversity = -1; + + /** + * Constructs a new Student with the given ID, GPA, and university preferences. + * + * @param id The unique identifier of the student. + * @param gpa The student's GPA. + * @param uniPreferences A list of the student's university preferences. + */ + public Student(String id, float gpa, ArrayList uniPreferences){ + assert id != null : "Student ID cannot be null"; + assert gpa >= 0.0 && gpa <= 5.0 : "GPA must be between 0.0 and 5.0"; // Assuming GPA scale from 0.0 to 5.0 + assert uniPreferences != null : "University preferences cannot be null"; + + this.id = id; + this.gpa = gpa; + this.uniPreferences = uniPreferences; + } + + /** + * Copy constructor that creates a deep copy of another Student object. + * + * @param other The Student object to copy. + */ + public Student(Student other) { + this.id = other.id; + this.gpa = other.gpa; + this.uniPreferences = new ArrayList<>(other.uniPreferences.size()); + for (Integer preference : other.uniPreferences) { + this.uniPreferences.add(preference); + } + this.isSuccessfullyAllocated = other.isSuccessfullyAllocated; + this.allocatedUniversity = other.allocatedUniversity; + } + + public float getGpa() { + return gpa; + } + + public String getId() { + return id; + } + + public ArrayList getUniPreferences() { + return uniPreferences; + } + + public boolean getSuccessfullyAllocated() { + return isSuccessfullyAllocated; + } + + public void setSuccessfullyAllocated(boolean success) { + isSuccessfullyAllocated = success; + } + + public void setAllocatedUniversity(int university) { + this.allocatedUniversity = university; + } + + public int getAllocatedUniversity() { + return this.allocatedUniversity; + } + + public void resetAllocationStatus() { + this.isSuccessfullyAllocated = false; + this.allocatedUniversity = -1; + } +} diff --git a/src/main/java/studentlist/StudentList.java b/src/main/java/studentlist/StudentList.java new file mode 100644 index 0000000000..7a21db6dcd --- /dev/null +++ b/src/main/java/studentlist/StudentList.java @@ -0,0 +1,601 @@ +package studentlist; + +import exceptions.SEPException; +import exceptions.SEPRangeException; +import exceptions.SEPEmptyException; +import exceptions.SEPFormatException; +import exceptions.SEPDuplicateException; + +import student.Student; +import ui.UI; +import university.University; +import university.UniversityRepository; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Set; +import java.util.HashSet; +import java.util.List; + +/** + * Manages a list of students and provides methods for adding, deleting, sorting, + * printing and validating student data such as student ID, GPA, and preferences. + */ +public class StudentList { + public static final String ADD_STUDENT_REGEX = "^add\\s+id/[\\S ]+\\s+gpa/[\\S ]+\\s+p/\\{[\\S ]+}$"; + public static final String ID_REGEX = "^[A-Z]\\d{7}[A-Z]$"; + public static final String GPA_REGEX = "\\d+(\\.\\d{1,2})?"; + + private ArrayList students; + private UI ui; + private boolean isAllocationComplete; + + /** + * Constructs a new StudentList with an empty list of students and the given UI object. + * + * @param ui The UI instance used to interact with the user. + */ + public StudentList(UI ui) { + this.students = new ArrayList<>(); + this.ui = ui; + } + + /** + * Copy constructor that creates a deep copy of another StudentList object. + * + * @param other The StudentList object to copy. + */ + public StudentList(StudentList other) { + this.students = new ArrayList<>(other.students.size()); + for (Student student : other.students) { + this.students.add(new Student(student)); + } + this.ui = other.ui; + } + + public ArrayList getList() { + return students; + } + + /** + * Adds a new student to the list if the student does not already exist. + * + * @param student The Student object to add. + * @throws SEPDuplicateException If a student with the same ID already exists in the list. + */ + public void addStudent(Student student) throws SEPDuplicateException { + for (Student existingStudent : students) { + if (existingStudent.getId().equals(student.getId())) { + throw SEPDuplicateException.rejectDuplicateStudent(); + } + } + students.add(student); + } + + /** + * Deletes a student from the list by their ID. + * + * @param input The input that includes the ID of the student to delete. + * @throws SEPException If the student ID is not found or the ID format is invalid. + */ + public void deleteStudent(String input) throws SEPException{ + String[] parts = input.split("delete", 2); + if (parts.length != 2) { + throw SEPFormatException.rejectIdFormat(); + } + String studentId = organiseId(parts[1]); + if (!studentId.matches(ID_REGEX)) { + throw SEPFormatException.rejectIdFormat(); + } + Student student = students.stream() + .filter(s -> s.getId().equals(studentId)) + .findFirst() + .orElse(null); + if (student == null) { + throw SEPEmptyException.rejectStudentNotFound(); + } + if (student.getSuccessfullyAllocated()) { + int uni = student.getAllocatedUniversity(); + University university = UniversityRepository.getUniversityByIndex(uni); + university.addASpot(); + } + students.remove(student); + } + + /** + * Sorts the provided student list by GPA in ascending order. + */ + public void sortStudentsByAscendingGPA(List studentList) { + Collections.sort(studentList, new Comparator() { + @Override + public int compare(Student s1, Student s2) { + return Float.compare(s1.getGpa(), s2.getGpa()); // Ascending order + } + }); + } + + /** + * Sorts the provided student list by GPA in descending order. + */ + public void sortStudentsByDescendingGPA(List studentList) { + Collections.sort(studentList, new Comparator() { + @Override + public int compare(Student s1, Student s2) { + return Float.compare(s2.getGpa(), s1.getGpa()); + } + }); + } + + /** + * Sorts the provided student list by student ID in ascending order. + */ + public void sortStudentsByAscendingId(List studentList) { + Collections.sort(studentList, new Comparator() { + @Override + public int compare(Student s1, Student s2) { + return s1.getId().compareTo(s2.getId()); + } + }); + } + + /** + * Sorts the provided student list by student ID in descending order. + */ + public void sortStudentsByDescendingId(List studentList) { + Collections.sort(studentList, new Comparator() { + @Override + public int compare(Student s1, Student s2) { + return s2.getId().compareTo(s1.getId()); // Reverse comparison for descending order + } + }); + } + + public int getNumStudents() { + return students.size(); + } + + /** + * Handles the creation of a Student object by validating the input for Student ID, GPA, and preferences. + * + * @param input The input string containing student information. + * @return The created Student object if all validations pass. + * @throws SEPException If any validation errors occur, a SEPException containing all error messages is thrown. + */ + public Student makeStudent(String input) throws SEPException { + Set errorMessages = new HashSet<>(); + + String[] parts = splitAddInput(input); + + String studentId = organiseId(parts[1]); + validateStudentId(studentId, errorMessages); + + float gpa = validateGpa(parts[2].trim(), errorMessages); + + ArrayList preferences = validatePreferences(parts[3].trim(), errorMessages); + + // If any errors occurred, throw a single exception with all error messages + if (!errorMessages.isEmpty()) { + throw new SEPException(String.join("\n", errorMessages)); + } + + return new Student(studentId, gpa, preferences); + } + + /** + * Calls the UI to print the allocation report. + */ + public void generateReport() { + try { + ui.generateReport(students); + } catch (SEPException e) { + ui.printResponse(e.getMessage()); + } + } + + /** + * Updates the current student list with a new one. + * + * @param studentList The new student list to set. + */ + public void setStudentList(StudentList studentList) { + this.students = studentList.students; // Overwrite the current ArrayList + } + + /** + * Finds a student in the student list or report by their student ID. + * Keywords inputted need not be a valid student ID, + * but rather keywords that the student ID contains. + * Note: find keyword input is case-sensitive. + * + * @param input The ID or keywords of the student to find. + * @throws SEPException If student find format inputted is wrong, or if student id cannot be found. + */ + public void findStudent(String input) throws SEPException { + String[] parts = validateFindFilterFormat(input, "find"); + ArrayList foundStudent = new ArrayList<>(); + + String command = parts[1]; + String studentId = parts[2]; + for (Student student : students) { + if (student.getId().contains(studentId)) { + foundStudent.add(student); + } + } + + switch (command) { + case "list": + if (foundStudent.isEmpty()) { + throw SEPEmptyException.rejectStudentNotFound(); + } + ui.printResponse("Finding for students... student(s) found."); + ui.printStudentList(foundStudent); + break; + case "report": + if (foundStudent.isEmpty()) { + throw SEPEmptyException.rejectStudentNotFound(); + } + ui.printResponse("Finding for students... student(s) found."); + ui.generateReport(foundStudent); + break; + default: + throw SEPFormatException.rejectFindFormat(); + } + } + + /** + * Filters the student list or generates a report based on the specified command and filter criteria. + * The input must follow the format "filter [list/report] [filter criteria]". + * + * @param input The input command for filtering, which includes the operation ("list" or "report") + * and the filter criteria (e.g., "id ascending", "gpa descending", "allocated", "unallocated"). + * @throws SEPException If the input format is invalid, or no students are found + */ + public void filterStudent(String input) throws SEPException { + String[] parts = validateFindFilterFormat(input, "filter"); + String command = parts[1].trim().toLowerCase(); + String filter = parts[2].trim().toLowerCase(); + + switch (command) { + case "list": + filterStudentList(filter); + break; + case "report": + filterStudentReport(filter); + break; + default: + throw SEPFormatException.rejectFilterFormat(); + } + } + + /** + * Filters the student list based on the specified filter criteria (ID, GPA, or allocation status) + * and performs the relevant sorting or filtering operation. + * + * @param filter The filter criteria, which includes the attribute to filter by + * @throws SEPException If the filter format is invalid, or if filtered student list is empty.. + */ + public void filterStudentList(String filter) throws SEPException { + String[] parts = filter.split("\\s+", 2); + String listCommand = parts[0]; + String command; + switch (listCommand) { + case "id": + if (parts.length != 2) { + throw SEPFormatException.rejectFilterFormat(); + } + command = parts[1].trim(); + filterStudentId(command); + break; + case "gpa": + if (parts.length != 2) { + throw SEPFormatException.rejectFilterFormat(); + } + command = parts[1].trim(); + filterStudentGpa(command); + break; + case "allocated": + case "unallocated": + if (parts.length == 2) { + throw SEPFormatException.rejectFilterFormat(); + } + filterStudentAllocationList(listCommand); + break; + default: + throw SEPFormatException.rejectFilterFormat(); + } + } + + /** + * Filters students based on their allocation status. + * Returns students who are either successfully allocated or unallocated. + * + * @param filter The allocation status to filter by, either "allocated" or "unallocated". + * @return An ArrayList of students who match the specified allocation status. + */ + public ArrayList filterByAllocationStatus(String filter) throws SEPException { + ArrayList filteredStudents = new ArrayList<>(); + boolean isAllocated = filter.equals("allocated"); + if (!isAllocated) { + if (!filter.equals("unallocated")) { + throw SEPFormatException.rejectFilterFormat(); + } + } + for (Student student : students) { + if (student.getSuccessfullyAllocated() == isAllocated) { + filteredStudents.add(student); + } + } + return filteredStudents; + } + + /** + * Filters students by allocation status (allocated or unallocated) + * and generates a list for the filtered students. + * + * @param command Either "allocated" or "unallocated" to filter by status. + * @throws SEPException If the list is empty. + */ + public void filterStudentAllocationList(String command) throws SEPException { + ArrayList filteredStudents = filterByAllocationStatus(command); + ui.printStudentList(filteredStudents); + } + + + /** + * Filters students by allocation status (allocated or unallocated) + * and generates a report for the filtered students. + * + * @param filter Either "allocated" or "unallocated" to filter by status. + * @throws SEPException If report is empty or if format is invalid. + */ + public void filterStudentReport(String filter) throws SEPException { + ArrayList filteredStudents = filterByAllocationStatus(filter); + ui.generateReport(filteredStudents); + } + + /** + * Filters the student list by student ID and sorts it in the specified order. + * The sorted list is then printed. + * + * @param command The sorting order for student IDs, either "ascending" or "descending". + * @throws SEPException If the command is invalid, or if filtered student list is empty. + */ + public void filterStudentId (String command) throws SEPException { + ArrayList filteredStudents = new ArrayList<>(students); + + switch (command) { + case "ascending": + sortStudentsByAscendingId(filteredStudents); + ui.printStudentList(filteredStudents); + break; + case "descending": + sortStudentsByDescendingId(filteredStudents); + ui.printStudentList(filteredStudents); + break; + default: + throw SEPFormatException.rejectFilterFormat(); + } + } + + /** + * Filters the student list by GPA and sorts it in the specified order. + * The sorted list is then printed. + * + * @param command The sorting order for GPA, either "ascending" or "descending". + * @throws SEPException If the command is invalid, or if filtered student list is empty. + */ + public void filterStudentGpa (String command) throws SEPException { + ArrayList filteredStudents = new ArrayList<>(students); + switch (command) { + case "ascending": + sortStudentsByAscendingGPA(filteredStudents); + ui.printStudentList(filteredStudents); + break; + case "descending": + sortStudentsByDescendingGPA(filteredStudents); + ui.printStudentList(filteredStudents); + break; + default: + throw SEPFormatException.rejectFilterFormat(); + } + } + + /** + * Validates the format for both find and filter methods (At least 3 spaced words). + * + * @param input The raw input string to be validated + * @return An organised string array for further processing. + * @throws SEPException If format is invalid. + */ + public String[] validateFindFilterFormat (String input, String keyword) throws SEPException { + String[] parts = input.split("\\s+", 3); + + if (parts.length != 3) { + if (keyword.equals("find")) { + throw SEPFormatException.rejectFindFormat(); + } else { + assert keyword.equals("filter"); + throw SEPFormatException.rejectFilterFormat(); + } + } + + assert parts.length == 3; + return parts; + } + + /** + * Organizes the student ID by removing all spaces, + * including any spaces in between the numbers or alphabets, + * before validating the student ID. + * + * @param id The raw student ID string. + * @return A trimmed and space-free student ID. + */ + private String organiseId(String id) { + String studentId = id.trim(); + return studentId.replaceAll("\\s+", ""); + } + + /** + * Splits the input string into parts based on the expected format + * and order: id/, gpa/, followed by p/. Any other order would be + * rejected and a format exception would be thrown. + * + * @param input The raw input string. + * @return A String array containing the parts of the input. + * @throws SEPFormatException If the input format or order is invalid. + */ + private String[] splitAddInput(String input) throws SEPException { + if (!input.matches(ADD_STUDENT_REGEX)) { + throw SEPFormatException.rejectAddStudentFormat(); + } + String[] parts = input.split("id/|gpa/|p/"); + if (parts.length != 4) { + throw SEPFormatException.rejectAddStudentFormat(); + } + return parts; + } + + /** + * Validates the student ID format: + * 1 alphabet, followed by 7 digits, then another alphabet. + * Alphabets must be in uppercase. + * + * @param studentId The student ID to validate. + * @param errorMessages A set to collect error messages. + */ + public void validateStudentId(String studentId, Set errorMessages) { + if (!studentId.matches(ID_REGEX)) { + errorMessages.add(SEPFormatException.rejectIdFormat().getMessage()); + } + } + + /** + * Filters students based on their university preferences. + * + * @param uniIndex The index of the university to filter students by. + * @return A list of students who have the specified university index in their preferences. + * @throws SEPException if no students have chosen the specified university. + */ + public List getStudentsByUniversityIndex(int uniIndex) throws SEPException { + List filteredStudents = new ArrayList<>(); + for (Student student : this.students) { + if (student.getSuccessfullyAllocated() && student.getAllocatedUniversity() == uniIndex) { + filteredStudents.add(student); + } + } + if (filteredStudents.isEmpty()) { + throw new SEPException("No students have been allocated to this university."); + } + return filteredStudents; + } + + /** + * Calculates the average GPA of students who chose the specified university. + * + * @param uniIndex The index of the university. + * @return The average GPA as a double. + * @throws SEPException if no students have chosen the specified university. + */ + public double calculateAverageGpaForUniversity(int uniIndex) throws SEPException { + List students = getStudentsByUniversityIndex(uniIndex); + return students.stream() + .mapToDouble(Student::getGpa) + .average() + .orElse(0.0); + } + + /** + * Calculates the minimum GPA of students who chose the specified university. + * + * @param uniIndex The index of the university. + * @return The minimum GPA as a double. + * @throws SEPException if no students have chosen the specified university. + */ + public double calculateMinGpaForUniversity(int uniIndex) throws SEPException { + List students = getStudentsByUniversityIndex(uniIndex); + return students.stream() + .mapToDouble(Student::getGpa) + .min() + .orElse(0.0); + } + + /** + * Validates the GPA format and range. + * GPA should be a float between 1 and 5, with a maximum of 2 decimal places. + * + * @param gpaStr The GPA string to validate. + * @param errorMessages A set to collect error messages. + * @return A float representing the valid GPA. + */ + public float validateGpa(String gpaStr, Set errorMessages) { + float gpa = 0.0f; + try { + if (!gpaStr.matches(GPA_REGEX)) { + throw new NumberFormatException(); + } + gpa = Float.parseFloat(gpaStr); + if (gpa < 0.0 || gpa > 5.0) { + errorMessages.add(SEPRangeException.rejectGpaRange().getMessage()); + } + } catch (NumberFormatException e) { + errorMessages.add(SEPFormatException.rejectGpaFormat().getMessage()); + } + return gpa; + } + + /** + * Validates the student preferences for valid range and format. + * Preference rankings should be wrapped in curly braces and have up to 3 rankings. + * + * @param preferencesData The preference string to validate. + * @param errorMessages A set to collect error messages. + * @return A list of valid preferences. + */ + public ArrayList validatePreferences(String preferencesData, Set errorMessages) { + ArrayList preferences = new ArrayList<>(); + String[] numberStrings = preferencesData.replaceAll("[{}]", "").split(","); + + if (numberStrings.length > 3 || numberStrings[0].trim().isEmpty()) { + errorMessages.add(SEPRangeException.rejectRankingsRange().getMessage()); + } + + for (String numberString : numberStrings) { + try { + int preference = Integer.parseInt(numberString.trim()); + if (preference < 1 || preference > 92) { + errorMessages.add(SEPRangeException.rejectPreferenceRange().getMessage()); + } else { + preferences.add(preference); + } + } catch (NumberFormatException e) { + errorMessages.add(SEPFormatException.rejectPreferenceFormat().getMessage()); + } + } + + return preferences; + } + + /** + * Reverts the allocation status of all elements in StudentList. + */ + public void revertAllocation() throws SEPEmptyException { + if (!isAllocationComplete) { + throw SEPEmptyException.rejectAllocationIncomplete(); + } + + for (Student student : students) { + student.resetAllocationStatus(); + } + this.isAllocationComplete = false; + } + + public boolean getAllocationStatus() { + return this.isAllocationComplete; + } + + public void setAllocationStatus(boolean status) { + this.isAllocationComplete = status; + } +} diff --git a/src/main/java/ui/Messages.java b/src/main/java/ui/Messages.java new file mode 100644 index 0000000000..88f7ec832a --- /dev/null +++ b/src/main/java/ui/Messages.java @@ -0,0 +1,60 @@ +package ui; + +public enum Messages { + WELCOME("Hi! Welcome to FindOurSEP! Enter help for the list of commands."), + EXIT("Goodbye! Have a great day!"), + ALLOCATE_COMPLETE("Allocation algorithm complete! Run generate command to view."), + REVERT_COMPLETE("Revert complete! Run the allocate command whenever you are ready."), + HELP(""" + Here is the list of possible commands: + add Adds a student with the specified ID, GPA, and preference rankings. + Example: add id/A1234567I gpa/5.0 p/{13,61,43} + Note: PREFERENCE_RANKINGS should be enclosed in curly braces + + delete Deletes the student with the specified ID from the list. + Example: delete A1234567I + + help Displays this help message. + + list Lists all students currently in the system. + + find Finds the student with the keyword, returning either a list or report. + Example: find list A1234567I + + minimum Sets the minimum GPA to be considered for exchange. + + filter Filters student data with a keyword, returning either a list or report. + User can choose between ascending/descending id/gpa and allocation status. + The format is: 'filter ', + or 'filter ' + Example: filter list gpa ascending + + stats Displays GPA stats for students who have been allocated a specific uni. + Usage: + stats -avggpa Displays the average GPA for the specified uni. + stats -mingpa Displays the minimum GPA for the specified uni. + + viewQuota Displays the index, name, and remaining quota for the specified uni. + Usage: viewQuota + + allocate Allocates students to available slots based on their preferences. + + revert Reverts the student list to original, pre-allocation state. + + generate Generates a report of allocations and student data. + + exit Exits the application. + """), + ERROR("An error occurred. Please try again."); + + private final String message; + + Messages(String message) { + this.message = message; + } + + @Override + public String toString() { + return this.message; + } +} diff --git a/src/main/java/ui/UI.java b/src/main/java/ui/UI.java new file mode 100644 index 0000000000..28f5eb9bea --- /dev/null +++ b/src/main/java/ui/UI.java @@ -0,0 +1,313 @@ +package ui; + +import de.vandermeer.asciitable.AsciiTable; +import de.vandermeer.asciitable.CWC_LongestLine; +import de.vandermeer.skb.interfaces.transformers.textformat.TextAlignment; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.stream.Collectors; + +import exceptions.SEPEmptyException; +import student.Student; +import university.University; +import university.UniversityRepository; + +import static filehandler.FileHandler.isValidPath; + +/** + * Handles user interface FindMySEP application. + * Methods to print messages, retrieve user input, display ASCII table + * for student data. + */ +public class UI { + private static final int LINE_LENGTH = 80; + public static final String HORIZONTAL_LINE = "-".repeat(LINE_LENGTH); + private final Scanner scanner; + + /** + * Constructor for UI class + */ + public UI() { + this.scanner = new Scanner(System.in); + } + + /** + * Prints the given item to the console, wrapped by horizontal lines above + * and below the specified item. + * + * @param item The item to be printed, which can be of any type. + * If the item is null, it prints "null". + */ + public void printResponse(T item) { + String content = (item == null) ? "null" : item.toString(); + System.out.println(HORIZONTAL_LINE + "\n" + content + "\n" + HORIZONTAL_LINE); + } + + /** + * Retrieves a line of user input from the console. + * + * @return The line of input entered by the user, or an empty string + * if an error occurs while reading input. + */ + public String getUserInput() { + try { + return scanner.nextLine().trim(); + } catch (Exception e) { + printResponse("Error reading input: " + e.getMessage()); + return ""; // Return an empty string + } + } + + /** + * Prints a greeting message to the console. + */ + public void sayHi() { + printResponse(Messages.WELCOME); + } + + /** + * Prints a farewell message to the console. + */ + public void cleanupAndExit() { + printResponse("Do you want to save your results?"); + } + + /** + * Prints table displaying student information. + * The table includes student IDs, GPAs, and preference rankings. + * + * @param studentList ArrayList of Student objects to be printed + * @throws SEPEmptyException if the student list is null or empty. + */ + public void printStudentList(ArrayList studentList) throws SEPEmptyException { + if (studentList == null || studentList.isEmpty()) { + throw SEPEmptyException.rejectEmptyStudentList(); + } + + AsciiTable at = new AsciiTable(); + at.addRule(); + at.addRow("Student", "GPA", "Preference Rankings"); + at.addRule(); + + for (Student s : studentList) { + // Using Java Streams to create a comma-separated preference rankings string + String uniPrefsString = s.getUniPreferences().stream() + .map(String::valueOf) // Convert each Integer to String + .collect(Collectors.joining(",")); // Join them with commas + + // Use the uniPrefsString for adding to the table + at.addRow(s.getId(), s.getGpa(), uniPrefsString); + } + at.addRule(); + + at.setTextAlignment(TextAlignment.CENTER); + at.setPaddingLeft(3); + at.setPaddingRight(3); + + at.getRenderer().setCWC(new CWC_LongestLine()); + printResponse("Here is the list:\n" + at.render()); + } + + /** + * Prints a buffering message when the algorithm is running. + * Upon algorithm completion, replaces the buffering message with a + * completed message. + */ + public void printAllocatingMessage() { + System.out.println(HORIZONTAL_LINE); + System.out.println("\r" + Messages.ALLOCATE_COMPLETE); + System.out.println(HORIZONTAL_LINE); + } + + /** + * Prints table displaying allocation results. + * The table includes student IDs and respective allocation outcomes. + * + * @param studentList ArrayList of Student objects to be printed + * @throws SEPEmptyException If the student list is null or empty, + * indicating that there are no students to generate. + */ + public void generateReport(ArrayList studentList) throws SEPEmptyException { + if (studentList == null || studentList.isEmpty()) { + throw SEPEmptyException.rejectEmptyStudentList(); + } + + AsciiTable at = new AsciiTable(); + at.addRule(); + at.addRow("Student", "University Granted"); + at.addRule(); + + for (Student s : studentList) { + int choice = s.getAllocatedUniversity(); + University allocated = UniversityRepository.getUniversityByIndex(choice); + + if (allocated != null) { + at.addRow(s.getId(), choice + ". " + allocated.getFullName()); + } else { + at.addRow(s.getId(), "Not Allocated"); // Handle cases with no allocation + } + } + at.addRule(); + + at.setTextAlignment(TextAlignment.LEFT); + at.setPaddingLeft(1); + at.setPaddingRight(1); + + at.getRenderer().setCWC(new CWC_LongestLine()); + printResponse("Here is the allocation report:\n" + at.render()); + } + + /** + * Prints a prompt for the user on program entry. + */ + public void printConfigMessage() { + StringBuilder output = new StringBuilder(); + output.append("Good day to you!" + "\n") + .append("Before we begin, would you like to:\n") + .append("1. Manually input students data\n") + .append("2. Upload a file (.csv, .txt, .json)\n") + .append("Please choose 1 or 2 or exit :)"); + printResponse(output.toString()); + } + + public void printProcessError() { + printResponse(""" + Process error! To re-upload a file, please restart the program by entering 'exit'\s + followed by 'no' and \ + ensure the file is formatted correctly before retrying. \ + + Otherwise, you can continue to use the program. Enter help for available commands."""); + } + + public void printFileLoadSuccessMessage() { + printResponse("File loaded successfully! Let's begin! Enter help for available commands."); + } + + /** + * Checks if the user's input matches the expected input format. + * + * @param option The user's input response to the initial prompt + * @return A boolean that indicates validity of the input string + */ + public boolean isValidOption(String option) { + return option.equals("1") || option.equals("2") || option.equals("exit") + || option.equals("bye") || option.equals("quit"); + } + + /** + * Gets the user's input for file storage option. + * + * @return A string guaranteed to contain the user's choice for preferred input mode. + */ + public String promptFilePath() { + String command = pollInitialInput(); + return processInput(command); + } + + /** + * Verifies the input required for processing the user's choice. + * + * @return A string guaranteed to be in the expected form. + */ + public String pollInitialInput() { + String input = getUserInput(); + while (!isValidOption(input)) { + printResponse("Boss, type 1 or 2 or exit only leh!"); + input = getUserInput().toLowerCase(); + } + if (input.equals("exit") || input.equals("quit") || input.equals("bye")) { + sayBye(); + System.exit(0); + } + return input; + } + + /** + * Verifies the input required for processing the input file path. + * + * @return The file path of the input file as a string guaranteed to be in the expected form. + */ + public String processFilePathInput() { + String input = getUserInput(); + while (input.isEmpty() || !isValidPath(input)) { + if (input.isEmpty()) { + printResponse("Filepath cannot be empty!"); + } + if (!isValidPath(input)) { + printResponse("Invalid filepath!"); + } + input = getUserInput(); + } + return input; + } + + /** + * Ensures that the response for selecting input mode is not empty. Exits the program if the user + * inputs the exit command. Prompts for file path if user chooses input file option. + * + * @param input The user's input response to the initial prompt + * @return If the user chooses to select an input file source, returns a string containing + * the file path of the user's student list file. + * If the user chooses manual input, returns an empty string. + */ + public String processInput(String input) { + if (input.equals("exit")) { + cleanupAndExit(); + System.exit(0); + } + if (input.equals("2")) { + printResponse("Example: C:\\Users\\bob\\OneDrive\\Documents\\tp\\test.csv" + + "\nPlease enter the ABSOLUTE path to the file: "); + return processFilePathInput(); + } + sayHi(); + return ""; + } + + /** + * Verifies that the user's choice of file extension is valid. + * + * @param input The user's choice of file output extension format. + * @return A boolean representing the validity of choice. + */ + public boolean isValidSaveChoice(String input) { + return input.equals("csv") || input.equals("txt") || input.equals("json"); + } + + /** + * Prompts the user for their choice of file type for the save file, and checks the input. + * + * @return A string guaranteed to contain a valid file extension. + */ + public String getSaveFileChoice() { + printResponse("Please choose a file type (CSV, JSON, TXT) to save your results."); + String input = getUserInput().toLowerCase(); + while (!isValidSaveChoice(input)) { + printResponse("Please enter a valid (CSV, JSON, TXT) save choice!"); + input = getUserInput(); + } + return input; + } + + /** + * Prompts the user whether they want to save the allocation results. + * + * @return A boolean representing whether the user chooses to save a file. + */ + public boolean toSave() { + String save = getUserInput().toLowerCase(); + while (!(save.equals("yes") || save.equals("no"))) { + printResponse("Please enter a valid command (Yes/No)!"); + save = getUserInput().toLowerCase(); + } + return save.equals("yes"); + } + + /** + * Prints a farewell message and shuts down the scanner object. + */ + public void sayBye() { + printResponse("Adios, amigo!"); + scanner.close(); + } +} diff --git a/src/main/java/university/University.java b/src/main/java/university/University.java new file mode 100644 index 0000000000..851aa209d6 --- /dev/null +++ b/src/main/java/university/University.java @@ -0,0 +1,76 @@ +package university; + +/** + * Represents a university with details such as its full name, acronym, and + * the number of available spots for allocation. + * The class provides methods to retrieve university information and update + * available spots when a student is allocated. + */ + +public class University { + private String fullName; + private String acronym; + private int spotsLeft; + + /** + * Constructs a University with the specified name, acronym, and available spots. + * + * @param fullName The full name of the university. + * @param acronym The acronym representing the university. + * @param spotsLeft The number of spots left for student allocation. + */ + + public University(String fullName, String acronym, int spotsLeft) { + assert fullName != null : "Full name cannot be null"; + assert acronym != null : "Acronym cannot be null"; + assert spotsLeft >= 0 : "Spots left cannot be negative"; + + this.fullName = fullName; + this.acronym = acronym; + this.spotsLeft = spotsLeft; + } + + public String getFullName() { + return fullName; + } + + public String getAcronym() { + return acronym; + } + + public int getSpotsLeft() { + return spotsLeft; + } + + /** + * Decrements the number of available spots by one, typically called when + * a student is successfully allocated to this university. + */ + + public void removeASpot(){ + assert spotsLeft > 0 : "Cannot remove a spot when none are left"; + this.spotsLeft--; + } + + /** + * Increments the number of available spots by one, typically called when + * a student is deleted from this university. + */ + public void addASpot(){ + assert spotsLeft >= 0 : "spot left cannot be negative"; + this.spotsLeft++; + } + + /** + * Returns a string representation of the university in the format: + * "Full Name (Acronym)". + * + * @return A string representation of the university. + */ + + @Override + public String toString() { + return fullName + " (" + acronym + ")"; + } +} + diff --git a/src/main/java/university/UniversityRepository.java b/src/main/java/university/UniversityRepository.java new file mode 100644 index 0000000000..c5ec20dadd --- /dev/null +++ b/src/main/java/university/UniversityRepository.java @@ -0,0 +1,132 @@ +package university; + +import java.util.HashMap; + +/** + * The UniversityRepository class acts as a central repository for all universities, + * storing information such as each university's name, acronym, and available spots. + * It provides a static method to retrieve a university by its index. + */ +public class UniversityRepository { + // HashMap storing universities with an Integer index as the key and University as the value + private static HashMap universityMap = new HashMap<>(); + + // Static block to initialize the repository with predefined universities + static { + initialiseMap(); + } + + /** + * Initialises the map of universities in this UniRepo class. + */ + private static void initialiseMap() { + universityMap.put(1, new University("The Uni of Western Australia", "UWA",1 )); + universityMap.put(2, new University("The University of Melbourne", "UM",1)); + universityMap.put(3, new University("The Australian National Uni", "TANU",1)); + universityMap.put(4, new University("Katholieke Universiteit Leuven", "KUL",1)); + universityMap.put(5, new University("University of Waterloo", "Waterloo",2)); + universityMap.put(6, new University("Simon Fraser University", "SFU",2)); + universityMap.put(7, new University("University of Ottawa", "Ottawa",2)); + universityMap.put(8, new University("McGill University", "McGill",2)); + universityMap.put(9, new University("University of Calgary", "Calgary",1)); + universityMap.put(10, new University("Concordia University", "Concordia",1)); + universityMap.put(11, new University("The Uni of British Columbia", "UBC",1)); + universityMap.put(12, new University("Tongji University", "Tongji",1)); + universityMap.put(13, new University("Chongqing University", "Chongqing",1)); + universityMap.put(14, new University("Zhejiang University", "Zhejiang",1)); + universityMap.put(15, new University("Tsinghua University", "Tsinghua",1)); + universityMap.put(16, new University("Peking University", "Peking",1)); + universityMap.put(17, new University("Nanjing University", "Nanjing",1)); + universityMap.put(18, new University("Shanghai Jiao Tong University", "SJTU",1)); + universityMap.put(19, new University("Fudan University", "Fudan",1)); + universityMap.put(20, new University("Aarhus University", "Aarhus",1)); + universityMap.put(21, new University("Technical Uni of Denmark", "DTU",1)); + universityMap.put(22, new University("Tallinn Uni of Technology", "TUT",1)); + universityMap.put(23, new University("CentSup - Ecole Centrale Paris", "ECP",1)); + universityMap.put(24, new University("Inst Nat des Sci Appl de Lyon", "INSA Lyon",2)); + universityMap.put(25, new University("TELECOM ParisTech", "ParisTech",2)); + universityMap.put(26, new University("Ecole Nat Sup des Mines Paris", "Mines Paris",1)); + universityMap.put(27, new University("Universite Grenoble Alpes", "UGA",2)); + universityMap.put(28, new University("Hamburg Uni of Technology", "HUT",1)); + universityMap.put(29, new University("Technical Uni of Munich", "TUM",2)); + universityMap.put(30, new University("University of Stuttgart", "Stuttgart",1)); + universityMap.put(31, new University("Karlsruhe Inst of Technology", "KIT",1)); + universityMap.put(32, new University("Technical Uni of Darmstadt", "TUD",1)); + universityMap.put(33, new University("The Hong Kong Polytechnic Uni", "HKPU",1)); + universityMap.put(34, new University("Hong Kong Uni of Sci & Tech", "HKUST",2)); + universityMap.put(35, new University("City University of Hong Kong", "TCUHK",2)); + universityMap.put(36, new University("The University of Hong Kong", "HKU",2)); + universityMap.put(37, new University("The Chinese Uni of Hong Kong", "TCUHK",1)); + universityMap.put(38, new University("University College Dublin", "UCD",1)); + universityMap.put(39, new University("Tohoku University", "Tohoku",1)); + universityMap.put(40, new University("Kyushu University", "Kyushu",1)); + universityMap.put(41, new University("The University of Tokyo", "UTokyo",1)); + universityMap.put(42, new University("Nagoya University", "Nagoya",1)); + universityMap.put(43, new University("Keio University", "Keio",1)); + universityMap.put(44, new University("Korea University", "KU",1)); + universityMap.put(45, new University("Korea Adv Inst S&T (KAIST)", "KAIST",1)); + universityMap.put(46, new University("Yonsei University", "Yonsei",1)); + universityMap.put(47, new University("Eindhoven Uni of Technology", "EUT",2)); + universityMap.put(48, new University("Delft University of Technology", "DUT",2)); + universityMap.put(49, new University("Victoria Uni of Wellington", "VUW",1)); + universityMap.put(50, new University("Norwegian U of Sci&Tech (NTNU)", "NTNU",1)); + universityMap.put(51, new University("University of Oslo", "UO",1)); + universityMap.put(52, new University("Universidad Autonoma de Madrid", "UAM",1)); + universityMap.put(53, new University("Chalmers Uni of Technology", "Chalmers",1)); + universityMap.put(54, new University("Uppsala University", "Uppsala",1)); + universityMap.put(55, new University("Stockholm University", "Stockholm",1)); + universityMap.put(56, new University("Lund University", "Lund",2)); + universityMap.put(57, new University("University of Gothenburg", "Gothenburg",1)); + universityMap.put(58, new University("ETH Zurich", "Zurich",1)); + universityMap.put(59, new University("Ecole Poly Federale Lausanne", "EPFL",1)); + universityMap.put(60, new University("University of Geneva", "Geneva",1)); + universityMap.put(61, new University("National Tsing Hua University", "NTHU",1)); + universityMap.put(62, new University("National Cheng Kung University", "NCKU",1)); + universityMap.put(63, new University("National Yang Ming Chiao Tung", "NYCU",2)); + universityMap.put(64, new University("Chulalongkorn University", "CU",1)); + universityMap.put(65, new University("Sabanci University", "Sabanci",1)); + universityMap.put(66, new University("King's College London", "KCL",2)); + universityMap.put(67, new University("The University of Manchester", "Manchester",1)); + universityMap.put(68, new University("The University of Sheffield", "Sheffield",1)); + universityMap.put(69, new University("The University of Nottingham", "Nottingham",1)); + universityMap.put(70, new University("University of Liverpool", "Liverpool",1)); + universityMap.put(71, new University("University of Leeds", "Leeds",1)); + universityMap.put(72, new University("University of Glasgow", "Glasgow",1)); + universityMap.put(73, new University("The University of Edinburgh", "Edinburgh",1)); + universityMap.put(74, new University("University of Bristol", "Bristol",1)); + universityMap.put(75, new University("University of Birmingham", "Birmingham",2)); + universityMap.put(76, new University("Boston College", "Boston College",1)); + universityMap.put(77, new University("University of Washington", "UW",1)); + universityMap.put(78, new University("University of Virginia", "UV",1)); + universityMap.put(79, new University("The Uni of Texas at Austin", "UTA",1)); + universityMap.put(80, new University("Purdue University", "Purdue",1)); + universityMap.put(81, new University("Pennsylvania State University", "Penn State",2)); + universityMap.put(82, new University("Uni of Michigan, Ann Arbor", "UMich",1)); + universityMap.put(83, new University("Georgia Inst of Technology", "GT",2)); + universityMap.put(84, new University("University of Connecticut", "UConn",2)); + universityMap.put(85, new University("Case Western Reserve Uni", "Case Western",1)); + universityMap.put(86, new University("Carnegie Mellon University", "CMU",1)); + universityMap.put(87, new University("University of California", "UC",5)); + universityMap.put(88, new University("Brandeis University", "Brandeis",1)); + universityMap.put(89, new University("Boston University", "Boston U",2)); + universityMap.put(90, new University("Tulane University", "Tulane",1)); + universityMap.put(91, new University("Clarkson University", "Clarkson",1)); + universityMap.put(92, new University("University of Georgia", "UG",1)); + } + + /** + * Retrieves a university from the repository based on the provided index. + * + * @param index The index of the university to retrieve. + * @return The University object corresponding to the given index, or null if no such university exists. + */ + public static University getUniversityByIndex(int index) { + return universityMap.get(index); + } + + public static void resetMap() { + universityMap = new HashMap<>(); + initialiseMap(); + } +} + diff --git a/src/test/java/allocator/AllocatorTest.java b/src/test/java/allocator/AllocatorTest.java new file mode 100644 index 0000000000..9a85f57366 --- /dev/null +++ b/src/test/java/allocator/AllocatorTest.java @@ -0,0 +1,81 @@ +package allocator; + +import exceptions.SEPDuplicateException; +import exceptions.SEPEmptyException; +import exceptions.SEPException; +import student.Student; +import studentlist.StudentList; +import ui.UI; +import university.UniversityRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AllocatorTest { + + private StudentList studentList; + private Allocator allocator; + + @BeforeEach + public void setUp() throws SEPDuplicateException { + Student student1 = new Student("1", 3.8f, new ArrayList<>(Arrays.asList(1, 2, 3))); + Student student2 = new Student("2", 3.5f, new ArrayList<>(Arrays.asList(3, 1, 2))); + Student student3 = new Student("3", 3.9f, new ArrayList<>(Arrays.asList(3, 2, 1))); + Student student4 = new Student("4", 3.6f, new ArrayList<>(Arrays.asList(3, 2, 1))); + + studentList = new StudentList(new UI()); + studentList.addStudent(student1); + studentList.addStudent(student2); + studentList.addStudent(student3); + studentList.addStudent(student4); + allocator = new Allocator(studentList); + } + + @Test + public void testSetMinimumGPA() throws SEPException { + allocator.setMinimumGPA("minimum 4.0"); + assertEquals(4.0, allocator.getMinimumGPA()); + } + + @Test + public void testAllocate() throws SEPEmptyException { + StudentList allocatedStudents = allocator.allocate(); + + assertTrue(allocatedStudents.getList().get(0).getSuccessfullyAllocated()); + assertFalse(allocatedStudents.getList().get(1).getSuccessfullyAllocated()); + assertTrue(allocatedStudents.getList().get(2).getSuccessfullyAllocated()); + assertTrue(allocatedStudents.getList().get(3).getSuccessfullyAllocated()); + + // Check if universities have the correct number of spots left + assertEquals(0, UniversityRepository.getUniversityByIndex(1).getSpotsLeft()); + assertEquals(0, UniversityRepository.getUniversityByIndex(2).getSpotsLeft()); + assertEquals(0, UniversityRepository.getUniversityByIndex(3).getSpotsLeft()); + + // Check if students are allocated to the correct universities + assertEquals(1, allocatedStudents.getList().get(0).getAllocatedUniversity()); + assertEquals(-1, allocatedStudents.getList().get(1).getAllocatedUniversity()); + assertEquals(3, allocatedStudents.getList().get(2).getAllocatedUniversity()); + assertEquals(2, allocatedStudents.getList().get(3).getAllocatedUniversity()); + } + + @Test + public void testAllocateWithCriteria() throws SEPException { + allocator.setMinimumGPA("minimum 4.0"); + assertThrows(SEPEmptyException.class, () -> allocator.allocate()); + } + + @Test + public void allocate_emptyStudentList_throwsEmptyException() throws SEPException { + studentList.setStudentList(new StudentList(new UI())); + allocator = new Allocator(studentList); + + assertThrows(SEPException.class, () -> allocator.allocate()); + assertFalse(studentList.getAllocationStatus()); + } +} diff --git a/src/test/java/filehandler/FileHandlerTest.java b/src/test/java/filehandler/FileHandlerTest.java new file mode 100644 index 0000000000..911224e388 --- /dev/null +++ b/src/test/java/filehandler/FileHandlerTest.java @@ -0,0 +1,129 @@ +package filehandler; + +import exceptions.SEPEmptyException; +import exceptions.SEPException; +import exceptions.SEPUnknownException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import parser.Parser; +import student.Student; +import studentlist.StudentList; +import ui.UI; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FileHandlerTest { + private static final Path CSV_FILE_PATH = Paths.get("test.csv"); + private static final Path JSON_FILE_PATH = Paths.get("test.json"); + private static final Path TXT_FILE_PATH = Paths.get("test.txt"); + + private FileHandler fileHandler; + private Parser parser; + private StudentList studentList; + private UI ui; + private ArrayList results; + + @BeforeEach + void setUp() throws SEPException, IOException { + this.ui = new UI(); + this.studentList = new StudentList(ui); + this.parser = new Parser(studentList, ui); + this.fileHandler = new FileHandler("test.csv", parser); + this.fileHandler.hasProcessFileSuccessfully(); + results = this.studentList.getList(); + } + + @Test + void testCSVFileProcessing() throws IOException, SEPException { + fileHandler = new FileHandler(CSV_FILE_PATH.toString(), parser); + + boolean result = fileHandler.hasProcessFileSuccessfully(); + + assertTrue(result, "Expected CSV file to be processed successfully."); + } + + @Test + void testJSONFileProcessing() throws IOException, SEPException { + fileHandler = new FileHandler(JSON_FILE_PATH.toString(), parser); + + boolean result = fileHandler.hasProcessFileSuccessfully(); + + assertTrue(result, "Expected JSON file to be processed successfully."); + } + + @Test + void testTXTFileProcessing() throws IOException, SEPException { + fileHandler = new FileHandler(TXT_FILE_PATH.toString(), parser); + + boolean result = fileHandler.hasProcessFileSuccessfully(); + + assertTrue(result, "Expected TXT file to be processed successfully."); + } + + @Test + void hasProcessFileSuccessfully_missingFile_fileNotFound() throws IOException { + fileHandler = new FileHandler("nonexistent.glb", parser); + + SEPException exception = assertThrows(SEPEmptyException.class, fileHandler::hasProcessFileSuccessfully); + assertEquals("This file don't exist...Don't make me go through your folder for nothing leh :(.\n" + + "Process Outcome: No data is loaded. You can continue using the program.", exception.getMessage()); + } + + @Test + void hasProcessFileSuccessfully_blankFile_emptyFileDetected() throws IOException, SEPException { + File emptyFile = Files.createTempFile("empty", ".csv").toFile(); + emptyFile.deleteOnExit(); + + fileHandler = new FileHandler(emptyFile.getAbsolutePath(), parser); + + SEPException exception = assertThrows(SEPEmptyException.class, fileHandler::hasProcessFileSuccessfully); + assertEquals("Nothing to read from the file. Please ensure there is content next time.", + exception.getMessage()); + } + + @Test + void hasProcessFileSuccessfully_invalidFileType_unknownFileDetected() throws IOException { + fileHandler = new FileHandler("test.xml", parser); + + SEPException exception = assertThrows(SEPUnknownException.class, fileHandler::hasProcessFileSuccessfully); + assertEquals("Unknown/Unsupported file type detected! " + + "\nYou can continue to use the program.", exception.getMessage()); + } + + + @Test + void saveAllocationResults_saveInCSV_resultsSavedSuccessfully() throws IOException, SEPException { + Path p = Paths.get("data/allocation_results.csv"); + fileHandler.saveAllocationResults(this.results, "csv"); + assertTrue(Files.exists(p)); + assertTrue(Files.size(p) != 0); + } + + + @Test + void saveAllocationResults_saveInJSON_resultsSavedSuccessfully() throws IOException { + Path p = Paths.get("data/allocation_results.json"); + this.fileHandler.saveAllocationResults(this.results, "json"); + assertTrue(Files.exists(p)); + assertTrue(Files.size(p) != 0); + } + + + @Test + void saveAllocationResults_saveInTXT_resultsSaveSuccessfully() throws IOException { + Path p = Paths.get("data/allocation_results.txt"); + this.fileHandler.saveAllocationResults(this.results, "txt"); + assertTrue(Files.exists(p)); + assertTrue(Files.size(p) != 0); + } +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/findoursep/FindOurSEPTest.java similarity index 80% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/findoursep/FindOurSEPTest.java index 2dda5fd651..d9c5ce9769 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/findoursep/FindOurSEPTest.java @@ -1,10 +1,10 @@ -package seedu.duke; +package findoursep; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -class DukeTest { +class FindOurSEPTest { @Test public void sampleTest() { assertTrue(true); diff --git a/src/test/java/parser/ParserTest.java b/src/test/java/parser/ParserTest.java new file mode 100644 index 0000000000..f8c4f9f292 --- /dev/null +++ b/src/test/java/parser/ParserTest.java @@ -0,0 +1,294 @@ +package parser; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import student.Student; +import studentlist.StudentList; +import ui.Messages; +import ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static ui.UI.HORIZONTAL_LINE; + +public class ParserTest { + private UI ui; + private StudentList studentList; + private Parser parser; + + @BeforeEach + void setUp() { + ui = new UI(); + studentList = new StudentList(this.ui); + this.parser = new Parser(this.studentList, this.ui); + } + + @Test + public void testAddCommand() { + // Simulate user input for 'add' command + String input = "add id/A1234567I gpa/5.0 p/{13,61,43}"; + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Assert increment of student list + assertEquals(1, this.studentList.getNumStudents()); + } + + @Test + public void testDeleteCommand() { + // Adds a student to the list + parser.parseInput("add id/A1234567I gpa/5.0 p/{13,61,43}"); + + // Simulate user input for 'delete' command + String input = "delete A1234567I"; + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Assert decrement of student list + assertEquals(0, this.studentList.getNumStudents()); + } + + @Test + public void testExitCommand() { + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Simulate user input for 'exit' command + String input = "exit"; + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser stops execution + assertFalse(result); + + // Verify the expected output + String expectedOutput = HORIZONTAL_LINE + "\n" + "Do you want to save your results?" + "\n" + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + @Test + public void testStatAvgCommand() { + // Simulate user input for 'stats -avggpa' command + parser.parseInput("add id/A1234567I gpa/4.0 p/{36,61,43}"); + parser.parseInput("add id/A1234567J gpa/3.5 p/{36,61,43}"); + parser.parseInput("add id/A1234567K gpa/4.0 p/{36,61,43}"); + parser.parseInput("allocate"); + String input = "stats -avggpa 36"; + + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Verify the expected output + String expectedOutput = HORIZONTAL_LINE + + "\n" + + "The average GPA for university index 36 (The University of Hong Kong) is: 4.00" + + "\n" + + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + @Test + public void testStatMinCommand() { + // Simulate user input for 'stats -avggpa' command + parser.parseInput("add id/A1234567I gpa/5.0 p/{37,61,43}"); + parser.parseInput("add id/A1234567J gpa/3.0 p/{37,61,43}"); + parser.parseInput("add id/A1234567K gpa/1.0 p/{37,61,43}"); + parser.parseInput("allocate"); + String input = "stats -mingpa 37"; + + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Verify the expected output + String expectedOutput = HORIZONTAL_LINE + + "\n" + + "The minimum GPA for university index 37 (The Chinese Uni of Hong Kong) is: 5.00" + + "\n" + + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + @Test + public void testViewQuotaCommand() { + // Simulate user input for 'viewQuota' command + String input = "viewQuota 36"; + + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Verify the expected output + String expectedOutput = HORIZONTAL_LINE + + "\n" + + " Index: 36" + + "\n" + + " Name: The University of Hong Kong" + + "\n" + + " Quota: 2" + + "\n" + + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + @Test + public void revertCommand_populatedStudentList_success() { + // Simulate user input for allocating a list of students + parser.parseInput("add id/A1234567I gpa/5.0 p/{38,61,43}"); + parser.parseInput("add id/A2468101J gpa/4.9 p/{32,50,8}"); + parser.parseInput("add id/A3691215K gpa/4.8 p/{29,61,17}"); + parser.parseInput("allocate"); + + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // After allocate command, ensure students are marked as allocated + for (Student student : studentList.getList()) { + assertTrue(student.getSuccessfullyAllocated()); + assertNotEquals(-1, student.getAllocatedUniversity()); + } + + // Then call revert command and verify reversion works + parser.parseInput("revert"); + + // Now check the reverted state + for (Student student : studentList.getList()) { + assertFalse(student.getSuccessfullyAllocated()); + assertEquals(-1, student.getAllocatedUniversity()); + } + + // Verify expected output + String expectedOutput = HORIZONTAL_LINE + "\n" + Messages.REVERT_COMPLETE + "\n" + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + @Test + public void revertCommand_unallocatedStudentList_throwsException() { + // Simulate user input for 'revert' command + String input = "revert"; + + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Verify that exception is thrown and error message is printed + String expectedOutput = HORIZONTAL_LINE + + "\nAllocation incomplete, try running allocate before reverting! :>\n" + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + + @Test + public void testHelpCommand() { + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Simulate user input for 'help' command + String input = "help"; + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Verify the expected output + String expectedOutput = HORIZONTAL_LINE + "\n" + Messages.HELP.toString() + "\n" + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } + + @Test + public void testUnknownCommand() { + // Set up the output stream to capture console output + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + // Simulate user input for 'unknown' command + String input = "8^jodja/!!! /add"; + + // Run the parser + boolean result = parser.parseInput(input); + + // Assert that the parser continues execution + assertTrue(result); + + // Verify the expected output + String expectedOutput = HORIZONTAL_LINE + "\n" + "Invalid command" + "\n" + HORIZONTAL_LINE; + assertEquals(expectedOutput,outContent.toString().trim()); + + // Reset the original System.out + System.setOut(originalOut); + } +} diff --git a/src/test/java/student/StudentTest.java b/src/test/java/student/StudentTest.java new file mode 100644 index 0000000000..93de2181f8 --- /dev/null +++ b/src/test/java/student/StudentTest.java @@ -0,0 +1,61 @@ +package student; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StudentTest { + private Student student; + + @BeforeEach + public void setUp(){ + student = new Student("A0271560J",5.0f,new ArrayList<>(Arrays.asList(1, 2, 3))); + } + + @Test + public void testConstruction() { + // Test constructor with valid inputs + assertNotNull(student); + assertEquals("A0271560J", student.getId()); + assertEquals(5.0f, student.getGpa()); + assertEquals(Arrays.asList(1, 2, 3), student.getUniPreferences()); + } + + @Test + public void testCopyConstructor() { + // Create a copy of the original student + Student copiedStudent = new Student(student); + + // Test if the copy has the same data but is a different object + assertNotNull(copiedStudent); + assertEquals(student.getId(), copiedStudent.getId()); + assertEquals(student.getGpa(), copiedStudent.getGpa()); + assertEquals(student.getUniPreferences(), copiedStudent.getUniPreferences()); + assertNotSame(student.getUniPreferences(), copiedStudent.getUniPreferences()); // Deep copy check + } + + @Test + public void testSetSuccessfullyAllocated() { + // Test updating allocation status + student.setSuccessfullyAllocated(true); + assertTrue(student.getSuccessfullyAllocated()); + + student.setSuccessfullyAllocated(false); + assertFalse(student.getSuccessfullyAllocated()); + } + + @Test + public void testSetAllocatedUniversity() { + // Test setting allocated university + student.setAllocatedUniversity(5); + assertEquals(5, student.getAllocatedUniversity()); + } +} diff --git a/src/test/java/studentlist/StudentListTest.java b/src/test/java/studentlist/StudentListTest.java new file mode 100644 index 0000000000..9acecbe67c --- /dev/null +++ b/src/test/java/studentlist/StudentListTest.java @@ -0,0 +1,218 @@ +package studentlist; + +import exceptions.SEPDuplicateException; +import exceptions.SEPException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import student.Student; +import ui.UI; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class StudentListTest { + private StudentList studentList; + private ArrayList uniPreferences = new ArrayList<>(Arrays.asList(1,2,3)); + private Student bob = new Student("A1234567K", (float)4.6, uniPreferences); + private Student student1 = new Student("A1234567B", 3.2f, new ArrayList<>()); + private Student student2 = new Student("A2345678C", 4.8f, new ArrayList<>()); + private Student student3 = new Student("A3456789D", 3.9f, new ArrayList<>()); + + @BeforeEach + public void setUp() { + this.studentList = new StudentList(new UI()); + } + + @Test + public void addStudent_duplicateStudent_exceptionThrown() { + assertDoesNotThrow(() -> this.studentList.addStudent(this.bob)); + assertThrows(SEPDuplicateException.class, () -> this.studentList.addStudent(this.bob)); + } + + @Test + public void testAddStudent() { + assertDoesNotThrow(() -> this.studentList.addStudent(this.bob)); + + assertEquals(1, this.studentList.getNumStudents()); + + assertEquals("A1234567K", studentList.getList().get(0).getId()); + } + + @Test + public void testDeleteStudent() { + assertDoesNotThrow(() -> this.studentList.addStudent(this.bob)); + assertEquals(1, studentList.getNumStudents()); + + assertDoesNotThrow(() -> this.studentList.deleteStudent("delete A1234567K")); + assertEquals(0, studentList.getNumStudents()); + } + + @Test + public void testSortStudentsByAscendingGPA() { + assertDoesNotThrow(() -> this.studentList.addStudent(student1)); + assertDoesNotThrow(() -> this.studentList.addStudent(student2)); + assertDoesNotThrow(() -> this.studentList.addStudent(student3)); + + studentList.sortStudentsByAscendingGPA(studentList.getList()); + + assertEquals("A2345678C", studentList.getList().get(2).getId()); // Highest GPA first + assertEquals("A1234567B", studentList.getList().get(0).getId()); // Lowest GPA last + } + + @Test + public void testSortStudentsByDescendingGPA() { + assertDoesNotThrow(() -> this.studentList.addStudent(student1)); + assertDoesNotThrow(() -> this.studentList.addStudent(student2)); + assertDoesNotThrow(() -> this.studentList.addStudent(student3)); + + studentList.sortStudentsByDescendingGPA(studentList.getList()); + + assertEquals("A2345678C", studentList.getList().get(0).getId()); // Highest GPA first + assertEquals("A1234567B", studentList.getList().get(2).getId()); // Lowest GPA last + } + + @Test + public void testSortStudentsByAscendingId() { + assertDoesNotThrow(() -> this.studentList.addStudent(student1)); + assertDoesNotThrow(() -> this.studentList.addStudent(student2)); + assertDoesNotThrow(() -> this.studentList.addStudent(student3)); + + studentList.sortStudentsByAscendingId(studentList.getList()); + + assertEquals("A1234567B", studentList.getList().get(0).getId()); // Lexicographical order + assertEquals("A3456789D", studentList.getList().get(2).getId()); + } + + @Test + public void testSortStudentsByDescendingId() { + assertDoesNotThrow(() -> this.studentList.addStudent(student1)); + assertDoesNotThrow(() -> this.studentList.addStudent(student2)); + assertDoesNotThrow(() -> this.studentList.addStudent(student3)); + + studentList.sortStudentsByDescendingId(studentList.getList()); + + assertEquals("A1234567B", studentList.getList().get(2).getId()); // Lexicographical order + assertEquals("A3456789D", studentList.getList().get(0).getId()); + } + + @Test + public void testMakeStudent() { + String input = "add id/A1234567B gpa/4.5 p/{1,2,3}"; + Student student = assertDoesNotThrow(() -> this.studentList.makeStudent(input)); + + assertNotNull(student); + assertEquals("A1234567B", student.getId()); + assertEquals(4.5f, student.getGpa()); + assertEquals(3, student.getUniPreferences().size()); + } + + @Test + public void makeStudent_idWithSpaces_studentCreated() { + String input = "add id/A55 345 67B gpa/3.99 p/{1,2,3}"; //ID contains spaces, but is handled + Student student = assertDoesNotThrow(() -> this.studentList.makeStudent(input)); + + assertNotNull(student); + assertEquals("A5534567B", student.getId()); + assertEquals(3.99f, student.getGpa()); + assertEquals(3, student.getUniPreferences().size()); + } + + @Test + public void makeStudent_invalidAddOrder_exceptionThrown() { + String input = "add id/A1234567B p/{1,2,3} gpa/4.5"; //Invalid add order + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_invalidAddFormat_exceptionThrown() { + String input = "add id(A1234567B) gpa(4.5) p({1,2,3})"; //Invalid add format + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_invalidIDFormat_exceptionThrown() { + String input = "add id/1234567A gpa/4.5 p/{1,2,3}"; // Invalid ID format (missing letter at start) + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_invalidGPAFormat_exceptionThrown() { + String input = "add id/A1234567B gpa/invalid p/{1,2,3}"; // Invalid GPA format + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_invalidGPADecimalPlaces_exceptionThrown() { + String input = "add id/A1234567B gpa/3.999 p/{1,2,3}"; // Invalid GPA, more than 2 decimal places + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_invalidPreferencesFormat_exceptionThrown() { + String input = "add id/A1234567B gpa/4.5 p/1,2,3"; // Missing curly braces around preferences + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_invalidGPARange_exceptionThrown() { + String input = "add id/A1234567B gpa/6.0 p/{1,2,3}"; // GPA out of range + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void makeStudent_preferencesOutOfRange_exceptionThrown() { + String input = "add id/A1234567B gpa/4.5 p/{0,4,95}"; // Preferences outside valid range + + assertThrows(SEPException.class, () -> this.studentList.makeStudent(input)); + } + + @Test + public void deleteStudent_nonExistentStudent_exceptionThrown() { + assertDoesNotThrow(() -> this.studentList.addStudent(this.bob)); + assertEquals(1, this.studentList.getNumStudents()); + + // Test deleting a student that doesn't exist + assertThrows(SEPException.class, () -> this.studentList.deleteStudent("delete B7654321C")); + } + + @Test + public void findStudent_nonExistentStudent_exceptionThrown() { + assertDoesNotThrow(() -> this.studentList.addStudent(this.bob)); + assertEquals(1, this.studentList.getNumStudents()); + + // Test finding a student that doesn't exist + assertThrows(SEPException.class, () -> this.studentList.findStudent("find list B7654321C")); + } + + @Test + public void findStudent_invalidFindFormat_exceptionThrown() { + String input = "find A1234567I"; // Invalid Find format (Missing 'list' or 'report') + + assertThrows(SEPException.class, () -> this.studentList.findStudent(input)); + } + + @Test + public void filterStudent_nonExistentStudent_exceptionThrown() { + // Test filtering an empty student list + assertThrows(SEPException.class, () -> this.studentList.findStudent("filter list allocated")); + } + + @Test + public void filterStudent_invalidFindFormat_exceptionThrown() { + String input = "filter list allocate"; // Invalid Filter format ('allocated not allocate') + assertThrows(SEPException.class, () -> this.studentList.findStudent(input)); + } +} diff --git a/src/test/java/ui/UITest.java b/src/test/java/ui/UITest.java new file mode 100644 index 0000000000..479adf1606 --- /dev/null +++ b/src/test/java/ui/UITest.java @@ -0,0 +1,103 @@ +package ui; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import exceptions.SEPEmptyException; +import student.Student; + +public class UITest { + private UI ui; + private ByteArrayOutputStream outContent; + + @BeforeEach + void setUp() { + // Redirect system output to capture it for tests + outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + + // Initialize UI with default constructor + ui = new UI(); + } + + @Test + void printResponse() { + String message = "Hello, World!"; + ui.printResponse(message); + String expected = "-".repeat(80) + "\n" + message + "\n" + "-".repeat(80) + "\n"; + assertEquals(expected.trim(), outContent.toString().trim()); + } + + @Test + void printResponse_emptyInput_null() { + ui.printResponse(null); + String expected = "-".repeat(80) + "\nnull\n" + "-".repeat(80) + "\n"; + assertEquals(expected.trim(), outContent.toString().trim()); + } + + @Test + void getUserInput_validInput() { + // Simulate user input + String simulatedInput = "test input\n"; + ByteArrayInputStream inContent = new ByteArrayInputStream(simulatedInput.getBytes()); + System.setIn(inContent); + + UI testUi = new UI(); + assertEquals("test input", testUi.getUserInput()); + } + + @Test + void getUserInput_emptyInput_error() { + // Simulate erroneous input + ByteArrayInputStream inContent = new ByteArrayInputStream(new byte[0]); + System.setIn(inContent); + + assertEquals("", ui.getUserInput()); + assertTrue(outContent.toString().contains("Error reading input")); + } + + @Test + void sayHi() { + ui.sayHi(); + assertTrue(outContent.toString().contains(Messages.WELCOME.toString())); + } + + @Test + void cleanupAndExit() { + ui.cleanupAndExit(); + assertTrue(outContent.toString().contains("Do you want to save your results?")); + } + + @Test + void printStudentList_emptyList_throwEmpty() { + ArrayList studentList = new ArrayList<>(); + assertThrows(SEPEmptyException.class, () -> ui.printStudentList(studentList)); + } + + @Test + void generateReport_emptyList_throwEmpty() { + ArrayList studentList = new ArrayList<>(); + assertThrows(SEPEmptyException.class, () -> ui.generateReport(studentList)); + } + + @Test + void printAllocatingMessage() throws InterruptedException { + Thread allocationThread = new Thread(() -> ui.printAllocatingMessage()); + allocationThread.start(); + + Thread.sleep(2000); // Let the loading message print for a while + allocationThread.interrupt(); // Interrupt the thread to stop the loading + + allocationThread.join(); // Ensure the thread has finished + + assertTrue(outContent.toString().contains(Messages.ALLOCATE_COMPLETE.toString())); + } +} diff --git a/src/test/java/university/UniversityTest.java b/src/test/java/university/UniversityTest.java new file mode 100644 index 0000000000..6effb82b0d --- /dev/null +++ b/src/test/java/university/UniversityTest.java @@ -0,0 +1,36 @@ +package university; + +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; + +public class UniversityTest { + private University uni; + + @BeforeEach + public void setUp(){ + uni = new University("National University of Singapore","NUS", 5); + } + + @Test + public void testConstruction(){ + assertNotNull(uni); + assertEquals("National University of Singapore", uni.getFullName()); + assertEquals("NUS", uni.getAcronym()); + assertEquals(5, uni.getSpotsLeft()); + } + + @Test + public void testRemoveASpot(){ + uni.removeASpot(); + assertEquals(4, uni.getSpotsLeft()); + } + + @Test + public void testToString(){ + assertEquals("National University of Singapore (NUS)", uni.toString()); + } + +} diff --git a/test.csv b/test.csv new file mode 100644 index 0000000000..60c72ae5c0 --- /dev/null +++ b/test.csv @@ -0,0 +1,22 @@ +ID,GPA,PREFERENCES +A1234567J,4.5,"{1,2,3}" +A1234567I,5,"{12,61,43}" +A7654321K,3.8,"{2,3,1}" +A1357913L,4,"{3,1,2}" +A9876543M,3.2,"{12,45,67}" +A2468101N,4.7,"{5,23,49}" +A1122334O,2.9,"{78,56,23}" +A3344556P,4,"{9,34,21}" +A5566778Q,3.5,"{41,12,89}" +A6677889R,2.4,"{30,77,18}" +A7788990S,3.9,"{60,15,2}" +A9988776T,4.3,"{88,55,13}" +A4455667U,3.1,"{27,44,91}" +A2233445V,5,"{20,1,50}" +A1123581W,4.8,"{33,9,76}" +A3141592X,3.6,"{65,22,48}" +A5454545C,3,"{17,58,36}" +A4343434B,4.6,"{6,35,40}" +A3232323A,3.4,"{28,19,70}" +A2121212Z,4.2,"{7,50,29}" +A0101010Y,2.7,"{14,63,5}" diff --git a/test.json b/test.json new file mode 100644 index 0000000000..6b4a1446a2 --- /dev/null +++ b/test.json @@ -0,0 +1,104 @@ +{ + "students": [ + { + "ID": "A1234567J", + "GPA": "4.5", + "PREFERENCES": "{1,2,3}" + }, + { + "ID": "A7654321K", + "GPA": "3.8", + "PREFERENCES": "{2,3,1}" + }, + { + "ID": "A1357913L", + "GPA": "5.0", + "PREFERENCES": "{3,1,2}" + }, + { + "ID": "A9876543M", + "GPA": "3.2", + "PREFERENCES": "{12,45,67}" + }, + { + "ID": "A2468101N", + "GPA": "4.7", + "PREFERENCES": "{5,23,49}" + }, + { + "ID": "A1122334O", + "GPA": "2.9", + "PREFERENCES": "{78,56,23}" + }, + { + "ID": "A3344556P", + "GPA": "4.0", + "PREFERENCES": "{9,34,21}" + }, + { + "ID": "A5566778Q", + "GPA": "3.5", + "PREFERENCES": "{41,12,89}" + }, + { + "ID": "A6677889R", + "GPA": "2.4", + "PREFERENCES": "{30,77,18}" + }, + { + "ID": "A7788990S", + "GPA": "3.9", + "PREFERENCES": "{60,15,2}" + }, + { + "ID": "A9988776T", + "GPA": "4.3", + "PREFERENCES": "{88,55,13}" + }, + { + "ID": "A4455667U", + "GPA": "3.1", + "PREFERENCES": "{27,44,91}" + }, + { + "ID": "A2233445V", + "GPA": "5.0", + "PREFERENCES": "{20,1,50}" + }, + { + "ID": "A1123581W", + "GPA": "4.8", + "PREFERENCES": "{33,9,76}" + }, + { + "ID": "A3141592X", + "GPA": "3.6", + "PREFERENCES": "{65,22,48}" + }, + { + "ID": "A0101010Y", + "GPA": "2.7", + "PREFERENCES": "{14,63,5}" + }, + { + "ID": "A2121212Z", + "GPA": "4.2", + "PREFERENCES": "{7,50,29}" + }, + { + "ID": "A3232323A", + "GPA": "3.4", + "PREFERENCES": "{28,19,70}" + }, + { + "ID": "A4343434B", + "GPA": "4.6", + "PREFERENCES": "{6,35,40}" + }, + { + "ID": "A5454545C", + "GPA": "3.0", + "PREFERENCES": "{17,58,36}" + } + ] +} diff --git a/test.txt b/test.txt new file mode 100644 index 0000000000..aa80c00036 --- /dev/null +++ b/test.txt @@ -0,0 +1,100 @@ +id/A3870193G, gpa/3.35, p/{15,8,28} +id/A2458965N, gpa/4.56, p/{77,36,1} +id/A6087297T, gpa/2.05, p/{53,89,37} +id/A6331949D, gpa/2.29, p/{19,89,13} +id/A2305667N, gpa/1.28, p/{71,72,80} +id/A8019526C, gpa/2.64, p/{29,34,18} +id/A8342146T, gpa/1.9, p/{20,73,49} +id/A1619446K, gpa/1.56, p/{79,91,51} +id/A1181485V, gpa/3.06, p/{62,83,2} +id/A1655134W, gpa/0.01, p/{39,71,36} +id/A2324942U, gpa/0.29, p/{4,45,26} +id/A7313759F, gpa/1.72, p/{69,78,82} +id/A0159579Y, gpa/3.09, p/{31,12,73} +id/A2640317U, gpa/4.89, p/{1,19,10} +id/A7701116D, gpa/1.84, p/{87,4,91} +id/A3914254H, gpa/2.25, p/{42,5,70} +id/A2997456B, gpa/4.05, p/{82,68,44} +id/A4065348Q, gpa/1.05, p/{81,74,5} +id/A3561970H, gpa/1.49, p/{89,80,65} +id/A6578239L, gpa/2.28, p/{82,43,62} +id/A5431876L, gpa/3.81, p/{64,54,38} +id/A4456745D, gpa/2.83, p/{37,85,51} +id/A9224708Q, gpa/3.53, p/{76,69,43} +id/A6526561K, gpa/3.27, p/{39,4,31} +id/A7901765P, gpa/0.12, p/{64,17,77} +id/A9833081H, gpa/2.83, p/{47,73,41} +id/A3976362V, gpa/2.36, p/{50,27,28} +id/A7969847Q, gpa/0.95, p/{71,30,30} +id/A4323764W, gpa/3.98, p/{30,29,28} +id/A7161895Y, gpa/2.05, p/{48,22,51} +id/A9897046A, gpa/0.15, p/{25,63,3} +id/A2447942I, gpa/4.23, p/{85,73,83} +id/A7546548U, gpa/3.67, p/{74,6,76} +id/A5203406X, gpa/2.32, p/{66,62,49} +id/A5533249K, gpa/1.98, p/{66,40,31} +id/A4251699G, gpa/4.03, p/{22,53,18} +id/A2944773R, gpa/2.28, p/{11,4,58} +id/A9634285A, gpa/2.22, p/{75,7,74} +id/A5341705T, gpa/0.05, p/{85,19,73} +id/A9110093T, gpa/4.87, p/{15,37,41} +id/A2303575D, gpa/3.73, p/{56,13,84} +id/A0408871Z, gpa/4.37, p/{39,22,75} +id/A0759637I, gpa/1.07, p/{84,71,79} +id/A7270360J, gpa/3.1, p/{91,35,12} +id/A8558963X, gpa/2.97, p/{92,15,3} +id/A2905844X, gpa/2.45, p/{1,23,77} +id/A2353965E, gpa/4.05, p/{69,5,12} +id/A4332852R, gpa/0.0, p/{31,58,20} +id/A3314873Q, gpa/0.98, p/{56,68,41} +id/A7256197J, gpa/2.54, p/{4,73,5} +id/A6049654U, gpa/0.12, p/{48,81,10} +id/A5722022U, gpa/4.24, p/{72,81,64} +id/A1797730K, gpa/3.68, p/{12,65,15} +id/A1025795K, gpa/1.67, p/{5,36,59} +id/A5005825T, gpa/0.59, p/{43,54,78} +id/A2572259R, gpa/0.42, p/{7,89,70} +id/A2013643N, gpa/0.8, p/{79,31,53} +id/A0696259H, gpa/4.66, p/{72,22,9} +id/A5319616J, gpa/0.62, p/{23,59,26} +id/A6647433K, gpa/0.07, p/{40,78,42} +id/A6611231X, gpa/4.83, p/{64,58,21} +id/A7303870A, gpa/4.06, p/{47,79,50} +id/A4534614H, gpa/1.21, p/{72,6,9} +id/A3102704C, gpa/4.86, p/{29,40,85} +id/A4405166E, gpa/0.18, p/{21,26,27} +id/A1016710N, gpa/4.08, p/{12,71,43} +id/A9726233U, gpa/0.86, p/{22,90,26} +id/A1474546D, gpa/0.98, p/{24,44,72} +id/A7620251P, gpa/1.55, p/{17,60,61} +id/A5957475P, gpa/2.1, p/{65,30,75} +id/A8007258V, gpa/4.88, p/{21,31,57} +id/A7730126G, gpa/4.61, p/{91,24,86} +id/A8834942D, gpa/0.02, p/{15,78,3} +id/A8016871B, gpa/4.87, p/{88,84,77} +id/A5538693M, gpa/3.25, p/{15,62,55} +id/A7041662E, gpa/2.34, p/{61,72,4} +id/A9938747Q, gpa/3.79, p/{62,85,27} +id/A0037588O, gpa/2.79, p/{58,23,87} +id/A5134074E, gpa/3.04, p/{13,58,91} +id/A4940716K, gpa/2.87, p/{79,86,14} +id/A8117435U, gpa/3.58, p/{28,7,21} +id/A8363748K, gpa/4.92, p/{83,63,68} +id/A0311910W, gpa/4.28, p/{67,60,18} +id/A1752452L, gpa/1.88, p/{56,51,78} +id/A3809382Q, gpa/3.15, p/{56,42,60} +id/A0352390J, gpa/4.94, p/{9,72,86} +id/A8549976W, gpa/4.99, p/{45,1,24} +id/A4940283F, gpa/0.71, p/{56,67,90} +id/A0588252S, gpa/2.01, p/{76,88,92} +id/A1639885X, gpa/0.03, p/{87,86,91} +id/A9506588W, gpa/4.3, p/{1,38,17} +id/A0954820C, gpa/0.15, p/{47,84,10} +id/A7914132C, gpa/2.04, p/{74,58,75} +id/A1130035D, gpa/4.3, p/{54,92,11} +id/A5268849N, gpa/4.75, p/{82,15,58} +id/A7082580A, gpa/2.43, p/{71,53,6} +id/A0134167Z, gpa/4.35, p/{82,85,84} +id/A5936071V, gpa/2.08, p/{36,73,86} +id/A8658492K, gpa/2.2, p/{63,81,72} +id/A8860220E, gpa/2.78, p/{49,8,18} diff --git a/test.xml b/test.xml new file mode 100644 index 0000000000..c1c2ab995d --- /dev/null +++ b/test.xml @@ -0,0 +1,23 @@ + + + + Project Alpha + Developing a cloud-based storage solution. + + Java + AWS + Docker + + Jane Cena + + + Project Beta + Creating a microservices architecture for an e-commerce platform. + + Python + Kubernetes + Kafka + + Bob The Builder + + diff --git a/test2.csv b/test2.csv new file mode 100644 index 0000000000..f8094bcb60 --- /dev/null +++ b/test2.csv @@ -0,0 +1,5 @@ +ID, GPA, PREFERENCES,, +A1234567J,4.5," {1,2,3}" +A1234567I,5," {12,61,43}", ] +A7654321K,3.8," {2,3,1}" +A1357913L,4," {3,1,2}" diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..c9b310fb48 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,44 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +-------------------------------------------------------------------------------- +Good day to you! +Before we begin, would you like to: +1. Manually input students data +2. Upload a file (.csv, .txt, .json) +Please choose 1 or 2 or exit :) +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Hi! Welcome to FindOurSEP! Enter help for the list of commands. +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Added A1234567J successfully. +There are 1 student(s) in the list. +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Added A2468101J successfully. +There are 2 student(s) in the list. +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Added A3691215J successfully. +There are 3 student(s) in the list. +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Here is the list: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Student β”‚ GPA β”‚ Preference Rankings β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ A1234567J β”‚ 5.0 β”‚ 13,63,21 β”‚ +β”‚ A2468101J β”‚ 5.0 β”‚ 88 β”‚ +β”‚ A3691215J β”‚ 5.0 β”‚ 19,66 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Removed student, 2 student(s) left +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Removed student, 1 student(s) left +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Do you want to save your results? +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +Adios, amigo! +-------------------------------------------------------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..2bf5d87fe5 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,9 @@ -James Gosling \ No newline at end of file +1 +add id/A1234567J gpa/5.00 p/{13,63,21} +add id/A2468101J gpa/5.00 p/{88} +add id/A3691215J gpa/5.00 p/{19,66} +list +delete A2468101J +delete A3691215J +exit +no