diff --git a/.classpath b/.classpath new file mode 100644 index 0000000000..9f9cc9f128 --- /dev/null +++ b/.classpath @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/.gitbook/assets/AddCommand.png b/.gitbook/assets/AddCommand.png new file mode 100644 index 0000000000..22032b8704 Binary files /dev/null and b/.gitbook/assets/AddCommand.png differ diff --git a/.gitbook/assets/AddCommandMessage.png b/.gitbook/assets/AddCommandMessage.png new file mode 100644 index 0000000000..e163dc5344 Binary files /dev/null and b/.gitbook/assets/AddCommandMessage.png differ diff --git a/.gitbook/assets/AddCommandStart.png b/.gitbook/assets/AddCommandStart.png new file mode 100644 index 0000000000..fd5926b781 Binary files /dev/null and b/.gitbook/assets/AddCommandStart.png differ diff --git a/.gitbook/assets/AddProfile.png b/.gitbook/assets/AddProfile.png new file mode 100644 index 0000000000..1682efb4cb Binary files /dev/null and b/.gitbook/assets/AddProfile.png differ diff --git a/.gitbook/assets/Architecture.png b/.gitbook/assets/Architecture.png new file mode 100644 index 0000000000..499cfb768e Binary files /dev/null and b/.gitbook/assets/Architecture.png differ diff --git a/.gitbook/assets/CreateDietSession.png b/.gitbook/assets/CreateDietSession.png new file mode 100644 index 0000000000..e0b246b576 Binary files /dev/null and b/.gitbook/assets/CreateDietSession.png differ diff --git a/.gitbook/assets/DeleteProfile.png b/.gitbook/assets/DeleteProfile.png new file mode 100644 index 0000000000..bd7f0970e6 Binary files /dev/null and b/.gitbook/assets/DeleteProfile.png differ diff --git a/.gitbook/assets/DeleteWS.png b/.gitbook/assets/DeleteWS.png new file mode 100644 index 0000000000..e1d8097007 Binary files /dev/null and b/.gitbook/assets/DeleteWS.png differ diff --git a/.gitbook/assets/DietSessionClear.png b/.gitbook/assets/DietSessionClear.png new file mode 100644 index 0000000000..95edff9cad Binary files /dev/null and b/.gitbook/assets/DietSessionClear.png differ diff --git a/.gitbook/assets/DietSessionDelete.png b/.gitbook/assets/DietSessionDelete.png new file mode 100644 index 0000000000..69f9c5dd64 Binary files /dev/null and b/.gitbook/assets/DietSessionDelete.png differ diff --git a/.gitbook/assets/DietSessionEdit.png b/.gitbook/assets/DietSessionEdit.png new file mode 100644 index 0000000000..52c7d2db85 Binary files /dev/null and b/.gitbook/assets/DietSessionEdit.png differ diff --git a/.gitbook/assets/DietSessionList.png b/.gitbook/assets/DietSessionList.png new file mode 100644 index 0000000000..8e46f9dff6 Binary files /dev/null and b/.gitbook/assets/DietSessionList.png differ diff --git a/.gitbook/assets/EditProfile.png b/.gitbook/assets/EditProfile.png new file mode 100644 index 0000000000..0355a8df1f Binary files /dev/null and b/.gitbook/assets/EditProfile.png differ diff --git a/.gitbook/assets/EditWS.png b/.gitbook/assets/EditWS.png new file mode 100644 index 0000000000..3659d2ff55 Binary files /dev/null and b/.gitbook/assets/EditWS.png differ diff --git a/.gitbook/assets/FoodItemAdd.png b/.gitbook/assets/FoodItemAdd.png new file mode 100644 index 0000000000..5eacafe70f Binary files /dev/null and b/.gitbook/assets/FoodItemAdd.png differ diff --git a/.gitbook/assets/FoodItemClear.png b/.gitbook/assets/FoodItemClear.png new file mode 100644 index 0000000000..fd73dd3de7 Binary files /dev/null and b/.gitbook/assets/FoodItemClear.png differ diff --git a/.gitbook/assets/FoodItemDelete.png b/.gitbook/assets/FoodItemDelete.png new file mode 100644 index 0000000000..582f826bb2 Binary files /dev/null and b/.gitbook/assets/FoodItemDelete.png differ diff --git a/.gitbook/assets/FoodItemList.png b/.gitbook/assets/FoodItemList.png new file mode 100644 index 0000000000..6246c70b1b Binary files /dev/null and b/.gitbook/assets/FoodItemList.png differ diff --git a/.gitbook/assets/ListWS.png b/.gitbook/assets/ListWS.png new file mode 100644 index 0000000000..2f4ef8767e Binary files /dev/null and b/.gitbook/assets/ListWS.png differ diff --git a/.gitbook/assets/Logic.png b/.gitbook/assets/Logic.png new file mode 100644 index 0000000000..d79e342ea4 Binary files /dev/null and b/.gitbook/assets/Logic.png differ diff --git a/.gitbook/assets/MainMenu.png b/.gitbook/assets/MainMenu.png new file mode 100644 index 0000000000..b7bccbf1d9 Binary files /dev/null and b/.gitbook/assets/MainMenu.png differ diff --git a/.gitbook/assets/NewWS.png b/.gitbook/assets/NewWS.png new file mode 100644 index 0000000000..5a52853281 Binary files /dev/null and b/.gitbook/assets/NewWS.png differ diff --git a/.gitbook/assets/ParseInput (1).png b/.gitbook/assets/ParseInput (1).png new file mode 100644 index 0000000000..8e4c846a3f Binary files /dev/null and b/.gitbook/assets/ParseInput (1).png differ diff --git a/.gitbook/assets/ParseInput.png b/.gitbook/assets/ParseInput.png new file mode 100644 index 0000000000..68933d2135 Binary files /dev/null and b/.gitbook/assets/ParseInput.png differ diff --git a/.gitbook/assets/ParseInputWorkoutSession.png b/.gitbook/assets/ParseInputWorkoutSession.png new file mode 100644 index 0000000000..cc6a82e5c3 Binary files /dev/null and b/.gitbook/assets/ParseInputWorkoutSession.png differ diff --git a/.gitbook/assets/SearchDietSession.png b/.gitbook/assets/SearchDietSession.png new file mode 100644 index 0000000000..6b1899cdc6 Binary files /dev/null and b/.gitbook/assets/SearchDietSession.png differ diff --git a/.gitbook/assets/SearchWS.png b/.gitbook/assets/SearchWS.png new file mode 100644 index 0000000000..7cd5cc9e70 Binary files /dev/null and b/.gitbook/assets/SearchWS.png differ diff --git a/.gitbook/assets/Ui.png b/.gitbook/assets/Ui.png new file mode 100644 index 0000000000..a85c91cb80 Binary files /dev/null and b/.gitbook/assets/Ui.png differ diff --git a/.gitbook/assets/ViewProfile.png b/.gitbook/assets/ViewProfile.png new file mode 100644 index 0000000000..bc2baa5332 Binary files /dev/null and b/.gitbook/assets/ViewProfile.png differ diff --git a/.gitbook/assets/WorkoutSessionAdd.png b/.gitbook/assets/WorkoutSessionAdd.png new file mode 100644 index 0000000000..f98edb346f Binary files /dev/null and b/.gitbook/assets/WorkoutSessionAdd.png differ diff --git a/.gitbook/assets/WorkoutSessionDelete.png b/.gitbook/assets/WorkoutSessionDelete.png new file mode 100644 index 0000000000..bdd3eca264 Binary files /dev/null and b/.gitbook/assets/WorkoutSessionDelete.png differ diff --git a/.gitbook/assets/WorkoutSessionList.png b/.gitbook/assets/WorkoutSessionList.png new file mode 100644 index 0000000000..50e88cdcf9 Binary files /dev/null and b/.gitbook/assets/WorkoutSessionList.png differ diff --git a/.gitbook/assets/WorkoutSessionSearch.png b/.gitbook/assets/WorkoutSessionSearch.png new file mode 100644 index 0000000000..9f5fd2a1d3 Binary files /dev/null and b/.gitbook/assets/WorkoutSessionSearch.png differ diff --git a/.gitbook/assets/add-new-food-item-step-2.JPG b/.gitbook/assets/add-new-food-item-step-2.JPG new file mode 100644 index 0000000000..aac93bc4e2 Binary files /dev/null and b/.gitbook/assets/add-new-food-item-step-2.JPG differ diff --git a/.gitbook/assets/add-new-food-item-step-3.JPG b/.gitbook/assets/add-new-food-item-step-3.JPG new file mode 100644 index 0000000000..0f72790646 Binary files /dev/null and b/.gitbook/assets/add-new-food-item-step-3.JPG differ diff --git a/.gitbook/assets/add-profile-step-1.png b/.gitbook/assets/add-profile-step-1.png new file mode 100644 index 0000000000..c2c5fed9e5 Binary files /dev/null and b/.gitbook/assets/add-profile-step-1.png differ diff --git a/.gitbook/assets/add-profile-step-2.png b/.gitbook/assets/add-profile-step-2.png new file mode 100644 index 0000000000..ea00e63756 Binary files /dev/null and b/.gitbook/assets/add-profile-step-2.png differ diff --git a/.gitbook/assets/add-profile-step-3.png b/.gitbook/assets/add-profile-step-3.png new file mode 100644 index 0000000000..b1e59b1054 Binary files /dev/null and b/.gitbook/assets/add-profile-step-3.png differ diff --git a/.gitbook/assets/main-help-step-2.png b/.gitbook/assets/main-help-step-2.png new file mode 100644 index 0000000000..39eb2ba49c Binary files /dev/null and b/.gitbook/assets/main-help-step-2.png differ diff --git a/.gitbook/assets/model.png b/.gitbook/assets/model.png new file mode 100644 index 0000000000..e64c0a89bd Binary files /dev/null and b/.gitbook/assets/model.png differ diff --git a/.gitbook/assets/new-dietsession-step-2.png b/.gitbook/assets/new-dietsession-step-2.png new file mode 100644 index 0000000000..d519ea8901 Binary files /dev/null and b/.gitbook/assets/new-dietsession-step-2.png differ diff --git a/.gitbook/assets/new-dietsession-step-3.png b/.gitbook/assets/new-dietsession-step-3.png new file mode 100644 index 0000000000..490054d863 Binary files /dev/null and b/.gitbook/assets/new-dietsession-step-3.png differ diff --git a/.gitbook/assets/new-dietsession-step-4.png b/.gitbook/assets/new-dietsession-step-4.png new file mode 100644 index 0000000000..9714482085 Binary files /dev/null and b/.gitbook/assets/new-dietsession-step-4.png differ diff --git a/.gitbook/assets/workout-help-step-1.png b/.gitbook/assets/workout-help-step-1.png new file mode 100644 index 0000000000..008d417226 Binary files /dev/null and b/.gitbook/assets/workout-help-step-1.png differ diff --git a/.gitbook/assets/workout-help-step-2.png b/.gitbook/assets/workout-help-step-2.png new file mode 100644 index 0000000000..979f91bb39 Binary files /dev/null and b/.gitbook/assets/workout-help-step-2.png differ diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fd8c44d086..133b0e7f2b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -31,20 +31,4 @@ jobs: java-package: jdk+fx - name: Build and check with Gradle - run: ./gradlew check - - - name: Perform IO redirection test (*NIX) - if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (MacOS) - if: always() && runner.os == 'macOS' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (Windows) - if: always() && runner.os == 'Windows' - working-directory: ${{ github.workspace }}/text-ui-test - shell: cmd - run: runtest.bat \ No newline at end of file + run: ./gradlew check \ No newline at end of file diff --git a/.gitignore b/.gitignore index f69985ef1f..18c7e79542 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT + +/saves/ +logs/ +data/ diff --git a/.project b/.project new file mode 100644 index 0000000000..a2174b1694 --- /dev/null +++ b/.project @@ -0,0 +1,34 @@ + + + tp-master + Project tp-master created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1601482289537 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000000..98515123a3 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index 8e359a0145..0000000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,9 +0,0 @@ -# Contributors - -Display | Name | Github Profile | Homepage ----|:---:|:---:|:---: -![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe) -![](https://avatars0.githubusercontent.com/u/1673303?s=100) | Damith C. Rajapakse | [Github](https://github.com/damithc/) | [Homepage](https://www.comp.nus.edu.sg/~damithch/) -# I would like to join this list. How can I help the project - -For more information, please refer to our [contributor's guide](https://oss-generic.github.io/process/). diff --git a/FoodItemAdd.png b/FoodItemAdd.png new file mode 100644 index 0000000000..f8d2391222 Binary files /dev/null and b/FoodItemAdd.png differ diff --git a/README.md b/README.md index 698b938529..ca9fd63249 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,52 @@ # Duke 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. +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. ## Setting up in Intellij -Prerequisites: JDK 11 (use the exact version), update Intellij to the most recent version. +Prerequisites: JDK 11 \(use the exact version\), update Intellij to the most recent version. 1. **Configure Intellij for JDK 11**, as described [here](https://se-education.org/guides/tutorials/intellijJdk.html). -1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -1. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: - ``` - > Task :compileJava +2. **Import the project** _**as a Gradle project**_, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). +3. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: + + ```text + > Task :compileJava UP-TO-DATE > Task :processResources NO-SOURCE - > Task :classes - + > Task :classes UP-TO-DATE + > Task :Duke.main() - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - - What is your name? + _________________________________________________________________________________________________ + _____ _ + / ____| | | + | (___ ___ | |__ __ __ __ _ _ __ ____ ___ _ __ ___ __ _ __ _ ___ _ __ + \___ \ / __|| '_ \\ \ /\ / // _` || '__||_ // _ \| '_ \ / _ \ / _` | / _` | / _ \| '__| + ____) || (__ | | | |\ V V /| (_| || | / /| __/| | | || __/| (_| || (_| || __/| | + |_____/ \___||_| |_| \_/\_/ \__,_||_| /___|\___||_| |_| \___| \__, | \__, | \___||_| + __/ | __/ | + |___/ |___/ + _________________________________________________________________________________________________ + + _________________________________________________________________________________________________ + Welcome back to The Schwarzenegger, Khoa! + How can I help you today? + _________________________________________________________________________________________________ + + Main Menu >>>>> ``` - Type some word and press enter to let the execution proceed to the end. + + Type `help` and press Enter to see the available commands under Main Menu. ## Build automation using Gradle -* This project uses Gradle for build automation and dependency management. It includes a basic build script as well (i.e. the `build.gradle` file). +* This project uses Gradle for build automation and dependency management. It includes a basic build script as well \(i.e. the `build.gradle` file\). * If you are new to Gradle, refer to the [Gradle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/gradle.html). ## Testing -### I/O redirection tests - -* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script. - ### JUnit tests -* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template. +* JUnit test files are placed under `src/test/java` with this project. * If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html). ## Checkstyle @@ -55,10 +62,6 @@ The project uses [GitHub actions](https://github.com/features/actions) for CI. W `/docs` folder contains a skeleton version of the project documentation. -Steps for publishing documentation to the public: -1. If you are using this project template for an individual project, go your fork on GitHub.
- If you are using this project template for a team project, go to the team fork on GitHub. -1. Click on the `settings` tab. -1. Scroll down to the `GitHub Pages` section. -1. Set the `source` as `master branch /docs folder`. -1. Optionally, use the `choose a theme` button to choose a theme for your documentation. +Steps for publishing documentation to the public: 1. If you are using this project template for an individual project, go your fork on GitHub. + If you are using this project template for a team project, go to the team fork on GitHub. 1. Click on the `settings` tab. 1. Scroll down to the `GitHub Pages` section. 1. Set the `source` as `master branch /docs folder`. 1. Optionally, use the `choose a theme` button to choose a theme for your documentation. + diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000000..1a705f503f --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,15 @@ +# Table of contents + +* [Duke project template](README.md) +* [The Schwarzenegger](docs/README.md) + * [team](docs/team/README.md) + * [Zhang Shukai - Project Portfolio Page](docs/team/zsk612.md) + * [Yu Jinyang - Project Portfolio Page](docs/team/yujinyang1998.md) + * [tienkhoa16](docs/team/tienkhoa16.md) + * [Wang Zesong - Project Portfolio Page](docs/team/wgzesg.md) + * [Zeon Chua Feiyi - Project Portfolio Page](docs/team/cfzeon.md) + * [The Schwarzenegger - About Us](docs/team/aboutus.md) + * [The Schwarzenegger - User Guide](docs/userguide.md) + * [The Schwarzenegger - Developer Guide](docs/developerguide.md) +* [CONTRIBUTORS](contributors.md) + diff --git a/build.gradle b/build.gradle index b0c5528fb5..2ab210b8dd 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,8 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'org.apache.commons:commons-lang3:3.5' } test { @@ -33,7 +35,7 @@ application { } shadowJar { - archiveBaseName = "duke" + archiveBaseName = "[CS2113T-F11-1][TheSchwarzenegger]" archiveClassifier = null } @@ -41,6 +43,15 @@ checkstyle { toolVersion = '8.23' } -run{ +run { standardInput = System.in + enableAssertions = true +} + +jar { + manifest { + attributes( + 'Main-Class': 'seedu.duke.Duke' + ) + } } diff --git a/contributors.md b/contributors.md new file mode 100644 index 0000000000..bd623c9e75 --- /dev/null +++ b/contributors.md @@ -0,0 +1,13 @@ +# CONTRIBUTORS + +## Contributors + +| Display | Name | Github Profile | Homepage | +| :--- | :---: | :---: | :---: | +| ![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe) | +| ![](https://avatars0.githubusercontent.com/u/1673303?s=100) | Damith C. Rajapakse | [Github](https://github.com/damithc/) | [Homepage](https://www.comp.nus.edu.sg/~damithch/) | + +## I would like to join this list. How can I help the project + +For more information, please refer to our [contributor's guide](https://oss-generic.github.io/process/). + diff --git a/docs/AboutUs.md b/docs/AboutUs.md deleted file mode 100644 index 0f072953ea..0000000000 --- a/docs/AboutUs.md +++ /dev/null @@ -1,9 +0,0 @@ -# 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) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md deleted file mode 100644 index 0ec3db103d..0000000000 --- a/docs/DeveloperGuide.md +++ /dev/null @@ -1,34 +0,0 @@ -# Developer Guide - -## Design & implementation - -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} - - -## Product scope -### Target user profile - -{Describe the target user profile} - -### Value proposition - -{Describe the value proposition: what problem does it solve?} - -## 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| - -## Non-Functional Requirements - -{Give non-functional requirements} - -## Glossary - -* *glossary item* - Definition - -## Instructions for manual testing - -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..f98de4cab7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,10 @@ -# Duke +# The Schwarzenegger -{Give product intro here} +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +Site Map: + +* [User Guide](userguide.md) +* [Developer Guide](developerguide.md) +* [About Us](team/aboutus.md) -Useful links: -* [User Guide](UserGuide.md) -* [Developer Guide](DeveloperGuide.md) -* [About Us](AboutUs.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md deleted file mode 100644 index abd9fbe891..0000000000 --- a/docs/UserGuide.md +++ /dev/null @@ -1,42 +0,0 @@ -# User Guide - -## Introduction - -{Give a product intro} - -## Quick Start - -{Give steps to get started quickly} - -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). - -## Features - -{Give detailed description of each feature} - -### Adding a todo: `todo` -Adds a new item to the list of todo items. - -Format: `todo n/TODO_NAME d/DEADLINE` - -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. - -Example of usage: - -`todo n/Write the rest of the User Guide d/next week` - -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` - -## FAQ - -**Q**: How do I transfer my data to another computer? - -**A**: {your answer here} - -## Command Summary - -{Give a 'cheat sheet' of commands here} - -* Add todo `todo n/TODO_NAME d/DEADLINE` diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c62a20ede8 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,4 @@ +theme: jekyll-theme-cayman +markdown: kramdown +plugins: + - jemoji diff --git a/docs/developerguide.md b/docs/developerguide.md new file mode 100644 index 0000000000..e4ad0e5888 --- /dev/null +++ b/docs/developerguide.md @@ -0,0 +1,1357 @@ +# The Schwarzenegger - Developer Guide + +By: `CS2113T-F11-1` Since: `2020` + +![Supported Java versions](https://img.shields.io/badge/Java-11-blue.svg) ![Supported OS](https://img.shields.io/badge/Supported%20OS-Windows|MacOS|Linux-yellow.svg) ![Java CI](https://github.com/AY2021S1-CS2113T-F11-1/tp/workflows/Java%20CI/badge.svg) + +## Table of Contents + +1. [**Introduction**](developerguide.md#intro) + + + 1.1. [Background](developerguide.md#background) + + + 1.2. [Purpose](developerguide.md#purpose) + + + 1.3. [Scope](developerguide.md#scope) + +2. [**Setting Up**](developerguide.md#setting-up) + + + 2.1. [Prerequisites](developerguide.md#prerequisites) + + + 2.2. [Setting up the Project in Your Computer](developerguide.md#setting-up-the-project-in-your-computer) + +3. [**Design**](developerguide.md#design) + + + 3.1. [Architecture](developerguide.md#architecture) + + + 3.2. [Ui Component](developerguide.md#ui-component) + + + 3.3. [Logic Component](developerguide.md#logic-component) + + + 3.4. [Model Component](developerguide.md#model-component) + + + 3.5. [Storage Component](developerguide.md#workoutSessionStorage-component) + + +     3.5.1. [Storage for Profile](developerguide.md#workoutSessionStorage-for-profile) + + +     3.5.2. [Storage for Diet](developerguide.md#workoutSessionStorage-for-diet) + + +     3.5.3. [Storage for Workout](developerguide.md#workoutSessionStorage-for-workout) + +4. [**Implementation**](developerguide.md#implementation) + + + 4.1. [Main Menu-related Features](developerguide.md#main-menu-related-features) + + + 4.2. [Profile-related Features](developerguide.md#profile-related-features) + + +     4.2.1. [Adding a Profile](developerguide.md#adding-a-profile) + + +     4.2.2. [Viewing a Profile](developerguide.md#viewing-a-profile) + + +     4.2.3. [Editing a Profile](developerguide.md#editing-a-profile) + + +     4.2.4. [Deleting a Profile](developerguide.md#deleting-a-profile) + + + 4.3. [Diet-related Features](developerguide.md#diet-related-features) + + +     4.3.1. [List Out All Commands](developerguide.md#list-out-all-commands) + + +     4.3.2. [Start Recording Diet Data](developerguide.md#start-recording-diet-data) + + +         4.3.2.1. [Showing Help Message](developerguide.md#showing-help-message) + + +         4.3.2.2. [Adding Food Items for the Current Diet](developerguide.md#adding-food-items-for-the-current-diet) + + +         4.3.2.3. [Listing Data for the Current Diet](developerguide.md#listing-data-for-the-current-diet) + + +         4.3.2.4. [Deleting Data from the Current Diet](developerguide.md#deleting-data-from-the-current-diet) + + +         4.3.2.5. [Clearing Data from the Current Diet](developerguide.md#clearing-data-from-the-current-diet) + + +         4.3.2.6. [Stopping the Recording of Diet Data](developerguide.md#stopping-the-recording-of-diet-data) + + +     4.3.3. [List All Past Diet Sessions](developerguide.md#list-all-past-diet-sessions) + + +     4.3.4. [Edit a Past Diet Session](developerguide.md#edit-a-past-diet-session) + + +     4.3.5. [Delete a Past Diet Session](developerguide.md#delete-a-past-diet-session) + + +     4.3.6. [Clear all Past Diet Sessions](developerguide.md#clear-all-past-diet-sessions) + + +     4.3.7. [Search for Past Diet Sessions](developerguide.md#search-for-past-diet-sessions) + + +     4.3.8. [Exit the Diet Manager](developerguide.md#exit-the-diet-manager) + + + 4.4. [Workout-related Features](developerguide.md#workout-related-features) + + +     4.4.1. [Creating a New Workout Session](developerguide.md#creating-a-new-workout-session) + + +         4.4.1.1. [Adding an Exercise](developerguide.md#adding-an-exercise) + + +         4.4.1.2. [Deleting an Exercise](developerguide.md#deleting-an-exercise) + + +         4.4.1.3. [Listing All Exercises in This Session](developerguide.md#listing-all-exercises-in-this-session) + + +         4.4.1.4. [Searching for Related Exercises](developerguide.md#searching-for-related-exercises) + + +     4.4.2. [Listing Past Workout Sessions](developerguide.md#listing-past-workout-sessions) + + +     4.4.3. [Editing Workout Session](developerguide.md#editing-workout-session) + + +     4.4.4. [Deleting a Workout Session](developerguide.md#deleting-a-workout-session) + + +     4.4.5. [Searching Based on Conditions](developerguide.md#searching-based-on-conditions) + + + 4.5. [Logging](developerguide.md#logging) + +5. [**Testing**](developerguide.md#testing) + + + 5.1. [Running Tests](developerguide.md#running-tests) + + + 5.2. [Types of Tests](developerguide.md#types-of-tests) + +6. [**Dev Ops**](developerguide.md#dev-ops) + + + 6.1. [Build Automation](developerguide.md#build-automation) + + + 6.2. [Continuous Integration](developerguide.md#continuous-integration) + + + 6.3. [Coverage Report](developerguide.md#coverage-report) + + + 6.4. [Making a Release](developerguide.md#making-a-release) + + + 6.5. [Managing Dependencies](developerguide.md#managing-dependencies) + +7. [**Appendices**](developerguide.md#appendices) + * [Appendix A: Product Scope](developerguide.md#appendix-a-product-scope) + * [Appendix B: User Stories](developerguide.md#appendix-b-user-stories) + * [Appendix C: Non-Functional Requirements](developerguide.md#appendix-c-non-functional-requirements) + * [Appendix D: Glossary](developerguide.md#appendix-d-glossary) + * [Appendix E: Supported Formats of Date Input](developerguide.md#appendix-e-supported-formats-of-date-input) + +## 1. [Introduction](developerguide.md) + +### 1.1. [Background](developerguide.md) + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +### 1.2. [Purpose](developerguide.md) + +This document contains the specified architecture and software design specifications for the application, The Schwarzenegger. + +### 1.3. [Scope](developerguide.md) + +This document describes the software architecture and software design requirements for The Schwarzenegger. This guide is mainly for developers, designers and software engineers that are or going to work on The Schwarzenegger. + +[Return to Top](developerguide.md#intro) + +## 2. [Setting Up](developerguide.md) + +### 2.1. [Prerequisites](developerguide.md) + +1. JDK `11`. +2. IntelliJ IDEA IDE. + +### 2.2. [Setting up the Project in Your Computer](developerguide.md) + +1. Fork [this repository](https://github.com/AY2021S1-CS2113T-F11-1/tp), and clone the fork to your computer. +2. Open IntelliJ \(if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first\). +3. Set up the correct JDK version for Gradle + 1. Click `Configure` > `Structure for New Projects` and then `Project Settings` > `Project` > `Project SDK`. + 2. If `JDK 11` is listed in the drop down, select it. Otherwise, click `New…` and select the directory where you installed `JDK 11`. + 3. Click `OK`. +4. Click `Import Project`. +5. Locate the `build.gradle` file and select it. Click `OK`. +6. Click `Open as Project`. +7. Click `OK` to accept the default settings if prompted. + +[Return to Top](developerguide.md#intro) + +## 3. [Design](developerguide.md) + +This section provides a high level overview of our application, The Schwarzenegger. + +### 3.1. [Architecture](developerguide.md) + +![Architecture](../.gitbook/assets/Architecture.png) + +The image above explains the design of the application, The Schwarzenegger. + +The main driver of the application is `Main: Duke`. It is responsible for mainly two phases: + +* At application launch + * This class will initialise the components in the correct sequence and is in charge of connecting them with each other. +* At shut down + * This class will invoke cleanup method for the components when necessary. + +In addition to that, the architecture of The Schwarzenegger is broken down into several packages, mainly the following: + +* `Ui`: This class mainly handles the interactions with user of the application. +* `Parser`: This class mainly handles the parsing and handling of user commands. +* `Command`: This class handles the type of command. +* `Profile`: This class manages the data of the user. +* `Diet`: This class manages the diet recording sessions. +* `Workout`: This class manages the data workout recording sessions. +* `Storage`: This class reads data from and writes data back into a text file for future uses. + +[Return to Top](developerguide.md#intro) + +### 3.2. [Ui Component](developerguide.md) + +![Ui Component](../.gitbook/assets/Ui.png) + +The `Ui` package is a combination class where the interactions with the user are formatted in a consistent way. + +The `Ui` component, + +* Takes in user commands +* Formats messages and prints out responses + +[Return to Top](developerguide.md#intro) + +### 3.3. [Logic Component](developerguide.md) + +![Logic Component](../.gitbook/assets/Logic.png) + +1. `The Schwarzenegger` uses `Parser` classes to parse the user command. +2. This splits the user input into interpretable portions by other functions. +3. All commands inherits from base class Command with an `execute()` method. They are stored in a hashmap `CommandLib` and retrieved using user's input as key. +4. Command interacts with parsers, models and storage to carry out the user's command. +5. The result of the command execution is encapsulated as a CommandResult object which is passed back to CommonUi to display the message. + +[Return to Top](developerguide.md#intro) + +### 3.4. [Model Component](developerguide.md) + +![Model Component](../.gitbook/assets/model.png) + +The Model component contains `Profile`, `DietManager`, `PastRecord` and `WorkoutSession` classes. + +* Profile: Stores the user profile data. +* Food: Stores food data that user consumes in a meal. +* PastRecord: Stores meta information of each WorkoutSession files. +* Exercises: Stores the exercise data done in each workout session. + +[Return to Top](developerguide.md#intro) + +### 3.5. [Storage Component](developerguide.md) + +Storage in the application refers to storing files of user profile and workout, diet sessions into respective local subdirectories sorted based on time in a local directory called `/saves` which is in the same directory as the project root. + +#### 3.5.1. [Storage for Profile](developerguide.md) + +Storage for profile saves user profile created as `profile.json` in the `/saves/profile` directory. Profile data file is created as follows: + +* `profile.json` is updated in the local hard disk after the user adds/ edits a profile by calling `ProfileAdd.execute()`/ `ProfileEdit.execute()`. +* `profile.json` content will be cleared after the user deletes a profile by calling `ProfileDelete.execute()`. + +**Implementation** Profile workoutSessionStorage handles reading of file data by calling `loadData()` and overwriting of file data by calling `saveData()`. + +[Return to Top](developerguide.md#intro) + +#### 3.5.2. [Storage for Diet](developerguide.md) + +Storage for diet saves diet sessions created as individual files sorted based on the time created in the `/saves/diet` directory. Each diet session file is created as follows: + +* Each file is created as a json file and named as `[DATE] [TAG].json`. +* A corresponding file is updated in the local file after the user enters a command into a diet session by calling DietSessionEdit.execute\(\), or DietSessionCreate.execute\(\). +* A corresponding file is deleted in the local file when the user deletes a diet session by calling DietSessionDelete.execute\(\) or clears all diet sessions by calling DietSessionClear.execute\(\). + +**Implementation** Storage handles reading of file data by calling readDietSession\(\) and overwriting of file data by calling writeToStorageDietSession\(\). + +[Return to Top](developerguide.md#intro) + +#### 3.5.3. [Storage for Workout](developerguide.md) + +Storage for workout saves workout sessions created as individual files named based on the time created in `/saves/workout` directory. The metainformation of the files such as createion date and last edit date is saved in `/saves/workout/history.json`. + +Only history.json file is load when initilizing the application. The rest of Session files are load on request, e.g. `edit`. When a new workout session is created, a new file will be stored and its meta information will be appended to `history.json`. When a workout session is deleted, the file will be removed and its record will be removed from `history.json`. + +[Return to Top](developerguide.md#intro) + +## 4. [Implementation](developerguide.md) + +This section describes some details on how the features are being implemented. All profile/ diet/ workout-related features. + +All profile/ diet/ workout-related features can be broken down into 4 distinct functionality, addition, viewing/ listing, deletion and editing. For diet and workout-related features, there is an additional functionality of searching. + +### 4.1. [Main Menu-related Features](developerguide.md) + +This feature allows user to access the different menu for workout, diet and profile. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command. The action will be aborted, and the program will advise the user to type "help" for command syntax reference. + +If the command is successful,the user will be put into the respective menu and a starting message on the entered menu will be displayed to the user. + +**Implementation** + +When the user attempts to access different menu for workout, diet and profile menu, the Duke, CommonUi, CommonParser and CommandLib classes will be accessed, and the following sequence of actions is called to prompt execution result to user: + +1. User executes a command + 1. `Duke` calls `CommonUi.getCommand()` to receive user input. + 2. `Duke` calls `CommonParser.parseCommand()` to parse user input into a string array. +2. `Duke` calls `CommandLib.getCommand` with the string arry containing the inputs. +3. Depending on the input,`Duke` creates `ProfileSession` or `DietManager` or `WorkoutManager` object. +4. After entering the `ProfileSession` or `DietManager` or `WorkoutManager` objects, the menus will have their own separate tasks. + +The sequence diagram below summarizes how Main Menu works: + +![Load Data Sequence Diagram](../.gitbook/assets/MainMenu.png) + +[Return to Top](developerguide.md#intro) + +### 4.2. [Profile-related Features](developerguide.md) + +#### 4.2.1. [Adding a Profile](developerguide.md) + +This feature allows user to add a new profile. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command format. The action will be aborted, and the program will advise the user to type "help" for command syntax reference. + +If the creation is successful, a confirmation message on the newly created profile will be displayed to the user. + +**Implementation** + +When the user attempts to add a new profile, the ProfileSession, CommonUi, ProfileParser, Command, CommandLib, ProfileStorage, Profile and CommandResult classes will be accessed, and the following sequence of actions is called to prompt execution result to user: + +1. User executes `add /n Schwarzenegger /h 188 /w 113 /e 100 /c 2500` + 1. `ProfileSession` calls `CommonUi.getCommand()` to receive user input. + 2. `ProfileSession` calls `ProfileParser.parseCommand()` to parse user input into a string array. +2. Creating `ProfileAdd` object. + 1. Based on the parsed input, `ProfileSession` calls `CommandLib` to return the correct Command Object `ProfileAdd`. +3. Executing command. + 1. `ProfileSession` calls `ProfileAdd.execute()` with the rest of parsed input. + 2. `ProfileAdd` calls `ProfileStorage.loadData()` to load existing profile in the system. If there is an existing profile, `ProfileAdd` returns a failure result to `ProfileSession`. Otherwise, the process continues with step `3`. + 3. `ProfileAdd` calls `ProfileParser.extractCommandTagAndInfo()` to parse user input into specific tags and information. + 4. Based on the parsed information from `ProfileParser.extractCommandTagAndInfo()`, `ProfileAdd` creates a new `Profile`. + 5. `ProfileAdd` calls `ProfileStorage.saveData()` to save the `Profile` object. + 6. `ProfileAdd` returns a successful result to `ProfileSession`. +4. Prompting result to user. + 1. `ProfileSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. + 2. `ProfileSession` calls `CommonUi.showToUser()` to show result to the user. + +All descriptions, warnings and responses will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how creating a new profile works: + +![Load Data Sequence Diagram](../.gitbook/assets/AddProfile.png) + +Below are the sub-diagrams: + +![Figure 4.2.1](../.gitbook/assets/ParseInput%20%281%29.png) + **Figure 4.2.1.** _Sub-diagram for Parsing Input in ProfileSession_ + + + **Figure 4.2.2.** _Sub-diagram for Showing Message to User in ProfileSession_ + +**Design considerations** + +Parsing of the user’s input command: + +* **Alternative 1 \(current choice\):** User’s command is split into size 2 array first containing command type and command arguments. Then arguments are split into command tag and information pairs. + * Pros: Command tags do not have to follow a fixed order. + * Cons: It takes multiple steps in parsing the command. +* **Alternative 2:** User’s command is divided by space. + * Pros: The parsing can be easily done by calling Java built-in function `split()`. Supports multiple tags or no tags. + * Cons: Values for each variable cannot contain spaces which makes the application restrictive, especially for user's name. + +[Return to Top](developerguide.md#intro) + +#### 4.2.2. [Viewing a Profile](developerguide.md) + +This feature allows user to view added profile with calculated BMI based on height and weight. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. redundant parameters. The action will be aborted, and the program will advise the user to type "help" for command syntax reference. + +If the data loading is successful, a message on the added profile will be displayed to the user. + +**Implementation** + +When the user attempts to view an added profile, the ProfileSession, CommonUi, ProfileParser, Command, CommandLib, ProfileStorage, Profile, DietManager and CommandResult classes will be accessed. The following sequence of steps will then occur: + +1. User executes `view` + 1. `ProfileSession` calls `CommonUi.getUserCommand()` to receive user input. + 2. ProfileSession`calls`ProfileParser.parseCommand\(\)\` to parse user input into a string array. +2. Creating `ProfileView` object. + 1. Based on the parsed input, `ProfileSession` calls `CommandLib` to return the correct Command Object `ProfileView`. +3. Executing command. + 1. `ProfileSession` calls `ProfileView.execute()` with the rest of parsed input. + 2. `ProfileView` calls `ProfileStorage.loadData()` to load existing profile in the system. If there is no existing profile, `ProfileView` returns a failure result to `ProfileSession`. Otherwise, the process continues with step `3`. + 3. `ProfileView` calls `DietManager.getTodayTotalCalories()` to get user's calories intake today. + 4. Based on user's calories intake today and string representation of `Profile`, `ProfileView` returns a result to `ProfileSession`. +4. Prompting result to user. + 1. `ProfileSession` calls `CommandResult.getCommandResult()` to get the `CommandResult` object. + 2. `ProfileSession` calls `CommonUi.showToUser()` to show result to the user. + +All descriptions, warnings and responses will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how viewing an added profile works: + +![Load Data Sequence Diagram](../.gitbook/assets/ViewProfile.png) + +You can refer to [Figure 4.2.1. Sub-diagram for Parsing Input in ProfileSession](developerguide.md#figure-4-2-1) and [Figure 4.2.2. Sub-diagram for Showing Message to User in ProfileSession](developerguide.md#figure-4-2-2) for the corresponding sub-diagrams. + +**Design considerations** + +Aspects: Loading of stored data + +* **Alternative 1 \(current choice\):** call public methods of Storage class to load the profile from hard disk every time the user wants to view profile. + * Pros: Profile data is up-to-date if the user prefers to edit it in text file rather than using commands in The Schwarzenegger. + * Cons: Execution time is slow down due to multiple times of loading the data. +* **Alternative 2:** call public methods of Storage class to load the profile from hard disk only when user enters Profile Menu. + * Pros: Execution time is fast. + * Cons: Profile data is not updated in real time if user edits it in text editor while running The Schwarzenegger. + +[Return to Top](developerguide.md#intro) + +#### 4.2.3. [Editing a Profile](developerguide.md) + +This feature allows user to anytime go back to edit a profile created in the past such as editing physique data and expected daily calories intake. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command format. The action will be aborted, and the program will advise the user to type "help" for command syntax reference. + +If the editing is successful, a confirmation message on the edited profile will be displayed to the user. + +**Implementation** + +When the user attempts to edit a profile, the ProfileSession, CommonUi, ProfileParser, Command, CommandLib, ProfileStorage, Profile and CommandResult classes will be accessed, and the following sequence of actions is called to prompt execution result to user: + +1. User executes `edit /w 60` + 1. `ProfileSession` calls `CommonUi.getCommand()` to receive user input. + 2. `ProfileSession` calls `ProfileParser.parseCommand()` to parse user input into a string array. +2. Creating `ProfileEdit` object. + 1. Based on the parsed input, `ProfileSession` calls `CommandLib` to return the correct Command Object `ProfileEdit`. +3. Executing command. + 1. `ProfileSession` calls `ProfileEdit.execute()` with the rest of parsed input. + 2. `ProfileEdit` calls `ProfileStorage.loadData()` to load existing profile in the system. If there is no existing profile, `ProfileAdd` returns a failure result to `ProfileSession`. Otherwise, the process continues with step `3`. + 3. `ProfileEdit` calls `ProfileParser.extractCommandTagAndInfo()` to parse user input into specific tags and information. + 4. Based on the parsed information from `ProfileParser.extractCommandTagAndInfo()`, `ProfileEdit` creates a new `Profile`. + 5. `ProfileEdit` calls `Profile.equals()` to compare the newly created and existing profile. If there are no changes, `ProfileEdit` returns a failure result to `ProfileSession`. Otherwise, the process continues with step `6`. + 6. `ProfileEdit` calls `ProfileStorage.saveData()` to save the newly created `Profile` object. + 7. `ProfileAdd` returns a successful result to `ProfileSession`. +4. Prompting result to user. + 1. `ProfileSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. + 2. `ProfileSession` calls `CommonUi.showToUser()` to show result to the user. + +All descriptions, warnings and responses will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how creating a new profile works: + +![Load Data Sequence Diagram](../.gitbook/assets/EditProfile.png) + +You can refer to [Figure 4.2.1. Sub-diagram for Parsing Input in ProfileSession](developerguide.md#figure-4-2-1) and [Figure 4.2.2. Sub-diagram for Showing Message to User in ProfileSession](developerguide.md#figure-4-2-2) for the corresponding sub-diagrams. + +**Design considerations** + +Parsing of the user’s input command: + +* **Alternative 1 \(current choice\):** User’s command is split into size 2 array first containing command type and command arguments. Then arguments are split into command tag and information pairs. + * Pros: Command tags do not have to follow a fixed order. + * Cons: It takes multiple steps in parsing the command. +* **Alternative 2:** User’s command is divided by space. + * Pros: The parsing can be easily done by calling Java built-in function `split()`. Supports multiple tags or no tags. + * Cons: Values for each variable cannot contain spaces which makes the application restrictive. + +[Return to Top](developerguide.md#intro) + +#### 4.2.4. [Deleting a Profile](developerguide.md) + +This feature allows user to delete a profile created in the past. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. redundant parameters. The action will be aborted, and the program will advise the user to type "help" for command syntax reference. + +If the deletion is successful, a confirmation message on the profile deletion will be displayed to the user. + +**Implementation** + +When the user attempts to delete an added profile, the ProfileSession, CommonUi, ProfileParser, Command, CommandLib, ProfileStorage, Profile and CommandResult classes will be accessed. The following sequence of steps will then occur: + +1. User executes `delete` + 1. `ProfileSession` calls `CommonUi.getUserCommand()` to receive user input. + 2. ProfileSession`calls`ProfileParser.parseCommand\(\)\` to parse user input into a string array. +2. Creating `ProfileDelete` object. + 1. Based on the parsed input, `ProfileSession` calls `CommandLib` to return the correct Command Object `ProfileDelete`. +3. Executing command. + 1. `ProfileSession` calls `ProfileDelete.execute()` with the rest of parsed input. + 2. `ProfileDelete` calls `ProfileStorage.loadData()` to load existing profile in the system. If there is no existing profile, `ProfileDelete` returns a failure result to `ProfileSession`. Otherwise, the process continues with step `3`. + 3. `ProfileDelete` calls `CommonUi.CheckConfirmation()` to get user's confirmation on the deletion since this action is irrevocable. If user fails to confirm, `ProfileDelete` returns an abort result to `ProfileSession`. Otherwise, the process continues with step `4`. + 4. `ProfileDelete` calls `ProfileStorage.saveData()` to save a `null` object which represents a deleted profile. + 5. `ProfileDelete` returns a result to `ProfileSession`. +4. Prompting result to user. + 1. `ProfileSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. + 2. `ProfileSession` calls `CommonUi.showToUser()` to show result to the user. + +All descriptions, warnings and responses will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how deleting an added profile works: + +![Load Data Sequence Diagram](../.gitbook/assets/DeleteProfile.png) + +You can refer to [Figure 4.2.1. Sub-diagram for Parsing Input in ProfileSession](developerguide.md#figure-4-2-1) and [Figure 4.2.2. Sub-diagram for Showing Message to User in ProfileSession](developerguide.md#figure-4-2-2) for the corresponding sub-diagrams. + +**Design considerations** + +Aspects: Loading of stored data + +* **Alternative 1 \(current choice\):** call public methods of Storage class to load the profile from hard disk every time the user wants to delete profile. + * Pros: Profile data is up-to-date if the user prefers to edit it in text file rather than using commands in The Schwarzenegger. + * Cons: Execution time is slow down due to loading the data. +* **Alternative 2:** call public methods of Storage class to load the profile from hard disk when user enter Profile Menu. + * Pros: Execution time is fast. + * Cons: Profile data is not updated in real time if user edits it in text file while running The Schwarzenegger. + +[Return to Top](developerguide.md#intro) + +### 4.3. [Diet-related Features](developerguide.md) + +#### 4.3.1. [Listing out all commands:](developerguide.md) `help` + +This command lists out all help commands in a typed list that indicates to the user all the commands available and how to use them. + +**Implementation** +When the user types `help` in a Diet Manager instance, the following sequence occurs. + +1. User executes `help` + 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. + 2. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. +2. Creating `DietSessionHelp` object. + 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionHelp`. +3. Executing command. + 1. `DietManager` calls `DietSessionHelp.execute()` with the rest of parsed input. + 2. `DietSessionHelp` appends onto a string builder a list of typed help commands. + 3. `DietSessionHelp` returns a `CommandResult` object with the help message. +4. Prompting result to user. + 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. + 2. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +[Return to Top](developerguide.md#intro) + +#### 4.3.2. [Start recording diet data:](developerguide.md) `new` + +The feature allows users to start recording diet data. + +**Implementation** +When the user types `new ` the following sequence occurs. + +1. User executes `new /d 2020-05-04 /t breakfast` + 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. + 2. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. +2. Creating `DietSessionHelp` object. + 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionCreate`. +3. Executing command. + 1. `DietManager` calls `DietSessionCreate.execute()` with the rest of parsed input. + 2. `DietSessionCreate` calls the `start()` method within an instantiated DietSession created with the parsed input. + 3. `DietSession` then proceeds to completion until the user types "end", saving after every command with `DietStorage`. + 4. `DietSessionHelp` returns a `CommandResult` object with the help message of the diet manager. +4. Prompting result to user. + 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. + 2. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how creating new diet session works: + +![Load Data Sequence Diagram](../.gitbook/assets/CreateDietSession.png) + +Figure 4.3.2.1. CreateDietSession-diagram for Parsing Input in DietManager + +[Return to Top](developerguide.md#intro) + +#### 4.3.2.1. [Showing help message:](developerguide.md) `help` + +This command lists out all help commands in a typed list that indicates to the user all the commands available and how to use them. + +**Implementation** +When the user types `help` the following sequence occurs. 1. The user keys in `help`. 1. `DietSession` calls `dietSessionUi.getCommand()` to receive user input. 1. `DietSession` calls `DietSessionParser.parseCommand()` to parse user input into a string array. 1. Creating `FoodItemHelp` object. 1. Based on the parsed input, `DietSession` calls `CommandLib` to return the correct Command Object `FoodItemHelp`. 1. Executing command. 1. `DietSession` calls `FoodItemHelp.execute()`. 1. `FoodItemHelp` appends onto a string builder a list of typed help commands. 1. `FoodItemHelp` returns a `CommandResult` object with the help message. 1. Prompting result to user. 1. `DietSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +[Return to Top](developerguide.md#intro) + +#### 4.3.2.2. [Adding food items for the current diet:](developerguide.md) `add` + +The feature allows users to add food items into the current diet session. + +**Implementation** +When the user types `add [FOOD_NAME] /c [CALORIES]` the following sequence occurs. 1. The user keys in `add bologna /c 123`. 1. `DietSession` calls `dietSessionUi.getCommand()` to receive user input. 1. `DietSession` calls `DietSessionParser.parseCommand()` to parse user input into a string array. 1. Creating `FoodItemAdd` object. 1. Based on the parsed input, `DietSession` calls `CommandLib` to return the correct Command Object `FoodItemAdd`. 1. Executing command. 1. `DietSession` calls `FoodItemAdd.execute()`. 1. A `Food` object is instantiated with the rest of the parameters, `bologna` and `123`. 1. The instantiated `Food` object is added to an ArrayList of Food objects in `DietSession` 1. `FoodItemHelp` returns a `CommandResult` object with the add food item message. 1. Prompting result to user. 1. `DietSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how adding a new food to the diet session works: + +![Load Data Sequence Diagram](../.gitbook/assets/FoodItemAdd.png) + +Below is the sub-diagram: + +![Load Data Sequence Diagram](../.gitbook/assets/ParseInput.png) + +Figure 4.3.2.2.1. Sub-diagram for Parsing Input in DietSession + +[Return to Top](developerguide.md#intro) + +#### 4.3.2.3. [Listing data for the current diet:](developerguide.md) `list` + +This command allows users to view all food items in the current diet session. + +**Implementation** +When the user types `list` the following sequence occurs. 1. The user keys in `list`. 1. `DietSession` calls `dietSessionUi.getCommand()` to receive user input. 1. `DietSession` calls `DietSessionParser.parseCommand()` to parse user input into a string array. 1. Creating `FoodItemList` object. 1. Based on the parsed input, `DietSession` calls `CommandLib` to return the correct Command Object `FoodItemList`. 1. Executing command. 1. `DietSession` calls `FoodItemList.execute()`. 1. The ArrayList of Food objects is iterated through and stored in a String. 1. `FoodItemList` returns a `CommandResult` object with the list of food items. 1. Prompting result to user. 1. `DietSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +![Load Data Sequence Diagram](../.gitbook/assets/FoodItemList.png) + +**Design considerations** + +Aspects: Displaying of listed data + +* **Alternative 1 \(current choice\):** Print out a neatly formatted list of food items. + * Pros: The information is easy to read due to neat formatting. + * Cons: Execution time is slower as it requires more calculations. +* **Alternative 2:** Print out toString\(\) for each Food item. + * Pros: Execution time is fast. + * Cons: The information is harder to filter through. + +[Return to Top](developerguide.md#intro) + +#### 4.3.2.4. [Deleting data from the current diet session:](developerguide.md) `delete` + +The feature allows users to remove food items into the current diet session. + +**Implementation** +When the user types `delete [INDEX_OF_FOOD]` the following sequence occurs. 1. The user keys in `delete 1`. 1. `DietSession` calls `dietSessionUi.getCommand()` to receive user input. 1. `DietSession` calls `DietSessionParser.parseCommand()` to parse user input into a string array. 1. Creating `FoodItemDelete` object. 1. Based on the parsed input, `DietSession` calls `CommandLib` to return the correct Command Object `FoodItemDelete`. 1. Executing command. 1. `DietSession` calls `FoodItemDelete.execute()`. 1. The index-1 of the ArrayList for the food is removed. 1. `FoodItemDelete` returns a `CommandResult` object with the delete success message. 1. Prompting result to user. 1. `DietSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +![Load Data Sequence Diagram](../.gitbook/assets/FoodItemDelete.png) + +[Return to Top](developerguide.md#intro) + +#### 4.3.2.5. [Clearing all data from the current diet session](developerguide.md) `clear` + +The feature allows users to remove food items into the current diet session. + +**Implementation** +When the user types `clear` the following sequence occurs. 1. The user keys in `clear`. 1. `DietSession` calls `dietSessionUi.getCommand()` to receive user input. 1. `DietSession` calls `DietSessionParser.parseCommand()` to parse user input into a string array. 1. Creating `FoodItemClear` object. 1. Based on the parsed input, `DietSession` calls `CommandLib` to return the correct Command Object `FoodItemClear`. 1. Executing command. 1. `DietSession` calls `FoodItemClear.execute()`. 1. A new ArrayList of Food is assigned to the original, leaving it with no data inside. 1. `FoodItemClear` returns a `CommandResult` object with the clear success message. 1. Prompting result to user. 1. `DietSession` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +![Load Data Sequence Diagram](../.gitbook/assets/FoodItemClear.png) + +**Design considerations** + +Aspects: Ram usage + +* **Alternative 1 \(current choice\):** Assigning a new ArrayList to the current variable. + * Pros: Fast. + * Cons: Garbage collection has to pick up the unassigned ArrayList. +* **Alternative 2:** delete every item in the ArrayList one by one. + * Pros: Less memory needed as there is nothing new to allocate. + * Cons: A lot slower as it has to iterate through every item. + +[Return to Top](developerguide.md#intro) + +#### 4.3.2.6. [Stopping the recording of diet session data:](developerguide.md) `end` + +The feature allows users to end the current diet session and return back to the diet manager. + +**Implementation** +When the user types `end` the following sequence occurs. 1. The user keys in `end`. + +1. A `DietSessionUi` component will call `dietSessionUI.getInput()`. +2. Input will be parsed in `processCommand()`. +3. Exiting of inputLoop\(\) + + The inputLoop\(\) exits when userInput.equals\("end"\). + +[Return to Top](developerguide.md#intro) + +#### 4.3.3. [List all past diet sessions:](developerguide.md) `list` + +The feature allows users to view all past created diet sessions. + +**Implementation** +When the user types `list` in a diet manager instance the following sequence occurs. 1. The user keys in `list`. 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. 1. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. 1. Creating `DietSessionList` object. 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionList`. 1. Executing command. 1. `DietManager` calls `DietSessionList.execute()` with the rest of parsed input. 1. The execute method opens a directed save folder on the drive then assigns it to a File array. 1. `DietSessionList` then calls the `formatList()` method which takes the File Array and converts it into an ArrayList. 1. `DietSessionList` then calls the `formatRow()` method from within formatList\(\) which converts the files into a formatted table output. 1. `DietSessionList` returns a CommandResult object with the entire table message of the diet sessions. 1. Prompting result to user. 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how listing past Diet sessions work: + +![Load Data Sequence Diagram](../.gitbook/assets/DietSessionList.png) + +* **Alternative 1 \(current choice\):** Print out a neatly formatted list of diet sessions. + * Pros: The information is easy to read due to neat formatting. + * Cons: Execution time is slower as it requires a lot more calculations. +* **Alternative 2:** Print out the file name. + * Pros: Execution time is fast. + * Cons: The information is harder to filter through. + +[Return to Top](developerguide.md#intro) + +#### 4.3.4. [Edit a past diet session:](developerguide.md) `edit` + +The feature allows users to edit previously created diet sessions. + +**Implementation** +When the user types `edit [INDEX_OF_SESSION]` the following sequence occurs. 1. The user keys in `edit 1`. 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. 1. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. 1. Creating `DietSessionEdit` object. 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionEdit`. 1. Executing command. 1. `DietManager` calls `DietSessionEdit.execute()` with the rest of parsed input. 1. The execute method then calls `readDietSession()` from DietStorage which returns a dietSession instance. 1. `DietSessionEdit` then calls the `start()` method within an instantiated DietSession created with the parsed input. 1. `DietSession` then proceeds to completion until the user types "end", saving after every command with `DietStorage`. 1. `DietSessionHelp` returns a CommandResult object with the help message of the diet manager. 1. Prompting result to user. 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how editing Diet session works: + +![Load Data Sequence Diagram](../.gitbook/assets/DietSessionEdit.png) + +**Design considerations** Saving of the user’s Diet sessions: + +* **Alternative 1:** Saving at the end of a diet session + * Pros: The cost of saving is low, file writes only happen once per Diet session instance. + * Cons: If any crashes occur during a diet session, no input data will be saved. +* **Alternative 2 \(current choice\):** Saving during any alterations made to the Diet session + * Pros: The files will still be saved even if a crash occurs. + * Cons: Saving often might be taxing on the user's computer especially on slower models. + +[Return to Top](developerguide.md#intro) + +#### 4.3.5. [Delete a previously created diet session:](developerguide.md) `delete` + +The feature allows users to delete previously created diet sessions. + +**Implementation** +When the user types `delete [INDEX_OF_SESSION]` from a Diet manager instance the following sequence occurs. 1. The user keys in `delete 1`. 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. 1. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. 1. Creating `DietSessionDelete` object. 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionDelete`. 1. Executing command. 1. `DietManager` calls `DietSessionDelete.execute()` with the rest of parsed input. 1. The execute method then deletes the file at the indicated index `1` if a file was present there. 1. `DietSessionDelete` returns a CommandResult object with the delete confirmation message from DietmanagerUi. 1. Prompting result to user. 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how Diet sessions are deleted: + +![Delete\_Diet\_Session\_Sequence\_Diagram](../.gitbook/assets/DietSessionDelete.png) + +* **Alternative 1 \(current choice\):** Provides an indexed array for the user to choose from to delete. + * Pros: The user can delete things easier as it only requires typing a number. + * Cons: Execution time is slower as it requires more calculations. +* **Alternative 2:** Delete based on a user string input of the file name. + * Pros: Easier to implement. + * Cons: Users are greatly inconvenienced by how much they have to type. + +[Return to Top](developerguide.md#intro) + +#### 4.3.6. [Clear all past diet session:](developerguide.md) `clear` + +The feature allows users to clear all previously created diet sessions at once. + +**Implementation** +When the user types `clear` the following sequence occurs. 1. The user keys in `clear`. 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. 1. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. 1. Creating `DietSessionClear` object. 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionClear`. 1. Executing command. 1. `DietManager` calls `DietSessionDelete.execute()` with the rest of parsed input. 1. The execute method then deletes the file at the indicated index `1` if a file was present there. 1. `DietSessionDelete` returns a CommandResult object with the delete confirmation message from DietmanagerUi. 1. Prompting result to user. 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how Diet sessions are all cleared: + +![Delete\_Diet\_Session\_Sequence\_Diagram](../.gitbook/assets/DietSessionClear.png) + +* **Alternative 1 \(current choice\):** Iterate through an array of files and delete everything. + * Pros: The file structure is more homogeneous. + * Cons: Execution time is slower as it requires iterating through every file in the array. +* **Alternative 2:** delete the folder with the save files in it. + * Pros: Execution time is faster though still limited by storage speed. + * Cons: File structure of the entire program is not as stable. + +[Return to Top](developerguide.md#intro) + +#### 4.3.7. [Search for Past Diet Sessions:](developerguide.md) `search` + +The feature allows users to search for previously created diet sessions within a date range or with a specified tag. + +**Implementation** +When the user types `search /s 2020-11-01 /e 2020-11-03 /t breakfast` the following sequence occurs. 1. The user keys in `search /s 2020-11-01 /e 2020-11-03 /t breakfast`. 1. `DietManager` calls `dietManagerUi.getCommand()` to receive user input. 1. `DietManager` calls `DietManagerParser.parseCommand()` to parse user input into a string array. 1. Creating `DietSessionSearch` object. 1. Based on the parsed input, `DietManager` calls `CommandLib` to return the correct Command Object `DietSessionSearch`. 1. Executing command. 1. `DietManager` calls `DietSessionSearch.execute()` with the rest of parsed input. 1. The execute method then iterates through the entire folder and looks for empty tags and folders with the methods `checkEmptyTag()` and `checkEmptyFolder`. 1. `DietSessionSearch` calls the `addToSearchResult()` method which from within calls the `addRow()` method that converts the file output into a table format. 1. `DietSessionSearch` returns a `CommandResult` object with the search results. 1. If the starting search date is after the ending search date, the method will return with an exception which is then returned with the `CommandResult` message. 1. Prompting result to user. 1. `DietManager` calls `CommandResult.getFeedbackMessage()` to get the execution feedback message. 1. `CommandResult` calls `Ui.showToUser()` to show result to the user. + +The sequence diagram below summarizes how Diet sessions is searched: + +![Search\_Diet\_Session\_Sequence\_Diagram](../.gitbook/assets/SearchDietSession.png) + +* **Alternative 1 \(current choice\):** Search by date and tags. + * Pros: Users can get a precise range of dates for their diet sessions. + * Cons: Execution time is slower as it requires more calculations. +* **Alternative 2:** Search only by tags. + * Pros: Easier to implement. + * Cons: The information is harder to filter through. + +[Return to Top](developerguide.md#intro) + +#### 4.3.8. [Exit the Diet manager:](developerguide.md) `end` + +The function returns the user back to the main menu of The Schwarzenegger. + +**Implementation** +When the user types `end` the following sequence occurs. 1. The user keys in `end`. + +1. A `DietSessionUi` component will call `dietSessionUI.getInput()`. +2. Input will be parsed in `processCommand()`. +3. Exiting of inputLoop\(\) + + The inputLoop\(\) exits when userInput.equals\("end"\), returning to the `Start()` method, then ending the `DietManager` instance. + +[Return to Top](developerguide.md#intro) + +### 4.4. [Workout-related Features](developerguide.md) + +#### 4.4.1. [Creating a New Workout Session](developerguide.md) + +Users can create a new workout session. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command or IO related errors. The action will be aborted. If the creation is successful, the user will go into the new workout session to edit the exercises in that session. + +The user can specify tags for the session. Creation time, last edit time and saving file name will be auto generated by the application and saved. + +**Implementation** + +When the user attempts to create a new workout session, the Ui, WorkoutManagerParser and CommandLib class will be accessed and the following sequence of actions are called to return a command object NewWs. + +1. User executes `new /t leg chest` + 1. `WorkoutManager` calls `Ui.getUserCommand()` to receive user input. + 2. `WorkoutManager` calls `WorkoutManagerParser.parse` into a string array +2. Creation of command object. + 1. Based on the parsed input, `WorkoutManager` calls `CommandLib` to return the correct Command Object `NewWs`. +3. Executing Command + 1. `WorkoutManager` calls `NewWS.execute()` with the rest of parsed input. + 2. `NewWS` parse the arguments to identify the tags + 3. `NewWS` calls `PastRecordList.add()` to create a new file to store information in this session. + + If the creation fails, the action is aborted. Else, this record will be stored and the file path will + + be returned. + + 4. `NewWS` creates a new `WorkoutSession` Object with the file path. + 5. `NewWS` calls `workoutSession. workoutSessionStart()` so that user can add information into this session. + 6. After user exits this workout, `WorkoutManager` returns a `CommandResult`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `Ui` to ensure consistence across the app. The following sequence diagram shows how the new command works + +The sequence diagram below summarizes how creating new workout session works: ![Load Data Sequence Diagram](../.gitbook/assets/NewWS.png) **Design considerations** Parsing of the user’s input command: + +* **Alternative 1 \(current choice\):** User’s commands are divided by space. + * Pros: The parsing can be easily done by calling Java built-in function .split\(\). Supports multiple tags or no tags. + * Cons: Values for each variable cannot contain spaces which makes the application restrictive. +* **Alternative 2:** Multiple prompts for user’s input of a workout data. + * Pros: Users would not have to make sure that their command is syntactically right. + * Cons: The constant prompting could subject the application to a negative experience in the difficulty to use the commands. + +[Return to Top](developerguide.md#intro) + +#### 4.4.1.1. [Adding an Exercise](developerguide.md) + +Users can add a new exercise. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command or IO related errors. The action will be aborted. If the addition is successful, a new exercise will be added to the exerciselist. + +**Implementation** + +When the user attempts to add a new exercise, the CommonUi, WorkoutSession, WorkoutSessionParser , CommandLib, WorkoutSessionAdd and WorkoutSessionStorage class will be accessed and the following sequence of actions are called to return a CommandResult object containing a message to show to user. + +1. User executes `add benchpress /n 6 /w 120` + 1. `WorkoutSession` calls `CommonUi.getUserCommand()` to receive user input. + 2. `WorkoutSession` calls `WorkoutSessionParser.workoutSessionParser` to convert the input to a string array. +2. Creation of command object. + 1. Based on the parsed input, `WorkoutSession` calls `CommandLib` to return the correct Command Object `WorkoutSessionAdd`. +3. Executing Command + 1. `WorkoutSession` calls `WorkoutSessionAdd.execute()` with the rest of parsed input. + 2. `WorkoutSessionAdd` parse the arguments to identify the repetitions and weight for the exercise. + 3. `WorkoutSessionAdd` calls `WorkOutSession.Storage.writeToFile()` to store information of all exercises recorded. + 4. `WorkoutSessionAdd` returns a `CommandResult` to WorkoutSession\`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how the add command works: ![Load Data Sequence Diagram](../.gitbook/assets/WorkoutSessionAdd.png) + +Below are the sub-diagrams: + +![Load Data Sequence Diagram](../.gitbook/assets/ParseInputWorkoutSession.png) + +Figure 4.4.1.1.1. Sub-diagram for Parsing Input in WorkoutSession + +Figure 4.4.1.1.2. Sub-diagram for Showing Message to User + +**Design considerations** Aspects: Making add and its parameters as seperate or a single input + +* **Alternative 1 \(current choice\):** Making add and its parameters as a single input + * Pros: Would be easier for the user to enter as it takes less time to enter and it is not too difficult of a command. + * Cons: It might cause the user to miss the format for inputting add. +* **Alternative 2:** Making add and its parameters as separate inputs + * Pros: Would make it neater and more clear to the user what to enter as they would only enter one input every time. + * Cons: It would take multiple actions to perform a single task, depending on the number of parameters. + +[Return to Top](developerguide.md#intro) + +#### 4.4.1.2. [Deleting an Exercise](developerguide.md) + +Users can delete an exercise from a pre-existing list of exercise. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command or IO related errors. The action will be aborted. If the deletion is successful, a new exercise will be added to the exerciselist. + +**Implementation** + +When the user attempts to delete an exercise, the CommonUi, WorkoutSession, WorkoutSessionParser , CommandLib, WorkoutSessionDelete and WorkoutSessionStorage class will be accessed and the following sequence of actions are called to return a CommandResult object containing a message to show to user. + +1. User executes `delete 1` + 1. `WorkoutSession` calls `CommonUi.getUserCommand()` to receive user input. + 2. `WorkoutSession` calls `WorkoutSessionParser.workoutSessionParser` to convert the input to a string array. +2. Creation of command object. + 1. Based on the parsed input, `WorkoutSession` calls `CommandLib` to return the correct Command Object `WorkoutSessionDelete`. +3. Executing Command + 1. `WorkoutSession` calls `WorkoutSessionDelete.execute()` with the rest of parsed input. + 2. `WorkoutSessionDelete` parse the arguments to identify the index of the exercise to be deleted. + 3. `WorkoutSessionDelete` calls `exerciseList.remove()` to delete the respective exercise. + 4. `WorkoutSessionDelete` calls `WorkOutSession.Storage.writeToFile()` to store information of all exercises recorded. + 5. `WorkoutSessionDelete` returns a `CommandResult` to WorkoutSession\`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how the delete command works: ![Load Data Sequence Diagram](../.gitbook/assets/WorkoutSessionDelete.png) + +You can refer to [Figure 4.4.1.1.1. Sub-diagram for Parsing Input in WorkoutSession](developerguide.md#figure-4-4-1-1-1) and [Figure 4.4.1.1.2. Sub-diagram for Showing Message to User](developerguide.md#figure-4-4-1-1-2) for the corresponding sub-diagrams. + +**Design considerations** Aspects: Making delete and index to delete as separate or a single input + +* **Alternative 1 \(current choice\):** Making delete and index to delete as a single input + * Pros: Would be easier for the user to enter as it takes less time to enter and it is not too difficult of a command. + * Cons: It might cause the user to miss the format for inputting delete. +* **Alternative 2:** Making delete and index to delete as separate inputs + * Pros: Would make it neater and more clear to the user what to enter as they would only enter one input every time. + * Cons: It would take two actions to perform a single task. + +[Return to Top](developerguide.md#intro) + +#### 4.4.1.3. [Listing All Exercises in This Session](developerguide.md) + +Users can list all exercise from a pre-existing list of exercise. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command or IO related errors. The action will be aborted. If the listing is successful, the user will be able to see the full list of exercises. + +**Implementation** + +When the user attempts to list all exercises, the CommonUi, WorkoutSession, WorkoutSessionParser , CommandLib, WorkoutSessionList and WorkoutSessionStorage class will be accessed and the following sequence of actions are called to return a CommandResult object containing a message to show to user. + +1. User executes `list` + 1. `WorkoutSession` calls `CommonUi.getUserCommand()` to receive user input. + 2. `WorkoutSession` calls `WorkoutSessionParser.workoutSessionParser` to convert the input to a string array. +2. Creation of command object. + 1. Based on the parsed input, `WorkoutSession` calls `CommandLib` to return the correct Command Object `WorkoutSessionList`. +3. Executing Command + 1. `WorkoutSession` calls `WorkoutSessionList.execute()` with the rest of parsed input. + 2. `WorkoutSessionList` calls `WorkoutSessionList.printList()` to check if the list is empty. + 3. `WorkoutSessionList.printList()` calls `WorkoutSessionList.formatList()` to arrange the list in a readable and dynamic format for the user. + 4. `WorkoutSessionList.formatList()` returns a String of formatted output to `WorkoutSessionList.printList()` then to `WorkoutSessionList`. + 5. `WorkoutSessionList` returns a `CommandResult` to `WorkoutSession`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how the list command works: ![Load Data Sequence Diagram](../.gitbook/assets/WorkoutSessionList.png) + +**Design considerations** Aspects: Length of results + +* **Alternative 1 \(current choice\):** Make the length for displaying exercise dynamic + * Pros: The table would look more appealing as the spacing would be dynamic. + * Cons: It is a lot more difficult to code. +* **Alternative 2:** Make the length allocated for exercise really long + * Pros: The code would be simpler. + * Cons: The table would look ugly for the user to look at. + +[Return to Top](developerguide.md#intro) + +#### 4.4.1.4. [Searching for Related Exercises](developerguide.md) + +Users can search for an exercise from a pre-existing list of exercise. The failure to do so will trigger an exception where the user will be notified of the reason, e.g. invalid command or IO related errors. The action will be aborted. If the searching is successful, the user will be able to see the list of exercises that match. + +**Implementation** + +When the user attempts to search for an exercise from all exercises, the CommonUi, WorkoutSession, WorkoutSessionParser , CommandLib, WorkoutSessionSearch and WorkoutSessionStorage class will be accessed and the following sequence of actions are called to return a CommandResult object containing a message to show to user. + +1. User executes `search bench` + 1. `WorkoutSession` calls `CommonUi.getUserCommand()` to receive user input. + 2. `WorkoutSession` calls `WorkoutSessionParser.workoutSessionParser` to convert the input to a string array. +2. Creation of command object. + 1. Based on the parsed input, `WorkoutSession` calls `CommandLib` to return the correct Command Object `WorkoutSessionSearch`. +3. Executing Command + 1. `WorkoutSession` calls `WorkoutSessionSearch.execute()` with the rest of parsed input. + 2. `WorkoutSessionSearch` checks if the search term is empty. If it is empty, `WorkoutSessionSearch` returns a failure result to `WorkoutSession`. Otherwise, the process continues with step `3` + 3. `WorkoutSessionSearch` calls `WorkoutSessionSearch.formatList()` to search the search term with the exerciseList. If it is empty, `WorkoutSessionSearch.formatList()` returns a failure result to `WorkoutSession`. Otherwise, the process continues with step `4` + 4. `WorkoutSessionSearch` returns a `CommandResult` to `WorkoutSession`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `CommonUi` to ensure consistence across the app. + +The sequence diagram below summarizes how the search command works: ![Load Data Sequence Diagram](../.gitbook/assets/WorkoutSessionSearch.png) + +**Design considerations** Aspects: Length of results + +* **Alternative 1 \(current choice\):** Make the length for displaying exercise dynamic + * Pros: The table would look more appealing as the spacing would be dynamic. + * Cons: It is a lot more difficult to code. +* **Alternative 2:** Make the length allocated for exercise really long + * Pros: The code would be simpler. + * Cons: The table would look ugly for the user to look at. + +[Return to Top](developerguide.md#intro) + +#### 4.4.2. [Listing Past Workout Sessions](developerguide.md) + +The feature to list workoutSessions allows the user to view a summary of all the history workout sessions, including their index, creation date and tags. + +**Implementation** When the user attempts to list workoutSessions, the WorkoutManger, WorkoutManagerParse, ListWS and WorkoutManagerStorage class will be called upon. The following sequence of steps will then occur: + +1. User executes `list /s 20201010 /e 20201025` + 1. `WorkoutManager` calls `Ui.getUserCommand()` to receive user input. + 2. `WorkoutManager` calls `WorkoutManagerParser.parse` into a string array +2. Creation of command object. + 1. Based on the parsed input, `WorkoutManager` calls `CommandLib` to return the correct Command Object `ListWS`. +3. Executing Command + 1. `WorkoutManager` calls `ListWS.execute()` to execute the command + 2. `ListWS` calls `PastRecordList.list()` + 3. `PastRecordList` will return formatted list. + 4. `WorkoutManager` returns a `CommandResult` which contains the formatted list and execution result. +4. Based on `CommandResult`, correct response will be printed to user. + +![Load Data Sequence Diagram](../.gitbook/assets/ListWS.png) + +**Design considerations** Aspects: Security of stored data + +* **Alternative 1 \(current choice\):** call public methods of Storage class to print the list + * Pros: pastRecord are private and it can only be manipulated through designed public methods. Only selected data will be printed and viewed. + * Cons: Most methods Storage needs to be a static. +* **Alternative 2:** Storage return a readonly list of pastRecord. + * Pros: More versatile operations can be done. + * Cons: All data of pastRecord will be exposed. + +[Return to Top](developerguide.md#intro) + +#### 4.4.3. [Editing Workout Session](developerguide.md) + +User can anytime go back to edit a workout session created in the past such as adding or removing exercies in that session. + +Each past workout session is stored in a different file name following its creation time. The meta information of these past records such as file name, creation time are stored in another file which will be loaded as the program initlises. The actual workout session record will only be loaded if needed e.g. when editting is called. + +**Implementation** When the user attempts to edit a past workout session, the Ui, WorkoutManagerParser, CommandLib and WorkoutStorage class will be accessed and the following sequence of actions are called. + +1. User executes `edit 1` + 1. `WorkoutManager` calls `Ui.getUserCommand()` to receive user input. + 2. `WorkoutManager` calls `WorkoutManagerParser.parse` into a string array +2. Creation of command object. + 1. Based on the parsed input, `WorkoutManager` calls `CommandLib` to return the correct Command Object `EditWS`. +3. Executing Command + 1. `WorkoutManager` calls `EditWS.execute()` with the rest of parsed input. + 2. `EditWS` calls `PastRecordList.edit()` to locate the file. If the does not exist, the action is aborted. Else, `PastRecordList` updates the meta information of the file and write to local workoutSessionStorage. The file path will be returned. + 3. `EditWS` creates a new `WorkoutSession` Object with the file path. `WorkoutSession` is initilised by loading the data in the file. + 4. `EditWS` calls `workoutSession.workoutSessionStart()` so that user start editing this session. + 5. After user exits this workout, `WorkoutManager` returns a `CommandResult`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `Ui` to ensure consistence across the app. The following sequence diagram shows how the new command works + +The sequence diagram below summarizes how editting past record works: ![Load Data Sequence Diagram](../.gitbook/assets/EditWS.png) **Design considerations** Past record workoutSessionStorage and model design: + +* **Alternative 1 \(current choice\):** store past workout sessions in different files and their meta information in a separate file + * Pros: Initialization will be faster as data loaded grows little even in long terms. + * Cons: Deleting files and creating files need to handle file names carefully. +* **Alternative 2:** Load all past records during initialization. + * Pros: Run time can retrieve data faster as there is no need to access data in hard disk. + * Cons: The application initialization will grow quickly as the application scales. + +[Return to Top](developerguide.md#intro) + +#### 4.4.4. [Deleting a workout Session](developerguide.md) + +User can delete a workout session created in the past by giving its index. + +Each past workout session is stored in a different file name following its creation time. The meta information of these past records such as file name, creation time are stored in another file which will be loaded as the program initlises. When the user tries to delete a file, the application refers to the meta information of the file to locate the file and delete it. Then the meta information of the record will be deleted. + +User can clear all data by iteratively delete the record until the meta data file is empty. To simplify that, user can use `clear` command to achieve that. + +**Implementation** + +When the user attempts to delete a past workout session, the Ui, WorkoutManagerParser, CommandLib and WorkoutStorage class will be accessed and the following sequence of actions are called. + +1. User executes `delete 1` or `clear` + 1. `WorkoutManager` calls `Ui.getUserCommand()` to receive user input. + 2. `WorkoutManager` calls `WorkoutManagerParser.parse` into a string array +2. Creation of command object. + 1. Based on the parsed input, `WorkoutManager` calls `CommandLib` to return the correct Command Object `DeleteWS` + + or `clearWS`. +3. Executing Command + 1. `WorkoutManager` calls `DeleteWS.execute()` with the rest of parsed input. + 2. `DeleteWS` calls `PastRecorList.delete()` to locate the file. If the does not exist, the action is aborted. Else, `PastRecorList` remove the meta information of the file and delete the local workoutSessionStorage file. + 3. After user exits this workout, `WorkoutManager` returns a `CommandResult`. +4. Based on `CommandResult`, correct response will be printed to user. + +All description, warnings and response will be handled by `Ui` to ensure consistence across the app. + +The sequence diagram below summarizes how deleting past record works: ![Load Data Sequence Diagram](../.gitbook/assets/DeleteWS.png) + +**Design considerations** + +* **Alternative 1 \(current choice\):** Delete `workoutSession` by specifying index of it. + * Pros: Quick and easy deletion by using ArrayList.get\(\). + * Cons: Lesser alternatives for the user and user would have to identify the index first by executing `list` to get index of the session to be deleted. +* **Alternative 2:** Delete `workoutSession` by specifying `workoutSession` tags or dates. + * Pros: More alternatives for users. Can bulk delete files with certain attributes. + * Cons: Tags and dates does not uniquely identify the record hence may result in accidental wrong deletion. + +[Return to Top](developerguide.md#intro) + +#### 4.4.5. [Searching Based on Conditions](developerguide.md) + +The feature `search` allows the user to view a summary of all the history workout sessions which satisfies certain conditions. + +The user can search by the date of creation, or the tags that the session has. User can put in 0 or 1 or 2 criteria during search. + +The user can attach variable number of tags after `/t` and one date after `/d`. The date must be specified in certain formats for it to be recognisable. Else, it will be treated as there is no date criteria given. [See here](developerguide.md#appendix-e-supported-formats-of-date-input) for all supported formats. + +The tag criterion selects sessions which contains all the tags that the user specified in the search. The date criterion selects the sessions which is created on that date. Only sessions that satisfies all conditions will be selected and displayed. + +The result is displayed in a table with the index of the selected records so that users can easily do further operations on them, e.g. `delete` or `edit`. + +**Implementation** + +When the user attempts to list workoutSessions, the WorkoutManger, DeleteWS, WorkoutManagerStorage and WorkoutManagerParse class will be called upon. The following sequence of steps will then occur: + +1. User executes `search /t leg /d 20201017` + 1. `WorkoutManager` calls `Ui.getUserCommand()` to receive user input. + 2. `WorkoutManager` calls `WorkoutManagerParser.parse` into a string array +2. Creation of command object. + 1. Based on the parsed input, `WorkoutManager` calls `CommandLib` to return the correct Command Object `SearchWS`. +3. Executing Command + 1. `WorkoutManager` calls `SearchWS.execute()` to execute the command + 2. `SearchWS` calls `PastRecorList.search()` + 3. `PastRecordList` will call `WorkoutManagerParser.parse` to parse the arguments into an array of predicates + 4. `PastRecordList` filters the pastRecord arraylist and return a string representation of the filtered records to `WorkoutManager` + 5. `WorkoutManager` returns a `CommandResult`. +4. Based on `CommandResult`, correct response will be printed to user. + +The sequence diagram below summarizes how searching record works: ![Load Data Sequence Diagram](../.gitbook/assets/SearchWS.png) **Design considerations** Aspects: indexing the selected results + +The index of a record is not stored in the schema because it easily varies with addition and deletion. Thus given a record, searching for its index will have higher time complexity. + +* **Alternative 1 \(current choice\):** print out the actual index of the record in the meta info file. + * Pros: The index is useful for user to use for future actions. + * Cons: Checking for the actual location complicates the search time complexity. +* **Alternative 2:** print out the index of the element in the result list. + * Pros: Easy to implement. Low time complexity. + * Cons: Since the index in result list is not the same as the index in actual record meta, user cannot use the index for further actions. + +[Return to Top](developerguide.md#intro) + +### 4.5. [Logging](developerguide.md) + +Logging in the application refers to storing exceptions, warnings and messages that occur during the execution of Kitchen Helper. It was included to help developers to identify bugs and to simplify their debugging process. + +The `java.util.logging` package in Java is used for logging. The logging mechanism can be managed from the `SchwarzeneggerLogger` class through the `logger` attribute. + +All controls of the logger for the application can be viewed/ altered in the class construction. The current settings for the logger are as follow: + +* All information is logged into a log file, `SchwarzeneggerLogs.log`. +* Logging is made to be displayed in the `SimpleFormatter` style where the date, class and error description are recorded. + +Logging Levels: + +* `Level.SEVERE`: a serious failure, which prevents normal execution of the program, for end users and system administrators. +* `Level.WARNING`: a potential problem, for end users and system administrators. +* `Level.INFO`: reasonably significant informational message for end users and system administrators. +* `Level.CONFIG`: hardware configuration, such as CPU type. +* `Level.FINE`, `Level.FINER`, `Level.FINEST`: three levels used for providing tracing information for the software developers. + +`SchwarzeneggerLogger` follows singleton design pattern. Thus, other classes can access the `logger` by calling `SchwarzeneggerLogger.getInstanceLogger()`, and logging can be done by invoking the function `log()`. This will ensure that all loggings will be made to the same file across the various classes. + +An example is shown below: + +```text +private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); +logger.log(Level.WARNING, DESCRIPTION_OF_WARNING, e.toString()); +``` + +[Return to Top](developerguide.md#intro) + +## 5. [Testing](developerguide.md) + +### 5.1. [Running Tests](developerguide.md) + +There are two ways to run tests for The Schwarzenegger. + +**Method 1: Using IntelliJ JUnit test runner** + +* To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'`. +* To run a subset of tests, you can right-click on a test package, test class, or a test and choose `Run 'ABC'`. + +**Method 2: Using Gradle** + +* To run all tests, open a console and run the command `gradlew clean test` \(MacOS/Linux: `./gradlew clean test`\) + + > **Note:** If you are new to Gradle, refer to this [Gradle Tutorial](developerguide.md#https://se-education.org/guides/tutorials/gradle.html) to get more tips on how to use Gradle commands. + +[Return to Top](developerguide.md#intro) + +### 5.2. [Types of Tests](developerguide.md) + +We have use types of tests: + +1. Unit tests targeting the lowest level methods/classes. e.g. profile.UtilsTest +2. Integration tests that are checking the integration of multiple code units \(those code units are assumed to be working\). e.g. logic.commands.workout.workoutsession.WorkoutSessionAddTest +3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how they are connected together. e.g. profile.ProfileSessionTest + +[Return to Top](developerguide.md#intro) + +## 6. [Dev Ops](developerguide.md) + +### 6.1. [Build Automation](developerguide.md) + +We use Gradle for tasks related to build automation, such as running tests, and checking code for style compliance. + +To run all build-related tasks: + +1. Open a terminal in the project’s root directory. +2. Run the command: + * Windows: `gradlew build` + * MacOS/Linux: `./gradlew build` +3. A message stating `BUILD SUCCESSFUL` will be shown in the terminal if all tasks run successfully. + + + Otherwise, use the error report provided to resolve the issue before trying again. + +[Return to Top](developerguide.md#intro) + +### 6.2. [Continuous Integration](developerguide.md) + +We use Github Actions for continuous integration. No setup will be required for users who fork from the main The Schwarzenegger repository. + +Whenever you create a pull request to the main repository for The Schwarzenegger: + +* Various checks will automatically be executed on your pull request. +* If any checks fail, click on it to view the cause of the error, and fix it in your branch before pushing it again. +* Ensure that all checks pass before merging your pull request. + +[Return to Top](developerguide.md#intro) + +### 6.3. [Coverage Report](developerguide.md) + +We use the IntelliJ IDEA’s coverage analysis tool for coverage reporting. A tutorial on how to install and use this tool can be found [here](https://www.youtube.com/watch?v=yNYzZvyA2ik). + +[Return to Top](developerguide.md#intro) + +### 6.4. [Making a Release](developerguide.md) + +You can follow the steps below to make a new release: 1. Generate the JAR file using Gradle by opening a terminal in the project’s root directory, and run the command: + +* Windows: `gradlew clean shadowJar` +* MacOS/Linux: `./gradlew clean shadowJar` + 1. Find the JAR file in the `build/libs` directory. + 2. Tag the repository with the new version number \(e.g. `v2.1`\). + 3. Create a new release using Github and upload the JAR file found in step 3. + +[Return to Top](developerguide.md#intro) + +### 6.5. [Managing Dependencies](developerguide.md) + +Currently, the [Gson library](developerguide.md#https://github.com/google/gson) is being used for JSON parsing, and the [Apache Commons Lang](developerguide.md#https://commons.apache.org/proper/commons-lang) for being used for string processing in The Schwarzenegger. Below are 2 ways to manage these dependencies. + +* Use Gradle to manage and automatically download dependencies \(Recommended\). +* Manually download and include those libraries in the repo \(this requires extra work and bloats the repo size\). + +[Return to Top](developerguide.md#intro) + +## Appendices + +### Appendix A: Product Scope + +**Target user profile**: + +* Can type fast. +* Is comfortable with using command line interface. +* Gyms regularly +* Keeps track of their diet. + +**Value Proposition**: + +* Manages workout and diet faster with greater efficiency than a typical GUI based fitness manager application. +* Gives users health advice based on their calorie intake of the day and weight expectation. + +[Return to Top](developerguide.md#intro) + +### Appendix B: User Stories + +| Priority | Version | As a ... | I want to ... | So that I can ... | +| :--- | :--- | :--- | :--- | :--- | +| `HIGH` | v1.0 | New user | View the available commands easily | Learn more about the product before I use it | +| `HIGH` | v1.0 | New user | Create a user profile | Add a new profile to store my data | +| `HIGH` | v1.0 | User | View my profile in the database | Reference my added data and know my fitness classification | +| `HIGH` | v1.0 | User | Save my profile into the database | Retrieve it in subsequent launches of the app | +| `HIGH` | v1.0 | User | Load my profile from the database at the start of the app | view my added user profile | +| `HIGH` | v1.0 | User | Delete my profile from the database | Correct accidental typos | +| `HIGH` | v1.0 | User | Create a new workout session | Start a recorded workout session | +| `HIGH` | v1.0 | User | Add moves into a workout session | Personalise and record moves in each workout session | +| `HIGH` | v1.0 | User | Delete workout session record | Correct accidental typos | +| `HIGH` | v1.0 | User | End my current workout session | Be sure that my workout has ended | +| `HIGH` | v1.0 | User | Check my current workout session record | Do my workout and keep track of everything easily | +| `HIGH` | v1.0 | User | List out all my past diet session records | Check what I have eaten in the past | +| `HIGH` | v1.0 | User | Create a diet session with date and tags | Identify when I ate which meal | +| `HIGH` | v1.0 | User | Add different kinds of food into my diet | Keep track fo what I eat | +| `HIGH` | v1.0 | User | Save my diet records | View it next time | +| `MEDIUM` | v1.0 | User | Edit user profile | Change my data if something changes | +| `MEDIUM` | v2.0 | User | Clear all my diet sessions | Clear memory space on my storage to store new things | +| `MEDIUM` | v2.0 | User | Clear all my workout session records | Clear all past redundant data | +| `LOW` | v2.0 | User | Search for past workout sessions | Easily filter through the data that I don't need | +| `LOW` | v2.0 | User | Search for my past diet sessions | See whether I have been eating properly lately | +| `LOW` | v2.1 | User | View how much more I need to eat in a day | plan my later meals easier | +| `LOW` | v2.1 | User | Get recommendation on my weight expectation | Adjust accordingly to achieve the Normal Weight BMI classification | + +[Return to Top](developerguide.md#intro) + +### Appendix C: Non-Functional Requirements + +Below are the non-functional requirements of The Schwarzenegger: 1. Should work on any mainstream OS as long as it has Java `11` or above installed. 2. A user with above average typing speed for regular English text \(i.e. not code, not system admin commands\) should be able to accomplish most of the tasks faster using commands than a program that uses the mouse. 3. Should not require user to install program file. 4. Should work for single user. 5. Should be able to run without internet connection. + +[Return to Top](developerguide.md#intro) + +### Appendix D: Glossary + +* _Mainstream OS_ - Windows, Linux, Unix, MacOS + +[Return to Top](developerguide.md#intro) + +### Appendix E: Supported Formats of Date Input + +Here shows all 12 valid formats. + +```text +`yyyyMMdd HH:mm` +`yyyy-MM-dd HH:mm` +`yyyy MM dd HH:mm` + +`yyyyMMdd HHmm` +`yyyy-MM-dd HHmm` +`yyyy MM dd HHmm` + +`yyyyMMdd` +`yyyy-MM-dd` +`yyyy MM dd` + +`dd MM yyyy` +`ddMMyyyy` +`dd-MM-yyyy` +``` + +[Return to Top](developerguide.md#intro) + diff --git a/docs/pictures/Shukai/FoodItemAdd.puml b/docs/pictures/Shukai/FoodItemAdd.puml new file mode 100644 index 0000000000..aba32f84f8 --- /dev/null +++ b/docs/pictures/Shukai/FoodItemAdd.puml @@ -0,0 +1,31 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +actor user +activate DietSession +user -> Ui++ : Add FoodItem Command +Ui --> DietSession -- : input + +ref over DietSession, DietSessionParser, CommandLib: parse user input in Diet Session Menu + +DietSession -> Command ++ : execute(foodList) +Command -> Food ** +activate Food +Food --> Command -- : new Food Item +Command -> DietSession: foodList.add(new Food Item) +DietSession --> Command +deactivate Food +Command --> DietSession -- + +deactivate Command + +DietSession -> CommandResult ++ : getFeedbackMessage(result) +CommandResult --> DietSession -- : message +DietSession -> Ui ++ : showToUser(message) +destroy CommandResult +Ui --> user +destroy Command +deactivate Ui + +@enduml diff --git a/docs/pictures/Shukai/FoodItemClear.puml b/docs/pictures/Shukai/FoodItemClear.puml new file mode 100644 index 0000000000..1ce3f156ed --- /dev/null +++ b/docs/pictures/Shukai/FoodItemClear.puml @@ -0,0 +1,38 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml +actor user +activate DietSession +user -> Ui++ : Clear FoodItem Command +Ui --> DietSession -- : input + +ref over DietSession, DietSessionParser, CommandLib: parse user input in Diet Session Menu + +DietSession -> Command ++ : execute() +Command -> Ui ++ : checkConfirmation() +Ui --> Command -- + alt isConfirmed + Command -> DietSession: foodList.clear() + DietSession --> Command + Command --> DietSession : result + deactivate Command + + DietSession -> CommandResult ++ : getFeedbackMessage(result) + destroy Command + CommandResult --> DietSession -- : message + DietSession -> Ui ++ : showToUser(message) + Ui --> user + deactivate Ui + + else else + DietSession -> CommandResult ++ : getFeedbackMessage(result) + CommandResult --> DietSession -- : message + DietSession -> Ui ++ : showToUser(message) + +Ui --> user +deactivate Ui + end +destroy CommandResult + + +@enduml \ No newline at end of file diff --git a/docs/pictures/Shukai/FoodItemDelete.puml b/docs/pictures/Shukai/FoodItemDelete.puml new file mode 100644 index 0000000000..03b7d523bd --- /dev/null +++ b/docs/pictures/Shukai/FoodItemDelete.puml @@ -0,0 +1,30 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml +actor user +activate DietSession +user -> Ui++ : Delete FoodItem Command +Ui --> DietSession -- : input + +ref over DietSession, DietSessionParser, CommandLib: parse user input in Diet Session Menu + +DietSession -> Command ++ : execute(foodList) +Command -> Food ** +activate Food +Food --> Command -- : Target Food Item +Command -> DietSession: foodList.delete(Target Food Item) +DietSession --> Command +deactivate Food +Command --> DietSession -- + +deactivate Command + +DietSession -> CommandResult ++ : getFeedbackMessage(result) +CommandResult --> DietSession -- : message +DietSession -> Ui ++ : showToUser(message) +destroy CommandResult +Ui --> user +destroy Command +deactivate Ui + +@enduml diff --git a/docs/pictures/Shukai/FoodItemList.puml b/docs/pictures/Shukai/FoodItemList.puml new file mode 100644 index 0000000000..b689afed5e --- /dev/null +++ b/docs/pictures/Shukai/FoodItemList.puml @@ -0,0 +1,24 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml +actor user +activate DietSession +user -> Ui++ : List FoodItem Command +Ui --> DietSession -- : input + +ref over DietSession, DietSessionParser, CommandLib: parse user input in Diet Session Menu + +DietSession -> Command ++ : execute() +Command --> DietSession -- + +deactivate Command + +DietSession -> CommandResult ++ : getFeedbackMessage(result) +CommandResult --> DietSession -- : message +DietSession -> Ui ++ : showToUser(message) +destroy CommandResult +destroy Command +Ui --> user +deactivate Ui + +@enduml \ No newline at end of file diff --git a/docs/pictures/Shukai/FoodItemSearch.png b/docs/pictures/Shukai/FoodItemSearch.png new file mode 100644 index 0000000000..3d5e57efca Binary files /dev/null and b/docs/pictures/Shukai/FoodItemSearch.png differ diff --git a/docs/pictures/Shukai/FoodItemSearch.puml b/docs/pictures/Shukai/FoodItemSearch.puml new file mode 100644 index 0000000000..a864e423ee --- /dev/null +++ b/docs/pictures/Shukai/FoodItemSearch.puml @@ -0,0 +1,25 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml +actor user +activate DietSession +user -> Ui++ : Search FoodItem Command +Ui --> DietSession -- : input + +ref over DietSession, DietSessionParser, CommandLib: parse user input in Diet Session Menu + +DietSession -> Command ++ : execute() +Command --> DietSession -- + +deactivate Command + +DietSession -> CommandResult ++ : getFeedbackMessage(result) +CommandResult --> DietSession -- : message +DietSession -> Ui ++ : showToUser(message) +destroy CommandResult +destroy Command + +Ui --> user +deactivate Ui + +@enduml \ No newline at end of file diff --git a/docs/pictures/Shukai/ParseInput.puml b/docs/pictures/Shukai/ParseInput.puml new file mode 100644 index 0000000000..c7b6344338 --- /dev/null +++ b/docs/pictures/Shukai/ParseInput.puml @@ -0,0 +1,13 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +mainframe sd parse user input in Profile Menu + + ProfileSession -> ProfileParser ++ : parseCommand(input) + ProfileParser --> ProfileSession -- : parsedInput + + ProfileSession -> CommandLib ++ : getCommand(parsedInput) + CommandLib --> ProfileSession -- : command + +@enduml diff --git a/docs/pictures/UG_screenshots/add-new-food-item-step-1.JPG b/docs/pictures/UG_screenshots/add-new-food-item-step-1.JPG new file mode 100644 index 0000000000..8c6615f455 Binary files /dev/null and b/docs/pictures/UG_screenshots/add-new-food-item-step-1.JPG differ diff --git a/docs/pictures/UG_screenshots/main-help-step-1.png b/docs/pictures/UG_screenshots/main-help-step-1.png new file mode 100644 index 0000000000..0ad02a6a12 Binary files /dev/null and b/docs/pictures/UG_screenshots/main-help-step-1.png differ diff --git a/docs/pictures/UG_screenshots/new-dietsession-step-1.png b/docs/pictures/UG_screenshots/new-dietsession-step-1.png new file mode 100644 index 0000000000..a2f4b914bd Binary files /dev/null and b/docs/pictures/UG_screenshots/new-dietsession-step-1.png differ diff --git a/docs/pictures/Zeon/CreateDietSession.puml b/docs/pictures/Zeon/CreateDietSession.puml new file mode 100644 index 0000000000..6af387b169 --- /dev/null +++ b/docs/pictures/Zeon/CreateDietSession.puml @@ -0,0 +1,37 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml +actor user +activate DietManager +user -> DietManager : "new" +DietManager -> DietManagerParser : parse(userInput) +activate DietManagerParser +DietManagerParser --> DietManager : commParts[] +deactivate DietManagerParser + +DietManager -> CommandLib : getCommand(commParts[0]) +activate CommandLib +CommandLib --> DietManager : command +deactivate CommandLib + +DietManager -> DietSessionCreate : execute() +activate DietSessionCreate + +DietSessionCreate -> DietSession : start() +activate DietSession +DietSession -> DietStorage : saveToFile(filePath, storage, ds) +activate DietStorage +DietStorage -> DietSession +deactivate DietStorage +DietSession --> DietSessionCreate +deactivate DietSession +DietSessionCreate --> DietManager : commandResult +destroy DietSession +deactivate DietSessionCreate +DietManager -> DietManagerUi : showToUser(message) +destroy DietSessionCreate +activate DietManagerUi +DietManagerUi --> user : output +deactivate DietManagerUi + +@enduml \ No newline at end of file diff --git a/docs/pictures/Zeon/DietSessionClear.puml b/docs/pictures/Zeon/DietSessionClear.puml new file mode 100644 index 0000000000..4f54f4d3d0 --- /dev/null +++ b/docs/pictures/Zeon/DietSessionClear.puml @@ -0,0 +1,35 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +actor user +user -> DietManager : "clear" +activate DietManager +DietManager -> DietManagerParser : parse(userInput) +activate DietManagerParser +DietManagerParser --> DietManager : commParts[] +deactivate DietManagerParser +DietManager -> CommandLib : getCommand(commParts[0]) +activate CommandLib +CommandLib --> DietManager : command +deactivate CommandLib + +DietManager -> DietSessionClear : execute() +activate DietSessionClear +alt checkConfirmation checkConfirmation(DIET_MENU_NAME, CLEAR_RECORD) +DietSessionClear -> DietSessionClear : deleteAllFiles() +activate DietSessionClear +DietSessionClear --> DietSessionClear +deactivate DietSessionClear +DietSessionClear --> DietManager : commandResult +else else +DietSessionClear --> DietManager : commandResult +end + +deactivate DietSessionClear +destroy DietSessionClear +DietManager -> DietManagerUi : showToUser(message) +activate DietManagerUi +DietManagerUi --> user : output +deactivate DietManagerUi +@enduml diff --git a/docs/pictures/Zeon/DietSessionDelete.puml b/docs/pictures/Zeon/DietSessionDelete.puml new file mode 100644 index 0000000000..3b380c311b --- /dev/null +++ b/docs/pictures/Zeon/DietSessionDelete.puml @@ -0,0 +1,28 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +actor user +user -> DietManager : "delete [INDEX]" +activate DietManager +DietManager -> DietManagerParser : parse(userInput) +activate DietManagerParser +DietManagerParser --> DietManager : commParts[] +deactivate DietManagerParser +DietManager -> CommandLib : getCommand(commParts[0]) +activate CommandLib +CommandLib --> DietManager : command +deactivate CommandLib + +DietManager -> DietSessionDelete : execute() +activate DietSessionDelete + +DietSessionDelete --> DietManager : result +deactivate DietSessionDelete +destroy DietSessionDelete + +DietManager -> DietManagerUi : showToUser(message) +activate DietManagerUi +DietManagerUi --> user : output +deactivate DietManagerUi +@enduml diff --git a/docs/pictures/Zeon/DietSessionEdit.puml b/docs/pictures/Zeon/DietSessionEdit.puml new file mode 100644 index 0000000000..008e436168 --- /dev/null +++ b/docs/pictures/Zeon/DietSessionEdit.puml @@ -0,0 +1,36 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml +actor user +user -> DietManager : input +activate DietManager +DietManager -> DietManagerParser : parse(userInput) +activate DietManagerParser +DietManagerParser --> DietManager : commParts[] +deactivate DietManagerParser +DietManager -> CommandLib : getCommand(commParts[0]) +activate CommandLib +CommandLib --> DietManager : command +deactivate CommandLib + +DietManager -> DietSessionEdit : execute() +activate DietSessionEdit + +DietSessionEdit -> DietStorage : readDietSession() +activate DietStorage +DietStorage --> DietSessionEdit : dietSession +deactivate DietStorage + +DietSessionEdit -> DietSession : start() +activate DietSession +DietSession --> DietSessionEdit +deactivate DietSession +DietSessionEdit --> DietManager : result +destroy DietSession +deactivate DietSessionEdit +destroy DietSessionEdit +DietManager -> DietManagerUi : showToUser(message) +activate DietManagerUi +DietManagerUi --> user : output +deactivate DietManagerUi +@enduml \ No newline at end of file diff --git a/docs/pictures/Zeon/DietSessionList.puml b/docs/pictures/Zeon/DietSessionList.puml new file mode 100644 index 0000000000..c4f0df36b5 --- /dev/null +++ b/docs/pictures/Zeon/DietSessionList.puml @@ -0,0 +1,34 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +actor user +user -> DietManager : "List" +activate DietManager +DietManager -> DietManagerParser : parse(userInput) +activate DietManagerParser +DietManagerParser --> DietManager : commParts[] +deactivate DietManagerParser +DietManager -> CommandLib : getCommand(commParts[0]) +activate CommandLib +CommandLib --> DietManager : command +deactivate CommandLib + +DietManager -> DietSessionList : execute() +activate DietSessionList + DietSessionList -> DietSessionList : formatList() + activate DietSessionList + DietSessionList -> DietSessionList : formatRow() + activate DietSessionList + DietSessionList --> DietSessionList + deactivate DietSessionList + DietSessionList --> DietSessionList + deactivate DietSessionList +DietSessionList --> DietManager +deactivate DietSessionList +DietManager -> DietManagerUi : showToUser(message) +destroy DietSessionList +activate DietManagerUi +DietManagerUi --> user : output +deactivate DietManagerUi +@enduml \ No newline at end of file diff --git a/docs/pictures/Zeon/SearchDietSession.puml b/docs/pictures/Zeon/SearchDietSession.puml new file mode 100644 index 0000000000..7ac820d0c6 --- /dev/null +++ b/docs/pictures/Zeon/SearchDietSession.puml @@ -0,0 +1,47 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +actor user +user -> DietManager : "search" +activate DietManager +DietManager -> DietManagerParser : parse(userInput) +activate DietManagerParser +DietManagerParser --> DietManager : commParts[] +deactivate DietManagerParser +DietManager -> CommandLib : getCommand(commParts[0]) +activate CommandLib +CommandLib --> DietManager : command +deactivate CommandLib + +DietManager -> DietSessionSearch : execute() +activate DietSessionSearch +alt startDate.compareTo(endDate) > 0 +DietSessionSearch --> DietManager : commandResult +else else + DietSessionSearch -> DietSessionSearch : checkEmptyTag(searchResult, tag) + activate DietSessionSearch + DietSessionSearch --> DietSessionSearch + deactivate DietSessionSearch + DietSessionSearch -> DietSessionSearch : checkEmptyFolder(listOfFiles, searchResult) + activate DietSessionSearch + DietSessionSearch --> DietSessionSearch + deactivate DietSessionSearch + DietSessionSearch -> DietSessionSearch : addToSearchResult() + activate DietSessionSearch + DietSessionSearch -> DietSessionSearch : addRow() + activate DietSessionSearch + DietSessionSearch --> DietSessionSearch + deactivate DietSessionSearch + DietSessionSearch --> DietSessionSearch + deactivate DietSessionSearch +end + +DietSessionSearch --> DietManager : commandResult +deactivate DietSessionSearch +DietManager -> DietManagerUi : showToUser(message) +destroy DietSessionSearch +activate DietManagerUi +DietManagerUi --> user : output +deactivate DietManagerUi +@enduml diff --git a/docs/pictures/jinyang/ParseInputWorkoutSession.puml b/docs/pictures/jinyang/ParseInputWorkoutSession.puml new file mode 100644 index 0000000000..69c0312f68 --- /dev/null +++ b/docs/pictures/jinyang/ParseInputWorkoutSession.puml @@ -0,0 +1,14 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +mainframe sd parse user input in WorkoutSession + +activate WorkoutSession +WorkoutSession -> WorkoutSessionParser ++ : workoutSessionParser(input) +WorkoutSessionParser --> WorkoutSession -- : parsedInput + +WorkoutSession -> CommandLib ++ : getCommand(parsedInput) +CommandLib --> WorkoutSession -- : command + +@enduml \ No newline at end of file diff --git a/docs/pictures/jinyang/ReturnMsgToUser.jpg b/docs/pictures/jinyang/ReturnMsgToUser.jpg new file mode 100644 index 0000000000..4e7ec1ba60 Binary files /dev/null and b/docs/pictures/jinyang/ReturnMsgToUser.jpg differ diff --git a/docs/pictures/jinyang/ReturnMsgToUser.png b/docs/pictures/jinyang/ReturnMsgToUser.png new file mode 100644 index 0000000000..eff9d2b18d Binary files /dev/null and b/docs/pictures/jinyang/ReturnMsgToUser.png differ diff --git a/docs/pictures/jinyang/ReturnMsgToUser.puml b/docs/pictures/jinyang/ReturnMsgToUser.puml new file mode 100644 index 0000000000..9a59118c03 --- /dev/null +++ b/docs/pictures/jinyang/ReturnMsgToUser.puml @@ -0,0 +1,18 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +mainframe sd show message to user in WorkoutSession +actor user + +activate WorkoutSession +WorkoutSession -> CommandResult ++ : getFeedbackMessage(result) +CommandResult --> WorkoutSession -- : message +WorkoutSession -> CommonUi ++ : showToUser(message) +destroy CommandResult +CommonUi --> user +deactivate CommonUi + + +@enduml + diff --git a/docs/pictures/jinyang/WokroutSessionHelp.png b/docs/pictures/jinyang/WokroutSessionHelp.png new file mode 100644 index 0000000000..619a17b5e0 Binary files /dev/null and b/docs/pictures/jinyang/WokroutSessionHelp.png differ diff --git a/docs/pictures/jinyang/WokroutSessionHelp.puml b/docs/pictures/jinyang/WokroutSessionHelp.puml new file mode 100644 index 0000000000..9d83424014 --- /dev/null +++ b/docs/pictures/jinyang/WokroutSessionHelp.puml @@ -0,0 +1,30 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +user -> CommonUi++ : Help +CommonUi --> WorkoutSession -- : input + +activate WorkoutSession + +ref over WorkoutSession, WorkoutSessionParser, CommandLib: parse user input in WorkoutSession + +WorkoutSession -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutSession : Command +deactivate CommandLib + +WorkoutSession -> WorkoutSessionHelp : execute() +activate WorkoutSessionHelp +participant WorkoutSessionUi << Class >> +WorkoutSessionHelp -> WorkoutSessionUi : printHelp() +activate WorkoutSessionUi +WorkoutSessionUi --> WorkoutSessionHelp +deactivate WorkoutSessionUi + +WorkoutSessionHelp --> WorkoutSession : result +deactivate WorkoutSessionHelp + +ref over WorkoutSession, CommonUi, user: show message to user in WorkoutSession +@enduml \ No newline at end of file diff --git a/docs/pictures/jinyang/WorkoutSessionAdd.puml b/docs/pictures/jinyang/WorkoutSessionAdd.puml new file mode 100644 index 0000000000..cdaf67ed4a --- /dev/null +++ b/docs/pictures/jinyang/WorkoutSessionAdd.puml @@ -0,0 +1,31 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +user -> CommonUi++ : Add +CommonUi --> WorkoutSession -- : input + +activate WorkoutSession + +ref over WorkoutSession, WorkoutSessionParser, CommandLib: parse user input in WorkoutSession + +WorkoutSession -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutSession : Command +deactivate CommandLib + +WorkoutSession -> WorkoutSessionAdd : execute() +activate WorkoutSessionAdd +WorkoutSessionAdd-> WorkoutSession : exerciseList.add() +WorkoutSession --> WorkoutSessionAdd +WorkoutSessionAdd -> WorkoutSessionStorage : writeToStorage() +activate WorkoutSessionStorage +WorkoutSessionStorage --> WorkoutSessionAdd +deactivate WorkoutSessionStorage + +WorkoutSessionAdd --> WorkoutSession : result +deactivate WorkoutSessionAdd + +ref over WorkoutSession, CommonUi, user: show message to user in WorkoutSession +@enduml \ No newline at end of file diff --git a/docs/pictures/jinyang/WorkoutSessionDelete.puml b/docs/pictures/jinyang/WorkoutSessionDelete.puml new file mode 100644 index 0000000000..bb05785f91 --- /dev/null +++ b/docs/pictures/jinyang/WorkoutSessionDelete.puml @@ -0,0 +1,29 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +user -> CommonUi++ : Delete +CommonUi --> WorkoutSession -- : input + +activate WorkoutSession + +ref over WorkoutSession, WorkoutSessionParser, CommandLib: parse user input in WorkoutSession + + + +WorkoutSession -> WorkoutSessionDelete : execute() +activate WorkoutSessionDelete + +WorkoutSessionDelete-> WorkoutSession : exerciseList.remove() +WorkoutSession --> WorkoutSessionDelete +WorkoutSessionDelete -> WorkoutSessionStorage : writeToStorage() +activate WorkoutSessionStorage +WorkoutSessionStorage --> WorkoutSessionDelete +deactivate WorkoutSessionStorage + +WorkoutSessionDelete --> WorkoutSession : result +deactivate WorkoutSessionDelete + +ref over WorkoutSession, CommonUi, user: show message to user in WorkoutSession +@enduml \ No newline at end of file diff --git a/docs/pictures/jinyang/WorkoutSessionEnd.png b/docs/pictures/jinyang/WorkoutSessionEnd.png new file mode 100644 index 0000000000..04dac28b58 Binary files /dev/null and b/docs/pictures/jinyang/WorkoutSessionEnd.png differ diff --git a/docs/pictures/jinyang/WorkoutSessionEnd.puml b/docs/pictures/jinyang/WorkoutSessionEnd.puml new file mode 100644 index 0000000000..6cc37beebe --- /dev/null +++ b/docs/pictures/jinyang/WorkoutSessionEnd.puml @@ -0,0 +1,33 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +user -> CommonUi++ : End +CommonUi --> WorkoutSession -- : input + +activate WorkoutSession + +ref over WorkoutSession, WorkoutSessionParser, CommandLib: parse user input in WorkoutSession + +WorkoutSession -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutSession : Command +deactivate CommandLib + +WorkoutSession -> WorkoutSessionEnd : execute() +activate WorkoutSessionEnd +WorkoutSessionEnd-> WorkoutSessionEnd : setEndWorkoutSessionT() +activate WorkoutSessionEnd +WorkoutSessionEnd --> WorkoutSessionEnd +deactivate WorkoutSessionEnd +WorkoutSessionEnd -> WorkoutSessionStorage : writeToStorage() +activate WorkoutSessionStorage +WorkoutSessionStorage --> WorkoutSessionEnd +deactivate WorkoutSessionStorage + +WorkoutSessionEnd --> WorkoutSession : result +deactivate WorkoutSessionEnd + +ref over WorkoutSession, CommonUi, user: show message to user in WorkoutSession +@enduml \ No newline at end of file diff --git a/docs/pictures/jinyang/WorkoutSessionList.puml b/docs/pictures/jinyang/WorkoutSessionList.puml new file mode 100644 index 0000000000..5400e4ac71 --- /dev/null +++ b/docs/pictures/jinyang/WorkoutSessionList.puml @@ -0,0 +1,38 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +user -> CommonUi++ : List +CommonUi --> WorkoutSession -- : input + +activate WorkoutSession + +ref over WorkoutSession, WorkoutSessionParser, CommandLib: parse user input in WorkoutSession + +WorkoutSession -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutSession : Command +deactivate CommandLib + +WorkoutSession -> WorkoutSessionList : execute() +activate WorkoutSessionList +WorkoutSessionList -> WorkoutSessionList : printList() +activate WorkoutSessionList +WorkoutSessionList -> WorkoutSessionList : formatList() +activate WorkoutSessionList +WorkoutSessionList --> WorkoutSessionList +deactivate WorkoutSessionList +WorkoutSessionList --> WorkoutSessionList +deactivate WorkoutSessionList + +WorkoutSessionList -> WorkoutSessionStorage : writeToStorage() +activate WorkoutSessionStorage +WorkoutSessionStorage --> WorkoutSessionList +deactivate WorkoutSessionStorage + +WorkoutSessionList --> WorkoutSession : result +deactivate WorkoutSessionList + +ref over WorkoutSession, CommonUi, user: show message to user in WorkoutSession +@enduml \ No newline at end of file diff --git a/docs/pictures/jinyang/WorkoutSessionSearch.puml b/docs/pictures/jinyang/WorkoutSessionSearch.puml new file mode 100644 index 0000000000..3614a807f3 --- /dev/null +++ b/docs/pictures/jinyang/WorkoutSessionSearch.puml @@ -0,0 +1,51 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +user -> CommonUi++ : Search +CommonUi --> WorkoutSession -- : input + +activate WorkoutSession + +ref over WorkoutSession, WorkoutSessionParser, CommandLib: parse user input in WorkoutSession + +WorkoutSession -> WorkoutSessionSearch : execute() +activate WorkoutSessionSearch + +alt searchTerm.length() > 0 + WorkoutSessionSearch -> WorkoutSessionSearch : formatList() + activate WorkoutSessionSearch + WorkoutSessionSearch --> WorkoutSessionSearch : formattedString + deactivate WorkoutSessionSearch + alt SearchResult is not Empty + WorkoutSessionSearch -> CommandResult ++ : searchResult + CommandResult --> WorkoutSession -- : result + + else else + participant WorkoutSessionUi << Class >> + WorkoutSessionSearch -> WorkoutSessionUi : searchResultsEmpty() + activate WorkoutSessionUi + WorkoutSessionUi --> WorkoutSessionSearch : SEARCH_RESULTS_EMPTY + deactivate WorkoutSessionUi + WorkoutSessionSearch -> CommandResult ++ : SEARCH_RESULTS_EMPTY + CommandResult --> WorkoutSession -- : result + end +else else + participant WorkoutSessionUi << Class >> + WorkoutSessionSearch -> WorkoutSessionUi : searchInputError() + activate WorkoutSessionUi + WorkoutSessionUi --> WorkoutSessionSearch : SEARCH_INPUT_ERROR + deactivate WorkoutSessionUi + WorkoutSessionSearch -> CommandResult ++ : SEARCH_INPUT_ERROR + CommandResult --> WorkoutSession -- : result +end + + +deactivate WorkoutSessionSearch + +ref over WorkoutSession, CommonUi, user: show message to user in WorkoutSession + + + +@enduml \ No newline at end of file diff --git a/docs/pictures/khoa/AddProfile.puml b/docs/pictures/khoa/AddProfile.puml new file mode 100644 index 0000000000..2c89845810 --- /dev/null +++ b/docs/pictures/khoa/AddProfile.puml @@ -0,0 +1,35 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +actor user +activate ProfileSession +user -> CommonUi++ : Add Profile Command +CommonUi --> ProfileSession -- : input + +ref over ProfileSession, ProfileParser, CommandLib: parse user input in ProfileSession + +ProfileSession -> Command ++ : execute() +Command -> ProfileStorage ++ : loadData() +ProfileStorage --> Command -- : profile + +alt hasExistingProfile + Command --> ProfileSession : result +else else + Command -> ProfileParser ++ : extractCommandTagAndInfo(parsedInput) + ProfileParser --> Command -- : parsedInfo + Command -> Profile ** : parsedInfo + activate Profile + Profile --> Command -- : newProfile + Command -> ProfileStorage ++ : saveData(newProfile) + destroy Profile + ProfileStorage --> Command -- + Command --> ProfileSession -- : result +end + +deactivate Command +destroy Command + +ref over user, ProfileSession, CommonUi: show message to user in ProfileSession + +@enduml diff --git a/docs/pictures/khoa/DeleteProfile.puml b/docs/pictures/khoa/DeleteProfile.puml new file mode 100644 index 0000000000..4e1816bcf1 --- /dev/null +++ b/docs/pictures/khoa/DeleteProfile.puml @@ -0,0 +1,35 @@ +@startuml +!include ../../umldiagram/style.puml + +actor user +activate ProfileSession +user -> CommonUi++ : Delete Profile Command +CommonUi --> ProfileSession -- : input + +ref over ProfileSession, ProfileParser, CommandLib: parse user input in ProfileSession + +ProfileSession -> Command ++ : execute() +Command -> ProfileStorage ++ : loadData() +ProfileStorage --> Command -- : profile + +alt hasExistingProfile + Command -> CommonUi ++ : getConfirmation() + CommonUi --> Command -- : isConfirmed + + alt isConfirmed + Command -> ProfileStorage ++ : saveData(null) + ProfileStorage --> Command -- + Command --> ProfileSession: result + else else + Command --> ProfileSession: result + end +else else + Command --> ProfileSession -- : result +end + +deactivate Command +destroy Command + +ref over user, ProfileSession, CommonUi: show message to user in ProfileSession + +@enduml diff --git a/docs/pictures/khoa/EditProfile.puml b/docs/pictures/khoa/EditProfile.puml new file mode 100644 index 0000000000..28eb638eba --- /dev/null +++ b/docs/pictures/khoa/EditProfile.puml @@ -0,0 +1,39 @@ +@startuml +!include ../../umldiagram/style.puml + +actor user +activate ProfileSession +user -> CommonUi++ : Edit Profile Command +CommonUi --> ProfileSession -- : input + +ref over ProfileSession, ProfileParser, CommandLib: parse user input in ProfileSession + +ProfileSession -> Command ++ : execute() +Command -> ProfileStorage ++ : loadData() +ProfileStorage --> Command -- : profile + +alt hasExistingProfile + Command -> ProfileParser ++ : extractCommandTagAndInfo(parsedInput) + ProfileParser --> Command -- : parsedInfo + Command -> Profile ** : parsedInfo + activate Profile + Profile --> Command -- : newProfile + Command -> Profile ++ : equals() + Profile --> Command -- : isEqual + + alt isEqual + Command -> ProfileStorage ++ : saveData(newProfile) + ProfileStorage --> Command -- + Command --> ProfileSession : result + else else + Command --> ProfileSession : result + end + destroy Profile +else else + Command --> ProfileSession -- : result +end +destroy Command + +ref over user, ProfileSession, CommonUi: show message to user in ProfileSession + +@enduml diff --git a/docs/pictures/khoa/MainMenu.puml b/docs/pictures/khoa/MainMenu.puml new file mode 100644 index 0000000000..59cd57c1d1 --- /dev/null +++ b/docs/pictures/khoa/MainMenu.puml @@ -0,0 +1,32 @@ +@startuml +!include ../../umldiagram/style.puml + +actor user +activate Duke +user -> CommonUi : Command +CommonUi --> Duke -- : input + +Duke -> CommonParser ++ : parseCommand(input) +CommonParser --> Duke -- : parsedInput + +Duke -> CommandLib ++ : getCommand(parsedInput) +CommandLib --> Duke -- : command + +alt command is "profile" + Duke -> ProfileSession ** + activate ProfileSession + ProfileSession --> Duke -- + ||| +else command is "diet" + Duke -> DietManager ** + activate DietManager + DietManager --> Duke -- + ||| +else command is "workout" + Duke -> WorkoutManager ** + activate WorkoutManager + WorkoutManager --> Duke -- + ||| +end + +@enduml diff --git a/docs/pictures/khoa/ParseInput.puml b/docs/pictures/khoa/ParseInput.puml new file mode 100644 index 0000000000..5958c2b1fb --- /dev/null +++ b/docs/pictures/khoa/ParseInput.puml @@ -0,0 +1,14 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +mainframe sd parse user input in ProfileSession + + activate ProfileSession + ProfileSession -> ProfileParser ++ : parseCommand(input) + ProfileParser --> ProfileSession -- : parsedInput + + ProfileSession -> CommandLib ++ : getCommand(parsedInput) + CommandLib --> ProfileSession -- : command + +@enduml diff --git a/docs/pictures/khoa/ShowMessage.png b/docs/pictures/khoa/ShowMessage.png new file mode 100644 index 0000000000..4f714bf7e6 Binary files /dev/null and b/docs/pictures/khoa/ShowMessage.png differ diff --git a/docs/pictures/khoa/ShowMessage.puml b/docs/pictures/khoa/ShowMessage.puml new file mode 100644 index 0000000000..bd53c10dee --- /dev/null +++ b/docs/pictures/khoa/ShowMessage.puml @@ -0,0 +1,15 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include ../../umldiagram/style.puml + +mainframe sd parse user input in ProfileSession + actor user + activate ProfileSession + ProfileSession -> CommandResult ++ : getFeedbackMessage(result) + CommandResult --> ProfileSession -- : message + ProfileSession -> CommonUi ++ : showToUser(message) + destroy CommandResult + CommonUi --> user + deactivate CommonUi + +@enduml diff --git a/docs/pictures/khoa/ViewProfile.puml b/docs/pictures/khoa/ViewProfile.puml new file mode 100644 index 0000000000..7052b22d04 --- /dev/null +++ b/docs/pictures/khoa/ViewProfile.puml @@ -0,0 +1,32 @@ +@startuml +!define PLANTUML_HOME %dirpath()/../../umldiagram +!include PLANTUML_HOME/style.puml + +actor user +activate ProfileSession +user -> CommonUi++ : View Profile Command +CommonUi --> ProfileSession -- : input + +ref over ProfileSession, ProfileParser, CommandLib: parse user input in ProfileSession + +ProfileSession -> Command ++ : execute() +Command -> ProfileStorage ++ : loadData() +ProfileStorage --> Command -- : profile + +alt hasExistingProfile + Command -> DietManager** + activate DietManager + DietManager --> Command -- + Command -> DietManager ++ : getTodayTotalCalories() + DietManager --> Command -- : calories + Command --> ProfileSession : result + destroy DietManager +else else + Command --> ProfileSession --: result +end + +deactivate Command +destroy Command + +ref over user, ProfileSession, CommonUi: show message to user in ProfileSession +@enduml diff --git a/docs/pictures/screenshots-labelling-template.pptx b/docs/pictures/screenshots-labelling-template.pptx new file mode 100644 index 0000000000..ad5602c1d3 Binary files /dev/null and b/docs/pictures/screenshots-labelling-template.pptx differ diff --git a/docs/pictures/zesong/Architecture.puml b/docs/pictures/zesong/Architecture.puml new file mode 100644 index 0000000000..a03fa76285 --- /dev/null +++ b/docs/pictures/zesong/Architecture.puml @@ -0,0 +1,28 @@ +@startuml +!include ../../umldiagram/style.puml + +rectangle "User" { +} + +rectangle "body" { +rectangle "Ui" +rectangle "CommandLibrary" +rectangle "Model" +rectangle "DietManager" +rectangle "ProfileManager" +rectangle "WorkoutManager" +} + +Ui --> CommandLibrary +CommandLibrary -down-> ProfileManager +CommandLibrary -down-> DietManager +CommandLibrary -down-> WorkoutManager + +rectangle "Storage" { +} +rectangle "Duke" { +} +User -down-> Ui +Duke -> Storage +Duke -down-> body +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/DeleteWS.puml b/docs/pictures/zesong/DeleteWS.puml new file mode 100644 index 0000000000..11153004e6 --- /dev/null +++ b/docs/pictures/zesong/DeleteWS.puml @@ -0,0 +1,29 @@ +@startuml +!include ../../umldiagram/style.puml +actor user +user -> WorkoutManager : input +activate WorkoutManager + +WorkoutManager -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutManager : Command +deactivate CommandLib + +WorkoutManager -> DeleteCommand : execute() +activate DeleteCommand + +DeleteCommand -> PastRecordList : delete() +activate PastRecordList +PastRecordList -> workoutManagerStorage: writeToStorage() +activate workoutManagerStorage +workoutManagerStorage --> PastRecordList +deactivate workoutManagerStorage +PastRecordList --> DeleteCommand +deactivate PastRecordList + +DeleteCommand --> WorkoutManager : CommandResult +deactivate DeleteCommand + +WorkoutManager --> user : output + +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/EditWS.puml b/docs/pictures/zesong/EditWS.puml new file mode 100644 index 0000000000..0cef6ffe2a --- /dev/null +++ b/docs/pictures/zesong/EditWS.puml @@ -0,0 +1,36 @@ +@startuml +!include ../../umldiagram/style.puml +actor user +user -> WorkoutManager : input +activate WorkoutManager + +WorkoutManager -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutManager : Command +deactivate CommandLib + +WorkoutManager -> EditCommand : execute() +activate EditCommand + +EditCommand -> PastRecordList : edit() +activate PastRecordList +PastRecordList --> EditCommand : filePath +deactivate PastRecordList + +EditCommand --> workoutSession ** : filePath +activate workoutSession +workoutSession --> EditCommand: loaded workoutSession +deactivate workoutSession + +EditCommand -> workoutSession : start() +activate workoutSession +workoutSession --> EditCommand +deactivate workoutSession + + +EditCommand --> WorkoutManager : CommandResult +deactivate EditCommand + +WorkoutManager --> user : output + +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/ListWS.puml b/docs/pictures/zesong/ListWS.puml new file mode 100644 index 0000000000..9aba8a2f7a --- /dev/null +++ b/docs/pictures/zesong/ListWS.puml @@ -0,0 +1,29 @@ +@startuml +!include ../../umldiagram/style.puml +actor user +user -> WorkoutManager : input +activate WorkoutManager + +WorkoutManager -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutManager : Command +deactivate CommandLib + +WorkoutManager -> ListCommand : execute() +activate ListCommand + +ListCommand -> PastRecordList : list() +activate PastRecordList +PastRecordList -> WorkoutManagerParser: listCommandParse() +activate WorkoutManagerParser +WorkoutManagerParser --> PastRecordList: ConditionList +deactivate WorkoutManagerParser +PastRecordList --> ListCommand : formated list +deactivate PastRecordList + +ListCommand --> WorkoutManager : CommandResult +deactivate ListCommand + +WorkoutManager -> user : output + +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/Logic.puml b/docs/pictures/zesong/Logic.puml new file mode 100644 index 0000000000..50973a3d90 --- /dev/null +++ b/docs/pictures/zesong/Logic.puml @@ -0,0 +1,20 @@ +@startuml +!include ../../umldiagram/style.puml + +rectangle "Parser"{ +rectangle "XYZCommandParser" +} + +rectangle "CommandLib" + +rectangle "Commands"{ +rectangle "Command" +rectangle "XYZCommand" +} +rectangle "CommandResult" + +XYZCommandParser ..> CommandLib +CommandLib ..> XYZCommand: executes +XYZCommand -left-|> Command +XYZCommand .right.> CommandResult: creates +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/NewWS.puml b/docs/pictures/zesong/NewWS.puml new file mode 100644 index 0000000000..848e98f053 --- /dev/null +++ b/docs/pictures/zesong/NewWS.puml @@ -0,0 +1,38 @@ +@startuml +!include ../../umldiagram/style.puml +actor user +activate WorkoutManager +user -> WorkoutManager +WorkoutManager -> WorkoutManagerParser : parse() +activate WorkoutManagerParser +WorkoutManagerParser --> WorkoutManager : parsed input +deactivate WorkoutManagerParser + +WorkoutManager -> Command : execute() +activate Command + +Command -> PastRecordList : add() +activate PastRecordList + +PastRecordList -> WorkoutManagerStorage : add() +activate WorkoutManagerStorage +WorkoutManagerStorage --> PastRecordList : filePath +deactivate WorkoutManagerStorage + +PastRecordList --> Command : filePath +deactivate PastRecordList + +Command -> WorkoutSession ** : filePath +activate WorkoutSession + WorkoutSession --> Command -- : newWorkoutSession +Command -> WorkoutSession : start() +activate WorkoutSession +WorkoutSession --> Command +deactivate WorkoutSession + +Command --> WorkoutManager : CommandResult +deactivate Command + +WorkoutManager --> user : output + +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/SearchWS.puml b/docs/pictures/zesong/SearchWS.puml new file mode 100644 index 0000000000..c726f221af --- /dev/null +++ b/docs/pictures/zesong/SearchWS.puml @@ -0,0 +1,29 @@ +@startuml +!include ../../umldiagram/style.puml +actor user +activate WorkoutManager +user -> WorkoutManager + +WorkoutManager -> CommandLib : get() +activate CommandLib +CommandLib --> WorkoutManager : Command +deactivate CommandLib + +WorkoutManager -> Command : execute() +activate Command + +Command -> PastRecordList : search() +activate PastRecordList +PastRecordList -> WorkoutManagerParser : parse() +activate WorkoutManagerParser +WorkoutManagerParser --> PastRecordList : list of predicates +deactivate WorkoutManagerParser +PastRecordList --> Command: filtered formatted list +deactivate PastRecordList + +Command --> WorkoutManager : CommandResult +deactivate Command + +WorkoutManager --> user : output + +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/Ui.puml b/docs/pictures/zesong/Ui.puml new file mode 100644 index 0000000000..cd0ce9d866 --- /dev/null +++ b/docs/pictures/zesong/Ui.puml @@ -0,0 +1,18 @@ +@startuml +skinparam Shadowing false +hide circle + +class CommonUi { + void showToUser() + void getCommand() + boolean checkConfirmation() + String helpFormatter(String, String, String) +} + +CommonUi ^-- ClassWorkoutManagerUi +CommonUi ^-- ClassDietManagerUi +CommonUi ^-- ClassWorkoutSessionUi +CommonUi ^-- ClassDietSessionUi +CommonUi ^-- ClassProfileUi + +@enduml \ No newline at end of file diff --git a/docs/pictures/zesong/model.puml b/docs/pictures/zesong/model.puml new file mode 100644 index 0000000000..3aaa2a86e0 --- /dev/null +++ b/docs/pictures/zesong/model.puml @@ -0,0 +1,25 @@ +@startuml +!include ../../umldiagram/style.puml +rectangle "PastRecordList" { +} +rectangle "Past workout records" { +rectangle "pastRecord" +} + +rectangle "Food" { +rectangle "food" +} + +rectangle "Exercises" { +rectangle "exercise" +} + +rectangle "ExerciseList" { +} + +rectangle "Profile" { +rectangle "profile" +} +PastRecordList *--> pastRecord: contains +ExerciseList *--> exercise: contains +@enduml \ No newline at end of file diff --git a/docs/team/README.md b/docs/team/README.md new file mode 100644 index 0000000000..d912e1fd8a --- /dev/null +++ b/docs/team/README.md @@ -0,0 +1,2 @@ +# team + diff --git a/docs/team/aboutus.md b/docs/team/aboutus.md new file mode 100644 index 0000000000..a7e58872e0 --- /dev/null +++ b/docs/team/aboutus.md @@ -0,0 +1,10 @@ +# The Schwarzenegger - About Us + +| Display | Name | Github Profile | Portfolio | +| :--- | :---: | :---: | :---: | +| ![](https://i.imgur.com/W2LwmOA.png) | Nguyen Tien Khoa | [Github](https://github.com/tienkhoa16) | [Portfolio](tienkhoa16.md) | +| ![](https://i.imgur.com/3pna8mw.png) | Zeon Chua Feiyi | [Github](https://github.com/CFZeon) | [Portfolio](cfzeon.md) | +| ![](https://avatars0.githubusercontent.com/u/57080256?s=400&u=677af6062d8d0cdeae80ee9f00b50aa01e2c4b84&v=4) | Zhang Shukai | [Github](https://github.com/zsk612) | [Portfolio](zsk612.md) | +| ![](https://i.imgur.com/1mhi7tF.jpeg) | Yu Jinyang | [Github](https://github.com/yujinyang1998) | [Portfolio](yujinyang1998.md) | +| ![](https://avatars1.githubusercontent.com/u/53573749?s=400&u=624be60ee5061b89cabc5c04b54795fdd4956a72&v=4) | Wang Zesong | [Github](https://github.com/wgzesg) | [Portfolio](wgzesg.md) | + diff --git a/docs/team/cfzeon.md b/docs/team/cfzeon.md new file mode 100644 index 0000000000..7582b32dbb --- /dev/null +++ b/docs/team/cfzeon.md @@ -0,0 +1,75 @@ +# Zeon Chua Feiyi - Project Portfolio Page + +## PROJECT: The Schwarzenegger + +## Overview + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +### Summary of Contributions + +* **Major enhancement**: + * **Search Diet Sessions** + * Functionality: This enhancement allows the user to search for specific diet sessions within a date range of a specified tag. + * Justification: This feature improves the product a lot more significantly as a user would want to search things they ate within a date range, or whatever they ate within a specified tag i.e. breakfast. This makes things more convenient for users as they would not need to manually filter through so much information to find what they need. + * Highlights: The search function allows for 3 parameters, the start date, the end date and the tag. This makes it very easy to filter for specific information. + * **Edit Diet Session** + * Functionality: This enhancement lets users access and edit diet sessions that they have created before by searching through the save folder and loading the specified file. + * Justification: The feature allows users to edit a previously created diet session instead of having to delete it and creating another one from scratch. + * Highlights: Users can edit the file like they would with a new file, as it is an instantiation of the same DietSession. + * **Clear all diet sessions** + * Functionality: This enhancement allows users to remove all diet session records from their local storage. + * Justification: The feature provides an accessible interface to allow users to delete all of their diet sessions at one go. + * Highlights: Clear iterates through every file in the folder then deletes them one by one. + * **Consistent file saving** + * Functionality: This enhancement saves the diet session after every command that a user inputs into diet session. + * Justification: The feature prevents users from accidentally deleting their data during usage. + * Highlights: Users are protected against accidental file modifications and errors. +* **Minor enhancement**: + * **Get Total Calories for a specific date** + + * This feature opens every file within a specified date and sums up the total calories within the files with matching dates. It is used to calculate the required calories left to consume to reach the daily requirement in the view command in profile. + * **Code contribution**: [Functional and Test code](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=CFZeon&tabRepo=AY2021S1-CS2113T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code) + * **Other contributions**: + * Documentation: + * Added instructions and format for all diet manager related commands in the User Guide. \(Pull Request: [\#84](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/84/files), [\#140](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/140)\) + * Added and updated instructions and format for all diet manager related commands in the Developer Guide. \(Pull Request: [\#149](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/149)\) + * Added and updated user stories in the Developer Guide \(Pull Request: [\#219](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/219)\) + * Community: + * Review Developer Guide from other teams in the class\(with non-trival review comments.\) \(ModTracker: [\#62](https://github.com/nus-cs2113-AY2021S1/tp/pull/62)\) + * Reported bugs for other teams in the class \(example: [ped](https://github.com/CFZeon/ped/issues)\) + * Evaluated code, tested and provided feedback to teammates during many online meetups. + + **Contributions to User Guide** + + ```text + Below are my contributions to the User Guide. + They show my ability to write documentation for end-users. + ``` + + * [Viewing Help](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-help) + * [Starting a New Diet Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-start) + * [Listing All Past Diet Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-list) + * [Editing a Past Diet Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-edit) + * [Deleting a Past Diet Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-delete) + * [Clearing All Past Diet Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-clear) + * [Searching for Past Diet Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-search) + * [Returning to Main Menu](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#diet-end) + + **Contributions to Developer Guide** + + ```text + Below are my contributions to the Developer Guide. + They show my ability to write technical documentation for other developers. + ``` + + * [List Out All Commands](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#list-out-all-commands) + * [Start Recordings Diet Data](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#start-recording-diet-data) + * [List All Past Diet Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#list-all-past-diet-sessions) + * [Edit a Past Diet Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#edit-a-past-diet-session) + * [Delete a Past Diet Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#delete-a-past-diet-session) + * [Clear All Past Diet Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#clear-all-past-diet-sessions) + * [Search for Past Diet Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#search-for-past-diet-sessions) + * [Exit the Diet Manager](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#exit-the-diet-manager) + * [Appendix B: User Stories](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#appendix-b-user-stories) + 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/tienkhoa16.md b/docs/team/tienkhoa16.md new file mode 100644 index 0000000000..953ba3ade7 --- /dev/null +++ b/docs/team/tienkhoa16.md @@ -0,0 +1,48 @@ +# tienkhoa16 + +## Nguyen Tien Khoa's Project Portfolio Page + +## Project: The Schwarzenegger + +### Overview + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +### Summary of Contributions + +* **Major Enhancement**: + * **Implemented the CommonUi class to support the interactions with users** + * Functionality: This enhancement supports the reading of user's commands and presenting the output messages. + * Justification: Creating a class to handle the output formatting helps to alleviate the concerns of output displaying from the logic classes. In addition, the output formatting can be changed easily and consistently throughout the software development life cycles. + * Highlights: Although the implementation of this class is not technically challenging, it is used frequently by other classes and plays an essential part to maintain the good coding quality. + * **Implemented the enhancement to allow users to view fitness data** + * Functionality: This enhancement enables the users to view their fitness data, including: physique measures, fitness classification \(e.g. underweight, overweight, obesity, etc.\), and the health advice based on the calorie intake of the day. In addition, the users also receive the advice on the weight target they set in the app. For example, if they are currently overweight, they will be advised to set the expected weight within the range of the normal weight. + * Justification: This enhancement is important as it informs the user on their fitness and how far they are from the diet goal. Based on the information and advice given in the app, the users can act on their daily diet to achieve the goal. + * Highlights: The development of this feature requires careful thoughts of the user story to provide the comprehensive information that meets the users' needs. + * **Implemented the enhancement to parse the users' commands** + * Functionality: This enhancement supports the parsing of users' commands for the program to execute. The arguments in the command can be specified in any order. + * Justification: This enhancement is essential as it identifies the operations to perform and extracts the relevant data from the users' commands. The parser can inteprete the arguments in any order, which gives the users more flexibility and thus enhances the user experience. + * Highlights: To parse the arguments arranged in an unknown order requires more efforts than to parse the arguments arranged in a fixed, pre-defined order. Efforts were spent on the string processing to identify the start and end indices and the type of the arguments. In addition, the implementation of this parser allows future extensions to support more types of arguments with minimum changes in the code. +* **Minor Enhancement**: + * Implemented `CommandResult` class to handle user's command execution result. + * Implemented `SchwarzeneggerLogger` with singleton patter to log program execution into file. [\#60](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/60) + * Implemented `ExceptionHandler` class to create consistent format to handle exception. [\#119](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/119) + * Added `logic/commands/main` to execute command under Main Menu. + * Added `formatList` method in `logic/commands/workout/workoutsession/WorkoutSessionList` to create dynamic column width while listing. +* **Code contribution**: [Functional and Test code](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=tienkhoa16&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship) +* **Contributions to the User Guide:** + * Add instructions for Main Menu and Profile Menu commands. \(This section was in several pull requests\). + * Update command formats in the User Guide. [\#126](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/126) + * Update screenshots for commands under Main Menu. [\#200](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/200) + * Add tips and notes for understanding our command format. [\#198](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/198) +* **Contributions to the Developer Guide:** + * Add Section 4.2. Profile-related Features. [\#99](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/99) + * Add Section 4.5.1. Storage for Profile. [\#156](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/156) + * Add Section 4.6. Logging. [\#135](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/135) + * Add Section 5. Testing and Section 6. Dev Ops. [\#199](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/199) + * Fix broken links in the Developer Guide. +* **Community**: + * Review Developer Guide from other teams in the class \(with non-trivial review comments\). [\#3](https://github.com/nus-cs2113-AY2021S1/tp/pull/3) + * Reported bugs for other teams in the class [ped](https://github.com/tienkhoa16/ped/issues). + * Evaluated code, tested and provided feedback to teammates during many online meetups. [\#29](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/29), [\#90](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/90), [\#94](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/90), [\#96](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/96), [\#120](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/120), [\#127](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/127), [\#131](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/131), [\#134](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/134), [\#139](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/139), [\#140](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/140), [\#144](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/144), [\#160](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/160), [\#202](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/202) + diff --git a/docs/team/wgzesg.md b/docs/team/wgzesg.md new file mode 100644 index 0000000000..9fbc0ccd7b --- /dev/null +++ b/docs/team/wgzesg.md @@ -0,0 +1,69 @@ +# Wang Zesong - Project Portfolio Page + +## PROJECT: The Schwarzenegger + +## Overview + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +### Summary of Contributions + +* **Major enhancement**: + * **Search Workout Sessions** + * Functionality: This enhancement allows the user to search for specific workout sessions created on a certain date range or containing certain tags. + * Justification: This feature improves the product significantly because a user can easily find the session she intends to edit or delete. + * **Command Library** + * Functionality: This enhancement organises the commands into a hashmap and retrieves correct Command object using user's input as key. + * Justification: This feature improves the code neatness and reduce long selection statement for menus with many availble commands. + * **Design Workout Session Meta-info File** + * Functionality: This enhancement creates the program create a file to store the meta-information of past workout sessions. + * Justification: This allows the program not to have to load every past session files at one go but only need to load the meta-information of each session. This greatly reduce the initialization time and makes the program more scalable. Past record files are only loaded on request. +* **Minor enhancement**: + * **Date Parser** + * This feature will parse the user's input into LocalDate. The parser supports 9 formats which gives user max flexibility when using the application. + * **List Workout Sessions** + * This feature allows the users to view records only ranging from a given period. This helps the users to avoid seeing long list of workout sessions from long time ago. +* **Code contribution**: [Functional and Test code](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#search=wgzesg&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=wgzesg&tabRepo=AY2021S1-CS2113T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other) +* **Other contributions**: + * Project management: + * Ensure that the team members are always aware of weekly deadlines and deliverables. + * Documentation: + * Added instructions and format for all workout manager related commands in the User Guide. \(Pull Request: [\#81](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/81), [\#83](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/83)\) + * Added and updated instructions and format for all workout manager related commands in the Developer Guide. \(Pull Request: [\#132](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/132)\) + * Added and updated diagrams in the Developer Guide \(Pull Request: [\#106](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/106)\) + * Community: + * Reported bugs for other teams in the class \(Pull Request: [\#36](https://github.com/nus-cs2113-AY2021S1/tp/pulls?q=is%3Aopen+is%3Apr+CS2113-T13-3+) \) + * Perform manual testing, reported bugs and gave suggestions to teammates. + +#### Contributions to the User Guide + +```text +Given below are sections I contributed to the User Guide. +They showcase my ability to write documentation targeting end-users. +``` + +* [Starting a New Workout Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-start) +* [Listing All Past Workout Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-list) +* [Editing a Past Workout Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-edit) +* [Deleting a Past Workout Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-delete) +* [Clearing All Past Workout Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-clear) +* [Searching for Past Workout Sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-search) +* [Returning to Main Menu](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#workout-end) +* [Notes](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#notes) + +#### Contributions to the Developer Guide + +```text + Given below are sections I contributed to the Developer Guide. + They showcase my ability to write technical documentation and the technical depth of my contributions + to the project. +``` + +* [Architecture](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#architecture) +* [Start recordings workout data](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#creating-a-new-workout-session) +* [List all past workout sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#442-listing-past-workout-sessions) +* [Edit a past workout session](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#443-editing-workout-session) +* [Delete a past workout session](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#444-deleting-a-workout-session) +* [Search on past workout sessions](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#445-searching-based-on-conditions) +* [Appendix F: Supported Formats of Date Input](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#appendix-f-supported-formats-of-date-input) + diff --git a/docs/team/yujinyang1998.md b/docs/team/yujinyang1998.md new file mode 100644 index 0000000000..9c1f8dfd4a --- /dev/null +++ b/docs/team/yujinyang1998.md @@ -0,0 +1,62 @@ +# Yu Jinyang - Project Portfolio Page + +## PROJECT: The Schwarzenegger + +## Overview + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +### Summary of Contributions + +* **Major enhancement**: + * **Search Workout Sessions** + * Functionality: This enhancement allows the user to search for specific exercises within the current list of exercises. + * Justification: This feature improves the product significantly because this allows the user to see at a glance what they want to focus on. + * Highlights: This enhancement shows the user a targeted list with an easy to read formatting, allowing for quick reading at a glance. + * **Dynamic list formatting** + * Functionality: With this enhancement, it changes the format of the list based on the max length of the name of exercise added. + * Justification: This feature allows the list shown to the user to look nice and proper even when the name of the exercise is very long. + * Highlights: This was used in displaying the list for both list and search functions. + * **Handling of different orders of parameters** + * Functionality: With this enhancement, it allows user to use any sequence of entering parameters when adding an exercise. + * Justification: This feature allows user to enter an exercise with more options of input. + * **Minor enhancement**: + * **List Exercises** + * This feature allows user to view all the exercise in the current workout session in a nicely formatted list. This feature can be easily be used by typing `list` in the command line. + * **Code contribution**: [Functional and Test code](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=yujinyang1998&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship) + * **Other contributions**: + * Coherent output format: + * Ensure that the team members had standardised formatting when showing messages to the user. + * Documentation: + * Added Workout Session and logic components into the Developer Guide. \(Pull Request: [\#105](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/105)\) + * Added Workout Session components for User Guide \(Pull Request: [\#84](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/84), [\#221](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/221)\) + * Community: + * Review Developer Guide from other teams in the class\(with non-trival review comments.\) \(Pull Request: [\#57](https://github.com/nus-cs2113-AY2021S1/tp/pull/57).\) + * Reported bugs for other teams in the class \(example: [ped](https://github.com/yujinyang1998/ped/issues) \) + * Perform manual testing, reported bugs and gave suggestions to teammates. + +#### Contributions to the User Guide + +```text + Given below are sections I contributed to the User Guide. + They showcase my ability to write documentation targeting end-users. +``` + +* [Adding an Exercise: `add`](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#ws-add) +* [Listing All Exercises in This Session: `list`](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#ws-list) +* [Deleting an Exercise: `delete`](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#ws-delete) +* [Searching for Related Exercises: `search`](https://ay2021s1-cs2113t-f11-1.github.io/tp/UserGuide.html#ws-search) + +#### Contributions to the Developer Guide + +```text +Given below are sections I contributed to the Developer Guide. +They showcase my ability to write technical documentation and the technical depth of my contributions +to the project. +``` + +* [Adding an Exercise](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#adding-an-exercise) +* [Deleting an Exercise](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#deleting-an-exercise) +* [Listing All Exercises in This Session](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#listing-all-exercises-in-this-session) +* [Searching for related exercises](https://ay2021s1-cs2113t-f11-1.github.io/tp/DeveloperGuide.html#searching-for-related-exercises) + diff --git a/docs/team/zsk612.md b/docs/team/zsk612.md new file mode 100644 index 0000000000..9d4b52a61a --- /dev/null +++ b/docs/team/zsk612.md @@ -0,0 +1,45 @@ +# Zhang Shukai - Project Portfolio Page + +## PROJECT: The Schwarzenegger + +### Overview + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +### Summary of Contributions + +* **Major Enhancement** + * **Search Food Items** + * Functionalities: + * This enhancement allows users to search for food items by typing food names or part of the names in the current meal. + * Justification: + * It improves usability and user experience of the app because as the food item list grows, it might be hard for the user to find a specific food item. + * It provides flexibility to allow user to type part of the food name to find all the related food items containing that part of food name. + * Highlights: + * It supports fuzzy search where the user can input part of the food name instead of the full name, and the food items whose names contain this part of the food name will be shown. + * It allows users to search for similar food items. For example, if the user searches for "rice" by typing `search rice`, everything containing rice such as "chicken rice" and "fried rice" will be shown to the user. + * **List Food Items** + * Functionalities: + * This enhancement allows users to see all the food items in the current diet session with calories of each food items as well as the total calories taken in the current meal. + * Justification: + * It improves usability and user experience of the app because as the user might want to see a complete list of food items he or she has eaten in the current meal. + * Highlights: + * All the food items are listed in a table format for better visualization of the food items. + * It lists all the food items in the chronological order, i.e. food items added earlier will be shown first. +* **Minor Enhancement**: + * Implemented `clear` commands for `clear`\(clearing all food\). + * Implemented `help` commands for the user to see an overview of available commands in diet session. +* **Code Contributed:** [View on RepoSense](https://nus-cs2113-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=zsk612&sort=groupTitle&sortWithin=title&since=2020-09-27&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=CFZeon&tabRepo=AY2021S1-CS2113T-F11-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code) +* **Contributions to the User Guide:** + * Add instructions for diet session commands. \(This section was in several pull requests\) + * Updated command formats in the User Guide. [\#158](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/158) + * Updated screenshots for commands related to diet. [\#150](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/193) +* **Contributions to the Developer Guide:** + * Updated command formats in the Developer Guide. [\#159](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/159) + * Updated date parser format in Developer Guide. [\#193](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/193) +* **Community**: + * Reviewed Developer Guide from other teams in the class \(with non-trivial review comments.\) \(DietaryBook: [\#42](https://github.com/nus-cs2113-AY2021S1/tp/pull/42).\) + * Checked and update Java Doc for team project. [\#240](https://github.com/AY2021S1-CS2113T-F11-1/tp/pull/240) + * Reported bugs for other teams in the class example: [ped](https://github.com/zsk612/ped/issues). + * Evaluated code, tested and provided feedback to teammates during many online meetups. + diff --git a/docs/umldiagram/Diet Class.puml b/docs/umldiagram/Diet Class.puml new file mode 100644 index 0000000000..2f94ad9be3 --- /dev/null +++ b/docs/umldiagram/Diet Class.puml @@ -0,0 +1,26 @@ +@startuml + +abstract class Command + +List <|-- AbstractList +Collection <|-- AbstractCollection + +Collection <|- List +AbstractCollection <|- AbstractList +AbstractList <|-- ArrayList + +class DietManager { + +} + +class DietSession { + +} + +enum TimeUnit { +DAYS +HOURS +MINUTES +} + +@enduml \ No newline at end of file diff --git a/docs/umldiagram/commands.puml b/docs/umldiagram/commands.puml new file mode 100644 index 0000000000..ae715b2fbb --- /dev/null +++ b/docs/umldiagram/commands.puml @@ -0,0 +1,17 @@ +@startuml +actor user + +user -> Ticket : Attend Event Request + +activate Ticket +Ticket -> Member : Create Member Request + +activate Member +Member -> Member : Create Member +Ticket <-- Member : Create Member Response +deactivate Member + +Ticket -> Ticket : Create Ticket +user <-- Ticket : Attend Event Response +deactivate Ticket +@enduml \ No newline at end of file diff --git a/docs/umldiagram/style.puml b/docs/umldiagram/style.puml new file mode 100644 index 0000000000..0389b33c39 --- /dev/null +++ b/docs/umldiagram/style.puml @@ -0,0 +1,62 @@ +!define UI_COLOR #1D8900 +!define UI_COLOR_T1 #83E769 +!define UI_COLOR_T2 #3FC71B +!define UI_COLOR_T3 #166800 +!define UI_COLOR_T4 #0E4100 + +!define LOGIC_COLOR #3333C4 +!define LOGIC_COLOR_T1 #C8C8FA +!define LOGIC_COLOR_T2 #6A6ADC +!define LOGIC_COLOR_T3 #1616B0 +!define LOGIC_COLOR_T4 #101086 + +!define MODEL_COLOR #9D0012 +!define MODEL_COLOR_T1 #F97181 +!define MODEL_COLOR_T2 #E41F36 +!define MODEL_COLOR_T3 #7B000E +!define MODEL_COLOR_T4 #51000A + +!define STORAGE_COLOR #A38300 +!define STORAGE_COLOR_T1 #FFE374 +!define STORAGE_COLOR_T2 #EDC520 +!define STORAGE_COLOR_T3 #806600 +!define STORAGE_COLOR_T2 #544400 + +!define USER_COLOR #000000 + +skinparam Class { + FontColor #000000 + BorderThickness 1 + BorderColor #000000 + StereotypeFontColor #FFFFFF + FontName Arial +} + +skinparam Actor { + BorderColor USER_COLOR + Color USER_COLOR + FontName Arial +} + +skinparam Sequence { + MessageAlign center + BoxFontSize 13 + BoxPadding 0 + BoxFontColor #FFFFFF + FontName Arial +} + +skinparam Participant { + Padding 20 +} + +skinparam MinClassWidth 50 +skinparam ParticipantPadding 10 +skinparam Shadowing false +skinparam DefaultTextAlignment center +skinparam packageStyle Rectangle +skinparam BackgroundColor #FFFFFFF + +hide footbox +hide members +hide circle diff --git a/docs/userguide.md b/docs/userguide.md new file mode 100644 index 0000000000..67ac81aed0 --- /dev/null +++ b/docs/userguide.md @@ -0,0 +1,1242 @@ +# The Schwarzenegger - User Guide + +By: `Team F11-1` Since: `Sept 2020` License: `MIT` + +## Table of Contents + +1. [**Introduction**](userguide.md#intro) +2. [**Quick Start**](userguide.md#quickstart) +3. [**Features**](userguide.md#features) + + + 3.1. [Main Menu](userguide.md#main-menu) + + +     3.1.1. [Viewing Help: `help`](userguide.md#main-help) + + +     3.1.2. [Entering Profile Menu: `profile`](userguide.md#main-profile) + + +     3.1.3. [Entering Diet Menu: `diet`](userguide.md#main-diet) + + +     3.1.4. [Entering Workout Menu: `workout`](userguide.md#main-workout) + + +     3.1.5. [Ending the Schwarzenegger: `end`](userguide.md#main-end) + + + 3.2. [Profile Menu](userguide.md#profile-menu) + + +     3.2.1. [Viewing Help: `help`](userguide.md#profile-help) + + +     3.2.2. [Adding a Profile: `add`](userguide.md#profile-add) + + +     3.2.3. [Viewing a Profile: `view`](userguide.md#profile-view) + + +     3.2.4. [Editing a Profile: `edit`](userguide.md#profile-edit) + + +     3.2.5. [Deleting a Profile: `delete`](userguide.md#profile-delete) + + +     3.2.6. [Returning to Main Menu: `end`](userguide.md#profile-end) + + + 3.3. [Diet Menu](userguide.md#diet-menu) + + +     3.3.1. [Viewing Help: `help`](userguide.md#diet-help) + + +     3.3.2. [Starting a New Diet Session: `new`](userguide.md#diet-start) + + +         3.3.2.1. [Viewing Help in Diet Session: `help`](userguide.md#meal-help) + + +         3.3.2.2. [Adding Food Items to the Current Diet Session: `add`](userguide.md#meal-add) + + +         3.3.2.3. [Listing Food Items from the Current Diet Session: `list`](userguide.md#meal-list) + + +         3.3.2.4. [Deleting Food Items from the Current Diet Session: `delete`](userguide.md#meal-delete) + + +         3.3.2.5. [Clearing All Food Items from the Current Diet Session: `clear`](userguide.md#meal-clear) + + +         3.3.2.6. [Searching for Food Items from the Current Diet Session: `search`](userguide.md#meal-search) + + +         3.3.2.7. [Ending the Current Diet Session: `end`](userguide.md#meal-end) + + +     3.3.3. [Listing All Past Diet Sessions: `list`](userguide.md#diet-list) + + +     3.3.4. [Editing a Past Diet Session: `edit`](userguide.md#diet-edit) + + +     3.3.5. [Deleting a Past Diet Session: `delete`](userguide.md#diet-delete) + + +     3.3.6. [Clearing All Past Diet Sessions: `clear`](userguide.md#diet-clear) + + +     3.3.7. [Searching for Past Diet Sessions: `search`](userguide.md#diet-search) + + +     3.3.8. [Returning to Main Menu: `end`](userguide.md#diet-end) + + + 3.4. [Workout Menu](userguide.md#workout-menu) + + +     3.4.1. [Viewing Help: `help`](userguide.md#workout-help) + + +     3.4.2. [Starting a New Workout Session: `new`](userguide.md#workout-start) + + +         3.4.2.1. [Viewing Help in Workout Session: `help`](userguide.md#ws-help) + + +         3.4.2.2. [Adding a Move to the Current Workout Session: `add`](userguide.md#ws-add) + + +         3.4.2.3. [Listing All Moves from the Current Workout Session: `list`](userguide.md#ws-list) + + +         3.4.2.4. [Deleting a Move From the Current Workout Session: `delete`](userguide.md#ws-delete) + + +         3.4.2.5. [Searching for a Keyword in the Current Workout Session: `search`](userguide.md#ws-search) + + +         3.4.2.6. [Ending the Current Workout Session: `end`](userguide.md#ws-end) + + +     3.4.3. [Listing All Past Workout Sessions: `list`](userguide.md#workout-list) + + +     3.4.4. [Editing a Workout Session: `edit`](userguide.md#workout-edit) + + +     3.4.5. [Deleting a Workout Session: `delete`](userguide.md#workout-delete) + + +     3.4.6. [Searching a List of Workout Sessions: `search`](userguide.md#workout-search) + + +     3.4.7. [Clearing All Workout Sessions: `clear`](userguide.md#workout-clear) + + +     3.4.8. [Returning to Main Menu: `end`](userguide.md#workout-end) + +4. [**Command Summary**](userguide.md#command-summary) +5. [**Notes**](userguide.md#notes) +6. [**FAQ**](userguide.md#faq) + +## 1. [Introduction](userguide.md) + +The Schwarzenegger is a desktop command line interface-based app for managing all your needs regarding fitness. With the built-in personal assistant, you are able to track your daily workout and diet sessions based on your profile. If you can type fast, The Schwarzenegger can help you maximise your efficiency for maintaining fitness. + +## 2. [Quick Start](userguide.md) \[Khoa\] + +When you first start using The Schwarzenegger, please ensure that you follow the instructions below: + +1. Ensure you have Java `11` or above installed in your Computer. +2. Download the latest `CS2113T-F11-1.TheSchwarzenegger.jar` from [here](https://github.com/AY2021S1-CS2113T-F11-1/tp/releases). +3. Copy the file to the folder you want to use as the home folder for The Schwarzenegger. +4. Open Command Prompt \(on Windows\) or Terminal \(on MacOS/ Linux\) and change to the directory of the folder of the application in step 3. +5. Key in `java -jar [CS2113T-F11-1][TheSchwarzenegger].jar` and press Enter. If the setup is correct, you should see something like below: + + ```text + _________________________________________________________________________________________________ + _____ _ + / ____| | | + | (___ ___ | |__ __ __ __ _ _ __ ____ ___ _ __ ___ __ _ __ _ ___ _ __ + \___ \ / __|| '_ \\ \ /\ / // _` || '__||_ // _ \| '_ \ / _ \ / _` | / _` | / _ \| '__| + ____) || (__ | | | |\ V V /| (_| || | / /| __/| | | || __/| (_| || (_| || __/| | + |_____/ \___||_| |_| \_/\_/ \__,_||_| /___|\___||_| |_| \___| \__, | \__, | \___||_| + __/ | __/ | + |___/ |___/ + _________________________________________________________________________________________________ + + _________________________________________________________________________________________________ + Welcome new user to Schwarzenegger! :D + Please add your profile under Profile Menu. + For more information on command syntax, please type "help". + _________________________________________________________________________________________________ + ``` + +6. To use The Schwarzenegger, simply type a valid command into the terminal and press Enter to run the command. **Example**: Typing `help` command in Main Menu and pressing Enter will show you a list of available commands in Main Menu of The Schwarzenegger and their descriptions. +7. Some example commands you can try: + * `help` : Shows all the available commands under Main Menu. + * `profile` : Directs you to Profile Menu where you can add and manage your profile. + * `diet` : Directs you to Diet Menu where you can add and manage your diet records. + * `workout` : Directs you to Workout Menu where you can add and manage your workout records. +8. The text before the cursor indicates which menu you are currently in \(e.g. `Main menu >>>>>` indicates that you are currently in the Main Menu\). +9. A summary of all the supported commands in The Schwarzenegger can be found in [Section 4. Command Summary](userguide.md#command-summary). + + + You can refer to [Section 3. Features](userguide.md#features) for the detailed instructions of the various features in The Schwarzenegger. + +## 3. [Features](userguide.md) + +This section includes 4 sub-sections which will guide you through the features available in Main Menu, Profile Menu, Workout Menu and Diet Menu of the Schwarzenegger. In explaining the syntax, we will adhere to the following format: + +**Command Format** \[Khoa\] + +* Words that are in `[UPPER_CASE]` format are the parameters to be supplied by you. + + + **Example**: in + `add /n [NAME] /h [HEIGHT] /w [CURRENT_WEIGHT] /e [EXPECTED_WEIGHT] /c [DAILY_CALORIE_INTAKE]` + command, `[NAME]`, `[HEIGHT]`, `[CURRENT_WEIGHT]`, `[EXPECTED_WEIGHT]` and `[DAILY_CALORIE_INTAKE]` are parameters which can be used as `add /n Schwarzenegger /h 188 /w 113 /e 100 /c 2500`. + +* Words that are enclosed with `<` and `>` are optional parameters. If all of the parameters are optional, you need to supply at least 1 of them. + + + **Example**: in + `edit ` + command, ``, ``, ``, `` and `` are optional parameters which can be used as `edit /h 180`, `edit /h 180 /w 50` or `edit /h 180 /w 50 /e 55`. Since all of them are optional parameters, you need to supply at least 1 of them. + +* Optional parameters with `…` after them can be used multiple times including zero times. + + + **Example**: for `...`, the following format for Search Command in Workout Menu: `search ` can be used as `search /t le` or `search /t chest, bicep`. + +* Parameters can be in any order. + + + **Example**: if the command specifies `/n [NAME] /h [HEIGHT] /w [CURRENT_WEIGHT] /e [EXPECTED_WEIGHT] /c [DAILY_CALORIE_INTAKE]`, `/h [HEIGHT] /w [CURRENT_WEIGHT] /n [NAME] /c [DAILY_CALORIE_INTAKE] /e [EXPECTED_WEIGHT]` is also acceptable. + +**Notations Used** \[Khoa\] + +Below are the meaning of icons for you to take note of while using a feature. + +* :bulb: indicates a tip. +* :warning: indicates a warning. + +### 3.1. [Main Menu](userguide.md) \[Khoa\] + +You can get access to Profile Menu, Diet Menu and Workout Menu from Main Menu. The available commands in Main Menu are listed below: + +#### 3.1.1. [Viewing Help: `help`](userguide.md) + +You can see a complete list of available commands under Main Menu and how to use them. + +**Format:** `help` + + +**Example:** Whenever you want to see get assistance in the Main Menu, you follow the steps below: 1. Type `help` into your console and press Enter to execute it. + +```text +![main-help-1](pictures/UG_screenshots/main-help-step-1.png) +``` + +1. You should be able to see a list of available commands like the screenshot below: + + ![main-help-2](../.gitbook/assets/main-help-step-2.png) + +#### 3.1.2. [Entering Profile Menu: `profile`](userguide.md) + +The program will direct you to the Profile Menu. + +**Format:** `profile` + +**Example**: 1. Type `profile` into your console and press Enter to execute it. 1. If the redirection is successful, you should be able to see the message below: + +```text + _________________________________________________________________________________________________ + Starting Profile Menu...... + _________________________________________________________________________________________________ +``` + +> :bulb: You can verify that you are in Profile Menu if the text before your cursor becomes `Profile Menu >>>>>`. +> +> :bulb: You can return to Main Menu from Profile Menu by entering command `end`. You can refer to [Section 3.2.6. Returning to Main Menu](userguide.md#profile-end) for more details. + +#### 3.1.3. [Entering Diet Menu: `diet`](userguide.md) + +The program will direct you to the Diet Menu. + +**Format:** `diet` + +**Example**: 1. Type `diet` into your console and press Enter to execute it. + +1. If the redirection is successful, you should be able to see the message below: + + ```text + _________________________________________________________________________________________________ + Starting Diet Menu... + _________________________________________________________________________________________________ + ``` + +> :bulb: You can verify that you are in Diet Menu if the text before your cursor becomes `Diet Menu >>>>>`. +> +> :bulb: You can return to Main Menu from Diet Menu by entering command `end`. You can refer to [Section 3.3.8. Returning to Main Menu](userguide.md#diet-end) for more details. + +#### 3.1.4. [Entering Workout Menu: `workout`](userguide.md) + +The program will direct you to the Workout Menu. + +**Format:** `workout` + +**Example**: 1. Type `workout` into your console and press Enter to execute it. 1. If the redirection is successful, you should be able to see the message below: + +```text + _________________________________________________________________________________________________ + Starting Workout Menu... + _________________________________________________________________________________________________ +``` + +> :bulb: You can verify that you are in Workout Menu if the text before your cursor becomes `Workout Menu >>>>>`. +> +> :bulb: You can return to Main Menu from Workout Menu by entering command `end`. You can refer to [Section 3.4.8. Returning to Main Menu](userguide.md#workout-end) for more details. + +#### 3.1.5. [Ending The Schwarzenegger: `end`](userguide.md) + +You use this command when you want to terminate The Schwarzenegger. + +**Format:** `end` + +**Example**: 1. Type `end` into your console and press Enter to execute it. 1. If the program exits successfully, you should be able to see the message below: + +```text + ______________________________________________________________________________ + Bye, you have exited The Schwarzenegger. + ______________________________________________________________________________ +``` + +### 3.2. [Profile Menu](userguide.md) \[Khoa\] + +Profile Menu manages your profile including your name, physique data and expected daily calories intake. + +Click [here](userguide.md#main-profile) to learn how to enter Profile Menu. + +#### 3.2.1. [Viewing Help: `help`](userguide.md) + +You can see a complete list of available commands under Profile Menu and how to use them. + +**Format:** `help` + +**Example:** 1. Type `help` into your console and press Enter to execute it. 2. If the execution is successful, you should be able to see the message below: + +```text + _________________________________________________________________________________________________ + Add FORMAT: add /n [NAME] /h [HEIGHT] /w [CURRENT_WEIGHT] /e [EXPECTED_WEIGHT] /c [DAILY_CALORIE_INTAKE] + DESCRIPTION: Add your new profile + View FORMAT: view + DESCRIPTION: View your profile + Edit FORMAT: edit + DESCRIPTION: Edit your existing profile. You may edit from 1 field to all fields + Delete FORMAT: delete + DESCRIPTION: Delete your existing profile + End FORMAT: end + DESCRIPTION: Go back to Main Menu + _________________________________________________________________________________________________ +``` + +#### 3.2.2. [Adding a Profile: `add`](userguide.md) + +You can add your profile for the most customized experience in The Schwarzenegger. Your height should in centimeters, your current weight and expected weight should be in kilograms, and your expected daily calorie intake should be in calories. + +**Format:** `add /n [NAME] /h [HEIGHT] /w [CURRENT_WEIGHT] /e [EXPECTED_WEIGHT] /c [DAILY_CALORIE_INTAKE]` + +**Example:** When you want to add your profile to the application, you follow the steps below: 1. Ensure that you are in Profile Menu. + Refer to [Section 3.1.2. Entering Profile Menu](userguide.md#main-profile) to learn how to enter Profile Menu. ![add-profile-step-1](../.gitbook/assets/add-profile-step-1.png) + +1. Type `add /n Schwarzenegger /h 188 /w 113 /e 100 /c 2500` into your console and press Enter to execute it. + This command adds a profile with the name Schwarzenegger, height 188 cm, weight 113 kg, expected weight 100 kg and expected daily intake of 2500 calories. + + ![add-profile-step-2](../.gitbook/assets/add-profile-step-2.png) + +2. If the execution is successful, you should be able to see the confirmation message below: + + ![add-profile-step-3](../.gitbook/assets/add-profile-step-3.png) + +> :warning: The Schwarzenegger currently does not support names containing "/" character. + +#### 3.2.3. [Viewing a Profile: `view`](userguide.md) + +You can view your profile recorded in the program. The Schwarzenegger will also show the additional information below: + +* Your current BMI classification so that you can have a good suggestion on your current fitness level. +* Comparison between your calorie intake today with your expected daily calorie intake. +* Suggestion on adjusting your weight expectation for better fitness where applicable. For example, if your expected weight results in the Underweight BMI classification, The Schwarzenegger will show you a tip on editing your weight expectation. + +**Format:** `view` + +**Example**: 1. Type `view` into your console and press Enter to execute it. 2. If the execution is successful, you should be able to see the message below: + +```text + _________________________________________________________________________________________________ + Here's your profile: + Name: Schwarzenegger + Height: 188 cm + Weight: 113.0 kg + Expected Weight: 100.0 kg + Expected daily calorie intake: 2500.0 calories + Your current BMI: 32.0 (Obesity Class 1) + By the way, take 2500.0 more calories to achieve your target for today! + TIP: Edit your expected weight to 76.7 kg to have Normal Weight BMI classification. + Just type "edit /e 76.7"! + _________________________________________________________________________________________________ +``` + +> :bulb: If you do not see the tip, it means that your current height and weight expectation is classified as Normal Weight already. + +#### 3.2.4. [Editing a Profile: `edit`](userguide.md) + +You can edit the profile after adding to the program. + +**Format:** `edit ` + +**Example**: + +* If you want to edit your height, you follow the steps below: 1. Type `edit /h 180` into your console and press Enter to execute it. + This command edits your current height to 180 centimeters. 2. If the execution is successful, you should be able to see the confirmation message below: + + ```text + ______________________________________________________________________________ + Yay! Your profile is edited successfully. Here's your new profile: + Name: Schwarzenegger + Height: 180 cm + Weight: 113.0 kg + Expected Weight: 100.0 kg + Expected daily calories intake: 2500.0 calories + Your BMI: 32.0 (Obesity Class 1) + ______________________________________________________________________________ + ``` + +* If you want to edit your height and weight, you follow the steps below: 1. Type `edit /h 180 /w 50` into your console and press Enter to execute it. + This command edits your current height to 180 centimeters and current weight to 50 kilograms. 2. If the execution is successful, you should be able to see the confirmation message below: + + ```text + ______________________________________________________________________________ + Yay! Your profile is edited successfully. Here's your new profile: + Name: Schwarzenegger + Height: 180 cm + Weight: 50.0 kg + Expected Weight: 100.0 kg + Expected daily calories intake: 2500.0 calories + Your BMI: 15.4 (Underweight) + ______________________________________________________________________________ + ``` + +* If you want to edit your height, weight and expected weight, you follow the steps below: 1. Type `edit /h 180 /w 50 /e 55` into your console and press Enter to execute it. + This command edits your current height to 180 centimeters, current weight to 50 kilograms and expected weight to 55 kilograms. 2. If the execution is successful, you should be able to see the confirmation message below: + + ```text + ______________________________________________________________________________ + Yay! Your profile is edited successfully. Here's your new profile: + Name: Schwarzenegger + Height: 180 cm + Weight: 50.0 kg + Expected Weight: 55.0 kg + Expected daily calories intake: 2500.0 calories + Your BMI: 15.4 (Underweight) + ______________________________________________________________________________ + ``` + +> :bulb: You may edit from 1 field to all fields in your profile. + +#### 3.2.5. [Deleting a Profile: `delete`](userguide.md) + +You can use this command to delete your profile from our system. + +This command is dangerous as you will not be able to recover the data. After typing this command, you will be asked to reconfirm it by typing in `YES`. Any other input will abort the deletion. + +**Format:** `delete` + +**Example**: 1. Type `delete` into your console and press Enter to execute it. 2. Type `YES` into your console and press Enter to confirm the deletion. 3. If the execution is successful, you should be able to see the message below: + +```text + _________________________________________________________________________________________________ + Alright, your profile has been cleared! + _________________________________________________________________________________________________ +``` + +> :warning: Your profile data cannot be recovered once cleared! + +#### 3.2.6. [Returning to Main Menu: `end`](userguide.md) + +You use this command to exit Profile Menu and return to the Main Menu. + +**Format:** `end` + +**Example**: 1. Type `end` into your console and press Enter to execute it. 2. If the execution is successful, you should be able to see the message below: + +```text + _________________________________________________________________________________________________ + Returning to Main Menu... + _________________________________________________________________________________________________ +``` + +### 3.3. [Diet Menu](userguide.md) \[Zeon\] + +Diet Menu manages your diet sessions which record food items and calories intake. + +Click [here](userguide.md#main-diet) to learn how to enter Diet Menu. + +#### 3.3.1. [Viewing Help: `help`](userguide.md) + +You can see a complete list of available commands under Diet Menu and how to use them. + +**Format:** `help` + +**Example:** `help` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + New FORMAT: new + DESCRIPTION: Create a new diet session + List FORMAT: list + DESCRIPTION: Show all past diet sessions + Delete FORMAT: delete [INDEX] + DESCRIPTION: Delete the diet session at the input index + Edit FORMAT: edit [INDEX] + DESCRIPTION: Edit the diet session at the input index + Search FORMAT: search + DESCRIPTION: Search the diet session in between starting and end dates with a specific tag + Clear FORMAT: clear + DESCRIPTION: Clear all past diet sessions + End FORMAT: end + DESCRIPTION: Go back to Main Menu + _________________________________________________________________________________________________ +``` + +#### 3.3.2. [Starting a New Diet Session: `new`](userguide.md) + +You can create a new diet session with this command. The date and tag can be added on creation with "/d" for date and "/t" for meal type. + +You will be directed immediately into the new diet session. You may verify that as seen from how the cursor changes from + +`Diet Menu >>>>>` + +to + +`Diet Menu > New Diet Session >>>>>`. + +**Format:** `new ` + +**Example:** + +* `new` + + + This command creates a new diet session tagged as unspecified with today's date. + +**Example:** Whenever you want to create a new Diet Session, you follow the steps below: 1. Ensure that you are in the Diet Menu. + Refer to [Section 3.1.3. Entering Diet Menu](userguide.md#main-diet) to learn how to enter the Diet Menu. + +```text +![new-diet-session-1](pictures/UG_screenshots/new-dietsession-step-1.png) +``` + +1. Type new /d 2020-11-09 /t lunch into your console and press Enter to execute it. This command starts a Diet Session of date 9th September 2020 with tag lunch. + + ![new-diet-session-2](../.gitbook/assets/new-dietsession-step-2.png) + +2. If the execution is successful, you should be able to see the confirmation message below. + + ![new-diet-session-3](../.gitbook/assets/new-dietsession-step-3.png) + +3. You will also be redirected to the Diet Session interface, as seen below. + + ![new-diet-session-4](../.gitbook/assets/new-dietsession-step-4.png) + +> :warning: You may choose not to add the date or tag, but diet sessions with the same date and tag will be overwritten! + +#### 3.3.2.1. [Viewing Help in Diet Session: `help`](userguide.md) \[Shukai\] + +You can see a complete list of available commands under Diet Session and how to use them. + +**Format:** `help` + +**Example:** `help` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Add FORMAT: add [FOOD_NAME] /c [CALORIES] + DESCRIPTION: Add a new food item + List FORMAT: list + DESCRIPTION: Show all food items + Delete FORMAT: delete [INDEX] + DESCRIPTION: Delete the food item at the input index + Search FORMAT: search [FOOD_NAME] + DESCRIPTION: Search the diet session for food with the name specified + Clear FORMAT: clear + DESCRIPTION: Clear all food items + End FORMAT: end + DESCRIPTION: Go back to the Diet Menu. + _________________________________________________________________________________________________ +``` + +#### 3.3.2.2. [Adding Food Items to the Current Diet Session: `add`](userguide.md) + +This command adds a food item into the current diet session. + +**Format:** `add [FOOD_NAME] /c [CALORIES]` + +**Example:** Whenever you want to add new food items in the diet session, you follow the steps below: 1. Locate yourself in the diet session menu as shown in the screenshot below: + +```text +![add-new-food-item-1](pictures/UG_screenshots/add-new-food-item-step-1.JPG) +``` + +1. You can add food items by following the format `add [FOOD_NAME] /c [CALORIES]` after the prompt as shown in the screenshot: + + ![add-new-food-item-2](../.gitbook/assets/add-new-food-item-step-2.JPG) + +2. You should be able to see a message showing that you have added the food item like the screenshot below: + + ![add-new-food-item-3](../.gitbook/assets/add-new-food-item-step-3.JPG) + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Yay! You have added chicken nuggets with calories: 120.0 + _________________________________________________________________________________________________ +``` + +> :bulb: Your calories per food item is capped at 200,000 + +#### 3.3.2.3. [Listing Food Items from the Current Diet Session: `list`](userguide.md) + +Lists all the added food items for the current diet session, with a numbered sequence according to sequence entered. + +**Format:** `list` + +**Example:** `list` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Index Food Calories + 1 chicken nuggets 120.0 + 2 fries 240.0 + + Your total calories for this meal is 360.0. + _________________________________________________________________________________________________ +``` + +#### 3.3.2.4. [Deleting Food Items from the Current Diet Session: `delete`](userguide.md) + +You can remove food items from your list according to the index in the current meal session list. + +**Format:** `delete [INDEX]` + +**Example:** `delete 1` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + You have deleted chicken nuggets with calories: 120.0 from your list! + _________________________________________________________________________________________________ +``` + +#### 3.3.2.5. [Clearing All Food Items from the Current Diet Session: `clear`](userguide.md) + +You can clear all the food items in the current diet session list. + +This command is dangerous as you will not be able to recover the data. After typing this command, you will be asked to reconfirm it by typing in `YES`. Any other input will abort the clearing. + +**Format:** `clear` + +**Example:** `clear` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Are you sure you want to clear all records? This action is irrevocable. + Key in "YES" to confirm. + _________________________________________________________________________________________________ + + Diet Menu > New Diet Session >>>>> YES + _________________________________________________________________________________________________ + Oops you have cleared all the food items. + _________________________________________________________________________________________________ +``` + +> :warning: Your food items cannot be recovered once cleared! + +#### 3.3.2.6. [Searching for Food Items from the Current Diet Session: `search`](userguide.md) + +You can search for all food items that contain the word entered, in the current diet session. + +**Format:** `search [FOOD_NAME]` + +**Example:** `search rice` + +Expected outcome: + +```text +Diet Menu > Diet Session 1 >>>>> search rice + _________________________________________________________________________________________________ + Here are the search results: + Index Food Calories + 1 chicken rice 332.0 + 2 fried rice 452.0 + + You have 2 record(s) + _________________________________________________________________________________________________ +``` + +#### 3.3.2.7. [Ending the Current Diet Session: `end`](userguide.md) + +You can return to the diet menu by ending your current diet session. + +**Format:** `end` + +**Example:** `end` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Exiting Diet Session! + _________________________________________________________________________________________________ +``` + +#### 3.3.3. [Listing All Past Diet Sessions: `list`](userguide.md) \[Zeon\] + +You can obtain a list of information about past diet sessions together with their numbered index and calories. + +**Format:** `list` + +**Example:** `list` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + You have 2 records + Index Tags Date Calories + 1 dinner 2020-10-29 110.0 + 2 lunch 2020-10-29 120.0 + _________________________________________________________________________________________________ +``` + +#### 3.3.4. [Editing a Past Diet Session: `edit`](userguide.md) + +You can edit a previous diet session based on a numbered index that can be found in the `list` command. + +**Format:** `edit [INDEX]` + +**Example:** `edit 2` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Starting Diet Session! + _________________________________________________________________________________________________ + +Diet Menu > Diet Session 2 >>>>> +``` + +> :bulb: Editing a diet session works exactly like how it does when you create a new diet session. + +#### 3.3.5. [Deleting a Past Diet Session: `delete`](userguide.md) + +You can delete a previously created diet session based on a numbered index that can be found in the `list` command. + +**Format:** `delete [INDEX]` + +**Example:** `delete 2` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + You have deleted that diet session! + _________________________________________________________________________________________________ +``` + +> :warning: Deleted diet sessions cannot be recovered! + +#### 3.3.6. [Clearing All Past Diet Sessions: `clear`](userguide.md) + +You can clear all previously saved diet sessions. + +**Format:** `clear` + +**Example:** `clear` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Are you sure you want to clear all records? This action is irrevocable. + Key in "YES" to confirm. + _________________________________________________________________________________________________ + +Diet Menu >>>>> YES + _________________________________________________________________________________________________ + You have cleared all diet sessions! + _________________________________________________________________________________________________ +``` + +> :warning: This command is dangerous as you will not be able to recover the data. After typing this command, you will be asked to reconfirm it by typing in `YES`. Any other input will abort the clearing. + +#### 3.3.7. [Searching for Past Diet Sessions: `search`](userguide.md) + +Searches for specified range of diet sessions with identifiers such as start date, end date and tags. + +**Format:** `search ` + +**Example:** `search /s 2020-05-06 /e 2020-05-10 /t breakfast` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Here are the search results! + Index Date Tag Calories + 1 2020-05-08 breakfast 112.0 + 2 2020-05-09 breakfast 250.0 + + You have 2 record(s) + _________________________________________________________________________________________________ +``` + +#### 3.3.8. [Returning to Main Menu: `end`](userguide.md) + +You can use this command to exit Diet Menu and return to the Main Menu. + +**Format:** `end` + +**Example:** `end` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Returning to Main Menu... + _________________________________________________________________________________________________ +``` + +### 3.4. [Workout Menu](userguide.md) \[Zesong\] + +This section shows the command you can use when you are in workout menu. + +Click [here](userguide.md#main-workout) to learn how to enter Workout Menu. + +#### 3.4.1. [Viewing Help: `help`](userguide.md) + +You can see a complete list of available commands under Workout Menu and how to use them. + +**Format:** `help` + +1. Type `help` into your console and press Enter to execute it. + + ![main-help-1](../.gitbook/assets/workout-help-step-1.png) + +2. You should be able to see a list of available commands like the screenshot below: ![main-help-2](../.gitbook/assets/workout-help-step-2.png) + +#### 3.4.2. [Starting a New Workout Session: `new`](userguide.md) + +You can a new workout session and go into the session. You can add tags with “/t”. + +> :bulb: Tags are optional and more than one tag can be attached to a session. If you want to add more than one tags, you should separate them by `,`. + +After Enter, you will be directed into workout session to manage the given session. You may verify that as seen from how the cursor changes from + +`Workout Menu >>>>>` + +to + +`Workout Menu > New Workout Session >>>>>`. + +**Format:** `new ` + +**Example:** `new /t legs day, tricep` + +Expected outcome: + +```text + ______________________________________________________________________________ + You have started a new workout session! + ______________________________________________________________________________ + +Workout Menu > New Workout Session >>>>> +``` + +#### 3.4.2.1. [Viewing Help in Workout Session: `help`](userguide.md) \[Jinyang\] + +You can see a complete list of available commands under Workout Session and how to use them. + +**Format:** `help` + +**Example:** `help` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Add FORMAT: add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT] + DESCRIPTION: Add a new move + List FORMAT: list + DESCRIPTION: Show all moves in this current session + Delete FORMAT: delete [INDEX] + DESCRIPTION: Delete a move according to the index in the list + Search FORMAT: search [NAME_OF_MOVE] + DESCRIPTION: Show a list of moves that match the entered keyword + End FORMAT: end + DESCRIPTION: Go back to the Workout Menu + _________________________________________________________________________________________________ +``` + +#### 3.4.2.2. [Adding a Move to the Current Workout Session: `add`](userguide.md) + +Adds a move with number of moves per set and weights of equipment \(if the move does not require weights, input 0 for weight\). + +**Format:** `add [NAME_OF_MOVE] /n [NUMBER_OF_MOVES_PER_SET] /w [WEIGHT]` + +**Example:** When you want to add an exercise to the current Workout Session, you follow the steps below: 1. Ensure that you are in a Workout Session. + Refer to [Section 3.4.2. Starting a New Workout Session](userguide.md#workout-start) to learn how to start a Workout Session. ![add-exercise-step-1](../.gitbook/assets/AddCommandStart.png) + +1. Type `add benchpress /w 45.5 /n 6` into your console and press Enter to execute it. + This command adds an exercise with the name benchpress, weight of 45.5 and 6 repetititons. + + ![add-exercise-step-2](../.gitbook/assets/AddCommand.png) + +2. If the execution is successful, you should be able to see the confirmation message below: + + ![add-exercise-step-3](../.gitbook/assets/AddCommandMessage.png) + +#### 3.4.2.3. [Listing All Moves from the Current Workout Session: `list`](userguide.md) + +Lists all the added moves for the current workout session, with a numbered sequence according to sequence entered. + +**Format:** `list` + +**Example:** `list` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Index Exercise Repetitions Weight + 1 squat 15 40.0 + _________________________________________________________________________________________________ +``` + +#### 3.4.2.4. [Deleting a Move From the Current Workout Session: `delete`](userguide.md) + +Deletes a move according to move index in the current workout session list. + +**Format:** `delete [INDEX]` + +**Example:** `delete 1` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + You have deleted squat from your list! + [Repetitions: 15 || Weight: 40.0] + _________________________________________________________________________________________________ +``` + +#### 3.4.2.5. [Searching for a Keyword in the Current Workout Session: `search`](userguide.md) + +Searches the current workout session for the keyword and shows the relevant data found in a neat list. + +**Format:** `search [NAME_OF_MOVE]` + +**Example:** `search bench` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Index Exercise Repetitions Weight + 1 bench 324 342.0 + 2 benchpress 324 342.0 + 3 bench press 324 342.0 + _________________________________________________________________________________________________ +``` + +#### 3.4.2.6. [Ending the Current Workout Session: `end`](userguide.md) + +Ends the current workout session and saves the relevant data. + +**Format:** `end` + +**Example:** `end` + +Expected outcome: + +```text + _________________________________________________________________________________________________ + Congratulations! You have finished today's workout! + _________________________________________________________________________________________________ +``` + +#### 3.4.3. [Listing All Past Workout Sessions: `list`](userguide.md) \[Zesong\] + +You can see all your past workout sessions. They will be summarised and printed in a table with their index, creation date and tags. + +You can specify start date and end date to show sessions created in a selected period using `\s` for start date and `\e` for end date. If start date is not specified, it will take the earliest date a start date. If end date is not specified, it will take today as the end date. + +**Format:** `list ` + +Example `list /e 20201026` + +Expected outcome: + +```text + ______________________________________________________________________________ + You have 2 records in the given period: + Index Creation date Tags + 1 2020-10-26 [legs day, tricep] + 2 2020-10-26 [chest] + ______________________________________________________________________________ +``` + +#### 3.4.4. [Editing a Workout Session: `edit`](userguide.md) + +You can edit a past workout session in the record list. + +**Format:** `edit [INDEX]` + +You will go into the specific workout session after typing this command. You may verify by seeing the cursor changes from + +`Workout Menu >>>>>` + +to + +`Workout Menu > Workout Session X >>>>>` . + +The index can be found by listing all results or searching the target record. + +**Example:** `edit 1` + +Expected outcome: + +```text +Workout Menu > Workout Session 1 >>>>> +``` + +#### 3.4.5. [Deleting a Workout Session: `delete`](userguide.md) + +You can delete a past workout session in the record list. + +**Format:** `delete [INDEX]` + +The index can be found by listing the results. + +Example: `delete 1` + +Expected outcome: + +```text + ______________________________________________________________________________ + You have deleted that record! + ______________________________________________________________________________ +``` + +> :warning: Your workout session record cannot be recovered once deleted! + +#### 3.4.6. [Searching a List of Workout Sessions: `search`](userguide.md) + +You can search for a list of workout sessions that match certain conditions. For example, you can search for sessions created on a specific day or sessions that contain certain tags. All sessions that satisify the condition will be summaried into a table and printed out. + +**Format:** `search ` + +* Tag condition + +You can search records containing \(a list of\) tags with `/t` followed by the tags. Multiple tags should be separated by `,`. If you give multiple tags, only sessions that contains all the tags will be selected. You can search with part of the tag as well. For example searching with + `search /t leg` +will match any tags that contains `leg`, e.g. `legs`. + +* Date condition + +You can search records created on a specific day with `/d` followed by a date. You should key in your date following one of the supported formats. [See here](userguide.md#notes) for a complete list of format supported. + +Both date and tag conditions are optional. You may have zero, one or both conditions while searching. If both conditions are given, only sessions that meet both conditions will be selected. + +**Example:** `search /t le` + +Expected outcome: + +```text + ______________________________________________________________________________ + 1 records are found: + Index Creation date Tags + 2 2020-10-26 [legs day, tricep] + ______________________________________________________________________________ +``` + +#### 3.4.7. [Clearing All Workout Sessions: `clear`](userguide.md) + +You can erase all workout sessions. + +**Format:** `clear` + +Example `clear` + +Expected outcome: + +```text + ______________________________________________________________________________ + Are you sure you want to clear all records? This action is irrevocable. + Key in "YES" to confirm. + ______________________________________________________________________________ + +Workout Menu >>>>> YES + ______________________________________________________________________________ + You have cleared all records! + ______________________________________________________________________________ + +Workout Menu >>>>> +``` + +> :warning: This command is dangerous as you will not be able to recover the data. After typing this command, you will be asked to reconfirm it by typing in `YES`. Any other input will abort the clearing. + +#### 3.4.8. [Returning to Main Menu: `end`](userguide.md) + +You can return to the main menu. + +**Format:** `end` + +After typing in this, you will see your prompt in your terminal changes from `Workout Menu>>>` to `Main Menu>>>`. + +Example `end` + +Expected output + +```text + ______________________________________________________________________________ + Returning to Main menu... + ______________________________________________________________________________ + +Main Menu >>>>> +``` + +## 4. [Command Summary](userguide.md) + +**Main Menu** \[Khoa\] + +| **Action** | **Format** | +| :--- | :--- | +| Help | `help` | +| Profile Menu | `profile` | +| Diet Menu | `diet` | +| Workout Menu | `workout` | +| End | `end` | + +**Profile Menu** \[Khoa\] + +| **Action** | **Format** | +| :--- | :--- | +| Help | `help` | +| Add | `add /n [NAME] /h [HEIGHT] /w [CURRENT_WEIGHT] /e [EXPECTED_WEIGHT] /c [DAILY_CALORIE_INTAKE]` E.g. `add /n Schwarzenegger /h 188 /w 113 /e 100 /c 2500` | +| View | `view` | +| Edit | `edit ` E.g. `edit /w 110`, `edit /h 175 /w 110`, `edit /h 175 /w 110 /e 90` | +| Delete | `delete` | +| Return to Main Menu | `end` | + +**Diet Menu** \[Zeon\] + +| **Action** | **Format** | +| :--- | :--- | +| Help | `help` | +| Start diet session | `new ` E.g. `new /d 2020-05-04 /t breakfast` | +| List | `list` | +| Edit | `edit [INDEX]` E.g. `edit 1` | +| Delete | `delete [INDEX]` E.g. `delete 1` | +| Search | `search ` E.g. `search /t lunch` | +| Clear | `clear` | +| Return to Main Menu | `end` | + +**Diet Session** \[Shukai\] + +| **Action** | **Format** | +| :--- | :--- | +| Help | `help` | +| Add | `add [FOOD_NAME] /c [CALORIES]` E.g. `add spinach /c 90` | +| List | `list` | +| Delete | `delete [INDEX]` E.g. `delete 1` | +| Search | `search [FOOD_NAME]` E.g. `search rice` | +| Clear | `clear` | +| Return to Diet Menu | `end` | + +**Workout Menu** \[Zesong\] + +| **Action** | **Format** | +| :--- | :--- | +| Help | `help` | +| Start workout session | `new ` E.g. `new /t leg, chest` | +| List | `list ` E.g. `list /s 20201001 /e 2020/10/25` | +| Edit | `edit ` E.g. `edit 1` | +| Delete | `delete [INDEX]` E.g. `delete 1` | +| Search | `search ` E.g. `search /t leg day, chest /d 2020-10-18` | +| Clear | `clear` | +| Return to Main Menu | `end` | + +**Workout Session** \[Jinyang\] + +| **Action** | **Format** | +| :--- | :--- | +| Help | `help` | +| Add | `add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]` E.g. `add squat /n 15 /w 40` | +| List | `list` | +| Delete | `delete [INDEX]` E.g. `delete 1` | +| Search | `search [NAME_OF_MOVE]` E.g. `search bench` | +| Return to Workout Menu | `end` | + +## 5. [Notes](userguide.md) \[Zesong\] + +\[1\] Here shows all 12 valid formats. + +```text +`yyyyMMdd HH:mm` +`yyyy-MM-dd HH:mm` +`yyyy MM dd HH:mm` + +`yyyyMMdd HHmm` +`yyyy-MM-dd HHmm` +`yyyy MM dd HHmm` + +`yyyyMMdd` +`yyyy-MM-dd` +`yyyy MM dd` + +`dd MM yyyy` +`ddMMyyyy` +`dd-MM-yyyy` +``` + +## 6. [FAQ](userguide.md) \[Khoa\] + +Below are the answers to some frequently asked questions about The Schwarzenegger: + +**Q**: Can I use The Schwarzenegger on another operating systems apart from Windows? + **A**: Yes. The Schwarzenegger is compatible with Windows, MacOS and Linux. + +**Q**: How do I transfer my data to another Computer? + **A**: Install The Schwarzenegger in the other computer and overwrite the `saves` folder it creates with the `saves` folder of your previous The Schwarzenegger. + +**Q**: Can I exit The Schwarzenegger without typing `end` command? + **A**: Yes. Your data is saved automatically to `saves` folder whenever it changes. Therefore, you can exit The Schwarzenegger worry-free. + diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9cce..9109989e3c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,103 +1,103 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/diet/dietmanager/DietManager.java b/src/main/java/diet/dietmanager/DietManager.java new file mode 100644 index 0000000000..f9ded769ef --- /dev/null +++ b/src/main/java/diet/dietmanager/DietManager.java @@ -0,0 +1,126 @@ +package diet.dietmanager; + +import logic.commands.Command; +import logic.commands.CommandLib; +import diet.dietsession.DietSession; +import exceptions.ExceptionHandler; +import exceptions.InvalidCommandWordException; +import exceptions.InvalidDateFormatException; +import exceptions.SchwarzeneggerException; +import exceptions.diet.InvalidSearchDateException; +import exceptions.profile.InvalidCommandFormatException; +import logger.SchwarzeneggerLogger; +import logic.commands.CommandResult; +import logic.parser.DietManagerParser; +import storage.diet.DietStorage; +import ui.diet.dietmanager.DietManagerUi; + +import java.io.File; +import java.time.LocalDate; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static seedu.duke.Constants.COMMAND_WORD_END; + +//@@author CFZeon +/** + * A class that is responsible for interacting with user in Diet Manager. + */ +public class DietManager { + + private CommandLib cl; + private DietManagerParser parser; + private DietManagerUi dietManagerUi; + private DietStorage storage; + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + + /** + * Constructs DietManager and initializes command library for dietManager. + */ + public DietManager() { + storage = new DietStorage(); + cl = new CommandLib(); + cl.initDietManagerCl(); + parser = new DietManagerParser(); + dietManagerUi = new DietManagerUi(); + } + + /** + * Starts diet manager to read user input. + */ + public void start() { + dietManagerUi.printOpening("Diet Menu"); + String input = dietManagerUi.getCommand("Diet Menu"); + assert input != null : "Null input before input loop"; + inputLoop(input); + dietManagerUi.printReturning("Main Menu"); + } + + /** + * Loops the command processing until user types "end". + * + * @param input user input String + */ + private void inputLoop(String input) { + while (!input.equals(COMMAND_WORD_END)) { + try { + processCommand(input); + } catch (SchwarzeneggerException e) { + dietManagerUi.showToUser(ExceptionHandler.handleCheckedExceptions(e)); + } catch (Exception e) { + dietManagerUi.showToUser(ExceptionHandler.handleUncheckedExceptions(e)); + } + input = dietManagerUi.getCommand("Diet Menu"); + } + } + + /** + * Processes the user input to interpret correct command words. + * + * @param input user input for command. + * @throws InvalidCommandWordException handles InvalidCommandWordException. + * @throws InvalidDateFormatException handles invalid date input + * @throws InvalidSearchDateException handles invalid search date + */ + public void processCommand(String input) throws InvalidCommandWordException, InvalidDateFormatException, + InvalidSearchDateException { + String[] commParts = parser.parseCommand(input.trim()); + try { + Command command = cl.getCommand(commParts[0]); + CommandResult commandResult = command.execute(commParts[1].trim(), storage); + assert commandResult != null : "commandResult is not null when command executes properly"; + dietManagerUi.showToUser(commandResult.getFeedbackMessage()); + } catch (ArrayIndexOutOfBoundsException | InvalidCommandFormatException e) { + logger.log(Level.WARNING, "Invalid command in diet session"); + throw new InvalidCommandWordException(); + } + } + + /** + * Gets the total calories of the diet sessions on the specified date. + * + * @param savePath path to storage folder + * @param date date to find total calories + * @return total calories + */ + public double getDateTotalCalories(String savePath, LocalDate date) { + double totalCalories = 0; + File folder = new File(savePath); + File[] listOfFiles = folder.listFiles(); + assert folder.exists() : "save folder must exist before getting total calories"; + try { + // if date on file is same as input date, add to TotalCalories + for (int i = 0; i < Objects.requireNonNull(listOfFiles).length; i++) { + DietSession ds = storage.readDietSession(savePath, listOfFiles[i].getName()); + if (ds.getDate().equals(date)) { + totalCalories += ds.getTotalCalories(); + } + } + logger.log(Level.INFO, "Calculated total calories so far today"); + } catch (NullPointerException e) { + logger.log(Level.WARNING, "No instances of diet sessions saved"); + } + return totalCalories; + } +} diff --git a/src/main/java/diet/dietsession/DietSession.java b/src/main/java/diet/dietsession/DietSession.java new file mode 100644 index 0000000000..e8110e0c36 --- /dev/null +++ b/src/main/java/diet/dietsession/DietSession.java @@ -0,0 +1,179 @@ +package diet.dietsession; + +import exceptions.ExceptionHandler; +import exceptions.InvalidCommandWordException; +import exceptions.InvalidDateFormatException; +import logger.SchwarzeneggerLogger; +import logic.commands.Command; +import logic.commands.CommandLib; +import logic.commands.CommandResult; +import logic.parser.DietSessionParser; +import models.Food; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; +import logic.parser.DateParser; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; + +//@@author zsk612 +/** + * A class that is responsible for interacting with user in Diet Session. + */ +public class DietSession { + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + private final ArrayList foodList; + + private final String dateInput; + private final String typeInput; + private final LocalDate date; + private boolean isNew; + private int index; + + private final DietSessionUi dietSessionUi; + private transient CommandLib cl; + private final DietStorage storage; + private final DietSessionParser parser = new DietSessionParser(); + public boolean endDietSession = false; + + /** + * Constructs DietSession and initialize command library for dietSession. + * + * @param typeInput User input for meal type + * @param dateInput User input for meal date + * @param isNew Boolean that indicates whether the Diet Session is new or not + * @param index Integer for the index of the Diet Session + * @throws InvalidDateFormatException handles invalid date input + */ + public DietSession(String typeInput, String dateInput, boolean isNew, int index) throws InvalidDateFormatException { + this.cl = new CommandLib(); + cl.initDietSessionCl(); + this.dateInput = dateInput; + this.date = DateParser.parseDate(dateInput).toLocalDate(); + this.typeInput = typeInput; + this.foodList = new ArrayList<>(); + storage = new DietStorage(); + dietSessionUi = new DietSessionUi(); + this.isNew = isNew; + this.index = index; + } + + public String getDateInput() { + return dateInput; + } + + public LocalDate getDate() { + return date; + } + + public String getTypeInput() { + return typeInput; + } + + public void setEndDietSession(Boolean hasEnded) { + this.endDietSession = hasEnded; + } + + /** + * Starts dietSession and initializes command library for dietSession. + * @param isNew Boolean that indicates whether the Diet Session is new or not + * @param index Integer for the index of the Diet Session + * @throws IOException handles input/output exception + */ + public void start(boolean isNew, int index) throws IOException { + + logger.log(Level.INFO, "starting diet session"); + this.cl = new CommandLib(); + cl.initDietSessionCl(); + dietSessionUi.printOpening(); + setEndDietSession(false); + this.isNew = isNew; + this.index = index; + // save the file upon creation + saveToFile(PATH_TO_DIET_FOLDER, storage, this); + dietSessionInputLoop(); + setEndDietSession(true); + } + + /** + * Starts reading user input for dietSession commands. + */ + private void dietSessionInputLoop() { + String input = ""; + + if (isNew) { + input = dietSessionUi.getCommand("Diet Menu > New Diet Session"); + } else { + input = dietSessionUi.getCommand("Diet Menu > Diet Session " + index); + } + + while (!input.equals("end")) { + + try { + processCommand(input); + } catch (NullPointerException e) { + dietSessionUi.showToUser(ExceptionHandler.handleUncheckedExceptions(e)); + break; + } catch (InvalidCommandWordException e) { + dietSessionUi.showToUser(ExceptionHandler.handleCheckedExceptions(e)); + } + if (isNew) { + input = dietSessionUi.getCommand("Diet Menu > New Diet Session"); + } else { + input = dietSessionUi.getCommand("Diet Menu > Diet Session " + index); + } + } + } + + /** + * Processes user input for dietSession commands. + * + * @param input user input for command + * @throws NullPointerException handles null pointer exception + * @throws InvalidCommandWordException handles invalid commands + */ + private void processCommand(String input) throws NullPointerException, InvalidCommandWordException { + String[] commParts = parser.parseCommand(input); + Command command = cl.getCommand(commParts[0]); + CommandResult commandResult = command.execute(commParts[1].trim(), foodList, storage, index); + dietSessionUi.showToUser(commandResult.getFeedbackMessage()); + saveToFile(PATH_TO_DIET_FOLDER, storage, this); + } + + /** + * Calculates the sum of all food calories in diet session. + * + * @return sum of calories of food + */ + public double getTotalCalories() { + double totalCalories = 0; + for (int i = 0; i < foodList.size(); i++) { + totalCalories += foodList.get(i).getCalories(); + } + return totalCalories; + } + + + /** + * Constructs method to save changes to storage file. + * + * @param filePath string for file path + * @param storage storage for diet manager + * @param ds dietSession that is being changed + */ + public void saveToFile(String filePath, DietStorage storage, DietSession ds) { + try { + storage.init(filePath, ds.getDate().toString() + " " + ds.getTypeInput()); + storage.writeToStorageDietSession(filePath, ds.getDate().toString() + " " + ds.getTypeInput(), ds); + logger.log(Level.INFO, "Diet session successfully saved"); + } catch (IOException e) { + logger.log(Level.WARNING, "save profile session failed"); + dietSessionUi.showToUser("Failed to save your diet session!"); + } + } +} diff --git a/src/main/java/exceptions/EndException.java b/src/main/java/exceptions/EndException.java new file mode 100644 index 0000000000..716dc9ae0e --- /dev/null +++ b/src/main/java/exceptions/EndException.java @@ -0,0 +1,16 @@ +package exceptions; + +import static ui.profile.ProfileUi.MESSAGE_END; + +/** + * Represents exception when user wants to return to Main Menu. + */ +public class EndException extends SchwarzeneggerException { + + /** + * Constructs EndException object inheriting abstract class SchwarzeneggerException. + */ + public EndException() { + super(MESSAGE_END); + } +} diff --git a/src/main/java/exceptions/ExceptionHandler.java b/src/main/java/exceptions/ExceptionHandler.java new file mode 100644 index 0000000000..13451641ec --- /dev/null +++ b/src/main/java/exceptions/ExceptionHandler.java @@ -0,0 +1,36 @@ +package exceptions; + +import logger.SchwarzeneggerLogger; + +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author tienkhoa16 +/** + * A class that deals with handling exceptions. + */ +public class ExceptionHandler { + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + + /** + * Handles checked exceptions. + * + * @param e Checked exceptions in The Schwarzenegger. + * @return Error message. + */ + public static String handleCheckedExceptions(SchwarzeneggerException e) { + logger.log(Level.WARNING, "handling Schwarzenegger Exception: " + e.toString()); + return e.getMessage(); + } + + /** + * Handles unchecked exceptions. + * + * @param e Unchecked exception in Duke. + * @return Error message. + */ + public static String handleUncheckedExceptions(Exception e) { + logger.log(Level.WARNING, "handling unchecked exception: " + e.toString()); + return e.toString(); + } +} diff --git a/src/main/java/exceptions/InsufficientArgumentException.java b/src/main/java/exceptions/InsufficientArgumentException.java new file mode 100644 index 0000000000..dfab9514a0 --- /dev/null +++ b/src/main/java/exceptions/InsufficientArgumentException.java @@ -0,0 +1,19 @@ +package exceptions; + +import static ui.workout.workoutmanager.WorkoutManagerUi.MESSAGE_INSUFFICIENT_ARGUMENT; + +//@@author wgzesg +/** + * Represents exception when user inputs insufficient arguments to command. + */ +public class InsufficientArgumentException extends SchwarzeneggerException { + + /** + * Constructs InsufficientArgumentException object inheriting abstract class SchwarzeneggerException. + * + * @param format Correct format of command. + */ + public InsufficientArgumentException(String format) { + super(String.format(MESSAGE_INSUFFICIENT_ARGUMENT, format)); + } +} diff --git a/src/main/java/exceptions/InvalidCommandWordException.java b/src/main/java/exceptions/InvalidCommandWordException.java new file mode 100644 index 0000000000..2b57a22c70 --- /dev/null +++ b/src/main/java/exceptions/InvalidCommandWordException.java @@ -0,0 +1,17 @@ +package exceptions; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_COMMAND_WORD; + +//@@author tienkhoa16 +/** + * Represents exception when command word is invalid. + */ +public class InvalidCommandWordException extends SchwarzeneggerException { + + /** + * Constructs InvalidCommandWordException object inheriting abstract class SchwarzeneggerException. + */ + public InvalidCommandWordException() { + super(MESSAGE_INVALID_COMMAND_WORD); + } +} diff --git a/src/main/java/exceptions/InvalidDateFormatException.java b/src/main/java/exceptions/InvalidDateFormatException.java new file mode 100644 index 0000000000..6115595134 --- /dev/null +++ b/src/main/java/exceptions/InvalidDateFormatException.java @@ -0,0 +1,15 @@ +package exceptions; + +/** + * Represents exception when date time format is invalid. + */ +public class InvalidDateFormatException extends SchwarzeneggerException { + + /** + * Constructs InvalidDateFormatException object inheriting abstract class SchwarzeneggerException. + */ + public InvalidDateFormatException() { + super("Wrong format, please enter in the format: \n" + + "\t new /d [DATE: DD-MM-YYYY]"); + } +} diff --git a/src/main/java/exceptions/SchwarzeneggerException.java b/src/main/java/exceptions/SchwarzeneggerException.java new file mode 100644 index 0000000000..7e3066b9d6 --- /dev/null +++ b/src/main/java/exceptions/SchwarzeneggerException.java @@ -0,0 +1,17 @@ +package exceptions; + +//@@author tienkhoa16 +/** + * A base class for the checked exceptions in Schwarzenegger. + */ +public abstract class SchwarzeneggerException extends Exception { + + /** + * Constructs SchwarzeneggerException object inheriting class Exception. + * + * @param message Error message. + */ + public SchwarzeneggerException(String message) { + super(message); + } +} diff --git a/src/main/java/exceptions/diet/InvalidSearchDateException.java b/src/main/java/exceptions/diet/InvalidSearchDateException.java new file mode 100644 index 0000000000..e29e5b1d27 --- /dev/null +++ b/src/main/java/exceptions/diet/InvalidSearchDateException.java @@ -0,0 +1,16 @@ +package exceptions.diet; + +import exceptions.SchwarzeneggerException; + +/** + * Represents exception when search date is invalid. + */ +public class InvalidSearchDateException extends SchwarzeneggerException { + + /** + * Constructs InvalidSearchDateException object inheriting class SchwarzeneggerException. + */ + public InvalidSearchDateException() { + super("Starting date should be earlier than end date."); + } +} diff --git a/src/main/java/exceptions/diet/NegativeCaloriesException.java b/src/main/java/exceptions/diet/NegativeCaloriesException.java new file mode 100644 index 0000000000..282f235e59 --- /dev/null +++ b/src/main/java/exceptions/diet/NegativeCaloriesException.java @@ -0,0 +1,19 @@ +package exceptions.diet; + + +import exceptions.SchwarzeneggerException; + +import static ui.diet.dietsession.DietSessionUi.MESSAGE_NEGATIVE_CALORIES; + +/** + * Represents exception when calories is negative. + */ +public class NegativeCaloriesException extends SchwarzeneggerException { + + /** + * Constructs NegativeCaloriesException object inheriting abstract class SchwarzeneggerException. + */ + public NegativeCaloriesException() { + super(MESSAGE_NEGATIVE_CALORIES); + } +} diff --git a/src/main/java/exceptions/diet/NoNameException.java b/src/main/java/exceptions/diet/NoNameException.java new file mode 100644 index 0000000000..e6fd0f7027 --- /dev/null +++ b/src/main/java/exceptions/diet/NoNameException.java @@ -0,0 +1,18 @@ +package exceptions.diet; + +import exceptions.SchwarzeneggerException; + +import static ui.diet.dietsession.DietSessionUi.MESSAGE_NO_FOOD_NAME; + +/** + * Represents exception when no food name is entered. + */ +public class NoNameException extends SchwarzeneggerException { + + /** + * Constructs SchwarzeneggerException object inheriting class Exception. + */ + public NoNameException() { + super(MESSAGE_NO_FOOD_NAME); + } +} diff --git a/src/main/java/exceptions/profile/InvalidCaloriesException.java b/src/main/java/exceptions/profile/InvalidCaloriesException.java new file mode 100644 index 0000000000..fea1d739df --- /dev/null +++ b/src/main/java/exceptions/profile/InvalidCaloriesException.java @@ -0,0 +1,19 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_CALORIES; + +//@@author tienkhoa16 +/** + * Represents exception when input calories is invalid. + */ +public class InvalidCaloriesException extends SchwarzeneggerException { + + /** + * Constructs InvalidCaloriesException object inheriting abstract class SchwarzeneggerException. + */ + public InvalidCaloriesException() { + super(MESSAGE_INVALID_CALORIES); + } +} diff --git a/src/main/java/exceptions/profile/InvalidCommandFormatException.java b/src/main/java/exceptions/profile/InvalidCommandFormatException.java new file mode 100644 index 0000000000..cde8e8a595 --- /dev/null +++ b/src/main/java/exceptions/profile/InvalidCommandFormatException.java @@ -0,0 +1,21 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_COMMAND_FORMAT; + +//@@author tienkhoa16 +/** + * Represents exception when command format is invalid. + */ +public class InvalidCommandFormatException extends SchwarzeneggerException { + + /** + * Constructs InvalidCommandFormatException object inheriting abstract class SchwarzeneggerException. + * + * @param format Correct format of the command. + */ + public InvalidCommandFormatException(String format) { + super(String.format(MESSAGE_INVALID_COMMAND_FORMAT, format)); + } +} diff --git a/src/main/java/exceptions/profile/InvalidHeightException.java b/src/main/java/exceptions/profile/InvalidHeightException.java new file mode 100644 index 0000000000..9b6d6b4c08 --- /dev/null +++ b/src/main/java/exceptions/profile/InvalidHeightException.java @@ -0,0 +1,19 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_HEIGHT; + +//@@author tienkhoa16 +/** + * Represents exception when input height is invalid. + */ +public class InvalidHeightException extends SchwarzeneggerException { + + /** + * Constructs InvalidHeightException object inheriting abstract class SchwarzeneggerException. + */ + public InvalidHeightException() { + super(MESSAGE_INVALID_HEIGHT); + } +} diff --git a/src/main/java/exceptions/profile/InvalidNameException.java b/src/main/java/exceptions/profile/InvalidNameException.java new file mode 100644 index 0000000000..8635e4e9fe --- /dev/null +++ b/src/main/java/exceptions/profile/InvalidNameException.java @@ -0,0 +1,19 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_NAME; + +//@@author tienkhoa16 +/** + * Represents exception when input name is invalid. + */ +public class InvalidNameException extends SchwarzeneggerException { + + /** + * Constructs InvalidNameException object inheriting abstract class SchwarzeneggerException. + */ + public InvalidNameException() { + super(MESSAGE_INVALID_NAME); + } +} diff --git a/src/main/java/exceptions/profile/InvalidSaveFormatException.java b/src/main/java/exceptions/profile/InvalidSaveFormatException.java new file mode 100644 index 0000000000..5f6e022c90 --- /dev/null +++ b/src/main/java/exceptions/profile/InvalidSaveFormatException.java @@ -0,0 +1,22 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_SAVE_FORMAT; + +//@@author tienkhoa16 +/** + * Represents exception when there is corruption in data save format. + */ +public class InvalidSaveFormatException extends SchwarzeneggerException { + + /** + * Constructs InvalidSaveFormatException object inheriting abstract class SchwarzeneggerException. + * + * @param filePath Path to file with invalid save format. + */ + public InvalidSaveFormatException(String filePath) { + super(String.format(MESSAGE_INVALID_SAVE_FORMAT, filePath)); + } + +} diff --git a/src/main/java/exceptions/profile/InvalidWeightException.java b/src/main/java/exceptions/profile/InvalidWeightException.java new file mode 100644 index 0000000000..15cf9bfc26 --- /dev/null +++ b/src/main/java/exceptions/profile/InvalidWeightException.java @@ -0,0 +1,19 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_INVALID_WEIGHT; + +//@@author tienkhoa16 +/** + * Represents exception when input weight is invalid. + */ +public class InvalidWeightException extends SchwarzeneggerException { + + /** + * Constructs InvalidWeightException object inheriting abstract class SchwarzeneggerException. + */ + public InvalidWeightException() { + super(MESSAGE_INVALID_WEIGHT); + } +} diff --git a/src/main/java/exceptions/profile/LoadingException.java b/src/main/java/exceptions/profile/LoadingException.java new file mode 100644 index 0000000000..18aea1d630 --- /dev/null +++ b/src/main/java/exceptions/profile/LoadingException.java @@ -0,0 +1,21 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_LOADING_ERROR; + +//@@author tienkhoa16 +/** + * Represents exception while loading data. + */ +public class LoadingException extends SchwarzeneggerException { + + /** + * Constructs LoadingException object inheriting abstract class SchwarzeneggerException. + * + * @param message The cause of the error. + */ + public LoadingException(String message) { + super(String.format(MESSAGE_LOADING_ERROR, message)); + } +} diff --git a/src/main/java/exceptions/profile/SavingException.java b/src/main/java/exceptions/profile/SavingException.java new file mode 100644 index 0000000000..bd4cd98ea9 --- /dev/null +++ b/src/main/java/exceptions/profile/SavingException.java @@ -0,0 +1,21 @@ +package exceptions.profile; + +import exceptions.SchwarzeneggerException; + +import static ui.profile.ProfileUi.MESSAGE_SAVING_ERROR; + +//@@author tienkhoa16 +/** + * Represents exception while saving data. + */ +public class SavingException extends SchwarzeneggerException { + + /** + * Constructs SavingException object inheriting abstract class SchwarzeneggerException. + * + * @param message The cause of the error. + */ + public SavingException(String message) { + super(String.format(MESSAGE_SAVING_ERROR, message)); + } +} diff --git a/src/main/java/exceptions/workout/workoutmanager/NotANumberException.java b/src/main/java/exceptions/workout/workoutmanager/NotANumberException.java new file mode 100644 index 0000000000..68c269ad4a --- /dev/null +++ b/src/main/java/exceptions/workout/workoutmanager/NotANumberException.java @@ -0,0 +1,19 @@ +package exceptions.workout.workoutmanager; + +import exceptions.SchwarzeneggerException; + +import static ui.workout.workoutmanager.WorkoutManagerUi.MESSAGE_NOT_A_NUMBER; + +//@@author wgzesg +/** + * Represents exception when input is not a number. + */ +public class NotANumberException extends SchwarzeneggerException { + + /** + * Constructs NotANumberException object inheriting abstract class SchwarzeneggerException. + */ + public NotANumberException() { + super(MESSAGE_NOT_A_NUMBER); + } +} diff --git a/src/main/java/exceptions/workout/workoutmanager/OutOfArrayException.java b/src/main/java/exceptions/workout/workoutmanager/OutOfArrayException.java new file mode 100644 index 0000000000..0e7496a261 --- /dev/null +++ b/src/main/java/exceptions/workout/workoutmanager/OutOfArrayException.java @@ -0,0 +1,19 @@ +package exceptions.workout.workoutmanager; + +import exceptions.SchwarzeneggerException; + +import static ui.workout.workoutmanager.WorkoutManagerUi.MESSAGE_OUT_OF_ARRAY; + +//@@author wgzesg +/** + * Represents exception when index is out of array. + */ +public class OutOfArrayException extends SchwarzeneggerException { + + /** + * Constructs OutOfArrayException object inheriting abstract class SchwarzeneggerException. + */ + public OutOfArrayException() { + super(MESSAGE_OUT_OF_ARRAY); + } +} diff --git a/src/main/java/exceptions/workout/workoutmanager/SchwIoException.java b/src/main/java/exceptions/workout/workoutmanager/SchwIoException.java new file mode 100644 index 0000000000..79b5110a9b --- /dev/null +++ b/src/main/java/exceptions/workout/workoutmanager/SchwIoException.java @@ -0,0 +1,19 @@ +package exceptions.workout.workoutmanager; + +import exceptions.SchwarzeneggerException; + +//@@author wgzesg +/** + * Represents exception when IOException is thrown in WorkoutManagerStorage. + */ +public class SchwIoException extends SchwarzeneggerException { + + /** + * Constructs SchwIoException object inheriting abstract class SchwarzeneggerException. + * + * @param content Content of the error message. + */ + public SchwIoException(String content) { + super(content); + } +} diff --git a/src/main/java/exceptions/workout/workoutsession/AddFormatException.java b/src/main/java/exceptions/workout/workoutsession/AddFormatException.java new file mode 100644 index 0000000000..cde7f11e3c --- /dev/null +++ b/src/main/java/exceptions/workout/workoutsession/AddFormatException.java @@ -0,0 +1,17 @@ +package exceptions.workout.workoutsession; + +import exceptions.SchwarzeneggerException; + +//@@author yujinyang1998 +/** + * Represents exception when add format in WorkoutSession is invalid. + */ +public class AddFormatException extends SchwarzeneggerException { + + /** + * Constructs AddFormatException object inheriting abstract class SchwarzeneggerException. + */ + public AddFormatException() { + super("AddFormatException"); + } +} diff --git a/src/main/java/exceptions/workout/workoutsession/DeleteFormatException.java b/src/main/java/exceptions/workout/workoutsession/DeleteFormatException.java new file mode 100644 index 0000000000..2fef74a41b --- /dev/null +++ b/src/main/java/exceptions/workout/workoutsession/DeleteFormatException.java @@ -0,0 +1,17 @@ +package exceptions.workout.workoutsession; + +import exceptions.SchwarzeneggerException; + +//@@author yujinyang1998 +/** + * Represents exception when delete format in WorkoutSession is invalid. + */ +public class DeleteFormatException extends SchwarzeneggerException { + + /** + * Constructs DeleteFormatException object inheriting abstract class SchwarzeneggerException. + */ + public DeleteFormatException() { + super("DeleteFormatException"); + } +} diff --git a/src/main/java/logger/SchwarzeneggerLogger.java b/src/main/java/logger/SchwarzeneggerLogger.java new file mode 100644 index 0000000000..4a9dfdba77 --- /dev/null +++ b/src/main/java/logger/SchwarzeneggerLogger.java @@ -0,0 +1,53 @@ +package logger; + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import static seedu.duke.Constants.PATH_TO_LOG_FILE; + +//@@author tienkhoa16 +/** + * A class to handle file logging. + */ +public class SchwarzeneggerLogger { + private static SchwarzeneggerLogger singleInstance = null; + private Logger logger; + + /** + * Constructs SchwarzeneggerLogger object. + */ + private SchwarzeneggerLogger() { + try { + File file = new File(PATH_TO_LOG_FILE); + file.getParentFile().mkdirs(); + file.createNewFile(); + + SimpleFormatter formatter = new SimpleFormatter(); + FileHandler fh = new FileHandler(PATH_TO_LOG_FILE); + fh.setFormatter(formatter); + + logger = Logger.getLogger("SchwarzeneggerLogger"); + logger.addHandler(fh); + logger.setUseParentHandlers(false); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Gets logger attribute of SchwarzeneggerLogger. + * + * @return Logger. + */ + public static Logger getInstanceLogger() { + if (singleInstance == null) { + singleInstance = new SchwarzeneggerLogger(); + } + return singleInstance.logger; + } +} diff --git a/src/main/java/logic/commands/Command.java b/src/main/java/logic/commands/Command.java new file mode 100644 index 0000000000..56c31c3f1c --- /dev/null +++ b/src/main/java/logic/commands/Command.java @@ -0,0 +1,121 @@ +package logic.commands; + +import exceptions.InvalidCommandWordException; +import exceptions.InvalidDateFormatException; +import exceptions.SchwarzeneggerException; +import exceptions.diet.InvalidSearchDateException; +import exceptions.profile.InvalidCommandFormatException; +import logger.SchwarzeneggerLogger; +import models.ExerciseList; +import models.Food; +import storage.diet.DietStorage; +import storage.profile.ProfileStorage; +import storage.workout.WorkoutSessionStorage; +import ui.CommonUi; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A base class for command. + */ +public abstract class Command { + protected static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + public CommonUi ui; + + /** + * Constructs Command object. + */ + public Command() { + ui = new CommonUi(); + } + + //@@author wgzesg + /** + * Executes the command with given arguments. + * + * @param args Array of user's input. + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + public CommandResult execute(String args) throws SchwarzeneggerException { + logger.log(Level.INFO, "Executing " + this); + return new CommandResult(); + } + + //@@author tienkhoa16 + + /** + * Executes the command with user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + logger.log(Level.INFO, "Executing " + this); + return new CommandResult(); + } + + //@@author + + /** + * Executes the command with user's input. + * + * @param input User's input for command. + * @param storage Diet Storage to load and save data. + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + * @throws InvalidCommandWordException If command word is invalid. + * @throws InvalidCommandFormatException If command format is invalid. + * @throws InvalidDateFormatException If date format is invalid. + * @throws InvalidSearchDateException If search date is invalid. + */ + public CommandResult execute(String input, DietStorage storage) throws InvalidCommandWordException, + InvalidCommandFormatException, InvalidDateFormatException, InvalidSearchDateException { + return new CommandResult(); + } + + /** + * Executes the command with user's input. + * + * @param input User's input for command. + * @param foodList List containing food in diet session. + * @param storage Diet Storage to load and save data. + * @param index Integer variable that shows the index of diet session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + * @throws InvalidCommandWordException If command word is invalid. + */ + public CommandResult execute(String input, ArrayList foodList, + DietStorage storage, Integer index) throws InvalidCommandWordException { + return new CommandResult(); + } + + + //@@author yujinyang1998 + + /** + * Executes the command with user's input. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + * @throws InvalidCommandWordException If command word is invalid. + */ + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) throws InvalidCommandWordException { + logger.log(Level.INFO, "Executing " + this); + return new CommandResult(); + } + //@@author +} diff --git a/src/main/java/logic/commands/CommandLib.java b/src/main/java/logic/commands/CommandLib.java new file mode 100644 index 0000000000..e318d3165c --- /dev/null +++ b/src/main/java/logic/commands/CommandLib.java @@ -0,0 +1,180 @@ +package logic.commands; + +import logic.commands.diet.dietmanager.DietSessionClear; +import logic.commands.diet.dietmanager.DietSessionCreate; +import logic.commands.diet.dietmanager.DietSessionDelete; +import logic.commands.diet.dietmanager.DietSessionEdit; +import logic.commands.diet.dietmanager.DietSessionHelp; +import logic.commands.diet.dietmanager.DietSessionList; +import logic.commands.diet.dietmanager.DietSessionSearch; +import logic.commands.diet.dietmanager.DietSessionWrong; +import logic.commands.diet.dietsession.FoodItemAdd; +import logic.commands.diet.dietsession.FoodItemClear; +import logic.commands.diet.dietsession.FoodItemDelete; +import logic.commands.diet.dietsession.FoodItemHelp; +import logic.commands.diet.dietsession.FoodItemList; +import logic.commands.diet.dietsession.FoodItemSearch; +import logic.commands.diet.dietsession.FoodItemWrong; +import logic.commands.main.MainEnd; +import logic.commands.main.MainHelp; +import logic.commands.main.MainWrong; +import logic.commands.main.ToDiet; +import logic.commands.main.ToProfile; +import logic.commands.main.ToWorkout; +import logic.commands.profile.ProfileAdd; +import logic.commands.profile.ProfileDelete; +import logic.commands.profile.ProfileEdit; +import logic.commands.profile.ProfileEnd; +import logic.commands.profile.ProfileHelp; +import logic.commands.profile.ProfileView; +import logic.commands.profile.ProfileWrong; +import logic.commands.workout.workoutmanager.ByeWS; +import logic.commands.workout.workoutmanager.ClearWS; +import logic.commands.workout.workoutmanager.DeleteWS; +import logic.commands.workout.workoutmanager.EditWS; +import logic.commands.workout.workoutmanager.HelpWS; +import logic.commands.workout.workoutmanager.ListWS; +import logic.commands.workout.workoutmanager.NewWS; +import logic.commands.workout.workoutmanager.SearchWS; +import logic.commands.workout.workoutmanager.WrongWS; +import logic.commands.workout.workoutsession.WorkoutSessionAdd; +import logic.commands.workout.workoutsession.WorkoutSessionDelete; +import logic.commands.workout.workoutsession.WorkoutSessionEnd; +import logic.commands.workout.workoutsession.WorkoutSessionHelp; +import logic.commands.workout.workoutsession.WorkoutSessionList; +import logic.commands.workout.workoutsession.WorkoutSessionSearch; +import logic.commands.workout.workoutsession.WorkoutSessionWrong; + +import java.util.Hashtable; + +import static seedu.duke.Constants.COMMAND_WORD_ADD; +import static seedu.duke.Constants.COMMAND_WORD_CLEAR; +import static seedu.duke.Constants.COMMAND_WORD_DELETE; +import static seedu.duke.Constants.COMMAND_WORD_DIET; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static seedu.duke.Constants.COMMAND_WORD_END; +import static seedu.duke.Constants.COMMAND_WORD_HELP; +import static seedu.duke.Constants.COMMAND_WORD_LIST; +import static seedu.duke.Constants.COMMAND_WORD_NEW; +import static seedu.duke.Constants.COMMAND_WORD_PROFILE; +import static seedu.duke.Constants.COMMAND_WORD_SEARCH; +import static seedu.duke.Constants.COMMAND_WORD_VIEW; +import static seedu.duke.Constants.COMMAND_WORD_WORKOUT; +import static seedu.duke.Constants.COMMAND_WORD_WRONG; + +//@@author wgzesg +/** + * A class for extracting the correct Command object for each command word. + */ +public class CommandLib { + + public Hashtable library; + + /** + * Constructs CommandLib object. + */ + public CommandLib() { + library = new Hashtable<>(); + } + + //@@author wgzesg + /** + * Initializes the commandLib with main menu's commands. + */ + public void initMainMenuCl() { + library.put(COMMAND_WORD_WRONG, new MainWrong()); + library.put(COMMAND_WORD_HELP, new MainHelp()); + library.put(COMMAND_WORD_DIET, new ToDiet()); + library.put(COMMAND_WORD_PROFILE, new ToProfile()); + library.put(COMMAND_WORD_WORKOUT, new ToWorkout()); + library.put(COMMAND_WORD_END, new MainEnd()); + } + + //@@author tienkhoa16 + /** + * Initializes the commandLib with profile menu's commands. + */ + public void initProfileSessionCl() { + library.put(COMMAND_WORD_HELP, new ProfileHelp()); + library.put(COMMAND_WORD_ADD, new ProfileAdd()); + library.put(COMMAND_WORD_DELETE, new ProfileDelete()); + library.put(COMMAND_WORD_VIEW, new ProfileView()); + library.put(COMMAND_WORD_EDIT, new ProfileEdit()); + library.put(COMMAND_WORD_END, new ProfileEnd()); + library.put(COMMAND_WORD_WRONG, new ProfileWrong()); + } + + //@@author + /** + * Initializes the commandLib with workout menu's commands. + */ + public void initWorkoutManagerCl() { + library.put(COMMAND_WORD_LIST, new ListWS()); + library.put(COMMAND_WORD_NEW, new NewWS()); + library.put(COMMAND_WORD_DELETE, new DeleteWS()); + library.put(COMMAND_WORD_END, new ByeWS()); + library.put(COMMAND_WORD_EDIT, new EditWS()); + library.put(COMMAND_WORD_CLEAR, new ClearWS()); + library.put(COMMAND_WORD_HELP, new HelpWS()); + library.put(COMMAND_WORD_WRONG, new WrongWS()); + library.put(COMMAND_WORD_SEARCH, new SearchWS()); + } + + //@@author yujinyang1998 + /** + * Initializes the commandLib with workout session's commands. + */ + public void initWorkoutSessionCl() { + library.put(COMMAND_WORD_ADD, new WorkoutSessionAdd()); + library.put(COMMAND_WORD_DELETE, new WorkoutSessionDelete()); + library.put(COMMAND_WORD_LIST, new WorkoutSessionList()); + library.put(COMMAND_WORD_END, new WorkoutSessionEnd()); + library.put(COMMAND_WORD_HELP, new WorkoutSessionHelp()); + library.put(COMMAND_WORD_SEARCH, new WorkoutSessionSearch()); + library.put(COMMAND_WORD_WRONG, new WorkoutSessionWrong()); + } + + //@@author + /** + * Initializes the commandLib with diet manager's commands. + */ + public void initDietManagerCl() { + library.put(COMMAND_WORD_LIST, new DietSessionList()); + library.put(COMMAND_WORD_NEW, new DietSessionCreate()); + library.put(COMMAND_WORD_HELP, new DietSessionHelp()); + library.put(COMMAND_WORD_CLEAR, new DietSessionClear()); + library.put(COMMAND_WORD_SEARCH, new DietSessionSearch()); + library.put(COMMAND_WORD_EDIT, new DietSessionEdit()); + library.put(COMMAND_WORD_DELETE, new DietSessionDelete()); + library.put(COMMAND_WORD_WRONG, new DietSessionWrong()); + } + + //@@author wgzesg + /** + * Initializes the commandLib with diet session's commands. + */ + public void initDietSessionCl() { + library.put(COMMAND_WORD_ADD, new FoodItemAdd()); + library.put(COMMAND_WORD_DELETE, new FoodItemDelete()); + library.put(COMMAND_WORD_HELP, new FoodItemHelp()); + library.put(COMMAND_WORD_CLEAR, new FoodItemClear()); + library.put(COMMAND_WORD_SEARCH, new FoodItemSearch()); + library.put(COMMAND_WORD_LIST, new FoodItemList()); + library.put(COMMAND_WORD_WRONG, new FoodItemWrong()); + } + + //@@author wgzesg + /** + * Gets specific Command object based on command keyword. + * + * @param keyword Keyword specifying type of command. + * @return Command object. + */ + public Command getCommand(String keyword) { + if (keyword == null || !library.containsKey(keyword.toLowerCase())) { + return library.get(COMMAND_WORD_WRONG); + } else { + return library.get(keyword.toLowerCase()); + } + } +} diff --git a/src/main/java/logic/commands/CommandResult.java b/src/main/java/logic/commands/CommandResult.java new file mode 100644 index 0000000000..e3475ddc29 --- /dev/null +++ b/src/main/java/logic/commands/CommandResult.java @@ -0,0 +1,58 @@ +package logic.commands; + +import static ui.CommonUi.EMPTY_STRING; + +/** + * A class representing result shown to user after executing the requested command. + */ +public class CommandResult { + private String feedbackMessage; + private ExecutionResult status; + + /** + * Constructs CommandResult object. + * + * @param feedbackMessage Feedback message after executing command. + * @param status Execution status. + */ + public CommandResult(String feedbackMessage, ExecutionResult status) { + this.feedbackMessage = feedbackMessage; + this.status = status; + } + + /** + * Constructs CommandResult object with status OK. + * + * @param feedbackMessage Feedback message after executing command. + */ + public CommandResult(String feedbackMessage) { + this.feedbackMessage = feedbackMessage; + status = ExecutionResult.OK; + } + + /** + * Constructs CommandResult object with empty feedback message and status OK. + */ + public CommandResult() { + feedbackMessage = EMPTY_STRING; + status = ExecutionResult.OK; + } + + /** + * Gets execution status. + * + * @return Execution status. + */ + public ExecutionResult getStatus() { + return status; + } + + /** + * Gets feedback message to user. + * + * @return Feedback message to user. + */ + public String getFeedbackMessage() { + return feedbackMessage; + } +} diff --git a/src/main/java/logic/commands/ExecutionResult.java b/src/main/java/logic/commands/ExecutionResult.java new file mode 100644 index 0000000000..dd7b0e14ac --- /dev/null +++ b/src/main/java/logic/commands/ExecutionResult.java @@ -0,0 +1,12 @@ +package logic.commands; + +//@@author wgzesg +/** + * Enumerations of execution result status. + */ +public enum ExecutionResult { + OK, + ABORTED, + FAILED, + SKIPPED +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionClear.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionClear.java new file mode 100644 index 0000000000..a6d1548eae --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionClear.java @@ -0,0 +1,53 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; +import ui.CommonUi; + +import java.io.File; +import java.util.Objects; +import java.util.logging.Level; + +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; +import static ui.diet.dietmanager.DietManagerUi.CLEAR_RECORD; +import static ui.diet.dietmanager.DietManagerUi.DIET_CLEAR_MSG; +import static ui.diet.dietmanager.DietManagerUi.DIET_MENU_NAME; +import static ui.diet.dietmanager.DietManagerUi.DIET_NOTHING_TO_CLEAR_MSG; +import static ui.diet.dietmanager.DietManagerUi.EMPTY_STRING; +import static ui.workout.workoutmanager.WorkoutManagerUi.CLEAR_ABORTED; + +//@@author CFZeon +/** + * A representation of the command for clear commands in diet manager. + */ +public class DietSessionClear extends Command { + /** + * Overrides execute for clear command to clear all diet sessions. + * + * @param input user input for command + * @param storage storage for diet manager + * @return CommandResult instance + */ + @Override + public CommandResult execute(String input, DietStorage storage) { + String resultMessage = EMPTY_STRING; + try { + if (ui.checkConfirmation(DIET_MENU_NAME, CLEAR_RECORD)) { + File folder = new File(PATH_TO_DIET_FOLDER); + File[] listOfFiles = folder.listFiles(); + for (int index = 0; index < Objects.requireNonNull(listOfFiles).length; index++) { + listOfFiles[index].delete(); + } + resultMessage = CommonUi.clearMsg(DIET_CLEAR_MSG); + logger.log(Level.INFO, "Cleared all diet sessions"); + } else { + resultMessage = CLEAR_ABORTED; + } + } catch (NullPointerException e) { + resultMessage = DIET_NOTHING_TO_CLEAR_MSG; + logger.log(Level.INFO, "No sessions in dietManager for deletion"); + } + return new CommandResult(resultMessage); + } +} \ No newline at end of file diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionCreate.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionCreate.java new file mode 100644 index 0000000000..ae1dae81a7 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionCreate.java @@ -0,0 +1,65 @@ +package logic.commands.diet.dietmanager; + + +import logic.parser.DietManagerParser; +import diet.dietsession.DietSession; +import exceptions.InvalidDateFormatException; +import exceptions.profile.InvalidCommandFormatException; +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; + +import java.io.IOException; +import java.util.HashMap; +import java.util.logging.Level; + +import static ui.diet.dietmanager.DietManagerUi.DIET_CREATE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_DATE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_IO_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_NEW_SUCCESS; +import static ui.diet.dietmanager.DietManagerUi.EMPTY_STRING; + +//@@author CFZeon +/** + * A representation of the command for create commands in diet manager. + */ +public class DietSessionCreate extends Command { + + private final DietManagerParser parser = new DietManagerParser(); + + /** + * Overrides execute for create command to create new diet sessions. + * + * @param input user input for command + * @param storage storage for diet manager + * @return CommandResult with ended diet session message + */ + @Override + public CommandResult execute(String input, DietStorage storage) { + String result = EMPTY_STRING; + try { + StringBuilder message = new StringBuilder(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + // extract the date and tags and assigns it to the string + String date = parser.extractNewDate(parsedParams, message); + String tag = parser.extractNewTag(parsedParams, message); + if (message.length() != 0) { + ui.showToUser(message.toString().trim()); + } + DietSession ds = new DietSession(tag, date, true, -1); + assert ds != null : "Diet session constructed without error"; + logger.log(Level.INFO, "Diet session successfully created"); + ds.start(true, -1); + result = DIET_NEW_SUCCESS; + } catch (IOException e) { + result = DIET_IO_WRONG_FORMAT; + } catch (InvalidDateFormatException e) { + logger.log(Level.WARNING, "Wrong date format"); + result = DIET_DATE_WRONG_FORMAT; + } catch (InvalidCommandFormatException e) { + logger.log(Level.WARNING, "Invalid command in dietSessionCreate"); + result = DIET_CREATE_WRONG_FORMAT; + } + return new CommandResult(result); + } +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionDelete.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionDelete.java new file mode 100644 index 0000000000..f9ca227ee6 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionDelete.java @@ -0,0 +1,51 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; + +import java.io.File; +import java.util.logging.Level; + +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; +import static ui.diet.dietmanager.DietManagerUi.DIET_DELETE_SUCCESS; +import static ui.diet.dietmanager.DietManagerUi.DIET_DELETE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_ARRAY_OUT_OF_BOUND; +import static ui.diet.dietmanager.DietManagerUi.DIET_NO_SESSIONS_SAVED; + +//@@author CFZeon +/** + * A representation of the command for delete commands in diet manager. + */ +public class DietSessionDelete extends Command { + + /** + * Overrides execute for delete command to delete diet sessions. + * + * @param input user input for command + * @param storage storage for diet manager + * @return CommandResult instance for delete message + */ + @Override + public CommandResult execute(String input, DietStorage storage) { + File folder = new File(PATH_TO_DIET_FOLDER); + File[] listOfFiles = folder.listFiles(); + String result = ""; + try { + assert !input.isEmpty() : "No files to delete or wrong folder"; + assert listOfFiles != null; + result = DIET_DELETE_SUCCESS; + listOfFiles[Integer.parseInt(input) - 1].delete(); + logger.log(Level.INFO, "Deleted Diet Session successfully"); + } catch (NumberFormatException e) { + result = DIET_DELETE_WRONG_FORMAT; + logger.log(Level.INFO, "No or wrong index for deletion in dietManager"); + } catch (IndexOutOfBoundsException e) { + result = DIET_FILE_ARRAY_OUT_OF_BOUND; + logger.log(Level.INFO, "No input for session index"); + } catch (NullPointerException e) { + result = DIET_NO_SESSIONS_SAVED; + } + return new CommandResult(result); + } +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionEdit.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionEdit.java new file mode 100644 index 0000000000..75eac0a880 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionEdit.java @@ -0,0 +1,58 @@ +package logic.commands.diet.dietmanager; + +import diet.dietsession.DietSession; +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.logging.Level; + +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; +import static ui.diet.dietmanager.DietManagerUi.DIET_EDIT_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_ARRAY_OUT_OF_BOUND; +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_CORRUPTED_MSG; +import static ui.diet.dietmanager.DietManagerUi.DIET_NEW_SUCCESS; +import static ui.diet.dietmanager.DietManagerUi.EMPTY_STRING; + +//@@author CFZeon +/** + * A representation of the command for diet commands in diet manager. + */ +public class DietSessionEdit extends Command { + /** + * Overrides execute for edit command to edit diet sessions. + * + * @param input user input for command + * @param storage storage for diet manager + * @return CommandResult instance to print command message + */ + @Override + public CommandResult execute(String input, DietStorage storage) { + String result = EMPTY_STRING; + File folder = new File(PATH_TO_DIET_FOLDER); + File[] listOfFiles = folder.listFiles(); + try { + DietSession ds = null; + assert listOfFiles != null : "List of files should not be null"; + ds = storage.readDietSession(PATH_TO_DIET_FOLDER, listOfFiles[Integer.parseInt(input) - 1].getName()); + ds.start(false, Integer.parseInt(input)); + logger.log(Level.INFO, "Diet session in edit mode"); + result = DIET_NEW_SUCCESS; + } catch (FileNotFoundException + | NumberFormatException e) { + result = DIET_EDIT_WRONG_FORMAT; + logger.log(Level.WARNING, "No file found at array index"); + } catch (IOException e) { + logger.log(Level.WARNING, "failed to execute diet session"); + } catch (IndexOutOfBoundsException e) { + result = DIET_FILE_ARRAY_OUT_OF_BOUND; + } catch (NullPointerException e) { + logger.log(Level.WARNING, "File might be corrupted"); + result = DIET_FILE_CORRUPTED_MSG; + } + return new CommandResult(result); + } +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionHelp.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionHelp.java new file mode 100644 index 0000000000..347f688b09 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionHelp.java @@ -0,0 +1,44 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; + +import java.util.logging.Level; + +import static ui.CommonUi.helpFormatter; + +//@@author CFZeon +/** + * A representation of the command for help commands in diet manager. + */ +public class DietSessionHelp extends Command { + /** + * Overrides execute for help command to display help information. + * + * @param input user input for command + * @param storage storage for diet manager + * @return CommandResult with help message + */ + @Override + public CommandResult execute(String input, DietStorage storage) { + StringBuilder message = new StringBuilder(); + message.append(helpFormatter("New", "new ", + "Create a new diet session")); + message.append(helpFormatter("List", "list", + "Show all past diet sessions")); + message.append(helpFormatter("Delete", "delete [INDEX]", + "Delete the diet session at the input index")); + message.append(helpFormatter("Edit", "edit [INDEX]", + "Edit the diet session at the input index")); + message.append(helpFormatter("Search", + "search ", + "Search the diet session in between starting and end dates with a specific tag")); + message.append(helpFormatter("Clear", "clear", + "Clear all past diet sessions")); + message.append(helpFormatter("End", "end", + "Go back to Main Menu")); + logger.log(Level.INFO, "Displayed help in dietManager"); + return new CommandResult(message.toString().trim()); + } +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionList.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionList.java new file mode 100644 index 0000000000..887ca38e5e --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionList.java @@ -0,0 +1,110 @@ +package logic.commands.diet.dietmanager; + +import diet.dietsession.DietSession; +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; +import ui.diet.dietmanager.DietManagerUi; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; +import static ui.CommonUi.LS; +import static ui.diet.dietmanager.DietManagerUi.EMPTY_STRING; + +//@@author CFZeon +/** + * A representation of the command for list commands in diet manager. + */ +public class DietSessionList extends Command { + + /** + * Overrides execute for list command to list diet sessions. + * + * @param input user input for command + * @param storage storage for diet manager + * @return CommandResult with list message + */ + @Override + public CommandResult execute(String input, DietStorage storage) { + String message = EMPTY_STRING; + File folder = new File(PATH_TO_DIET_FOLDER); + File[] listOfFiles = folder.listFiles(); + StringBuilder listResult = new StringBuilder(); + assert folder.exists(); + try { + String dietSessionListSize = "You have " + listOfFiles.length + " record(s)" + LS; + String dietSessionList = formatList(listOfFiles, storage); + listResult.append(dietSessionListSize); + listResult.append(dietSessionList); + message = listResult.toString(); + logger.log(Level.INFO, "Listed all available diet sessions"); + } catch (NullPointerException | NoSuchElementException e) { + message = DietManagerUi.DIET_NO_SESSION_SAVED; + logger.log(Level.WARNING, "No instances of diet sessions saved"); + } + return new CommandResult(message); + } + + //@@author CFZeon-reused + + /** + * Formats the list of files into a table input. + * + * @param listOfFiles list of files in the folder + * @param storage storage instance to load files from folder + * @return string with formatted table of files + */ + private String formatList(File[] listOfFiles, DietStorage storage) { + ArrayList fileArrayList = new ArrayList<>(); + // converts all files in the array to an arraylist format + Collections.addAll(fileArrayList, listOfFiles); + // converts the file names into a stream + ArrayList fileNames = (ArrayList) fileArrayList.stream() + .map(f -> f.getName().split(" ", 2)[1].trim()).collect(Collectors.toList()); + // determine length of column for dynamic resizing + int descriptionMaxLenInt = Math.max(8, + fileNames.stream().max(Comparator.comparingInt(String::length)).get().length()); + + String descriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt + 1) + "s"; + String returnString = String.format("%-8s", "Index") + String.format(descriptionFormat, "Tags") + + String.format("%-12s", "Date") + String.format("%-10s", "Calories") + LS; + + StringBuilder infoBuilder = new StringBuilder(returnString); + String listDescriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt) + "s %-11s %s"; + // adds the contents of each diet session and consolidates it into table format + for (int i = 0; i < fileArrayList.size(); i++) { + DietSession ds = storage.readDietSession(PATH_TO_DIET_FOLDER, listOfFiles[i].getName()); + double totalCalories = ds.getTotalCalories(); + // formats each diet session entry into column form + String rowContent = formatRow(fileArrayList, listDescriptionFormat, i, totalCalories); + String row = String.format("%-8s", i + 1) + rowContent + LS; + infoBuilder.append(row); + } + returnString = infoBuilder.toString().trim(); + return returnString; + } + + /** + * Formats the row of text for each file. + * + * @param fileArrayList list of files in the folder converted to arraylist + * @param listDescriptionFormat description of the file + * @param i iterator integer for the file + * @param totalCalories double for calories in that loaded diet session + * @return formatted string of a row + */ + private String formatRow(ArrayList fileArrayList, String listDescriptionFormat, int i, double totalCalories) { + String rowContent = String.format(listDescriptionFormat, + fileArrayList.get(i).getName().replaceFirst("[.][^.]+$", "").split(" ", 2)[1], + fileArrayList.get(i).getName().replaceFirst("[.][^.]+$", "").split(" ", 2)[0], + totalCalories); + return rowContent; + } +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionSearch.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionSearch.java new file mode 100644 index 0000000000..51aa731b16 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionSearch.java @@ -0,0 +1,212 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.parser.DateParser; +import logic.parser.DietManagerParser; +import diet.dietsession.DietSession; +import exceptions.InvalidDateFormatException; +import exceptions.diet.InvalidSearchDateException; +import exceptions.profile.InvalidCommandFormatException; +import storage.diet.DietStorage; + +import java.io.File; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; +import static ui.CommonUi.LS; +import static ui.diet.dietmanager.DietManagerUi.DIET_DATE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_NO_SESSIONS_SAVED; +import static ui.diet.dietmanager.DietManagerUi.DIET_SEARCH_EMPTY_TAG; +import static ui.diet.dietmanager.DietManagerUi.DIET_SEARCH_RESULTS_MESSAGE; +import static ui.diet.dietmanager.DietManagerUi.EMPTY_STRING; + +//@@author CFZeon +/** + * A representation of the command for search commands in diet manager. + */ +public class DietSessionSearch extends Command { + private final DietManagerParser parser = new DietManagerParser(); + + /** + * Overrides execute for search command to search diet sessions. + * + * @param input user input for search command + * @param storage storage file + * @return CommandResult string variable + * @throws InvalidDateFormatException if the date is in wrong format + * @throws InvalidSearchDateException if the starting date is later than end date + */ + public CommandResult execute(String input, DietStorage storage) throws InvalidDateFormatException, + InvalidSearchDateException { + String message = EMPTY_STRING; + File folder = new File(PATH_TO_DIET_FOLDER); + File[] listOfFiles = folder.listFiles(); + StringBuilder searchResult = new StringBuilder(); + try { + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + LocalDateTime startDate = parser.extractStartDates(parsedParams, searchResult); + + LocalDateTime endDate = parser.extractEndDates(parsedParams, searchResult); + if (startDate.compareTo(endDate) > 0) { + throw new InvalidSearchDateException(); + } + String tag = parser.extractSearchTag(parsedParams, searchResult); + //check for empty tag in search parameter + checkEmptyTag(searchResult, tag); + searchResult.append(DIET_SEARCH_RESULTS_MESSAGE + "\n\t "); + //check for presence of files in diet session save folder + checkEmptyFolder(listOfFiles, searchResult); + addToSearchResult(listOfFiles, searchResult, startDate, endDate, tag, storage); + message = searchResult.toString(); + } catch (NullPointerException | InvalidCommandFormatException e) { + message = "Wrong format, please enter in the format:\n\t " + + "search "; + } catch (InvalidDateFormatException e) { + searchResult.append(DIET_DATE_WRONG_FORMAT + "\n\t "); + logger.log(Level.WARNING, "Invalid date format in diet session search"); + throw new InvalidDateFormatException(); + } catch (InvalidSearchDateException e) { + logger.log(Level.WARNING, "Invalid date format in diet session search"); + throw new InvalidSearchDateException(); + } catch (NoSuchElementException e) { + logger.log(Level.WARNING, "No such element in diet session search"); + ui.showToUser("Sorry, there is nothing found in your diet menu."); + } + return new CommandResult(message.trim()); + } + + /** + * Checks if the folder is empty. + * + * @param listOfFiles Array for list of files in folder + * @param searchResult String builder for search result + */ + private void checkEmptyFolder(File[] listOfFiles, StringBuilder searchResult) { + if (Objects.requireNonNull(listOfFiles).length == 0) { + searchResult.append(DIET_NO_SESSIONS_SAVED + "\n\t "); + } + } + + /** + * Checks if the tag is empty in the diet session. + * + * @param searchResult String builder for search result + * @param tag string to check if tag is empty + */ + private void checkEmptyTag(StringBuilder searchResult, String tag) { + if (tag.isEmpty()) { + searchResult.append(DIET_SEARCH_EMPTY_TAG + "\n\t "); + } + } + + //@@author CFZeon-reused + /** + * Prints search results. + * + * @param listOfFiles list of files from local storage + * @param searchResult string builder that accumulates warning messages + * @param startDate starting date for search + * @param endDate end date for search + * @param tag tag for search + * @param storage storage for diet sessions + * @throws InvalidDateFormatException if date is in wrong format + */ + private void addToSearchResult(File[] listOfFiles, StringBuilder searchResult, LocalDateTime startDate, + LocalDateTime endDate, String tag, DietStorage storage) + throws InvalidDateFormatException { + //convert the file array to an arraylist for easier manipulation + ArrayList fileArrayList = new ArrayList<>(); + Collections.addAll(fileArrayList, listOfFiles); + + ArrayList fileNames = (ArrayList) fileArrayList.stream() + .map(f -> f.getName().split(" ", 2)[1].trim()).collect(Collectors.toList()); + // get column boundaries for the table format for printing + int descriptionMaxLenInt = Math.max(8, + fileNames.stream().max(Comparator.comparingInt(String::length)).get().length()); + + String descriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt + 1) + "s"; + String returnString = String.format("%-8s", "Index") + String.format(descriptionFormat, "Date") + + String.format("%-12s", "Tag") + String.format("%-10s", "Calories") + LS; + searchResult.append(returnString); + String listDescriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt) + "s %-11s %s"; + int numberOfResult = 0; + numberOfResult = addRow(listOfFiles, searchResult, startDate, endDate, tag, storage, fileArrayList, + listDescriptionFormat, numberOfResult); + String dietSessionSearchSize = "\n\t You have " + numberOfResult + " record(s)" + LS; + if (numberOfResult == 0) { + searchResult.setLength(0); + searchResult.append("Sorry, there is nothing found in your diet menu."); + } + searchResult.append(dietSessionSearchSize); + } + + /** + * Adds to the string builder for table printing. + * + * @param listOfFiles list of files from local storage + * @param searchResult string builder that accumulates warning messages + * @param startDate starting date for search + * @param endDate end date for search + * @param tag tag for search + * @param storage storage for diet sessions + * @param fileArrayList for file array converted to arraylist + * @param listDescriptionFormat the description of the files in array list + * @param numberOfResult integer of results + * @return number of results total + * @throws InvalidDateFormatException if date is in wrong format + */ + private int addRow(File[] listOfFiles, StringBuilder searchResult, LocalDateTime startDate, + LocalDateTime endDate, String tag, DietStorage storage, ArrayList fileArrayList, + String listDescriptionFormat, int numberOfResult) throws InvalidDateFormatException { + for (int i = 0; i < fileArrayList.size(); i++) { + //instantiates stored diet session to get total calorie count + DietSession ds = storage.readDietSession(PATH_TO_DIET_FOLDER, listOfFiles[i].getName()); + double totalCalories = ds.getTotalCalories(); + //extract tags and dates and assigns to string from filename + String fileTag = getFileTag(fileArrayList, i); + String fileDate = getFileDate(fileArrayList, i); + // converts extracted date string to java dateFormat + LocalDateTime dateFormat = DateParser.parseDate(fileDate); + if (startDate.compareTo(dateFormat) <= 0 && endDate.compareTo(dateFormat) >= 0 && fileTag.contains(tag)) { + String rowContent = String.format(listDescriptionFormat, fileDate, fileTag, totalCalories); + String row = String.format("%-8s", ++numberOfResult) + rowContent + LS; + searchResult.append(row); + } + } + return numberOfResult; + } + + /** + * Reads file name and returns date. + * + * @param fileArrayList list of files from local storage into array list + * @param i index of file from search result + * @return date as string + */ + private String getFileDate(ArrayList fileArrayList, int i) { + String fileDate = fileArrayList.get(i).getName().replaceFirst("[.][^.]+$", "") + .split(" ", 2)[0]; + return fileDate; + } + + /** + * Reads file name and returns tags. + * + * @param fileArrayList list of files from local storage into array list + * @param i index of file from search result + * @return date as string + */ + private String getFileTag(ArrayList fileArrayList, int i) { + return fileArrayList.get(i).getName().replaceFirst("[.][^.]+$", "") + .split(" ", 2)[1]; + } +} diff --git a/src/main/java/logic/commands/diet/dietmanager/DietSessionWrong.java b/src/main/java/logic/commands/diet/dietmanager/DietSessionWrong.java new file mode 100644 index 0000000000..ffff4c30be --- /dev/null +++ b/src/main/java/logic/commands/diet/dietmanager/DietSessionWrong.java @@ -0,0 +1,25 @@ +package logic.commands.diet.dietmanager; + +import exceptions.InvalidCommandWordException; +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.diet.DietStorage; + +//@@author CFZeon +/** + * A representation of the command for wrong commands in diet manager. + */ +public class DietSessionWrong extends Command { + + /** + * Overrides execute for wrong command. + * + * @param input user input for command. + * @param storage storage for diet manager. + * @return CommandResult with invalid input message. + */ + @Override + public CommandResult execute(String input, DietStorage storage) throws InvalidCommandWordException { + throw new InvalidCommandWordException(); + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemAdd.java b/src/main/java/logic/commands/diet/dietsession/FoodItemAdd.java new file mode 100644 index 0000000000..fda7481960 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemAdd.java @@ -0,0 +1,64 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import logic.parser.DietSessionParser; +import models.Food; +import exceptions.diet.NegativeCaloriesException; +import exceptions.diet.NoNameException; +import logic.commands.CommandResult; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; +import java.util.logging.Level; + +import static profile.Constants.CALORIES_UPPER_BOUND; + +//@@author zsk612 +/** + * A representation of the command for add commands in diet session. + */ +public class FoodItemAdd extends Command { + + /** + * Overrides execute for add command to add food items. + * + * @param input user input for command + * @param foodList arraylist that stored all the food items + * @param storage storage for diet session + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, DietStorage storage, Integer index) { + DietSessionParser parser = new DietSessionParser(); + String result = ""; + try { + assert !input.isEmpty(); + StringBuilder userOutput = new StringBuilder(); + Double calories = parser.processFoodCalories(input); + Food temp = new Food(parser.processFoodName(input), Math.min(calories, CALORIES_UPPER_BOUND)); + foodList.add(temp); + if (calories > CALORIES_UPPER_BOUND) { + userOutput.append(DietSessionUi.MESSAGE_HIGH_CALORIES); + } + userOutput.append("Yay! You have added " + temp.toString()); + result = userOutput.toString(); + logger.log(Level.INFO, "Added food to arraylist"); + } catch (IndexOutOfBoundsException e) { + result = DietSessionUi.MESSAGE_ADD_WRONG_FORMAT; + logger.log(Level.WARNING, "Wrong Add food item format"); + } catch (NumberFormatException e) { + result = DietSessionUi.MESSAGE_WRONG_CALORIES; + logger.log(Level.WARNING, "Put calories in a wrong format"); + } catch (NegativeCaloriesException e) { + result = DietSessionUi.MESSAGE_NEGATIVE_CALORIES; + logger.log(Level.WARNING, "Put negative calories"); + } catch (NoNameException e) { + result = DietSessionUi.MESSAGE_NO_FOOD_NAME; + logger.log(Level.WARNING, "no food name"); + } + return new CommandResult(result); + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemClear.java b/src/main/java/logic/commands/diet/dietsession/FoodItemClear.java new file mode 100644 index 0000000000..573168ed50 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemClear.java @@ -0,0 +1,48 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import models.Food; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; +import java.util.logging.Level; + +//@@author zsk612 +/** + * A representation of the command for clear commands in diet session. + */ +public class FoodItemClear extends Command { + + /** + * Overrides execute for clear command to clear all food items. + * + * @param input user input for command + * @param foodList arraylist that stored all the food items + * @param storage storage for diet session + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, DietStorage storage, Integer index) { + String result = ""; + String prompt; + if (index <= 0) { + prompt = DietSessionUi.DIET_INPUT_PROMPT_NEW; + } else { + prompt = DietSessionUi.DIET_INPUT_PROMPT_EDIT + index; + } + if (ui.checkConfirmation(prompt, "clear all records")) { + foodList.clear(); + result = DietSessionUi.MESSAGE_CLEAR_SUCCESS; + logger.log(Level.INFO, "Cleared all food in arraylist"); + return new CommandResult(result, ExecutionResult.OK); + } else { + result = DietSessionUi.MESSAGE_CLEAR_ABORTED; + return new CommandResult(result, ExecutionResult.ABORTED); + } + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemDelete.java b/src/main/java/logic/commands/diet/dietsession/FoodItemDelete.java new file mode 100644 index 0000000000..76a14bf68a --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemDelete.java @@ -0,0 +1,48 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import models.Food; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; +import java.util.logging.Level; + +//@@author zsk612 +/** + * A representation of the command for delete commands in diet session. + */ +public class FoodItemDelete extends Command { + + /** + * Overrides execute for delete command to delete food items. + * + * @param input user input for command + * @param foodList arraylist that stored all the food items + * @param storage storage for diet session + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, DietStorage storage, Integer index) { + String result = ""; + try { + assert !input.isEmpty(); + int indexOfSession = Integer.parseInt(input); + Food temp = foodList.get(indexOfSession - 1); + result = "You have deleted " + temp.toString() + " from your list!"; + foodList.remove(temp); + logger.log(Level.INFO, "Removed food from arraylist"); + } catch (IndexOutOfBoundsException e) { + result = DietSessionUi.MESSAGE_NO_SUCH_INDEX; + logger.log(Level.WARNING, "Did not input index"); + } catch (NumberFormatException e) { + result = DietSessionUi.MESSAGE_DELETE_WRONG_FORMAT; + logger.log(Level.WARNING, "Did not input correct index"); + } + return new CommandResult(result, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemHelp.java b/src/main/java/logic/commands/diet/dietsession/FoodItemHelp.java new file mode 100644 index 0000000000..a5bace8d94 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemHelp.java @@ -0,0 +1,35 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import models.Food; +import logic.commands.CommandResult; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; +import java.util.logging.Level; + +//@@author zsk612 +/** + * A representation of the command for help commands in diet session. + */ +public class FoodItemHelp extends Command { + + /** + * Overrides execute for help command to display help information for dietSession. + * + * @param input user input for command + * @param foodList arraylist that stored all the food items + * @param storage storage for diet session + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, DietStorage storage, Integer index) { + StringBuilder helpMessage = new StringBuilder(); + DietSessionUi.printHelp(helpMessage); + logger.log(Level.INFO, "Displayed help in dietSession"); + return new CommandResult(helpMessage.toString().trim()); + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemList.java b/src/main/java/logic/commands/diet/dietsession/FoodItemList.java new file mode 100644 index 0000000000..c7e81a840b --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemList.java @@ -0,0 +1,84 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import models.Food; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static ui.CommonUi.LS; + +//@@author zsk612 +/** + * A representation of the command for list commands in diet session. + */ +public class FoodItemList extends Command { + + /** + * Overrides execute for list command to list food items. + * + * @param input user input for command + * @param foodList arraylist that stored all the food items + * @param storage storage for diet session + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, DietStorage storage, Integer index) { + String result = ""; + try { + double totalCalories = 0; + StringBuilder listResult = new StringBuilder(); + if (foodList.size() > 0) { + for (int i = 0; i < foodList.size(); i++) { + totalCalories += foodList.get(i).getCalories(); + } + String totalMealCalories = "\n" + LS + "Your total calories for this meal is " + totalCalories + "."; + String formattedList = formatList(foodList); + listResult.append(formattedList); + listResult.append(totalMealCalories); + result = listResult.toString().trim(); + logger.log(Level.INFO, "Listed all foods in Diet Session"); + } else { + listResult.append(DietSessionUi.MESSAGE_NO_FOOD); + result = listResult.toString().trim(); + } + } catch (NullPointerException e) { + result = DietSessionUi.MESSAGE_NO_FOOD; + logger.log(Level.WARNING, "No item in food list"); + } + return new CommandResult(result, ExecutionResult.OK); + } + + private String formatList(ArrayList foodList) { + + ArrayList foodNames = (ArrayList) foodList.stream() + .map(Food::getName).collect(Collectors.toList()); + int descriptionMaxLenInt = Math.max(10, + foodNames.stream().max(Comparator.comparingInt(String::length)).get().length()); + + String descriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt + 1) + "s"; + + String returnString = String.format("%-8s", "Index") + String.format(descriptionFormat, "Food") + + String.format("%-9s", "Calories") + LS; + + StringBuilder infoBuilder = new StringBuilder(returnString); + + String listDescriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt) + "s %-9s "; + for (int i = 0; i < foodList.size(); i++) { + String rowContent = String.format(listDescriptionFormat, foodList.get(i).getName(), + foodList.get(i).getCalories()); + String row = String.format("%-8s", i + 1) + rowContent + LS; + infoBuilder.append(row); + } + returnString = infoBuilder.toString().trim(); + return returnString; + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemSearch.java b/src/main/java/logic/commands/diet/dietsession/FoodItemSearch.java new file mode 100644 index 0000000000..9ad9381c5c --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemSearch.java @@ -0,0 +1,86 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import models.Food; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static ui.CommonUi.LS; + +//@@author zsk612 +/** + * A representation of the command for search commands in diet session. + */ +public class FoodItemSearch extends Command { + + DietSessionUi ui = new DietSessionUi(); + + /** + * Overrides execute for search command to search food items. + * + * @param input user input for command + * @param foodList arraylist that stored all the food items + * @param storage storage for diet session + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, DietStorage storage, Integer index) { + String result = ""; + try { + StringBuilder searchResult = new StringBuilder(); + searchResult.append(DietSessionUi.MESSAGE_SEARCH_PROMPT); + String formattedList = formatList(foodList, input.trim()); + searchResult.append(formattedList); + result = searchResult.toString().trim(); + logger.log(Level.INFO, "Listed all searched foods in Diet Session"); + } catch (NullPointerException e) { + result = DietSessionUi.MESSAGE_NO_FOOD; + logger.log(Level.WARNING, "No item in food list for search"); + } + return new CommandResult(result, ExecutionResult.OK); + } + + private String formatList(ArrayList foodList, String searchTag) { + + ArrayList foodNames = (ArrayList) foodList.stream() + .map(Food::getName).collect(Collectors.toList()); + int descriptionMaxLenInt = Math.max(10, + foodNames.stream().max(Comparator.comparingInt(String::length)).get().length()); + + String descriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt + 1) + "s"; + + String returnString = String.format("%-8s", "Index") + String.format(descriptionFormat, "Food") + + String.format("%-9s", "Calories") + LS; + + StringBuilder infoBuilder = new StringBuilder(returnString); + + String listDescriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt) + "s %-9s "; + int numberOfResults = 0; + for (int i = 0; i < foodList.size(); i++) { + String foodName = foodList.get(i).getName(); + Double calories = foodList.get(i).getCalories(); + if (foodName.contains(searchTag)) { + String rowContent = String.format(listDescriptionFormat, foodName, calories); + String row = String.format("%-8s", ++numberOfResults) + rowContent + LS; + infoBuilder.append(row); + } + } + String foodItemSearchSize = "\n\t You have " + numberOfResults + " record(s)" + LS; + infoBuilder.append(foodItemSearchSize); + if (numberOfResults == 0) { + infoBuilder.setLength(0); + infoBuilder.append("Sorry, there is nothing found in your food list."); + } + returnString = infoBuilder.toString().trim(); + return returnString; + } +} diff --git a/src/main/java/logic/commands/diet/dietsession/FoodItemWrong.java b/src/main/java/logic/commands/diet/dietsession/FoodItemWrong.java new file mode 100644 index 0000000000..373ed8d993 --- /dev/null +++ b/src/main/java/logic/commands/diet/dietsession/FoodItemWrong.java @@ -0,0 +1,33 @@ +package logic.commands.diet.dietsession; + +import logic.commands.Command; +import models.Food; +import exceptions.InvalidCommandWordException; +import logic.commands.CommandResult; +import storage.diet.DietStorage; + +import java.util.ArrayList; + +//@@author zsk612 + +/** + * A representation of the command for wrong commands in diet session. + */ +public class FoodItemWrong extends Command { + + /** + * Overrides execute for wrong command. + * + * @param input user input for command. + * @param foodList arraylist that stored all the food items. + * @param storage storage for diet session. + * @param index Integer variable that shows the index of the session + * @return An object CommandResult containing the executing status and feedback message to be displayed + * to user. + */ + @Override + public CommandResult execute(String input, ArrayList foodList, + DietStorage storage, Integer index) throws InvalidCommandWordException { + throw new InvalidCommandWordException(); + } +} diff --git a/src/main/java/logic/commands/main/MainEnd.java b/src/main/java/logic/commands/main/MainEnd.java new file mode 100644 index 0000000000..23a1b5b6e5 --- /dev/null +++ b/src/main/java/logic/commands/main/MainEnd.java @@ -0,0 +1,33 @@ +package logic.commands.main; + +import exceptions.EndException; +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; + +//@@author tienkhoa16 + +/** + * A representation of the command for exiting The Schwarzenegger. + */ +public class MainEnd extends Command { + + /** + * Overrides execute method of class Command to execute end command requested by user's input. + * + * @param args User's input. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + assert args != null : "arguments cannot be null"; + super.execute(args); + + if (!args.isEmpty()) { + ui.showWarning("\"end\" command does not take in parameters"); + } + + throw new EndException(); + } +} diff --git a/src/main/java/logic/commands/main/MainHelp.java b/src/main/java/logic/commands/main/MainHelp.java new file mode 100644 index 0000000000..15237ea762 --- /dev/null +++ b/src/main/java/logic/commands/main/MainHelp.java @@ -0,0 +1,51 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import org.apache.commons.lang3.StringUtils; + +import static seedu.duke.Constants.COMMAND_WORD_DIET; +import static seedu.duke.Constants.COMMAND_WORD_END; +import static seedu.duke.Constants.COMMAND_WORD_PROFILE; +import static seedu.duke.Constants.COMMAND_WORD_WORKOUT; +import static ui.CommonUi.helpFormatter; + +//@@author tienkhoa16 + +/** + * A representation of the command for showing help message in Main Menu. + */ +public class MainHelp extends Command { + + /** + * Executes help command in Main Menu. + * + * @param args User's input. + * @return Help message. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + assert args != null : "arguments cannot be null"; + super.execute(args); + + if (!args.isEmpty()) { + ui.showWarning("\"help\" command does not take in parameters"); + } + + StringBuilder helpMessage = new StringBuilder(); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_PROFILE), COMMAND_WORD_PROFILE, + "Go to Profile Menu to manage your profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_WORKOUT), COMMAND_WORD_WORKOUT, + "Go to Workout Menu to manage your workout records and create new workout records")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_DIET), COMMAND_WORD_DIET, + "Go to Diet Menu to manage your past diet records" + + " and create new diet records")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_END), "end", + "Exit The Schwarzenegger")); + ui.showToUser(helpMessage.toString().trim()); + return new CommandResult("", ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/main/MainWrong.java b/src/main/java/logic/commands/main/MainWrong.java new file mode 100644 index 0000000000..ab1abe04db --- /dev/null +++ b/src/main/java/logic/commands/main/MainWrong.java @@ -0,0 +1,28 @@ +package logic.commands.main; + +import exceptions.InvalidCommandWordException; +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; + +//@@author tienkhoa16 + +/** + * A representation of invalid command in Main Menu. + */ +public class MainWrong extends Command { + + /** + * Notifies user invalid input command. + * + * @param args User's input. + * @return Invalid input command message. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + assert args != null : "arguments cannot be null"; + super.execute(args); + throw new InvalidCommandWordException(); + } +} diff --git a/src/main/java/logic/commands/main/ToDiet.java b/src/main/java/logic/commands/main/ToDiet.java new file mode 100644 index 0000000000..9c53a85fc6 --- /dev/null +++ b/src/main/java/logic/commands/main/ToDiet.java @@ -0,0 +1,36 @@ +package logic.commands.main; + +import diet.dietmanager.DietManager; +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; + +//@@author tienkhoa16 + +/** + * A representation of the command for redirecting to Diet Menu from Main Menu. + */ +public class ToDiet extends Command { + + /** + * Executes redirecting to Diet Menu command from Main Menu. + * + * @param args User's input. + * @return Redirecting to Diet Menu message. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + assert args != null : "arguments cannot be null"; + super.execute(args); + + if (!args.isEmpty()) { + ui.showWarning("\"diet\" command does not take in parameters"); + } + + DietManager dietManager = new DietManager(); + dietManager.start(); + return new CommandResult("", ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/main/ToProfile.java b/src/main/java/logic/commands/main/ToProfile.java new file mode 100644 index 0000000000..fb0e14e608 --- /dev/null +++ b/src/main/java/logic/commands/main/ToProfile.java @@ -0,0 +1,39 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import profile.ProfileSession; + +import static seedu.duke.Constants.PATH_TO_PROFILE_FILE; +import static seedu.duke.Constants.PATH_TO_PROFILE_FOLDER; + +//@@author tienkhoa16 + +/** + * A representation of the command for redirecting to Profile Menu from Main Menu. + */ +public class ToProfile extends Command { + + /** + * Executes redirecting to Profile Menu command from Main Menu. + * + * @param args User's input. + * @return Redirecting to Profile Menu message. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + assert args != null : "arguments cannot be null"; + super.execute(args); + + if (!args.isEmpty()) { + ui.showWarning("\"profile\" command does not take in parameters"); + } + + ProfileSession profileSession = ProfileSession.getInstance(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + profileSession.run(); + return new CommandResult("", ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/main/ToWorkout.java b/src/main/java/logic/commands/main/ToWorkout.java new file mode 100644 index 0000000000..c23d312a6a --- /dev/null +++ b/src/main/java/logic/commands/main/ToWorkout.java @@ -0,0 +1,37 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import workout.workoutmanager.WorkoutManager; + +//@@author tienkhoa16 + +/** + * A representation of the command for redirecting to Workout Menu from Main Menu. + */ +public class ToWorkout extends Command { + + /** + * Executes redirecting to Workout Menu command from Main Menu. + * + * @param args User's input. + * @return Redirecting to Workout Menu message. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + assert args != null : "arguments cannot be null"; + super.execute(args); + + if (!args.isEmpty()) { + ui.showWarning("\"workout\" command does not take in parameters"); + } + + WorkoutManager workoutManager = new WorkoutManager(); + workoutManager.start(); + + return new CommandResult("", ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/profile/ProfileAdd.java b/src/main/java/logic/commands/profile/ProfileAdd.java new file mode 100644 index 0000000000..03eb436470 --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileAdd.java @@ -0,0 +1,78 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidSaveFormatException; +import logic.commands.Command; +import logic.commands.CommandResult; +import models.Profile; +import storage.profile.ProfileStorage; + +import java.util.HashMap; + +import static logic.commands.ExecutionResult.FAILED; +import static logic.commands.ExecutionResult.OK; +import static logic.parser.ProfileParser.extractCalories; +import static logic.parser.ProfileParser.extractCommandTagAndInfo; +import static logic.parser.ProfileParser.extractExpectedWeight; +import static logic.parser.ProfileParser.extractHeight; +import static logic.parser.ProfileParser.extractName; +import static logic.parser.ProfileParser.extractWeight; +import static logic.parser.ProfileParser.findInvalidTags; +import static profile.Constants.CALORIES_UPPER_BOUND; +import static seedu.duke.Constants.COMMAND_WORD_ADD; +import static ui.profile.ProfileUi.MESSAGE_ADJUST_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_CREATE_PROFILE_ACK; +import static ui.profile.ProfileUi.MESSAGE_PROFILE_EXIST; + +//@@author tienkhoa16 + +/** + * A representation of the command for adding profile. + */ +public class ProfileAdd extends Command { + + /** + * Overrides execute method of class Command to execute the add profile command requested by user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + + Profile profile; + try { + profile = storage.loadData(); + return new CommandResult(MESSAGE_PROFILE_EXIST, FAILED); + } catch (InvalidSaveFormatException e) { + HashMap parsedParams = extractCommandTagAndInfo(COMMAND_WORD_ADD, commandArgs); + + String invalidTags = findInvalidTags(parsedParams); + if (!invalidTags.isEmpty()) { + ui.showWarning("\"add\" command does not take in the following parameters: " + invalidTags); + } + + String name = extractName(parsedParams); + int height = extractHeight(parsedParams); + double weight = extractWeight(parsedParams); + double expectedWeight = extractExpectedWeight(parsedParams); + double calories = extractCalories(parsedParams); + double adjustedCalories = Math.min(calories, CALORIES_UPPER_BOUND); + + profile = new Profile(name, height, weight, expectedWeight, adjustedCalories); + storage.saveData(profile); + + if (calories > CALORIES_UPPER_BOUND) { + ui.showWarning(MESSAGE_ADJUST_CALORIES); + } + + return new CommandResult(String.format(MESSAGE_CREATE_PROFILE_ACK, profile.toString()), OK); + } + } +} diff --git a/src/main/java/logic/commands/profile/ProfileDelete.java b/src/main/java/logic/commands/profile/ProfileDelete.java new file mode 100644 index 0000000000..fa17ef7a70 --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileDelete.java @@ -0,0 +1,56 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidSaveFormatException; +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.Profile; +import storage.profile.ProfileStorage; + +import static logic.commands.ExecutionResult.FAILED; +import static logic.commands.ExecutionResult.OK; +import static ui.profile.ProfileUi.MESSAGE_DELETE_NOTHING; +import static ui.profile.ProfileUi.MESSAGE_DELETE_PROFILE; +import static ui.workout.workoutmanager.WorkoutManagerUi.CLEAR_ABORTED; + +//@@author tienkhoa16 + +/** + * A representation of the command for deleting user profile. + */ +public class ProfileDelete extends Command { + + /** + * Overrides execute method of class Command to execute the delete profile command requested by user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + + if (!commandArgs.isEmpty()) { + ui.showWarning("\"delete\" command does not take in parameters"); + } + + try { + Profile profile = storage.loadData(); + + if (!ui.checkConfirmation("Profile Menu", "clear your profile")) { + return new CommandResult(CLEAR_ABORTED, ExecutionResult.ABORTED); + } + + storage.saveData(null); + return new CommandResult(MESSAGE_DELETE_PROFILE, OK); + } catch (InvalidSaveFormatException e) { + return new CommandResult(MESSAGE_DELETE_NOTHING, FAILED); + } + } +} diff --git a/src/main/java/logic/commands/profile/ProfileEdit.java b/src/main/java/logic/commands/profile/ProfileEdit.java new file mode 100644 index 0000000000..735f16a83f --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileEdit.java @@ -0,0 +1,100 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidSaveFormatException; +import logic.commands.Command; +import logic.commands.CommandResult; +import models.Profile; +import storage.profile.ProfileStorage; + +import java.util.HashMap; + +import static logic.commands.ExecutionResult.FAILED; +import static logic.commands.ExecutionResult.OK; +import static logic.commands.ExecutionResult.SKIPPED; +import static logic.parser.ProfileParser.extractCalories; +import static logic.parser.ProfileParser.extractCommandTagAndInfo; +import static logic.parser.ProfileParser.extractExpectedWeight; +import static logic.parser.ProfileParser.extractHeight; +import static logic.parser.ProfileParser.extractName; +import static logic.parser.ProfileParser.extractWeight; +import static logic.parser.ProfileParser.findInvalidTags; +import static profile.Constants.CALORIES_UPPER_BOUND; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static ui.profile.ProfileUi.MESSAGE_ADJUST_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_EDIT_NOTHING; +import static ui.profile.ProfileUi.MESSAGE_EDIT_PROFILE_ACK; +import static ui.profile.ProfileUi.MESSAGE_PROFILE_NOT_EXIST; + +//@@author tienkhoa16 + +/** + * A representation of the command for editing profile. + */ +public class ProfileEdit extends Command { + + /** + * Overrides execute method of class Command to execute the edit profile command requested by user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + + try { + Profile profile = storage.loadData(); + assert profile != null : "profile should not be null after loading"; + + HashMap parsedParams = extractCommandTagAndInfo(COMMAND_WORD_EDIT, commandArgs); + + String invalidTags = findInvalidTags(parsedParams); + if (!invalidTags.isEmpty()) { + ui.showWarning("\"edit\" command does not take in the following parameter(s): " + invalidTags); + } + + Profile editedProfile = createEditedProfile(parsedParams, profile); + + if (profile.equals(editedProfile)) { + return new CommandResult(MESSAGE_EDIT_NOTHING, SKIPPED); + } + + storage.saveData(editedProfile); + + return new CommandResult(String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()), OK); + } catch (InvalidSaveFormatException e) { + return new CommandResult(String.format(MESSAGE_PROFILE_NOT_EXIST, COMMAND_WORD_EDIT), FAILED); + } + } + + /** + * Creates a new Profile object from edited information. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @param profile User's existing profile. + * @return Edited Profile object. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + private Profile createEditedProfile(HashMap parsedParams, Profile profile) + throws SchwarzeneggerException { + String name = parsedParams.containsKey("/n") ? extractName(parsedParams) : profile.getName(); + int height = parsedParams.containsKey("/h") ? extractHeight(parsedParams) : profile.getHeight(); + double weight = parsedParams.containsKey("/w") ? extractWeight(parsedParams) : profile.getWeight(); + double expectedWeight = parsedParams.containsKey("/e") + ? extractExpectedWeight(parsedParams) : profile.getExpectedWeight(); + double calories = parsedParams.containsKey("/c") ? extractCalories(parsedParams) : profile.getCalories(); + double adjustedCalories = Math.min(calories, CALORIES_UPPER_BOUND); + + if (calories > CALORIES_UPPER_BOUND) { + ui.showWarning(MESSAGE_ADJUST_CALORIES); + } + + return new Profile(name, height, weight, expectedWeight, adjustedCalories); + } +} diff --git a/src/main/java/logic/commands/profile/ProfileEnd.java b/src/main/java/logic/commands/profile/ProfileEnd.java new file mode 100644 index 0000000000..1934a89c3e --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileEnd.java @@ -0,0 +1,37 @@ +package logic.commands.profile; + +import exceptions.EndException; +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.profile.ProfileStorage; + +//@@author tienkhoa16 + +/** + * A representation of the command for exiting Profile Menu. + */ +public class ProfileEnd extends Command { + + /** + * Overrides execute method of class Command to execute end command requested by user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + + if (!commandArgs.isEmpty()) { + ui.showWarning("\"end\" command does not take in parameters"); + } + + throw new EndException(); + } +} diff --git a/src/main/java/logic/commands/profile/ProfileHelp.java b/src/main/java/logic/commands/profile/ProfileHelp.java new file mode 100644 index 0000000000..77a0a6dd65 --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileHelp.java @@ -0,0 +1,58 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import org.apache.commons.lang3.StringUtils; +import storage.profile.ProfileStorage; + +import static profile.Constants.ADD_PROFILE_FORMAT; +import static profile.Constants.EDIT_PROFILE_FORMAT; +import static seedu.duke.Constants.COMMAND_WORD_ADD; +import static seedu.duke.Constants.COMMAND_WORD_DELETE; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static seedu.duke.Constants.COMMAND_WORD_END; +import static seedu.duke.Constants.COMMAND_WORD_VIEW; +import static ui.CommonUi.helpFormatter; + +//@@author tienkhoa16 + +/** + * A representation of the command for showing help message in Profile Menu. + */ +public class ProfileHelp extends Command { + + /** + * Overrides execute method of class Command to execute help command requested by user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + + if (!commandArgs.isEmpty()) { + ui.showWarning("\"help\" command does not take in parameters"); + } + + StringBuilder helpMessage = new StringBuilder(); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_ADD), ADD_PROFILE_FORMAT, + "Add your new profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_VIEW), COMMAND_WORD_VIEW, + "View your profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_EDIT), EDIT_PROFILE_FORMAT, + "Edit your existing profile. You may edit from 1 field to all fields")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_DELETE), COMMAND_WORD_DELETE, + "Delete your existing profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_END), COMMAND_WORD_END, + "Go back to Main Menu")); + + return new CommandResult(helpMessage.toString().trim()); + } +} diff --git a/src/main/java/logic/commands/profile/ProfileView.java b/src/main/java/logic/commands/profile/ProfileView.java new file mode 100644 index 0000000000..114b5e74d7 --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileView.java @@ -0,0 +1,120 @@ +package logic.commands.profile; + +import diet.dietmanager.DietManager; +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidSaveFormatException; +import logic.commands.Command; +import logic.commands.CommandResult; +import models.Profile; +import storage.profile.ProfileStorage; + +import java.time.LocalDate; +import java.util.Objects; + +import static logic.commands.ExecutionResult.FAILED; +import static profile.Constants.NORMAL_WEIGHT_AVERAGE; +import static profile.Constants.NORMAL_WEIGHT_THRESHOLD; +import static profile.Constants.UNDER_WEIGHT_THRESHOLD; +import static seedu.duke.Constants.COMMAND_WORD_VIEW; +import static seedu.duke.Constants.PATH_TO_DIET_FOLDER; +import static ui.CommonUi.EMPTY_STRING; +import static ui.profile.ProfileUi.MESSAGE_ENOUGH_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_MORE_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_PROFILE_NOT_EXIST; +import static ui.profile.ProfileUi.MESSAGE_SET_EXPECTED_WEIGHT; +import static ui.profile.ProfileUi.MESSAGE_VIEW_PROFILE; + +//@@author tienkhoa16 + +/** + * A representation of the command for viewing profile. + */ +public class ProfileView extends Command { + private String pathToDietData; + private LocalDate date; + + /** + * Constructs ProfileView object. + * + * @param pathToDietData Path to diet folder to get total calories. + * @param date Date to get total calories. + */ + public ProfileView(String pathToDietData, LocalDate date) { + super(); + this.pathToDietData = Objects.requireNonNullElse(pathToDietData, PATH_TO_DIET_FOLDER); + this.date = Objects.requireNonNullElseGet(date, LocalDate::now); + } + + /** + * Constructs ProfileView object with default path to data file and current date. + */ + public ProfileView() { + this(PATH_TO_DIET_FOLDER, LocalDate.now()); + } + + /** + * Overrides execute method of class Command to execute the view profile command requested by user's input. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + + if (!commandArgs.isEmpty()) { + ui.showWarning("\"view\" command does not take in parameters"); + } + + try { + Profile profile = storage.loadData(); + assert profile != null : "profile should not be null after loading"; + + return new CommandResult(String.format(MESSAGE_VIEW_PROFILE, profile.toString(), + getCalorieProgressMsg(profile), getExpectedWeightTip(profile)).trim()); + } catch (InvalidSaveFormatException e) { + return new CommandResult(String.format(MESSAGE_PROFILE_NOT_EXIST, COMMAND_WORD_VIEW), FAILED); + } + } + + /** + * Gets message for user's today calorie intake progress. + * + * @param profile User's profile. + * @return Message for user's today calorie intake progress. + */ + private String getCalorieProgressMsg(Profile profile) { + double totalCalories = new DietManager().getDateTotalCalories(pathToDietData, date); + double caloriesToGoal = profile.getCalories() - totalCalories; + + String caloriesMessage; + if (caloriesToGoal > 0) { + caloriesMessage = String.format(MESSAGE_MORE_CALORIES, caloriesToGoal); + } else { + caloriesMessage = MESSAGE_ENOUGH_CALORIES; + } + return caloriesMessage; + } + + /** + * Gets tip for setting expected weight to achieve Normal Weight classification. + * + * @param profile User's profile. + * @return Tip for setting expected weight to achieve Normal Weight classification. + */ + private String getExpectedWeightTip(Profile profile) { + double bmiIndex = profile.calculateBmi(profile.getHeight(), profile.getExpectedWeight()); + + if (bmiIndex < UNDER_WEIGHT_THRESHOLD || bmiIndex > NORMAL_WEIGHT_THRESHOLD) { + double normalWeight = Math.pow((double) profile.getHeight() / 100, 2) * NORMAL_WEIGHT_AVERAGE; + return String.format(MESSAGE_SET_EXPECTED_WEIGHT, normalWeight, normalWeight); + } + + return EMPTY_STRING; + } +} diff --git a/src/main/java/logic/commands/profile/ProfileWrong.java b/src/main/java/logic/commands/profile/ProfileWrong.java new file mode 100644 index 0000000000..365b73dbb8 --- /dev/null +++ b/src/main/java/logic/commands/profile/ProfileWrong.java @@ -0,0 +1,32 @@ +package logic.commands.profile; + +import exceptions.InvalidCommandWordException; +import exceptions.SchwarzeneggerException; +import logic.commands.Command; +import logic.commands.CommandResult; +import storage.profile.ProfileStorage; + +//@@author tienkhoa16 + +/** + * A representation of invalid command in Profile Menu. + */ +public class ProfileWrong extends Command { + + /** + * Notifies user invalid input command. + * + * @param commandArgs User's input arguments. + * @param storage Profile Storage to load and save data. + * @return Result of command execution. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String commandArgs, ProfileStorage storage) throws SchwarzeneggerException { + assert commandArgs != null : "command args cannot be null"; + assert storage != null : "profile storage cannot be null"; + + super.execute(commandArgs, storage); + throw new InvalidCommandWordException(); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/ByeWS.java b/src/main/java/logic/commands/workout/workoutmanager/ByeWS.java new file mode 100644 index 0000000000..e40fc35ff7 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/ByeWS.java @@ -0,0 +1,26 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import exceptions.EndException; +import exceptions.SchwarzeneggerException; + +/** + * A representation of the command for bye commands in workout manager. + */ +//@@author wgzesg +public class ByeWS extends Command { + + /** + * Terminates workout manager and return to main menu. + * + * @param args User's input. + * @return null. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + throw new EndException(); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/ClearWS.java b/src/main/java/logic/commands/workout/workoutmanager/ClearWS.java new file mode 100644 index 0000000000..efe7ae1dd6 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/ClearWS.java @@ -0,0 +1,37 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import exceptions.SchwarzeneggerException; +import models.PastRecordList; + +import static ui.CommonUi.clearMsg; +import static ui.workout.workoutmanager.WorkoutManagerUi.CLEAR_ABORTED; + +//@@author wgzesg +/** + * A representation of the command for clear commands in workout manager. + */ +public class ClearWS extends Command { + + /** + * Executes all workout sessions. + * + * @param args User's input. + * @return Status OK and feedback message if the execution is affirmed. + * Status ABORTED if the execution is withdrawn. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + if (!ui.checkConfirmation("Workout Menu", "clear all records")) { + return new CommandResult(CLEAR_ABORTED, ExecutionResult.ABORTED); + } + PastRecordList.getInstance().clear(); + logger.info("Cleared successfully"); + String content = clearMsg("past workout records have"); + return new CommandResult(content, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/DeleteWS.java b/src/main/java/logic/commands/workout/workoutmanager/DeleteWS.java new file mode 100644 index 0000000000..aa4475e0da --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/DeleteWS.java @@ -0,0 +1,38 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import exceptions.SchwarzeneggerException; +import exceptions.workout.workoutmanager.OutOfArrayException; +import models.PastRecordList; +import logic.parser.WorkoutManagerParser; + +import static ui.workout.workoutmanager.WorkoutManagerUi.DELETE_SUCCESS; + +//@@author wgzesg +/** + * A representation of the command for delete commands in workout manager. + */ +public class DeleteWS extends Command { + + /** + * Deletes a record at a specific index. + * + * @param args User's input. + * @return Status OK and feedback message if file is deleted. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + int index = WorkoutManagerParser.getInstance().parseIndex(args); + try { + PastRecordList.getInstance().delete(index); + } catch (IndexOutOfBoundsException e) { + throw new OutOfArrayException(); + } + logger.info("deleted successfully"); + return new CommandResult(DELETE_SUCCESS, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/EditWS.java b/src/main/java/logic/commands/workout/workoutmanager/EditWS.java new file mode 100644 index 0000000000..b6b58ce0c9 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/EditWS.java @@ -0,0 +1,60 @@ +package logic.commands.workout.workoutmanager; + +import exceptions.InsufficientArgumentException; +import exceptions.SchwarzeneggerException; +import exceptions.workout.workoutmanager.NotANumberException; +import exceptions.workout.workoutmanager.OutOfArrayException; +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.PastRecordList; +import workout.workoutsession.WorkoutSession; + +import static ui.workout.workoutmanager.WorkoutManagerUi.EDIT_SUCCESS; +import static ui.workout.workoutmanager.WorkoutManagerUi.START_NEW_SESSION; + +//@@author wgzesg +/** + * A representation of the command for edit commands in workout manager. + */ +public class EditWS extends Command { + + /** + * Edits a record at a given index. + * + * @param args User's input. + * @return Status OK and feedback message if file is edit. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + int index; + try { + index = Integer.parseInt(args); + } catch (NumberFormatException e) { + logger.warning("Number format exception caught"); + throw new NotANumberException(); + } catch (IndexOutOfBoundsException e) { + logger.warning("Insufficient arguments given!"); + throw new InsufficientArgumentException("edit [INDEX]"); + } + + String filePath; + try { + filePath = PastRecordList.getInstance().edit(index); + } catch (IndexOutOfBoundsException e) { + logger.warning("Index Out Of Bounds Exception caught"); + throw new OutOfArrayException(); + } + WorkoutSession ws = new WorkoutSession(filePath, false, index); + logger.info("editing workout session created"); + + ui.showToUser(START_NEW_SESSION); + ws.workoutSessionStart(); + + logger.info("edited successfully"); + return new CommandResult(EDIT_SUCCESS, ExecutionResult.OK); + } + +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/HelpWS.java b/src/main/java/logic/commands/workout/workoutmanager/HelpWS.java new file mode 100644 index 0000000000..7081cbcaee --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/HelpWS.java @@ -0,0 +1,42 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import exceptions.SchwarzeneggerException; + +import static ui.CommonUi.helpFormatter; + +//@@author wgzesg +/** + * A representation of the command for help commands in workout manager. + */ +public class HelpWS extends Command { + + /** + * Executes help command in Workout Menu. + * + * @param args User's input. + * @return Status OK and information to be printed. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + String helpMessage = helpFormatter("New", "new ", + "Create a new workout session and tags. Multiple tags are separated by ','") + + helpFormatter("List", "list ", + "Show all past sessions. Can display sessions between a certain period") + + helpFormatter("Delete", "delete [INDEX]", + "Delete the record indexed at x") + + helpFormatter("Edit", "edit [INDEX]", + "Edit the record indexed at x") + + helpFormatter("Clear", "clear", + "Clear all past results") + + helpFormatter("Search", "search ", + "Search records based on tags and dates. Multiple tags are seperated by ','") + + helpFormatter("End", "end", + "Go back to Main Menu"); + return new CommandResult(helpMessage.trim(), ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/ListWS.java b/src/main/java/logic/commands/workout/workoutmanager/ListWS.java new file mode 100644 index 0000000000..37bb6a730d --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/ListWS.java @@ -0,0 +1,30 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import exceptions.SchwarzeneggerException; +import models.PastRecordList; + +import static logic.commands.ExecutionResult.OK; + +//@@author wgzesg +/** + * A representation of the command for list commands in workout manager. + */ +public class ListWS extends Command { + + /** + * Lists past record of workout sessions. + * + * @param args User's input. + * @return Status OK and all past records in a table. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + String formattedInfo = PastRecordList.getInstance().list(args); + logger.info("Listed successfully"); + return new CommandResult(formattedInfo, OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/NewWS.java b/src/main/java/logic/commands/workout/workoutmanager/NewWS.java new file mode 100644 index 0000000000..1f1d00f110 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/NewWS.java @@ -0,0 +1,44 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import exceptions.SchwarzeneggerException; +import logic.parser.WorkoutManagerParser; +import models.PastRecordList; +import workout.workoutsession.WorkoutSession; + +import java.util.ArrayList; + +import static logic.commands.ExecutionResult.OK; +import static ui.workout.workoutmanager.WorkoutManagerUi.NEW_SUCCESS; +import static ui.workout.workoutmanager.WorkoutManagerUi.START_NEW_SESSION; + +//@@author wgzesg +/** + * A representation of the command for create commands in workout manager. + */ +public class NewWS extends Command { + + /** + * Creates new workout session. + * + * @param args User's input + * @return Status OK and feedback message. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + super.execute(args); + ArrayList tags = WorkoutManagerParser.getInstance().parseTags(args); + String filePath = PastRecordList.getInstance().add(tags); + WorkoutSession ws = new WorkoutSession(filePath, true, -1); + logger.info("New workout session created"); + + ui.showToUser(START_NEW_SESSION); + + ws.workoutSessionStart(); + + logger.info("Ended workout session"); + return new CommandResult(NEW_SUCCESS, OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/SearchWS.java b/src/main/java/logic/commands/workout/workoutmanager/SearchWS.java new file mode 100644 index 0000000000..ee956aeb8a --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/SearchWS.java @@ -0,0 +1,29 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import exceptions.SchwarzeneggerException; +import models.PastRecordList; + +//@@author wgzesg +/** + * A representation of the command for search commands in workout manager. + */ +public class SearchWS extends Command { + + /** + * Searches workout session. + * + * @param args User's input. + * @return Status OK and information to be printed. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + + super.execute(args); + String formattedInfo = PastRecordList.getInstance().search(args); + return new CommandResult(formattedInfo, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutmanager/WrongWS.java b/src/main/java/logic/commands/workout/workoutmanager/WrongWS.java new file mode 100644 index 0000000000..5ac992cb35 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutmanager/WrongWS.java @@ -0,0 +1,25 @@ +package logic.commands.workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandResult; +import exceptions.InvalidCommandWordException; +import exceptions.SchwarzeneggerException; + +//@@author wgzesg +/** + * A representation of the command for wrong commands in workout manager. + */ +public class WrongWS extends Command { + + /** + * Notifies user invalid input command. + * + * @param args User's input. + * @return Status MISSING and COMMAND_NOT_FOUND feedback. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + @Override + public CommandResult execute(String args) throws SchwarzeneggerException { + throw new InvalidCommandWordException(); + } +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionAdd.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionAdd.java new file mode 100644 index 0000000000..fd1e84eebb --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionAdd.java @@ -0,0 +1,52 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import exceptions.workout.workoutsession.AddFormatException; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.Exercise; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; +import logic.parser.WorkoutSessionParser; +import models.ExerciseList; + +import java.io.IOException; + +//@@author yujinyang1998 +/** + * A representation of the command for adding an exercise. + */ +public class WorkoutSessionAdd extends Command { + + /** + * Adds an exercise to the exercise list. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return Status OK and information to be printed. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) { + assert (inputs != null && exerciseList != null && filePath != null && workoutSessionStorage != null + && hasEndedWorkoutSessions != null) : "File Corrupted"; + String result = ""; + try { + exerciseList.exerciseList.add(WorkoutSessionParser.addParser(inputs)); + workoutSessionStorage.writeToStorage(filePath, exerciseList); + Exercise addedExercise = exerciseList.exerciseList.get(exerciseList.exerciseList.size() - 1); + result = WorkoutSessionUi.addExerciseSuccess(addedExercise); + } catch (NumberFormatException e) { + return new CommandResult(WorkoutSessionUi.ADD_FORMAT_ERROR); + } catch (IOException e) { + return new CommandResult(WorkoutSessionUi.PRINT_ERROR); + } catch (AddFormatException e) { + return new CommandResult(WorkoutSessionUi.ADD_FORMAT_NEGATIVE_ERROR); + } + return new CommandResult(result, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionDelete.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionDelete.java new file mode 100644 index 0000000000..a0ce529904 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionDelete.java @@ -0,0 +1,55 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import exceptions.SchwarzeneggerException; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.Exercise; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; +import logic.parser.WorkoutSessionParser; +import models.ExerciseList; + +import java.io.IOException; + +//@@author yujinyang1998 +/** + * A representation of the command for deleting an exercise. + */ +public class WorkoutSessionDelete extends Command { + + /** + * Deletes an exercise from the exercise list. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return Status OK and information to be printed. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) { + assert (inputs != null && exerciseList != null && filePath != null + && workoutSessionStorage != null && hasEndedWorkoutSessions != null) : "File Corrupted"; + String result = ""; + try { + int removeIndex = WorkoutSessionParser.deleteParser(inputs); + Exercise deletedExercise = exerciseList.exerciseList.get(removeIndex - 1); + exerciseList.exerciseList.remove(removeIndex - 1); + workoutSessionStorage.writeToStorage(filePath, exerciseList); + result = WorkoutSessionUi.deleteExerciseSuccess(deletedExercise); + } catch (IOException e) { + return new CommandResult(WorkoutSessionUi.PRINT_ERROR); + } catch (ArrayIndexOutOfBoundsException e) { + return new CommandResult(WorkoutSessionUi.DELETE_FORMAT_ERROR); + } catch (IndexOutOfBoundsException e) { + return new CommandResult(WorkoutSessionUi.DELETE_INDEX_ERROR); + } catch (SchwarzeneggerException e) { + return new CommandResult(WorkoutSessionUi.DELETE_FORMAT_ERROR); + } + return new CommandResult(result, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionEnd.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionEnd.java new file mode 100644 index 0000000000..99241b0be8 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionEnd.java @@ -0,0 +1,48 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; +import models.ExerciseList; + +import java.io.IOException; + +//@@author yujinyang1998 +/** + * A representation of the command for ending the current Workout Session. + */ +public class WorkoutSessionEnd extends Command { + + /** + * Edits a boolean array to signal the end of the workout session. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return Status OK and information to be printed. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) { + assert (inputs != null && exerciseList != null && filePath != null + && workoutSessionStorage != null && hasEndedWorkoutSessions != null) : "File Corrupted"; + setEndWorkoutSessionT(hasEndedWorkoutSessions); + try { + workoutSessionStorage.writeToStorage(filePath, exerciseList); + } catch (IOException e) { + return new CommandResult(WorkoutSessionUi.PRINT_ERROR); + } + return new CommandResult("", ExecutionResult.OK); + } + + private void setEndWorkoutSessionT(boolean[] hasEndedWorkoutSessions) { + hasEndedWorkoutSessions[0] = true; + } + + +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionHelp.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionHelp.java new file mode 100644 index 0000000000..ade35440d9 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionHelp.java @@ -0,0 +1,36 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.ExerciseList; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; + +//@@author yujinyang1998 + +/** + * A representation of the command for displaying help message for Workout Session. + */ +public class WorkoutSessionHelp extends Command { + + /** + * Gets ready a help message for Workout Session. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return Status OK and information to be printed. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) { + assert (inputs != null && exerciseList != null && filePath != null + && workoutSessionStorage != null && hasEndedWorkoutSessions != null) : "File Corrupted"; + String result = WorkoutSessionUi.printHelp(); + return new CommandResult(result, ExecutionResult.OK); + } +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionList.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionList.java new file mode 100644 index 0000000000..071940823c --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionList.java @@ -0,0 +1,88 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.Exercise; +import models.ExerciseList; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.stream.Collectors; + +import static ui.CommonUi.LS; + +//@@author yujinyang1998 + +/** + * A representation of the command for listing all exercise of current workout session. + */ +public class WorkoutSessionList extends Command { + + /** + * Gets a formatted list to be printed to the user of all current exercises. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return Status OK and information to be printed. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) { + assert (inputs != null && exerciseList != null && filePath != null + && workoutSessionStorage != null && hasEndedWorkoutSessions != null) : "File Corrupted"; + String result = ""; + try { + result = printList(exerciseList.exerciseList); + workoutSessionStorage.writeToStorage(filePath, exerciseList); + } catch (IOException e) { + return new CommandResult(WorkoutSessionUi.PRINT_ERROR); + } + return new CommandResult(result, ExecutionResult.OK); + } + + private String printList(ArrayList exercise) { + assert exercise != null : "exercise list not found"; + String list = ""; + if (exercise.size() <= 0) { + list = WorkoutSessionUi.EMPTY_LIST_ERROR; + } else { + list = formatList(exercise); + } + return list; + } + + private String formatList(ArrayList exercise) { + + ArrayList exerciseNames = (ArrayList) exercise.stream() + .map(Exercise::getDescription).collect(Collectors.toList()); + int descriptionMaxLenInt = Math.max(20, + exerciseNames.stream().max(Comparator.comparingInt(String::length)).get().length()); + + String descriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt + 1) + "s"; + + String returnString = String.format("%-8s", "Index") + String.format(descriptionFormat, "Exercise") + + String.format("%-15s", "Repetitions") + String.format("%-10s", "Weight") + LS; + + StringBuilder infoBuilder = new StringBuilder(returnString); + + String listDescriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt) + "s %-14s %s"; + for (int i = 0; i < exercise.size(); i++) { + String rowContent = String.format(listDescriptionFormat, exercise.get(i).getDescription(), + exercise.get(i).getRepetitions(), exercise.get(i).getWeight()); + String row = String.format("%-8s", i + 1) + rowContent + LS; + infoBuilder.append(row); + } + returnString = infoBuilder.toString().trim(); + return returnString; + } + + +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionSearch.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionSearch.java new file mode 100644 index 0000000000..21c5adb936 --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionSearch.java @@ -0,0 +1,92 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import models.Exercise; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; +import logic.parser.WorkoutSessionParser; +import models.ExerciseList; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import static ui.CommonUi.LS; + +//@@author yujinyang1998 +/** + * A representation of the command for searching for an exercise. + */ +public class WorkoutSessionSearch extends Command { + private boolean isEmptySearchResult; + + /** + * Searches exercise list for matching exercises. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @return Status OK and information to be printed. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) { + assert (inputs != null && exerciseList != null && filePath != null + && workoutSessionStorage != null && hasEndedWorkoutSessions != null) : "File Corrupted"; + isEmptySearchResult = true; + String result = ""; + String searchTerm = WorkoutSessionParser.searchParser(inputs).toLowerCase(); + try { + if (searchTerm.length() > 0) { + + String searchResult = formatList(exerciseList.exerciseList, searchTerm); + + if (!isEmptySearchResult) { + result = (searchResult); + } else { + result = WorkoutSessionUi.SEARCH_RESULTS_EMPTY; + } + } else { + result = WorkoutSessionUi.SEARCH_INPUT_ERROR; + } + } catch (NoSuchElementException e) { + return new CommandResult(WorkoutSessionUi.SEARCH_RESULTS_EMPTY); + } + return new CommandResult(result, ExecutionResult.OK); + } + + private String formatList(ArrayList exercise, String searchTerm) { + + ArrayList exerciseNames = (ArrayList) exercise.stream() + .map(Exercise::getDescription).collect(Collectors.toList()); + + int descriptionMaxLenInt = Math.max(20, + exerciseNames.stream().max(Comparator.comparingInt(String::length)).get().length()); + + String descriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt + 1) + "s"; + + String formattedString = String.format("%-8s", "Index") + String.format(descriptionFormat, "Exercise") + + String.format("%-15s", "Repetitions") + String.format("%-10s", "Weight") + LS; + + StringBuilder infoBuilder = new StringBuilder(formattedString); + + String listDescriptionFormat = "%-" + String.format("%d", descriptionMaxLenInt) + "s %-14s %s"; + for (int i = 0; i < exercise.size(); i++) { + if (exercise.get(i).getDescription().toLowerCase().contains(searchTerm)) { + String rowContent = String.format(listDescriptionFormat, exercise.get(i).getDescription(), + exercise.get(i).getRepetitions(), exercise.get(i).getWeight()); + String row = String.format("%-8s", i + 1) + rowContent + LS; + infoBuilder.append(row); + isEmptySearchResult = false; + } + } + formattedString = infoBuilder.toString().trim(); + return formattedString; + } +} diff --git a/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionWrong.java b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionWrong.java new file mode 100644 index 0000000000..de1902130a --- /dev/null +++ b/src/main/java/logic/commands/workout/workoutsession/WorkoutSessionWrong.java @@ -0,0 +1,32 @@ +package logic.commands.workout.workoutsession; + +import logic.commands.Command; +import exceptions.InvalidCommandWordException; +import logic.commands.CommandResult; +import storage.workout.WorkoutSessionStorage; +import models.ExerciseList; + +//@@author yujinyang1998 +/** + * A representation of invalid command in Workout Session. + */ +public class WorkoutSessionWrong extends Command { + /** + * Notifies user invalid input command. + * + * @param inputs Array of user's input. + * @param exerciseList List of exercise. + * @param filePath Path to data file. + * @param workoutSessionStorage Workout Session Storage to load and save data. + * @param hasEndedWorkoutSessions Array of booleans indicating if user has ended workout sessions. + * @throws InvalidCommandWordException If the user input does not match the ones stated in help. + */ + @Override + public CommandResult execute(String[] inputs, ExerciseList exerciseList, + String filePath, WorkoutSessionStorage workoutSessionStorage, + boolean[] hasEndedWorkoutSessions) throws InvalidCommandWordException { + assert (exerciseList != null && filePath != null && workoutSessionStorage != null + && hasEndedWorkoutSessions != null) : "File Corrupted"; + throw new InvalidCommandWordException(); + } +} diff --git a/src/main/java/logic/parser/CommonParser.java b/src/main/java/logic/parser/CommonParser.java new file mode 100644 index 0000000000..1d492bbf40 --- /dev/null +++ b/src/main/java/logic/parser/CommonParser.java @@ -0,0 +1,33 @@ +package logic.parser; + +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 + +/** + * A base class for dealing with making sense of user command. + */ +public class CommonParser { + + public static final int COMMAND_ARGS_INDEX = 1; + public static final int COMMAND_SPLIT_LIMIT = 2; + public static final int COMMAND_TYPE_INDEX = 0; + public static final String GREEDY_WHITE_SPACE = "\\s+"; + + /** + * Parses and returns the Command associated with the user input. + * + * @param userInputString User's raw input string. + * @return Size 2 array; first element is the command type and second element is the arguments string. + */ + public String[] parseCommand(String userInputString) { + assert userInputString != null : "user input cannot be null"; + assert !userInputString.isEmpty() : "user input cannot be empty"; + + String[] split = userInputString.trim().split(GREEDY_WHITE_SPACE, COMMAND_SPLIT_LIMIT); + String commandType = split[COMMAND_TYPE_INDEX].toLowerCase(); + String commandArgs = (split.length == COMMAND_SPLIT_LIMIT ? split[COMMAND_ARGS_INDEX] : EMPTY_STRING); + + return new String[]{commandType, commandArgs}; + } +} diff --git a/src/main/java/logic/parser/DateParser.java b/src/main/java/logic/parser/DateParser.java new file mode 100644 index 0000000000..ce4f7f058a --- /dev/null +++ b/src/main/java/logic/parser/DateParser.java @@ -0,0 +1,66 @@ +package logic.parser; + +import exceptions.InvalidDateFormatException; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.Arrays; +import java.util.List; + +//@@author wgzesg +/** + * Represents the class containing all the date formats. + */ +public class DateParser { + private static final List dtFormatters = Arrays.asList( + DateTimeFormatter.ofPattern("uuuuMMdd HH:mm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu MM dd HH:mm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu/MM/dd HH:mm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuuMMdd HHmm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu-MM-dd HHmm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu MM dd HHmm").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu/MM/dd HHmm").withResolverStyle(ResolverStyle.STRICT) + ); + + private static final List dFormatters = Arrays.asList( + DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu/MM/dd").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu-MM-dd").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("uuuu MM dd").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("dd-MM-uuuu").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("dd/MM/uuuu").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("dd MM uuuu").withResolverStyle(ResolverStyle.STRICT), + DateTimeFormatter.ofPattern("ddMMuuuu").withResolverStyle(ResolverStyle.STRICT) + ); + + /** + * Parses a given string following one of the accepted format into date-time format. + * + * @param targetString String input to be parsed. + * @return Parsed result in the form of LocalDateTime or null if no value parsing is found. + * @throws InvalidDateFormatException If date format is invalid. + */ + public static LocalDateTime parseDate(String targetString) throws InvalidDateFormatException { + for (DateTimeFormatter dtf : dtFormatters) { + try { + return LocalDateTime.parse(targetString, dtf); + } catch (DateTimeParseException e) { + continue; + } + } + + for (DateTimeFormatter dtf : dFormatters) { + try { + return LocalDate.parse(targetString, dtf).atStartOfDay(); + } catch (DateTimeParseException e) { + continue; + } + } + + throw new InvalidDateFormatException(); + } +} diff --git a/src/main/java/logic/parser/DietManagerParser.java b/src/main/java/logic/parser/DietManagerParser.java new file mode 100644 index 0000000000..c23a537a0f --- /dev/null +++ b/src/main/java/logic/parser/DietManagerParser.java @@ -0,0 +1,199 @@ +package logic.parser; + +import exceptions.InvalidDateFormatException; +import exceptions.profile.InvalidCommandFormatException; +import logger.SchwarzeneggerLogger; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A class that is responsible for parsing user inputs in Diet Manager. + */ +public class DietManagerParser extends CommonParser { + + public Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + + /** + * Extracts out date and time by looking for date strings in YYYY-MM-DD format. + * + * @param parsedParams user input for new diet session + * @param extractDateMessage string builder that appends warnings and messages + * @return date in MMM dd yyyy if the user inputs date in YYYY-MM-DD format; + * else returns original string + * @throws IllegalStateException if the date is in wrong state + * @throws InvalidDateFormatException if the date is in wrong format + */ + public String extractNewDate(HashMap parsedParams, StringBuilder extractDateMessage) + throws IllegalStateException, InvalidDateFormatException { + try { + String dateString = parsedParams.get("/d").trim(); + if (!dateString.isEmpty()) { + return DateParser.parseDate(dateString).format(DateTimeFormatter.ofPattern("dd-MM-yyyy")); + } else { + extractDateMessage.append("Date input is empty.\n"); + } + } catch (InvalidDateFormatException e) { + throw new InvalidDateFormatException(); + } catch (NullPointerException e) { + extractDateMessage.append("No date input is detected.\n"); + } + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + extractDateMessage.append("\t I've replaced it with today's date.\n\t "); + return dtf.format(now); + } + + /** + * Extracts out tag of the diet session. + * + * @param parsedParams user input for new diet session + * @param extractMealMessage string builder that appends warnings and messages + * @return tag input if there is any; + * else returns "unspecified" + * @throws NullPointerException if there is nothing in tag input + */ + public String extractNewTag(HashMap parsedParams, StringBuilder extractMealMessage) + throws NullPointerException { + try { + String tag = parsedParams.get("/t").trim(); + if (tag.isEmpty()) { + extractMealMessage.append("Tag input is empty, " + + "and it is replaced with \"unspecified\"."); + return "unspecified"; + } else { + return tag; + } + } catch (NullPointerException e) { + extractMealMessage.append("No tag is detected, " + + "and the session is tagged as \"unspecified\"."); + return "unspecified"; + } + } + + /** + * Extracts out starting date, end date and tag information. + * + * @param cmd user command + * @param commandArgs user input + * @return a hashmap where each information corresponds to the correct separator + * @throws InvalidCommandFormatException if user enters invalid commands + */ + public HashMap extractDietManagerCommandTagAndInfo(String cmd, String commandArgs) + throws InvalidCommandFormatException { + + HashMap parsedParams = new HashMap<>(); + int startIndex = 0; + int endIndex = 0; + if ((cmd.equals("search")) && (commandArgs.isEmpty() || !(commandArgs.contains("/t") + || commandArgs.contains("/s") || commandArgs.contains("/e")))) { + throw new InvalidCommandFormatException("Wrong format, please enter in the format:\n\t " + + "search "); + } + + try { + while (commandArgs.indexOf("/", startIndex) != -1) { + endIndex = commandArgs.indexOf("/", startIndex + 1); + + if (endIndex == -1) { + endIndex = commandArgs.length(); + } + + String parsedOption = commandArgs.substring(startIndex + 2, endIndex).trim(); + String optionIndicator = commandArgs.substring(startIndex, startIndex + 2).trim().toLowerCase(); + parsedParams.put(optionIndicator, parsedOption); + + startIndex = endIndex; + } + + return parsedParams; + } catch (StringIndexOutOfBoundsException e) { + if (cmd.equals("search")) { + logger.log(Level.WARNING, "Wrong format for search input"); + throw new InvalidCommandFormatException("Wrong format, please enter in the format:\n\t " + + "search "); + } else if (cmd.equals("new")) { + logger.log(Level.WARNING, "Wrong format for create input"); + throw new InvalidCommandFormatException("Wrong format, please enter in the format:\n\t " + + "new /d [DATE] /t [TAG]"); + } else { + throw new InvalidCommandFormatException("Wrong format"); + } + } + } + + /** + * Extracts out search tag. + * + * @param parsedParams a hashmap that contains information about tag + * @param searchResult string build that contains warning messages + * @return search tag + */ + public String extractSearchTag(HashMap parsedParams, StringBuilder searchResult) { + try { + String tag = parsedParams.get("/t").trim(); + return tag; + } catch (NullPointerException e) { + logger.log(Level.WARNING, "It looks like there is no input for search tag"); + } + return ""; + } + + /** + * Extracts out starting date. + * + * @param parsedParams a hashmap that contains information about starting date + * @param searchResult string build that contains warning messages + * @return starting date + * @throws InvalidDateFormatException if the date is in wrong format + */ + public LocalDateTime extractStartDates(HashMap parsedParams, StringBuilder searchResult) + throws InvalidDateFormatException { + + try { + String startDate = parsedParams.get("/s"); + if (!startDate.isEmpty()) { + return DateParser.parseDate(startDate); + } + } catch (NullPointerException e) { + logger.log(Level.WARNING, "It looks like there is no date input in start date"); + } catch (InvalidDateFormatException e) { + logger.log(Level.WARNING, "Invalid date in start date"); + throw new InvalidDateFormatException(); + } + searchResult.append("Starting date is empty, " + + "and it is replaced with the earliest date.\n\t "); + return DateParser.parseDate("0001-01-01"); + } + + /** + * Extracts out end date. + * + * @param parsedParams a hashmap that contains information about end date + * @param searchResult string build that contains warning messages + * @return end date + * @throws InvalidDateFormatException if the date is in wrong format + */ + public LocalDateTime extractEndDates(HashMap parsedParams, StringBuilder searchResult) + throws InvalidDateFormatException { + + try { + String endDate = parsedParams.get("/e"); + if (!endDate.isEmpty()) { + return DateParser.parseDate(endDate); + } + } catch (NullPointerException e) { + logger.log(Level.WARNING, "It looks like there is no date input in end date"); + } catch (InvalidDateFormatException e) { + logger.log(Level.WARNING, "Invalid date in start date"); + throw new InvalidDateFormatException(); + } + searchResult.append("End date is empty, " + + "and it is replaced with the latest date.\n\t "); + return DateParser.parseDate("9999-12-12"); + } + +} diff --git a/src/main/java/logic/parser/DietSessionParser.java b/src/main/java/logic/parser/DietSessionParser.java new file mode 100644 index 0000000000..ee3253b501 --- /dev/null +++ b/src/main/java/logic/parser/DietSessionParser.java @@ -0,0 +1,51 @@ +package logic.parser; + +import exceptions.diet.NegativeCaloriesException; +import exceptions.diet.NoNameException; +import logger.SchwarzeneggerLogger; + +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author zsk612 +/** + * A class that is responsible for parsing user inputs in Diet Session. + */ +public class DietSessionParser extends CommonParser { + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + + /** + * Processes the name of the food item. + * + * @param food string for food content + * @return food name + * @throws IndexOutOfBoundsException handles exception for not inputting food name or calories + * @throws NoNameException handles empty food name + */ + public String processFoodName(String food) throws IndexOutOfBoundsException, NoNameException { + String[] temp = food.trim().split("/c", 2); + if (temp[0].trim().isEmpty()) { + throw new NoNameException(); + } + return temp[0].trim(); + } + + /** + * Processes the calories of the food item. + * + * @param food string for food content + * @return food calories + * @throws NumberFormatException handles exception for wrong calories input + * @throws NegativeCaloriesException handles negative calories input + */ + public double processFoodCalories(String food) throws NumberFormatException, NegativeCaloriesException { + String[] temp = food.trim().split("/c", 2); + if (Double.parseDouble(temp[1]) < 0) { + throw new NegativeCaloriesException(); + } + logger.log(Level.INFO, "Processed food calories successfully"); + return Double.parseDouble(temp[1]); + } + + +} diff --git a/src/main/java/logic/parser/ProfileParser.java b/src/main/java/logic/parser/ProfileParser.java new file mode 100644 index 0000000000..63303b58bd --- /dev/null +++ b/src/main/java/logic/parser/ProfileParser.java @@ -0,0 +1,272 @@ +package logic.parser; + +import exceptions.InsufficientArgumentException; +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidCaloriesException; +import exceptions.profile.InvalidCommandFormatException; +import exceptions.profile.InvalidHeightException; +import exceptions.profile.InvalidNameException; +import exceptions.profile.InvalidWeightException; +import org.apache.commons.lang3.text.WordUtils; +import profile.Utils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import static profile.Constants.ADD_PROFILE_FORMAT; +import static profile.Constants.EDIT_PROFILE_FORMAT; +import static seedu.duke.Constants.COMMAND_WORD_ADD; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static seedu.duke.Constants.COMMAND_WORD_HELP; + +//@@author tienkhoa16 + +/** + * A class that deals with making sense of user's command inside Profile Session. + */ +public class ProfileParser extends CommonParser { + + public static final String NAME_SPECIFIER = "/n"; + public static final String HEIGHT_SPECIFIER = "/h"; + public static final String WEIGHT_SPECIFIER = "/w"; + public static final String EXPECTED_WEIGHT_SPECIFIER = "/e"; + public static final String CALORIES_SPECIFIER = "/c"; + public static final String SPECIFIER_START_INDICATOR = "/"; + public static final int CHARACTER_NOT_FOUND_INDEX = -1; + public static final int STRING_START_INDEX = 0; + public static final int NEXT_INDEX_OFFSET = 1; + public static final int PARSED_OPTION_OFFSET = 2; + + /** + * Extracts command tags from user input to get option indicator and parsed option. + * + * @param command Command being executed. + * @param commandArgs User's input arguments. + * @return HashMap containing option indicator and parsed option pairs. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + public static HashMap extractCommandTagAndInfo(String command, String commandArgs) + throws SchwarzeneggerException { + if (!commandArgs.contains(SPECIFIER_START_INDICATOR)) { + throwInvalidCommandFormat(command); + } + + HashMap parsedParams = new HashMap<>(); + int startIndex = STRING_START_INDEX; + int endIndex; + + try { + while (commandArgs.indexOf(SPECIFIER_START_INDICATOR, startIndex) != CHARACTER_NOT_FOUND_INDEX) { + endIndex = commandArgs.indexOf(SPECIFIER_START_INDICATOR, startIndex + NEXT_INDEX_OFFSET); + + if (endIndex == CHARACTER_NOT_FOUND_INDEX) { + endIndex = commandArgs.length(); + } + + String parsedOption = commandArgs.substring(startIndex + PARSED_OPTION_OFFSET, endIndex).trim(); + String optionIndicator = commandArgs.substring(startIndex, startIndex + PARSED_OPTION_OFFSET) + .trim().toLowerCase(); + parsedParams.put(optionIndicator, parsedOption); + + startIndex = endIndex; + } + + if (!checkSufficientParams(command, parsedParams)) { + throwInsufficientArgument(command); + } + } catch (StringIndexOutOfBoundsException e) { + throwInvalidCommandFormat(command); + } + + return parsedParams; + } + + /** + * Checks if user inputs sufficient number of params to the command. + * + * @param command Type of command being executed. + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return If the number of params input to the command is sufficient. + */ + private static boolean checkSufficientParams(String command, HashMap parsedParams) { + boolean isSufficient = true; + + switch (command) { + case COMMAND_WORD_ADD: + if (!parsedParams.containsKey(NAME_SPECIFIER) + || !parsedParams.containsKey(HEIGHT_SPECIFIER) + || !parsedParams.containsKey(WEIGHT_SPECIFIER) + || !parsedParams.containsKey(EXPECTED_WEIGHT_SPECIFIER) + || !parsedParams.containsKey(CALORIES_SPECIFIER)) { + isSufficient = false; + } + break; + case COMMAND_WORD_EDIT: + if (!(parsedParams.containsKey(NAME_SPECIFIER) + || parsedParams.containsKey(HEIGHT_SPECIFIER) + || parsedParams.containsKey(WEIGHT_SPECIFIER) + || parsedParams.containsKey(EXPECTED_WEIGHT_SPECIFIER) + || parsedParams.containsKey(CALORIES_SPECIFIER))) { + isSufficient = false; + } + break; + default: + isSufficient = true; + break; + } + return isSufficient; + } + + /** + * Finds invalid tags (option indicators) in user's input. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return String containing invalid tags. + */ + public static String findInvalidTags(HashMap parsedParams) { + StringBuilder result = new StringBuilder(); + + Set validTags = new HashSet<>(Arrays.asList(NAME_SPECIFIER, HEIGHT_SPECIFIER, WEIGHT_SPECIFIER, + EXPECTED_WEIGHT_SPECIFIER, CALORIES_SPECIFIER)); + parsedParams.keySet().stream() + .filter(tag -> !validTags.contains(tag)) + .forEach(tag -> result.append(String.format("\"%s\", ", tag))); + + String trimmedResult = result.toString().trim(); + return trimmedResult.isEmpty() + ? trimmedResult + : trimmedResult.substring(STRING_START_INDEX, trimmedResult.length() - NEXT_INDEX_OFFSET); + } + + /** + * Throws InsufficientArgumentException with the correct param based on the command. + * + * @param command Command with invalid format. + * @throws InsufficientArgumentException If command has invalid format. + */ + private static void throwInsufficientArgument(String command) + throws InsufficientArgumentException { + if (command.equals(COMMAND_WORD_ADD)) { + throw new InsufficientArgumentException(ADD_PROFILE_FORMAT); + } else if (command.equals(COMMAND_WORD_EDIT)) { + throw new InsufficientArgumentException(EDIT_PROFILE_FORMAT); + } else { + throw new InsufficientArgumentException(COMMAND_WORD_HELP); + } + } + + /** + * Throws InvalidCommandFormatException with the correct param based on the command. + * + * @param command Command with invalid format. + * @throws InvalidCommandFormatException If command has invalid format. + */ + private static void throwInvalidCommandFormat(String command) throws InvalidCommandFormatException { + if (command.equals(COMMAND_WORD_ADD)) { + throw new InvalidCommandFormatException(ADD_PROFILE_FORMAT); + } else if (command.equals(COMMAND_WORD_EDIT)) { + throw new InvalidCommandFormatException(EDIT_PROFILE_FORMAT); + } else { + throw new InvalidCommandFormatException(COMMAND_WORD_HELP); + } + } + + /** + * Extracts name from parsed HashMap. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return User's name. + * @throws InvalidNameException If input name is empty. + */ + public static String extractName(HashMap parsedParams) throws InvalidNameException { + String name = WordUtils.capitalizeFully(parsedParams.get(NAME_SPECIFIER)); + + if (!Utils.checkValidName(name)) { + throw new InvalidNameException(); + } + + return name; + } + + /** + * Extracts calories from parsed HashMap. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return User's calories. + * @throws InvalidCaloriesException If input calories amount is invalid. + */ + public static double extractCalories(HashMap parsedParams) throws InvalidCaloriesException { + try { + double calories = Double.parseDouble(parsedParams.get(CALORIES_SPECIFIER)); + + if (!Utils.checkValidCalories(calories)) { + throw new InvalidCaloriesException(); + } + return calories; + } catch (NumberFormatException | NullPointerException e) { + throw new InvalidCaloriesException(); + } + } + + /** + * Extracts height from parsed HashMap. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return User's height. + * @throws InvalidHeightException If input height is invalid. + */ + public static int extractHeight(HashMap parsedParams) throws InvalidHeightException { + try { + int height = Integer.parseInt(parsedParams.get(HEIGHT_SPECIFIER)); + + if (!Utils.checkValidHeight(height)) { + throw new InvalidHeightException(); + } + return height; + } catch (NumberFormatException e) { + throw new InvalidHeightException(); + } + } + + /** + * Extracts weight from parsed HashMap. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return User's weight. + * @throws InvalidWeightException If input weight is invalid. + */ + public static double extractWeight(HashMap parsedParams) throws InvalidWeightException { + try { + double weight = Double.parseDouble(parsedParams.get(WEIGHT_SPECIFIER)); + + if (!Utils.checkValidWeight(weight)) { + throw new InvalidWeightException(); + } + return weight; + } catch (NumberFormatException | NullPointerException e) { + throw new InvalidWeightException(); + } + } + + /** + * Extracts expected weight from parsed HashMap. + * + * @param parsedParams HashMap containing option indicator and parsed option pairs. + * @return User's expected weight. + * @throws InvalidWeightException If input expected weight is invalid. + */ + public static double extractExpectedWeight(HashMap parsedParams) throws InvalidWeightException { + try { + double expectedWeight = Double.parseDouble(parsedParams.get(EXPECTED_WEIGHT_SPECIFIER)); + + if (!Utils.checkValidWeight(expectedWeight)) { + throw new InvalidWeightException(); + } + return expectedWeight; + } catch (NumberFormatException | NullPointerException e) { + throw new InvalidWeightException(); + } + } +} diff --git a/src/main/java/logic/parser/WorkoutManagerParser.java b/src/main/java/logic/parser/WorkoutManagerParser.java new file mode 100644 index 0000000000..8364b844c3 --- /dev/null +++ b/src/main/java/logic/parser/WorkoutManagerParser.java @@ -0,0 +1,165 @@ +package logic.parser; + +import exceptions.InvalidDateFormatException; +import exceptions.workout.workoutmanager.NotANumberException; +import logger.SchwarzeneggerLogger; +import models.PastWorkoutSessionRecord; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.function.Predicate; +import java.util.logging.Level; + +//@@author wgzesg +/** + * A class that is responsible for parsing user inputs in Workout Manager. + */ +public class WorkoutManagerParser { + + public static final String TAG_SPECIFIER = "/t"; + public static final String TAG_SPLITTER = ","; + public static final String DATE_SPECIFIER = "/d"; + public static final String START_DATE_SPECIFIER = "/s"; + public static final String END_DATE_SPECIFIER = "/e"; + private static WorkoutManagerParser wmp; + + /** + * Gets instance of PastRecordList. + * + * @return Instance of PastRecordList. + */ + public static WorkoutManagerParser getInstance() { + if (wmp == null) { + wmp = new WorkoutManagerParser(); + } + return wmp; + } + + /** + * Parses user inputs into single words. + * @param comm User's raw input. + * @return An array of strings. + */ + public String[] parseCommandKw(String comm) { + return comm.split(" ", 2); + } + + /** + * Parses user input into a list of tags. + * @param arr User inputs. + * @return A list of tags. + */ + public ArrayList parseTags(String arr) { + ArrayList result = new ArrayList<>(); + String[] content = arr.split(" ", 2); + + try { + if (!content[0].equals(TAG_SPECIFIER)) { + return result; + } + String[] tags = content[1].split(TAG_SPLITTER); + for (String tag : tags) { + if (!result.contains(tag.trim())) { + result.add(tag.trim()); + } + } + } catch (Exception e) { + return new ArrayList<>(); + } + return result; + } + + /** + * Parses user input into a list of predicates which will be used as search conditions. + * @param arr User input. + * @return A list of predicate which will be used as search conditions. + * @throws InvalidDateFormatException handles invalid date input. + */ + public ArrayList> parseSearchConditions(String arr) + throws InvalidDateFormatException { + ArrayList tags = new ArrayList<>(); + ArrayList> test = new ArrayList<>(); + + // parse tag conditions + try { + String[] part1 = arr.split(TAG_SPECIFIER); + String[] tagPart = part1[1].split(DATE_SPECIFIER); + String[] tgs = tagPart[0].split(","); + for (String t : tgs) { + tags.add(t.trim()); + } + test.add(x -> x.containsAll(tags)); + } catch (ArrayIndexOutOfBoundsException e) { + SchwarzeneggerLogger.getInstanceLogger().log(Level.INFO, "No tag identifier is given."); + } + + // parse date conditions + try { + String[] part2 = arr.split(DATE_SPECIFIER); + String[] datePart = part2[1].split(TAG_SPECIFIER); + LocalDateTime finalDate = DateParser.parseDate(datePart[0].trim()); + if (finalDate != null) { + test.add(x -> x.isCreatedOn(finalDate.toLocalDate())); + } + } catch (ArrayIndexOutOfBoundsException e) { + SchwarzeneggerLogger.getInstanceLogger().log(Level.INFO, "No date identifier is given."); + } + + return test; + } + + /** + * Parses the given input into a integer index. + * + * @param args user input. + * @return a integer which is the index given. + * @throws NotANumberException if args is null, empty or not a number. + */ + public int parseIndex(String args) throws NotANumberException { + int index; + try { + index = Integer.parseInt(args); + } catch (NumberFormatException e) { + throw new NotANumberException(); + } + return index; + } + + /** + * Parses user input into a list of predicates which will be used to limit period of record being listed. + * + * @param args user input. + * @return predicates to limit period of record being listed. + * @throws InvalidDateFormatException handles invalid date input. + */ + public ArrayList> parseList(String args) + throws InvalidDateFormatException { + ArrayList> test = new ArrayList<>(); + + // parse start date + try { + String[] part2 = args.split(START_DATE_SPECIFIER); + String[] datePart = part2[1].split(END_DATE_SPECIFIER); + LocalDateTime start = DateParser.parseDate(datePart[0].trim()); + if (start != null) { + test.add(x -> x.isCreatedAfter(start.toLocalDate())); + } + } catch (ArrayIndexOutOfBoundsException e) { + SchwarzeneggerLogger.getInstanceLogger().log(Level.INFO, "No start date identifier is given."); + } + + // parse end date + try { + String[] part2 = args.split(END_DATE_SPECIFIER); + String[] datePart = part2[1].split(START_DATE_SPECIFIER); + LocalDateTime end = DateParser.parseDate(datePart[0].trim()); + if (end != null) { + test.add(x -> x.isCreatedBefore(end.toLocalDate())); + } + } catch (ArrayIndexOutOfBoundsException e) { + SchwarzeneggerLogger.getInstanceLogger().log(Level.INFO, "No end date identifier is given."); + } + + return test; + } +} diff --git a/src/main/java/logic/parser/WorkoutSessionParser.java b/src/main/java/logic/parser/WorkoutSessionParser.java new file mode 100644 index 0000000000..400b506aaf --- /dev/null +++ b/src/main/java/logic/parser/WorkoutSessionParser.java @@ -0,0 +1,93 @@ +package logic.parser; + +import exceptions.workout.workoutsession.AddFormatException; +import exceptions.workout.workoutsession.DeleteFormatException; + +import models.Exercise; + +import java.util.Arrays; + + +//@@author yujinyang1998 + +/** + * A class that deals with making sense of user's command inside Workout Session. + */ +public class WorkoutSessionParser { + + /** + * Splits the user's input by space and return as a string array. + * + * @param input User's input arguments. + * @return String array containing the user's inputs + */ + public static String[] workoutSessionParser(String input) { + + return input.split(" "); + } + + /** + * Sorts the string array into parts for Exercise. + * + * @param input String array containing the user's inputs. + * @return Exercise containing the user's input information. + * @throws AddFormatException If input is empty or is negative. + */ + public static Exercise addParser(String[] input) throws AddFormatException { + String[] returnString = new String[4]; + Arrays.fill(returnString, ""); + int tracker = 0; + for (String s : input) { + if (s.compareTo("/n") == 0) { + tracker = 2; + } else if (s.compareTo("/w") == 0) { + tracker = 3; + } else if (s.compareTo("add") == 0) { + tracker = 1; + } else if (tracker == 2 || tracker == 3) { + returnString[tracker] = s; + } else { + returnString[tracker] += (s + " "); + } + } + int repetitions = Integer.parseInt(returnString[2]); + double weight = Double.parseDouble(returnString[3]); + String description = returnString[1].trim(); + if (repetitions < 0 || weight < 0 || description.length() <= 0) { + throw new AddFormatException(); + } + + return new Exercise(description, repetitions, weight); + } + + /** + * Sorts the string array to return an delete value. + * + * @param input String array containing the user's inputs. + * @return Integer for the index of the exercise to be deleted. + * @throws DeleteFormatException If input is not an integer. + */ + public static int deleteParser(String[] input) throws DeleteFormatException { + int returnInt; + try { + returnInt = Integer.parseInt(input[1]); + } catch (NumberFormatException e) { + throw new DeleteFormatException(); + } + return returnInt; + } + + /** + * Sorts the string array to return an string that represents the search term. + * + * @param input String array containing the user's inputs. + * @return String that the user whats to search. + */ + public static String searchParser(String[] input) { + StringBuilder returnString = new StringBuilder(); + for (int i = 1; i < input.length; i++) { + returnString.append(" ").append(input[i]); + } + return returnString.toString().trim(); + } +} diff --git a/src/main/java/models/Exercise.java b/src/main/java/models/Exercise.java new file mode 100644 index 0000000000..4100cc2385 --- /dev/null +++ b/src/main/java/models/Exercise.java @@ -0,0 +1,66 @@ +package models; + +//@@author yujinyang1998 +/** + * A class that contains Exercise information. + */ +public class Exercise { + + private String moveName; + private int repetitions; + private double weight; + + /** + * Constructs Exercise object. + * + * @param moveName Name of exercise. + * @param repetitions Number of repetitions. + * @param weight Weight of exercise. + */ + public Exercise(String moveName, int repetitions, double weight) { + + this.moveName = moveName; + this.repetitions = repetitions; + this.weight = weight; + } + + /** + * To get a string representation of Exercise. + * + * @return String of exercise information. + */ + @Override + public String toString() { + + return moveName + + ", Repetitions:" + repetitions + + ", Weight=" + weight; + } + + /** + * Gets description of exercise. + * + * @return Description of exercise. + */ + public String getDescription() { + return moveName; + } + + /** + * Gets number of repetition of this exercise. + * + * @return Number of repetition. + */ + public String getRepetitions() { + return Integer.toString(repetitions); + } + + /** + * Gets weight of this exercise. + * + * @return Weight of this exercise. + */ + public String getWeight() { + return Double.toString(weight); + } +} diff --git a/src/main/java/models/ExerciseList.java b/src/main/java/models/ExerciseList.java new file mode 100644 index 0000000000..5cabe8a844 --- /dev/null +++ b/src/main/java/models/ExerciseList.java @@ -0,0 +1,11 @@ +package models; + +import java.util.ArrayList; + +//@@author yujinyang1998 +/** + * A class that contains an arraylist of Exercise. + */ +public class ExerciseList { + public ArrayList exerciseList = new ArrayList<>(); +} diff --git a/src/main/java/models/Food.java b/src/main/java/models/Food.java new file mode 100644 index 0000000000..b5b942798f --- /dev/null +++ b/src/main/java/models/Food.java @@ -0,0 +1,24 @@ +package models; + +//@@author zsk612 +public class Food { + protected String name; + protected double calories; + + public Food(String name, double calories) { + this.name = name; + this.calories = calories; + } + + public String toString() { + return this.name + " with calories: " + this.calories; + } + + public double getCalories() { + return calories; + } + + public String getName() { + return this.name; + } +} diff --git a/src/main/java/models/PastRecordList.java b/src/main/java/models/PastRecordList.java new file mode 100644 index 0000000000..04f4324ae9 --- /dev/null +++ b/src/main/java/models/PastRecordList.java @@ -0,0 +1,196 @@ +package models; + +import exceptions.InvalidDateFormatException; +import exceptions.SchwarzeneggerException; +import exceptions.workout.workoutmanager.SchwIoException; +import logger.SchwarzeneggerLogger; +import logic.parser.WorkoutManagerParser; +import storage.workout.WorkOutManagerStorage; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static ui.CommonUi.LS; +import static ui.CommonUi.searchRecords; + +//@@author wgzesg +/** + * A singleton class representing list of past records. + */ +public class PastRecordList { + public static final int OFFSET = 1; + private static PastRecordList singlePastFile = null; + private final Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + + private static List pastFiles; + WorkOutManagerStorage storage; + + /** + * Constructs PastRecordList object. + */ + private PastRecordList() { + storage = new WorkOutManagerStorage(); + storage.init(); + try { + pastFiles = storage.readPastRecords(); + logger.log(Level.INFO, "Workout records loaded."); + } catch (SchwIoException e) { + pastFiles = new ArrayList<>(); + logger.log(Level.INFO, "Workout records cannot be loaded."); + System.out.println("got bug"); + } + } + + /** + * Gets instance of PastRecordList. + * + * @return Instance of PastRecordList. + */ + public static PastRecordList getInstance() { + if (singlePastFile == null) { + singlePastFile = new PastRecordList(); + } + return singlePastFile; + } + + /** + * Clears all the local storage file and past records. + * + * @throws SchwIoException If error occurred writing to file. + */ + public void clear() throws SchwIoException { + while (pastFiles.size() != 0) { + delete(1); + } + logger.log(Level.WARNING, "Record is cleared."); + } + + /** + * Deletes a file and its past record at a given index. + * + * @param index Index of the file to be cleared. + * @throws SchwIoException If error occurred while writing to file. + */ + public void delete(int index) throws SchwIoException { + PastWorkoutSessionRecord deletedRecord; + deletedRecord = pastFiles.get(index - 1); + pastFiles.remove(index - 1); + File myFile = new File(deletedRecord.getFilePath()); + myFile.delete(); + storage.writePastRecords(pastFiles); + logger.log(Level.INFO, "Record is deleted."); + } + + /** + * Searches based on conditions and returns a string representation of all the records + * that satisfies the condition. + * + * @param args String of user input. + * @return String representation of all the records that satisfies the condition. + * @throws InvalidDateFormatException handles invalid date input. + */ + public String search(String args) throws InvalidDateFormatException { + ArrayList> conditions = + WorkoutManagerParser.getInstance().parseSearchConditions(args); + + List result = pastFiles.stream() + .filter(conditions.stream().reduce(x -> true, Predicate::and)) + .collect(Collectors.toList()); + + String info; + + if (result.size() == 0) { + info = "You do not have any record that satisfies the condition."; + logger.log(Level.INFO, "Search completed."); + return info; + } + + info = searchRecords(result.size(), ((result.size() > 1) ? "records have" : "record has")); + + info = getListInTable(result, info); + logger.log(Level.INFO, "Search completed."); + return info; + } + + private String getListInTable(List result, String info) { + info += String.format("%-8s", "Index") + String.format("%-16s", "Creation date") + + String.format("%-8s", "Tags") + LS; + StringBuilder infoBuilder = new StringBuilder(info); + int index; + for (PastWorkoutSessionRecord wsr : result) { + index = pastFiles.indexOf(wsr); + String row = String.format("%-8s", index + OFFSET) + wsr.toString() + LS; + infoBuilder.append(row); + } + info = infoBuilder.toString().trim(); + return info; + } + + /** + * Edits a file and its past record at a given index. + * + * @param index Index of the file to be edited. + * @return File path string. + * @throws SchwIoException If error occurred while reading or writing to file. + */ + public String edit(int index) throws SchwIoException { + PastWorkoutSessionRecord editedRecord; + editedRecord = pastFiles.get(index - 1); + PastWorkoutSessionRecord newRecord = editedRecord.edit(); + pastFiles.set(index - 1, newRecord); + storage.writePastRecords(pastFiles); + logger.log(Level.INFO, "Edit completed."); + return newRecord.getFilePath(); + } + + /** + * Adds a new file and records. + * + * @param tags Tags on the new record. + * @return new file path string. + * @throws SchwarzeneggerException If error occurred while reading or writing to file. + */ + public String add(ArrayList tags) throws SchwarzeneggerException { + String newFilePath = storage.createfile(); + PastWorkoutSessionRecord newRecord = new PastWorkoutSessionRecord(newFilePath, tags); + pastFiles.add(newRecord); + storage.writePastRecords(pastFiles); + logger.log(Level.INFO, "Add completed."); + return newFilePath; + } + + /** + * Lists all records. + * + * @param args User's input. + * @return message for dates. + * @throws InvalidDateFormatException handles invalid dates format. + */ + public String list(String args) throws InvalidDateFormatException { + + ArrayList> conditions = WorkoutManagerParser.getInstance().parseList(args); + + List result = pastFiles.stream() + .filter(conditions.stream().reduce(x -> true, Predicate::and)) + .collect(Collectors.toList()); + + String info = ""; + if (pastFiles.size() == 0) { + info = "You do not have any record yet. Key in 'new' to start one."; + logger.log(Level.INFO, "List completed."); + return info; + } else if (result.size() == 0) { + info = "You do not have any records between that period."; + logger.log(Level.INFO, "List completed."); + return info; + } + info = getListInTable(result, info); + logger.log(Level.INFO, "List completed."); + return info; + } +} diff --git a/src/main/java/models/PastWorkoutSessionRecord.java b/src/main/java/models/PastWorkoutSessionRecord.java new file mode 100644 index 0000000000..a71707a0a6 --- /dev/null +++ b/src/main/java/models/PastWorkoutSessionRecord.java @@ -0,0 +1,136 @@ +package models; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; + +//@@author wgzesg +/** + * A class that represents past workout session records. + */ +public class PastWorkoutSessionRecord implements Comparable { + + private final String filePath; + private final LocalDateTime creationTime; + private final LocalDateTime lastEditTime; + private final ArrayList tags; + + /** + * Constructor of a new PastWorkoutSessionRecord. + * + * @param filePath File path of where the records of the session are stored. + * @param tags Tags attached to this session. + */ + public PastWorkoutSessionRecord(String filePath, ArrayList tags) { + this.filePath = filePath; + creationTime = LocalDateTime.now(); + lastEditTime = LocalDateTime.now(); + this.tags = tags; + } + + /** + * Constructor of a new PastWorkoutSessionRecord. + * + * @param filePath File path of where the records of the session are stored. + * @param creationTime Creation time. + * @param lastEditTime Last edited time. + * @param tags Tags attached to this session. + */ + public PastWorkoutSessionRecord(String filePath, LocalDateTime creationTime, + LocalDateTime lastEditTime, ArrayList tags) { + this.filePath = filePath; + this.creationTime = creationTime; + this.lastEditTime = lastEditTime; + this.tags = tags; + } + + /** + * Checks if this record contains all the given tag. + * + * @param tgs list of tags specified. + * @return True if all contained; otherwise false + */ + public boolean containsAll(ArrayList tgs) { + for (String s : tgs) { + if (!isContained(s)) { + return false; + } + } + return true; + } + + private boolean isContained(String s) { + for (String t : tags) { + if (t.contains(s)) { + return true; + } + } + return false; + } + + /** + * Checks if this record is created on a given date. + * + * @param date the given date + * @return True if it is created on that day; otherwise false + */ + public boolean isCreatedOn(LocalDate date) { + return date.equals(creationTime.toLocalDate()); + } + + /** + * Check if this record is created after a given date(inclusive). + * + * @param date the given date. + * @return True if it is created after or on that day; otherwise false. + */ + public boolean isCreatedAfter(LocalDate date) { + return date.equals(creationTime.toLocalDate()) || date.isBefore(creationTime.toLocalDate()); + } + + /** + * Check if this record is created before a given date(inclusive). + * + * @param date the given date. + * @return True if it is created before or on that day; otherwise false. + */ + public boolean isCreatedBefore(LocalDate date) { + return date.equals(creationTime.toLocalDate()) || date.isAfter(creationTime.toLocalDate()); + } + + public String getFilePath() { + return filePath; + } + + @Override + public String toString() { + return String.format("%-15s %s", creationTime.toLocalDate(), tags.toString()); + } + + @Override + public int compareTo(PastWorkoutSessionRecord o) { + if (creationTime.isBefore(o.creationTime)) { + return -1; + } + return 1; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof PastWorkoutSessionRecord)) { + return false; + } + return creationTime.equals(((PastWorkoutSessionRecord) o).creationTime) + && lastEditTime.equals(((PastWorkoutSessionRecord) o).lastEditTime) + && tags.equals(((PastWorkoutSessionRecord) o).tags); + } + + /** + * Updates last edited time when edited. + * + * @return A new PastWorkoutSessionRecord with updated lastEdit time. + */ + public PastWorkoutSessionRecord edit() { + return new PastWorkoutSessionRecord(filePath, creationTime, LocalDateTime.now(), this.tags); + } +} diff --git a/src/main/java/models/Profile.java b/src/main/java/models/Profile.java new file mode 100644 index 0000000000..397073780a --- /dev/null +++ b/src/main/java/models/Profile.java @@ -0,0 +1,153 @@ +package models; + +import profile.Constants; + +import static profile.Constants.PROFILE_STRING_REPRESENTATION; +import static profile.Utils.checkValidProfile; + +//@@author tienkhoa16 + +/** + * A class that contains user profile. + */ +public class Profile { + + protected String name; + protected int height; + protected double weight; + protected double expectedWeight; + protected double calories; + + /** + * Constructs Profile object. + * + * @param name User's name. + * @param height User's height in centimeters. + * @param weight User's weight in kilograms. + * @param expectedWeight User's expected weight in kilograms. + * @param calories User's expected calories intake daily. + */ + public Profile(String name, int height, double weight, double expectedWeight, double calories) { + this.name = name; + this.height = height; + this.weight = weight; + this.expectedWeight = expectedWeight; + this.calories = calories; + } + + /** + * Overrides toString method of class Object to get string presentation of Profile object. + * + * @return String presentation of Profile object. + */ + public String toString() { + assert checkValidProfile(this) : "Profile is invalid"; + return String.format(PROFILE_STRING_REPRESENTATION, getName(), getHeight(), getWeight(), + getExpectedWeight(), getCalories(), getBmiClassification()); + } + + /** + * Gets user's name. + * + * @return User's name. + */ + public String getName() { + return name; + } + + /** + * Gets user's height. + * + * @return User's height. + */ + public int getHeight() { + return height; + } + + /** + * Gets user's weight. + * + * @return User's weight. + */ + public double getWeight() { + return weight; + } + + /** + * Gets user's expected weight. + * + * @return User's expected weight. + */ + public double getExpectedWeight() { + return expectedWeight; + } + + /** + * Gets user's expected calories intake daily. + * + * @return User's expected calories intake daily. + */ + public double getCalories() { + return calories; + } + + /** + * Calculates user's BMI. + * + * @param height User's height. + * @param weight User's weight. + * @return User's BMI. + */ + public double calculateBmi(int height, double weight) { + return weight / Math.pow((double) height / 100, 2); + } + + /** + * Gets user's bmi index and classification. + * + * @return User's bmi index and classification. + */ + public String getBmiClassification() { + String classification; + + double bmiIndex = calculateBmi(height, weight); + if (bmiIndex < Constants.UNDER_WEIGHT_THRESHOLD) { + classification = "Underweight"; + } else if (bmiIndex <= Constants.NORMAL_WEIGHT_THRESHOLD) { + classification = "Normal Weight"; + } else if (bmiIndex <= Constants.OVERWEIGHT_THRESHOLD) { + classification = "Overweight"; + } else if (bmiIndex <= Constants.OBESITY_CLASS_ONE_THRESHOLD) { + classification = "Obesity Class 1"; + } else if (bmiIndex <= Constants.OBESITY_CLASS_TWO_THRESHOLD) { + classification = "Obesity Class 2"; + } else { + classification = "Extreme Obesity Class 3"; + } + + return String.format("%.1f (%s)", bmiIndex, classification); + } + + /** + * Overrides equals method of class Object to compare Profile object. + * + * @param o Object to compare. + * @return True if this Profile object is the same as the obj + * argument; false otherwise. + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Profile profile = (Profile) o; + return name.equals(profile.name) + && calories == profile.calories + && height == profile.height + && Double.compare(profile.weight, weight) == 0 + && Double.compare(profile.expectedWeight, expectedWeight) == 0; + } +} diff --git a/src/main/java/profile/Constants.java b/src/main/java/profile/Constants.java new file mode 100644 index 0000000000..94f5f4899a --- /dev/null +++ b/src/main/java/profile/Constants.java @@ -0,0 +1,38 @@ +package profile; + +import static ui.CommonUi.LS; + +//@@author tienkhoa16 + +/** + * Constants used in profile-related code. + */ +public class Constants { + + public static final String ADD_PROFILE_FORMAT = "add /n [NAME] /h [HEIGHT] " + + "/w [CURRENT_WEIGHT] /e [EXPECTED_WEIGHT] /c [DAILY_CALORIE_INTAKE]"; + public static final String EDIT_PROFILE_FORMAT = "edit " + + " "; + + public static final String EXAMPLE_BMI = "32.0 (Obesity Class 1)"; + public static final double EXAMPLE_CALORIES = 2500; + public static final double EXAMPLE_EXPECTED_WEIGHT = 100; + public static final int EXAMPLE_HEIGHT = 188; + public static final String EXAMPLE_NAME = "Schwarzenegger"; + public static final double EXAMPLE_WEIGHT = 113; + public static final int HEIGHT_LOWER_BOUND = 63; + public static final int HEIGHT_UPPER_BOUND = 231; + public static final double NORMAL_WEIGHT_AVERAGE = 21.7; + public static final double NORMAL_WEIGHT_THRESHOLD = 24.9; + public static final double OBESITY_CLASS_ONE_THRESHOLD = 34.9; + public static final double OBESITY_CLASS_TWO_THRESHOLD = 39.9; + public static final double OVERWEIGHT_THRESHOLD = 29.9; + public static final String PROFILE_STRING_REPRESENTATION = "\tName: %s" + LS + + "\tHeight: %d cm" + LS + "\tWeight: %.1f kg" + LS + "\tExpected Weight: %.1f kg" + + LS + "\tExpected daily calorie intake: %.1f calories" + LS + "\tYour current BMI: %s"; + public static final double UNDER_WEIGHT_THRESHOLD = 18.5; + public static final double WEIGHT_LOWER_BOUND = 2.1; + public static final double WEIGHT_UPPER_BOUND = 635; + public static final double CALORIES_UPPER_BOUND = 200000; + public static final double CALORIES_LOWER_BOUND = 0; +} diff --git a/src/main/java/profile/ProfileSession.java b/src/main/java/profile/ProfileSession.java new file mode 100644 index 0000000000..0505f2a0e0 --- /dev/null +++ b/src/main/java/profile/ProfileSession.java @@ -0,0 +1,124 @@ +package profile; + +import exceptions.EndException; +import exceptions.ExceptionHandler; +import exceptions.SchwarzeneggerException; +import logger.SchwarzeneggerLogger; +import logic.commands.Command; +import logic.commands.CommandLib; +import logic.commands.CommandResult; +import logic.parser.CommonParser; +import storage.profile.ProfileStorage; +import ui.CommonUi; + +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static logic.parser.CommonParser.COMMAND_ARGS_INDEX; +import static logic.parser.CommonParser.COMMAND_TYPE_INDEX; + +//@@author tienkhoa16 + +/** + * A class that is responsible for interacting with user in Profile Session. + */ +public class ProfileSession { + private static ProfileSession singleInstance = null; + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + private CommonUi ui; + private CommonParser parser; + private ProfileStorage storage; + private CommandLib cl; + + /** + * Constructs ProfileSession object with customised path to save data file. + * + * @param pathToProfileFolder Path to profile data folder. + * @param pathToProfileFile Path to profile data file. + */ + private ProfileSession(Path pathToProfileFolder, Path pathToProfileFile) { + logger.log(Level.INFO, "initialising ProfileSession object"); + ui = new CommonUi(); + storage = new ProfileStorage(pathToProfileFolder, pathToProfileFile); + parser = new CommonParser(); + cl = new CommandLib(); + cl.initProfileSessionCl(); + } + + /** + * Gets the single instance of ProfileSession class. + * + * @param pathToProfileFolder Path to profile data folder. + * @param pathToProfileFile Path to profile data file. + * @return Single instance of ProfileSession class. + */ + public static ProfileSession getInstance(Path pathToProfileFolder, Path pathToProfileFile) { + if (singleInstance == null) { + singleInstance = new ProfileSession(pathToProfileFolder, pathToProfileFile); + } + return singleInstance; + } + + /** + * Runs Profile Session. + */ + public void run() { + start(); + runCommandLoopTillEnd(); + } + + /** + * Starts up Profile Session. + */ + private void start() { + logger.log(Level.INFO, "starting profile session"); + ui.printOpening("Profile Menu"); + } + + /** + * Gets user's command and executes repeatedly until user requests to exit Profile Session. + */ + private void runCommandLoopTillEnd() { + logger.log(Level.INFO, "executing profile session loop"); + + while (true) { + String userCommand = ui.getCommand("Profile Menu"); + assert userCommand != null : "input should not be null before process loop"; + + try { + CommandResult result = processCommand(userCommand); + ui.showToUser(result.getFeedbackMessage()); + } catch (SchwarzeneggerException e) { + ui.showToUser(ExceptionHandler.handleCheckedExceptions(e)); + + if (e instanceof EndException) { + break; + } + } catch (Exception e) { + ui.showToUser(ExceptionHandler.handleUncheckedExceptions(e)); + } + } + logger.log(Level.INFO, "exiting profile session loop"); + } + + /** + * Processes and displays command execution result to user. + * + * @param userCommand User's trimmed input. + * @return Result after processing command. + * @throws SchwarzeneggerException If there are caught exceptions. + */ + public CommandResult processCommand(String userCommand) throws SchwarzeneggerException { + String[] commParts = parser.parseCommand(userCommand); + assert commParts != null : "parsed array should not be null before process loop"; + + Command command = cl.getCommand(commParts[COMMAND_TYPE_INDEX]); + assert command != null : "command object should not be null null"; + + CommandResult result = command.execute(commParts[COMMAND_ARGS_INDEX], storage); + assert result != null : "command result object should not be null null"; + + return result; + } +} diff --git a/src/main/java/profile/Utils.java b/src/main/java/profile/Utils.java new file mode 100644 index 0000000000..f4e4904b75 --- /dev/null +++ b/src/main/java/profile/Utils.java @@ -0,0 +1,73 @@ +package profile; + +import models.Profile; + +import static profile.Constants.CALORIES_LOWER_BOUND; +import static profile.Constants.HEIGHT_LOWER_BOUND; +import static profile.Constants.HEIGHT_UPPER_BOUND; +import static profile.Constants.WEIGHT_LOWER_BOUND; +import static profile.Constants.WEIGHT_UPPER_BOUND; + +//@@author tienkhoa16 + +/** + * A class containing utility methods used in Profile Session. + */ +public class Utils { + /** + * Verifies if user's input when creating profile is valid. + * + * @param profile User Profile object. + * @return Whether input profile is valid. + */ + public static boolean checkValidProfile(Profile profile) { + return (profile != null + && checkValidName(profile.getName()) + && checkValidCalories(profile.getCalories()) + && checkValidHeight(profile.getHeight()) + && checkValidWeight(profile.getWeight()) + && checkValidWeight(profile.getExpectedWeight())); + } + + /** + * Verifies if user's input name is not empty string. + * + * @param name User's input name. + * @return Whether input name is valid. + */ + public static boolean checkValidName(String name) { + return name != null && !name.isEmpty(); + } + + /** + * Verifies if user's input calories is not negative. + * + * @param calories User's expected calories intake daily. + * @return Whether calories amount is not negative. + */ + public static boolean checkValidCalories(double calories) { + return (calories >= CALORIES_LOWER_BOUND); + } + + /** + * Verifies if user's input height is in the valid range + * (between {@link HEIGHT_LOWER_BOUND} and {@link HEIGHT_UPPER_BOUND} inclusive). + * + * @param height User's input height. + * @return Whether input height is valid. + */ + public static boolean checkValidHeight(int height) { + return (height >= HEIGHT_LOWER_BOUND && height <= HEIGHT_UPPER_BOUND); + } + + /** + * Verifies if user's input weight is in the valid range + * (between {@link WEIGHT_LOWER_BOUND} and {@link WEIGHT_UPPER_BOUND} inclusive). + * + * @param weight User's input weight. + * @return Whether input weight is valid. + */ + public static boolean checkValidWeight(double weight) { + return (weight >= WEIGHT_LOWER_BOUND && weight <= WEIGHT_UPPER_BOUND); + } +} diff --git a/src/main/java/seedu/duke/Constants.java b/src/main/java/seedu/duke/Constants.java new file mode 100644 index 0000000000..3c84a0ae5a --- /dev/null +++ b/src/main/java/seedu/duke/Constants.java @@ -0,0 +1,40 @@ +package seedu.duke; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Represents class that contains all the constants. + */ +public class Constants { + public static final String COMMAND_WORD_DIET = "diet"; + public static final String COMMAND_WORD_PROFILE = "profile"; + public static final String COMMAND_WORD_WORKOUT = "workout"; + + public static final String COMMAND_WORD_ADD = "add"; + public static final String COMMAND_WORD_NEW = "new"; + public static final String COMMAND_WORD_DELETE = "delete"; + public static final String COMMAND_WORD_EDIT = "edit"; + public static final String COMMAND_WORD_END = "end"; + public static final String COMMAND_WORD_HELP = "help"; + public static final String COMMAND_WORD_VIEW = "view"; + public static final String COMMAND_WORD_CLEAR = "clear"; + public static final String COMMAND_WORD_SEARCH = "search"; + public static final String COMMAND_WORD_LIST = "list"; + public static final String COMMAND_WORD_WRONG = "wrong"; + + public static final String PROJECT_ROOT = System.getProperty("user.dir"); + public static final String DATA_FOLDER = "saves"; + + public static final Path PATH_TO_PROFILE_FILE = Paths.get(PROJECT_ROOT, DATA_FOLDER, "profile", "profile.json"); + public static final Path PATH_TO_PROFILE_FOLDER = Paths.get(PROJECT_ROOT, DATA_FOLDER, "profile"); + + public static final String PATH_TO_WORKOUT_SESSION_HISTORY = Paths.get(PROJECT_ROOT, DATA_FOLDER, "workout", + "history.json").toString(); + public static final String PATH_TO_WORKOUT_SESSION_FOLDER = Paths.get(PROJECT_ROOT, DATA_FOLDER, "workout", + "workoutSession").toString(); + + public static final String PATH_TO_DIET_FOLDER = "saves/diet/"; + + public static final String PATH_TO_LOG_FILE = Paths.get(PROJECT_ROOT, "logs", "SchwarzeneggerLogs.log").toString(); +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 5c74e68d59..d7f1fed633 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -1,21 +1,121 @@ package seedu.duke; -import java.util.Scanner; +import exceptions.EndException; +import exceptions.ExceptionHandler; +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidSaveFormatException; +import logger.SchwarzeneggerLogger; +import logic.commands.Command; +import logic.commands.CommandLib; +import logic.parser.CommonParser; +import models.Profile; +import storage.profile.ProfileStorage; +import ui.CommonUi; +import java.util.logging.Logger; + +import static logic.parser.CommonParser.COMMAND_ARGS_INDEX; +import static logic.parser.CommonParser.COMMAND_TYPE_INDEX; +import static seedu.duke.Constants.PATH_TO_PROFILE_FILE; +import static seedu.duke.Constants.PATH_TO_PROFILE_FOLDER; +import static ui.CommonUi.LOGO; +import static ui.profile.ProfileUi.MESSAGE_WELCOME_EXISTING_USER; +import static ui.profile.ProfileUi.MESSAGE_WELCOME_NEW_USER; +import static ui.profile.ProfileUi.MESSAGE_WELCOME_WITH_INVALID_SAVE_FORMAT; + +/** + * The Schwarzenegger program implements an application that keeps track of the user's gym and diet record. + */ public class Duke { + private static Logger logger; + private CommandLib cl; + private CommonUi ui; + private CommonParser parser; + + /** + * Constructs Duke object. + */ + private Duke() { + logger = SchwarzeneggerLogger.getInstanceLogger(); + cl = new CommandLib(); + cl.initMainMenuCl(); + ui = new CommonUi(); + parser = new CommonParser(); + } + /** - * Main entry-point for the java.duke.Duke application. + * Main entry-point for the java.seedu.duke.Duke application. + * + * @param args Unused in Schwarzenegger. */ 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()); + new Duke().run(); + } + + /** + * Runs Schwarzenegger. + */ + private void run() { + start(); + runCommandLoopTillEnd(); + end(); + } + + /** + * Starts up Duke with greeting message. + */ + private void start() { + Profile profile; + + try { + ui.showToUser(LOGO); + profile = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE).loadData(); + ui.showToUser(String.format(MESSAGE_WELCOME_EXISTING_USER, profile.getName())); + } catch (SchwarzeneggerException e) { + if (e instanceof InvalidSaveFormatException) { + ui.showToUser(MESSAGE_WELCOME_WITH_INVALID_SAVE_FORMAT); + } else { + ui.showToUser(ExceptionHandler.handleCheckedExceptions(e)); + ui.showToUser(MESSAGE_WELCOME_NEW_USER); + } + } catch (Exception e) { + if (!(e instanceof NullPointerException)) { + ui.showToUser(ExceptionHandler.handleUncheckedExceptions(e)); + } + ui.showToUser(MESSAGE_WELCOME_NEW_USER); + } + } + + /** + * Gets user's command and executes repeatedly until user requests to end Schwarzenegger. + */ + private void runCommandLoopTillEnd() { + logger.info("running main menu loop"); + + while (true) { + String userInput = ui.getCommand("Main Menu"); + String[] commParts = parser.parseCommand(userInput); + Command cm = cl.getCommand(commParts[COMMAND_TYPE_INDEX]); + + try { + cm.execute(commParts[COMMAND_ARGS_INDEX]); + } catch (SchwarzeneggerException e) { + if (e instanceof EndException) { + break; + } + + ui.showToUser(ExceptionHandler.handleCheckedExceptions(e)); + } catch (Exception e) { + ui.showToUser(ExceptionHandler.handleUncheckedExceptions(e)); + } + } + } + + /** + * Ends Schwarzenegger. + */ + private void end() { + ui.showToUser("Bye, you have exited The Schwarzenegger."); + System.exit(0); } } diff --git a/src/main/java/storage/diet/DietStorage.java b/src/main/java/storage/diet/DietStorage.java new file mode 100644 index 0000000000..5e52418d69 --- /dev/null +++ b/src/main/java/storage/diet/DietStorage.java @@ -0,0 +1,106 @@ +package storage.diet; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.MalformedJsonException; +import diet.dietsession.DietSession; +import logger.SchwarzeneggerLogger; +import ui.diet.dietsession.DietSessionUi; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_CORRUPTED; +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_NOT_FOUND; + +//@@author CFZeon +/** + * This class holds the data loaded during runtime and read and writes to the local storage. + */ +public class DietStorage { + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + private static Gson gson; + private static File file = null; + private static DietSessionUi ui = new DietSessionUi(); + + /** + * Initialise the database with locally stored data. + * If the local file is not found. It creates the relevant file and folder. + * @param filePath a string that represents file path. + * @param filePathName a string that represents file path name. + * + * @throws IOException If director or file cannot be created. + */ + public void init(String filePath, String filePathName) throws IOException { + logger.log(Level.INFO, "creating diet session save file"); + + gson = new GsonBuilder().setPrettyPrinting() + .create(); + + String fileName = filePath + filePathName + ".json"; + file = new File(fileName); + + file.getParentFile().mkdirs(); + file.createNewFile(); + + } + + /** + * Writes the content in dietSession to a local file. + * If the local file is not found. It creates the relevant file and folder. + * @param filePath a string that represents file path. + * @param filePathName a string that represents file path name. + * @param dietSession that represents the class diet session + * + * @throws IOException If director or file cannot be created. + */ + public void writeToStorageDietSession(String filePath, String filePathName, + DietSession dietSession) throws IOException { + logger.log(Level.INFO, "saving file to location"); + File file = new File(filePath + filePathName + ".json"); + if (file.exists()) { + file.delete(); + } + FileWriter writer = new FileWriter(file.getPath()); + gson.toJson(dietSession, writer); + logger.log(Level.INFO, "file saving complete"); + writer.flush(); + writer.close(); + } + + /** + * Reads the content of the .json file and instantiates as a DietSession. + * + * @param filePath path from source folder to save folder + * @param filePathName name of file + * @return DietSession instance + */ + public DietSession readDietSession(String filePath, String filePathName) { + Gson gson = new Gson(); + DietSession dietSession; + dietSession = null; + try { + File file = new File(System.getProperty("user.dir") + "/" + + filePath + filePathName); + Reader reader = new FileReader(file.getPath()); + dietSession = gson.fromJson(reader, DietSession.class); + reader.close(); + } catch (FileNotFoundException e) { + ui.showToUser(DIET_FILE_NOT_FOUND); + logger.log(Level.WARNING, "Diet Session file not found"); + } catch (MalformedJsonException | JsonSyntaxException e) { + ui.showToUser(DIET_FILE_CORRUPTED); + logger.log(Level.WARNING, "Corrupted diet session"); + } catch (IOException e) { + logger.log(Level.WARNING, "Could not read diet session"); + } + return dietSession; + } +} diff --git a/src/main/java/storage/profile/ProfileStorage.java b/src/main/java/storage/profile/ProfileStorage.java new file mode 100644 index 0000000000..963b86ff4c --- /dev/null +++ b/src/main/java/storage/profile/ProfileStorage.java @@ -0,0 +1,158 @@ +package storage.profile; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import exceptions.profile.InvalidSaveFormatException; +import exceptions.profile.LoadingException; +import exceptions.profile.SavingException; +import logger.SchwarzeneggerLogger; +import models.Profile; +import profile.Utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +/** + * A class that saves and loads user profile data on local hard disk. + */ +public class ProfileStorage { + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + private Gson gson; + private Path pathToProfileFolder; + private Path pathToProfileFile; + + /** + * Constructs Storage object. + * + * @param pathToProfileFolder Path to profile data folder. + * @param pathToProfileFile Path to profile data file. + */ + public ProfileStorage(Path pathToProfileFolder, Path pathToProfileFile) { + gson = new GsonBuilder().setPrettyPrinting().create(); + this.pathToProfileFolder = pathToProfileFolder; + this.pathToProfileFile = pathToProfileFile; + } + + /** + * Loads user profile from data file. + * + * @return User profile. + * @throws LoadingException If there are failed or interrupted I/O operations. + * @throws InvalidSaveFormatException If data file saving format is corrupted. + */ + public Profile loadData() throws LoadingException, InvalidSaveFormatException { + Profile profile = null; + + if (Files.exists(pathToProfileFolder)) { + try { + logger.log(Level.INFO, "starting to decode profile data"); + profile = decodeProfile(); + assert profile != null : "profile should not be null"; + logger.log(Level.INFO, "finishing profile data decoding"); + } catch (FileNotFoundException e) { + createDataFile(); + } + } else { + createDataFolder(); + createDataFile(); + } + + return profile; + } + + /** + * Decodes user profile save data to a profile object. + * + * @return Profile object. + * @throws InvalidSaveFormatException If the saving format is invalid. + * @throws FileNotFoundException If data file is not found. + */ + public Profile decodeProfile() throws InvalidSaveFormatException, FileNotFoundException { + try { + logger.log(Level.INFO, "decoding profile data"); + Type profileType = new TypeToken() { + }.getType(); + File file = new File(pathToProfileFile.toString()); + JsonReader reader = new JsonReader(new FileReader(file.getPath())); + Profile profile = gson.fromJson(reader, profileType); + + if (!Utils.checkValidProfile(profile)) { + logger.log(Level.WARNING, "processing invalid profile data"); + throw new InvalidSaveFormatException(pathToProfileFile.toString()); + } + + return profile; + } catch (JsonSyntaxException e) { + logger.log(Level.WARNING, "processing invalid syntax in data file", e); + throw new InvalidSaveFormatException(pathToProfileFile.toString()); + } + } + + /** + * Creates data file. + * + * @throws LoadingException If there are failed or interrupted I/O operations. + */ + public void createDataFile() throws LoadingException { + try { + Files.createFile(pathToProfileFile); + logger.log(Level.INFO, "created saves/profile/profile.txt"); + } catch (IOException e) { + throw new LoadingException(e.getMessage()); + } + } + + /** + * Creates data folder. + * + * @throws LoadingException If there are failed or interrupted I/O operations. + */ + public void createDataFolder() throws LoadingException { + try { + Files.createDirectories(pathToProfileFolder); + logger.log(Level.INFO, "created saves/profile"); + } catch (IOException e) { + throw new LoadingException(e.getMessage()); + } + } + + /** + * Saves user profile data to hard disk after profile changes. + * + * @param profile User's profile. + * @throws SavingException If there are failed or interrupted I/O operations. + */ + public void saveData(Profile profile) throws SavingException { + try { + logger.log(Level.INFO, "starting to save profile data"); + FileWriter fw = new FileWriter(pathToProfileFile.toString()); + + if (profile == null) { + fw.write(EMPTY_STRING); + } else { + gson.toJson(profile, fw); + fw.flush(); + } + + fw.close(); + logger.log(Level.INFO, "finishing data saving"); + } catch (IOException e) { + logger.log(Level.WARNING, "processing IOException while saving data", e); + throw new SavingException(e.getMessage()); + } + } +} diff --git a/src/main/java/storage/workout/WorkOutManagerStorage.java b/src/main/java/storage/workout/WorkOutManagerStorage.java new file mode 100644 index 0000000000..d73c78ef32 --- /dev/null +++ b/src/main/java/storage/workout/WorkOutManagerStorage.java @@ -0,0 +1,132 @@ +package storage.workout; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import exceptions.workout.workoutmanager.SchwIoException; +import logger.SchwarzeneggerLogger; +import models.PastWorkoutSessionRecord; +import seedu.duke.Constants; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +//@@author wgzesg +/** + * A class that is responsible for storing Workout Manager data. + */ +public class WorkOutManagerStorage { + + /** + * This list contains all the past record files' file name and some other relevant info. + * It is saved in a file called history.json. + * Each time it initialises, it will read history.json file and save it to this list. + * Each time adding/removing record will update this list and write again to local storage. + */ + private Gson gson; + private final Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + + /** + * This variable keeps track of file name. + */ + private int recordCount = 0; + + public void init() { + gson = new GsonBuilder().setPrettyPrinting().create(); + } + + /** + * Reads all past records. + * + * @return A list of PastWorkoutSessionRecord. + * @throws SchwIoException If error in reading the file. + */ + public ArrayList readPastRecords() throws SchwIoException { + logger.info("Start loading files"); + + ArrayList pastFiles; + File file = new File(Constants.PATH_TO_WORKOUT_SESSION_HISTORY); + Type taskListType = new TypeToken>() { + }.getType(); + try { + JsonReader reader = new JsonReader(new FileReader(file.getPath())); + pastFiles = gson.fromJson(reader, taskListType); + } catch (FileNotFoundException e) { + logger.info("File is not found."); + createMetaFile(Constants.PATH_TO_WORKOUT_SESSION_HISTORY); + pastFiles = new ArrayList<>(); + } catch (JsonSyntaxException e) { + logger.info("File is corrupted."); + pastFiles = new ArrayList<>(); + } + if (pastFiles == null) { + pastFiles = new ArrayList<>(); + } + assert (pastFiles != null); + recordCount = pastFiles.size(); + logger.info("Loading completed"); + return pastFiles; + } + + /** + * Writes all past records to local storage. + * + * @param pastFiles The list of records to be stored. + * @throws SchwIoException If error occurs in writing to file. + */ + public void writePastRecords(List pastFiles) throws SchwIoException { + logger.info("Saving the changes..."); + File file = new File(Constants.PATH_TO_WORKOUT_SESSION_HISTORY); + FileWriter writer; + try { + writer = new FileWriter(file.getPath()); + gson.toJson(pastFiles, writer); + writer.flush(); + writer.close(); + } catch (IOException e) { + logger.info("Error occured when saving the progress."); + throw new SchwIoException("Error occured when saving the progress..."); + } + logger.info("All changes saved."); + recordCount = pastFiles.size(); + } + + private void createMetaFile(String path) throws SchwIoException { + File file = new File(path); + file.getParentFile().mkdirs(); + try { + file.createNewFile(); + } catch (IOException e) { + logger.info("File cannot be created at " + path + "."); + throw new SchwIoException("The local storage file cannot be created at " + path); + } + } + + /** + * Creates a new file to store a new workout session data. + * + * @return File path of new created file + * @throws SchwIoException If error occurs in creating the file. + */ + public String createfile() throws SchwIoException { + String newFilePath = Constants.PATH_TO_WORKOUT_SESSION_FOLDER + recordCount + ".json"; + File file = new File(newFilePath); + file.getParentFile().mkdirs(); + try { + file.createNewFile(); + return newFilePath; + } catch (IOException e) { + logger.info("File is not created at " + newFilePath + "."); + throw new SchwIoException("The local storage file cannot be created at " + newFilePath); + } + } +} diff --git a/src/main/java/storage/workout/WorkoutSessionStorage.java b/src/main/java/storage/workout/WorkoutSessionStorage.java new file mode 100644 index 0000000000..ee78589c01 --- /dev/null +++ b/src/main/java/storage/workout/WorkoutSessionStorage.java @@ -0,0 +1,70 @@ +package storage.workout; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import models.Exercise; +import models.ExerciseList; +import ui.CommonUi; +import ui.workout.workoutsession.WorkoutSessionUi; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; + +//@@author yujinyang1998 +/** + * A class that saves and loads Workout Session data on local hard disk. + */ +public class WorkoutSessionStorage { + private CommonUi ui = new CommonUi(); + private static Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + /** + * Writes to storage file with exercise data. + * + * @param filePath Path to data file. + * @param exerciseList List of exercise. + * @throws IOException If file is missing or corrupted. + */ + public void writeToStorage(String filePath, ExerciseList exerciseList) throws IOException { + assert (filePath != null && exerciseList != null) : "File corrupted"; + File file = new File(filePath); + FileWriter writer = new FileWriter(file.getPath()); + gson.toJson(exerciseList.exerciseList, writer); + writer.flush(); + writer.close(); + } + + /** + * Reads file from drive and loads exercise information. + * + * @param filePath Path to data file. + * @param exerciseList List of exercise. + * @throws FileNotFoundException If file is missing or corrupted. + */ + public void readFileContents(String filePath, ExerciseList exerciseList) throws FileNotFoundException { + assert (filePath != null && exerciseList != null) : "File corrupted"; + File file = new File(filePath); + + Type taskListType = new TypeToken>() { + }.getType(); + JsonReader reader = new JsonReader(new FileReader(file.getPath())); + exerciseList.exerciseList.clear(); + try { + exerciseList.exerciseList.addAll(gson.fromJson(reader, taskListType)); + } catch (NullPointerException e) { + System.out.print(""); + } catch (JsonSyntaxException e) { + ui.showToUser(WorkoutSessionUi.saveCorruptedError(filePath)); + } + } + + +} diff --git a/src/main/java/ui/CommonUi.java b/src/main/java/ui/CommonUi.java new file mode 100644 index 0000000000..7d27e305a6 --- /dev/null +++ b/src/main/java/ui/CommonUi.java @@ -0,0 +1,137 @@ +package ui; + +import java.util.Scanner; + +import static logic.parser.CommonParser.GREEDY_WHITE_SPACE; + +//@@author tienkhoa16 +/** + * A base class for interacting with user. + */ +public class CommonUi { + + public static final String EMPTY_STRING = ""; + public static final String LINE_PREFIX = "\t "; + public static final String LS = System.lineSeparator() + LINE_PREFIX; + public static final String LOGO = " _____ _" + LS + + " / ____| | |" + LS + + " | (___ ___ | |__ __ __ __ _ _ __ ____ ___ _ __ ___ __ _ __ _ ___ _ __ " + LS + + " \\___ \\ / __|| '_ \\\\ \\ /\\ / // _` || '__||_ // _ \\| '_ \\ / _ \\ / _` | / _` | / _ \\| " + + "'__|" + LS + + " ____) || (__ | | | |\\ V V /| (_| || | / /| __/| | | || __/| (_| || (_| || __/| |" + LS + + " |_____/ \\___||_| |_| \\_/\\_/ \\__,_||_| /___|\\___||_| |_| \\___| \\__, | \\__, | \\___||_|" + LS + + " __/ | __/ |" + LS + + " |___/ |___/"; + public static final String HELP_FORMAT = "%-8s FORMAT: %s" + LS + "%-8s DESCRIPTION: %s" + LS; + public static final String WARNING_FORMAT = "! WARNING: %s!"; + public static final String MESSAGE_CLEAR_FORMAT = "Alright, your %s been cleared!"; + public static final String HORIZONTAL_LINE = LINE_PREFIX + + "_________________________________________________________________________________________________"; + public static final String MESSAGE_FORMAT_GENERAL = HORIZONTAL_LINE + LS + "%s" + System.lineSeparator() + + HORIZONTAL_LINE + System.lineSeparator(); + + /** + * Shows a message to the user. + * + * @param result Message to be displayed. + */ + public void showToUser(String result) { + assert result != null : "result should not be null"; + System.out.println(String.format(MESSAGE_FORMAT_GENERAL, result)); + } + + /** + * Adjusts help message in the correct format. + * + * @param commandName User command input + * @param commandFormat Command formatter + * @param description Description of the commands + * @return Formatted string of help message + */ + public static String helpFormatter(String commandName, String commandFormat, String description) { + return String.format(HELP_FORMAT, commandName, commandFormat, EMPTY_STRING, description); + } + + /** + * Adjusts clear message in the correct format. + * + * @param content Clear message + * @return Formatted string of clear message + */ + public static String clearMsg(String content) { + return String.format(MESSAGE_CLEAR_FORMAT, content); + } + + /** + * Adjusts search result in the correct format. + * + * @param num Index of results + * @param content Content of searched items + * @return Formatted string of the search result + */ + public static String searchRecords(int num, String content) { + return String.format("%d %s been found!", num, content) + LS; + } + + /** + * Prints formatted warning message. + * + * @param message Warning message. + */ + public void showWarning(String message) { + showToUser(String.format(WARNING_FORMAT, message)); + } + + /** + * Gets input from user. + * + * @param menuName Current menu name. + * @return User input in a string. + */ + public String getCommand(String menuName) { + System.out.print(menuName + " >>>>> "); + Scanner sc = new Scanner(System.in); + String inputLine = sc.nextLine(); + + // Silently consume all blank lines + while (inputLine.trim().isEmpty()) { + System.out.print(menuName + " >>>>> "); + inputLine = sc.nextLine(); + } + + return inputLine.replaceAll(GREEDY_WHITE_SPACE, " ").trim(); + } + + /** + * Gets user confirmation a specific command. + * + * @param menuName Current menu name. + * @param action Action description. + * @return If user confirms the command execution. + */ + public boolean checkConfirmation(String menuName, String action) { + String message = String.format("Are you sure you want to %s? This action is irrevocable.\n\t " + + "Key in \"YES\" to confirm.", action); + showToUser(message); + String input = getCommand(menuName); + return input.equals("YES"); + } + + /** + * Prints opening message to user. + * + * @param menuName Name of menu entering. + */ + public void printOpening(String menuName) { + showToUser(String.format("Starting %s...", menuName)); + } + + /** + * Prints returning message to user. + * + * @param menuName Name of menu to return to. + */ + public void printReturning(String menuName) { + showToUser(String.format("Returning to %s...", menuName)); + } +} diff --git a/src/main/java/ui/diet/dietmanager/DietManagerUi.java b/src/main/java/ui/diet/dietmanager/DietManagerUi.java new file mode 100644 index 0000000000..824f01721c --- /dev/null +++ b/src/main/java/ui/diet/dietmanager/DietManagerUi.java @@ -0,0 +1,33 @@ +package ui.diet.dietmanager; + +import ui.CommonUi; + +//@@author CFZeon +/** + * A class that is responsible for user interface of Diet Manager. + */ +public class DietManagerUi extends CommonUi { + public static String DIET_NO_SESSION_SAVED = "Sorry! It seems like you have no diet sessions saved!"; + public static String DIET_IO_WRONG_FORMAT = "It seems like we ran into some problems saving your session..."; + public static String DIET_FILE_ARRAY_OUT_OF_BOUND = "Sorry, there is no file at that index."; + public static String DIET_FILE_NOT_FOUND = "Sorry, there seems to be no file."; + public static String DIET_FILE_CORRUPTED = "Sorry, it seems like your file is corrupted..."; + public static String DIET_NEW_SUCCESS = "Exiting Diet Session!"; + public static String DIET_DELETE_SUCCESS = "You have deleted that diet session!"; + public static String DIET_EDIT_WRONG_FORMAT = "Wrong format, please enter in the format:\n\t " + + "edit [INDEX_OF_SESSION]"; + public static String DIET_DELETE_WRONG_FORMAT = "Wrong format, please enter in the format:\n\t delete [INDEX]"; + public static String DIET_CREATE_WRONG_FORMAT = "Wrong format, please enter in the format: \n\t " + + "new "; + public static String DIET_DATE_WRONG_FORMAT = "Wrong date format, please enter in the format:\n\t /d yyyy-MM-dd"; + public static String DIET_NO_SESSIONS_SAVED = "It looks like you have no sessions saved!"; + public static String DIET_SEARCH_RESULTS_MESSAGE = "Here are the search result(s)!"; + public static String DIET_SEARCH_EMPTY_TAG = "Tag is empty, " + + "all the sessions within input dates will be shown."; + public static String DIET_MENU_NAME = "Diet Menu"; + public static String CLEAR_RECORD = "clear all records"; + public static String DIET_CLEAR_MSG = "diet sessions have"; + public static String DIET_NOTHING_TO_CLEAR_MSG = "Sorry, there is no diet session to be cleared!"; + public static String DIET_FILE_CORRUPTED_MSG = "File Corrupted... Returning to Diet Menu..."; + public static String EMPTY_STRING = ""; +} diff --git a/src/main/java/ui/diet/dietsession/DietSessionUi.java b/src/main/java/ui/diet/dietsession/DietSessionUi.java new file mode 100644 index 0000000000..babc464ccf --- /dev/null +++ b/src/main/java/ui/diet/dietsession/DietSessionUi.java @@ -0,0 +1,55 @@ +package ui.diet.dietsession; + +import ui.CommonUi; + +//@@author zsk612 +/** + * A class that contains templates of message to show to user. + */ +public class DietSessionUi extends CommonUi { + + public static final String DIET_INPUT_PROMPT_EDIT = "Diet Menu > Diet Session "; + public static final String DIET_INPUT_PROMPT_NEW = "Diet Menu > New Diet Session"; + public static final String MESSAGE_ADD_WRONG_FORMAT = "Wrong format, please enter in the format:\n\t " + + "add [FOOD_NAME] /c [CALORIES]"; + public static final String MESSAGE_CLEAR_ABORTED = "You have aborted clear operation."; + public static final String MESSAGE_CLEAR_SUCCESS = "Alright, your food items have been cleared."; + public static final String MESSAGE_DELETE_WRONG_FORMAT = "Wrong format, please enter in the format:\n\t " + + "delete [INDEX]"; + public static final String MESSAGE_HIGH_CALORIES = "Your calories for this food item seems a little high, " + + "so I've set it to 200,000.\n\t "; + public static final String MESSAGE_NEGATIVE_CALORIES = "Please enter a positive number for calories!"; + public static final String MESSAGE_NO_FOOD = "Sorry, there is nothing in your food list."; + public static final String MESSAGE_NO_FOOD_NAME = "Please enter food name!"; + public static final String MESSAGE_NO_SUCH_INDEX = "Sorry, there is no food item at that index."; + public static final String MESSAGE_SEARCH_PROMPT = "Here are the search results: \n\t "; + public static final String MESSAGE_WRONG_CALORIES = "Please input a number for calories."; + + /** + * Prints help message. + * + * @param helpMessage appends all the help messages + */ + public static void printHelp(StringBuilder helpMessage) { + + helpMessage.append(helpFormatter("Add", "add [FOOD_NAME] /c [CALORIES]", + "Add a new food item")); + helpMessage.append(helpFormatter("List", "list", + "Show all food items")); + helpMessage.append(helpFormatter("Delete", "delete [INDEX]", + "Delete the food item at the input index")); + helpMessage.append(helpFormatter("Search", "search [FOOD_NAME]", + "Search the diet session for food with the name specified")); + helpMessage.append(helpFormatter("Clear", "clear", + "Clear all food items")); + helpMessage.append(helpFormatter("End", "end", + "Go back to the Diet Menu.")); + } + + /** + * Prints opening messages for diet sessions. + */ + public void printOpening() { + showToUser("Starting Diet Session!"); + } +} diff --git a/src/main/java/ui/profile/ProfileUi.java b/src/main/java/ui/profile/ProfileUi.java new file mode 100644 index 0000000000..5a4302a618 --- /dev/null +++ b/src/main/java/ui/profile/ProfileUi.java @@ -0,0 +1,68 @@ +package ui.profile; + +import ui.CommonUi; + +import static profile.Constants.CALORIES_LOWER_BOUND; +import static profile.Constants.CALORIES_UPPER_BOUND; +import static profile.Constants.HEIGHT_LOWER_BOUND; +import static profile.Constants.HEIGHT_UPPER_BOUND; +import static profile.Constants.WEIGHT_LOWER_BOUND; +import static profile.Constants.WEIGHT_UPPER_BOUND; + +//@@author tienkhoa16 + +/** + * A class that contains templates of message to show to user. + */ +public class ProfileUi extends CommonUi { + + public static final String MESSAGE_ADJUST_CALORIES = "Your expected daily calorie intake seems a little " + + "high, so I've set it to 200,000"; + public static final String MESSAGE_CREATE_PROFILE_ACK = "Got it. Here's a confirmation of your profile:" + + LS + "%s"; + public static final String MESSAGE_DELETE_PROFILE = String.format(MESSAGE_CLEAR_FORMAT, "profile has"); + public static final String MESSAGE_EDIT_PROFILE_ACK = "Yay! Your profile is edited successfully. " + + "Here's your new profile:" + LS + "%s"; + public static final String MESSAGE_END = "Returning to Main Menu..."; + public static final String MESSAGE_ENOUGH_CALORIES = "you've achieved your target calories intake for today!"; + public static final String MESSAGE_HELP_FOR_MORE_INFO = "For more information on command syntax, please type " + + "\"help\"."; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Wrong format, please enter in the format:" + LS + + "%s" + LS + MESSAGE_HELP_FOR_MORE_INFO; + public static final String MESSAGE_MORE_CALORIES = "take %.1f more calories to achieve your target for today!"; + public static final String MESSAGE_SET_EXPECTED_WEIGHT = "TIP: Edit your expected weight to %.1f kg to have " + + "Normal Weight BMI classification." + LS + " Just type \"edit /e %.1f\"!"; + public static final String MESSAGE_PROFILE_EXIST = "There's currently a profile in the database. " + LS + + "Please delete it using \"delete\" command before adding a new one."; + public static final String MESSAGE_PROFILE_NOT_EXIST = "There's no profile to %s. " + + "Please add a new one using \"add\" command :D"; + public static final String MESSAGE_VIEW_PROFILE = "Here's your profile:" + LS + "%s" + LS + "By the way, %s" + + LS + "%s"; + public static final String MESSAGE_WELCOME_EXISTING_USER = "Welcome back to The Schwarzenegger, %s!" + LS + + "How can I help you today?"; + public static final String MESSAGE_WELCOME_NEW_USER = "Welcome new user to The Schwarzenegger! :D" + LS + + "Please add your profile under Profile Menu." + LS + MESSAGE_HELP_FOR_MORE_INFO; + public static final String MESSAGE_WELCOME_WITH_INVALID_SAVE_FORMAT = "Welcome back to The Schwarzenegger!" + LS + + "It seems like your profile data is corrupted!" + LS + + "Please add your profile again under Profile Menu." + LS + MESSAGE_HELP_FOR_MORE_INFO; + public static final String SAD_FACE = ":( OOPS!!! "; + public static final String MESSAGE_INVALID_WEIGHT = String.format( + SAD_FACE + "Please input current weight/ expected weight as a number from %.1f to %.1f.", + WEIGHT_LOWER_BOUND, WEIGHT_UPPER_BOUND); + public static final String MESSAGE_EDIT_NOTHING = SAD_FACE + + "The information you entered is the same as your current profile!"; + public static final String MESSAGE_DELETE_NOTHING = SAD_FACE + "You haven't added any profile yet!"; + public static final String MESSAGE_SAVING_ERROR = SAD_FACE + "An error has occurred while saving data." + + LS + "%s"; + public static final String MESSAGE_LOADING_ERROR = SAD_FACE + "An error has occurred while loading data." + + LS + "%s"; + public static final String MESSAGE_INVALID_HEIGHT = String.format( + SAD_FACE + "Please input height as an integer from %d to %d.", HEIGHT_LOWER_BOUND, HEIGHT_UPPER_BOUND); + public static final String MESSAGE_INVALID_CALORIES = SAD_FACE + + String.format("Please input expected daily calorie intake as a number from %.1f to %.1f", + CALORIES_LOWER_BOUND, CALORIES_UPPER_BOUND); + public static final String MESSAGE_INVALID_NAME = SAD_FACE + "Name cannot be empty."; + public static final String MESSAGE_INVALID_SAVE_FORMAT = SAD_FACE + "Save format in %s is invalid."; + public static final String MESSAGE_INVALID_COMMAND_WORD = SAD_FACE + "Sorry, but I don't know what that means." + + LS + "Please enter \"help\" for a list of available commands."; +} diff --git a/src/main/java/ui/workout/workoutmanager/WorkoutManagerUi.java b/src/main/java/ui/workout/workoutmanager/WorkoutManagerUi.java new file mode 100644 index 0000000000..611f87ddc3 --- /dev/null +++ b/src/main/java/ui/workout/workoutmanager/WorkoutManagerUi.java @@ -0,0 +1,23 @@ +package ui.workout.workoutmanager; + +import ui.CommonUi; + +import static ui.profile.ProfileUi.MESSAGE_HELP_FOR_MORE_INFO; + +//@@author wgzesg +/** + * A class that is responsible for user interface in Workout Manager. + */ +public class WorkoutManagerUi extends CommonUi { + + public static String CLEAR_ABORTED = "You have aborted the action!"; + public static String DELETE_SUCCESS = "You have deleted that record!"; + public static String EDIT_SUCCESS = "Congratulations! You have successfully edited the workout!"; + public static String NEW_SUCCESS = "Congratulations! You have finished today's workout!"; + public static String START_NEW_SESSION = "Starting Workout Session!"; + + public static String MESSAGE_NOT_A_NUMBER = "Index must be a number. Your input is not a number!"; + public static String MESSAGE_INSUFFICIENT_ARGUMENT = "Insufficient arguments are given! " + + "Please follow this format:" + LS + "%s" + LS + MESSAGE_HELP_FOR_MORE_INFO; + public static String MESSAGE_OUT_OF_ARRAY = "The record specified is not found. Please check the index!"; +} diff --git a/src/main/java/ui/workout/workoutsession/WorkoutSessionUi.java b/src/main/java/ui/workout/workoutsession/WorkoutSessionUi.java new file mode 100644 index 0000000000..7ed7c51756 --- /dev/null +++ b/src/main/java/ui/workout/workoutsession/WorkoutSessionUi.java @@ -0,0 +1,81 @@ +package ui.workout.workoutsession; + +import models.Exercise; +import ui.CommonUi; + + +//@@author yujinyang1998 +/** + * A class that contains templates of message to show to user. + */ +public class WorkoutSessionUi extends CommonUi { + public static final String PRINT_ERROR = "File not found, re-creating file."; + public static final String ADD_FORMAT_ERROR = "Wrong format, please enter in the format:\n\t " + + "add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]"; + public static final String ADD_FORMAT_NEGATIVE_ERROR = "Wrong format, please enter in the format:\n\t " + + "add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]\n\t " + + "Please make sure [NUMBER_OF_REPETITIONS] and [WEIGHT] are non negative numbers."; + public static final String EMPTY_LIST_ERROR = "List is empty. Please enter something."; + public static final String DELETE_FORMAT_ERROR = "Wrong format, please enter in the format:\n\t delete [INDEX]"; + public static final String DELETE_INDEX_ERROR = "Index does not exist. Please refer to the list."; + public static final String EMPTY_INPUT_ERROR = "Please enter something."; + public static final String SEARCH_INPUT_ERROR = "Wrong format, please enter in the format:\n\t search " + + "[NAME_OF_MOVE]"; + public static final String SEARCH_RESULTS_EMPTY = "No matching result has been found."; + + /** + * Formats a message when the file is corrupted. + * + * @param filePath Path to data file. + * @return Message to show to user. + */ + public static String saveCorruptedError(String filePath) { + return (":( Save format in " + + filePath + " is invalid.\n\t " + + "File is cleared."); + } + + /** + * Formats a message when the file is corrupted. + * + * @return Message to show to user. + */ + public static String printHelp() { + StringBuilder helpMessage = new StringBuilder(); + helpMessage.append(helpFormatter("Add", "add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]", + "Add a new move")); + helpMessage.append(helpFormatter("List", "list", + "Show all moves in this current session")); + helpMessage.append(helpFormatter("Delete", "delete [INDEX]", + "Delete a move according to the index in the list")); + helpMessage.append(helpFormatter("Search", "search [NAME_OF_MOVE]", + "Show a list of moves that match the entered keyword")); + helpMessage.append(helpFormatter("End", "end", + "Go back to the Workout Menu")); + return (helpMessage.toString().trim()); + } + + /** + * Formats a message when an Exercise is added. + * + * @param exercise Exercise to be added. + * @return Message to show to user. + */ + public static String addExerciseSuccess(Exercise exercise) { + String toPrint = String.format("Yay! You have added %s to your list.\n\t [Repetitions: %s || Weight: %s]", + exercise.getDescription(), exercise.getRepetitions(), exercise.getWeight()); + return (toPrint); + } + + /** + * Formats a message when an Exercise is deleted. + * + * @param exercise Exercise to be deleted. + * @return Message to show to user. + */ + public static String deleteExerciseSuccess(Exercise exercise) { + String toPrint = String.format("You have deleted %s from your list!\n\t [Repetitions: %s || Weight: %s]", + exercise.getDescription(), exercise.getRepetitions(), exercise.getWeight()); + return (toPrint); + } +} diff --git a/src/main/java/workout/workoutmanager/WorkoutManager.java b/src/main/java/workout/workoutmanager/WorkoutManager.java new file mode 100644 index 0000000000..f3dff59524 --- /dev/null +++ b/src/main/java/workout/workoutmanager/WorkoutManager.java @@ -0,0 +1,68 @@ +package workout.workoutmanager; + +import logic.commands.Command; +import logic.commands.CommandLib; +import logic.commands.CommandResult; +import exceptions.EndException; +import exceptions.SchwarzeneggerException; +import logger.SchwarzeneggerLogger; +import logic.parser.WorkoutManagerParser; +import models.PastRecordList; +import ui.workout.workoutmanager.WorkoutManagerUi; + +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author wgzesg +/** + * A class that is responsible for interacting with user in Workout Manager. + */ +public class WorkoutManager { + + private static final Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + private final CommandLib cl; + private final WorkoutManagerUi ui; + + /** + * Constructs Workout Manager. + */ + public WorkoutManager() { + cl = new CommandLib(); + cl.initWorkoutManagerCl(); + logger.log(Level.INFO, "Initialised workout manager command library"); + ui = new WorkoutManagerUi(); + } + + /** + * Starts a workout manager. Goes into a REPL until 'end' command is given. + */ + public void start() { + logger.log(Level.INFO, "Entered workout manager"); + ui.printOpening("Workout Menu"); + + while (true) { + + String command = ui.getCommand("Workout Menu"); + logger.log(Level.INFO, "Received input" + command); + + String[] commParts = WorkoutManagerParser.getInstance().parseCommandKw(command); + try { + processCommand(commParts); + } catch (EndException e) { + logger.log(Level.INFO, "exiting workout manager"); + break; + } catch (SchwarzeneggerException e) { + logger.log(Level.WARNING, "processing SchwarzeneggerException", e); + ui.showToUser(e.getMessage()); + } + } + + ui.printReturning("Main Menu"); + } + + private void processCommand(String[] commands) throws SchwarzeneggerException { + Command command = cl.getCommand(commands[0]); + CommandResult result = command.execute((commands.length > 1) ? commands[1].trim() : ""); + ui.showToUser(result.getFeedbackMessage()); + } +} diff --git a/src/main/java/workout/workoutsession/WorkoutSession.java b/src/main/java/workout/workoutsession/WorkoutSession.java new file mode 100644 index 0000000000..2aa2c174eb --- /dev/null +++ b/src/main/java/workout/workoutsession/WorkoutSession.java @@ -0,0 +1,98 @@ +package workout.workoutsession; + +import logic.commands.Command; +import logic.commands.CommandLib; +import exceptions.ExceptionHandler; +import exceptions.InvalidCommandWordException; +import logger.SchwarzeneggerLogger; +import logic.commands.CommandResult; +import logic.parser.WorkoutSessionParser; +import storage.workout.WorkoutSessionStorage; +import ui.CommonUi; +import ui.workout.workoutsession.WorkoutSessionUi; +import models.ExerciseList; + +import java.io.FileNotFoundException; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author yujinyang1998 +/** + * A class that is responsible for interacting with user in a Workout Session. + */ +public class WorkoutSession { + private static Logger logger = SchwarzeneggerLogger.getInstanceLogger(); + private String filePath = null; + private boolean[] endWorkoutSession; + public ExerciseList exerciseList; + private boolean isNew; + private int index; + + private transient CommandLib cl; + private final WorkoutSessionStorage workoutSessionStorage; + private CommonUi ui; + + /** + * Constructs WorkoutSession object. + * + * @param filePath Path to data file. + * @param isNew New or existing file. + * @param index Index of this WorkoutSession in the list of WorkoutSession. + */ + public WorkoutSession(String filePath, boolean isNew, int index) { + this.filePath = filePath; + this.exerciseList = new ExerciseList(); + this.workoutSessionStorage = new WorkoutSessionStorage(); + this.endWorkoutSession = new boolean[1]; + this.ui = new CommonUi(); + this.isNew = isNew; + this.index = index; + } + + private void setEndWorkoutSessionF() { + + this.endWorkoutSession[0] = false; + } + + /** + * Starts workout session. + */ + public void workoutSessionStart() { + + setEndWorkoutSessionF(); + logger.log(Level.INFO, "starting workout session"); + this.cl = new CommandLib(); + cl.initWorkoutSessionCl(); + + try { + workoutSessionStorage.readFileContents(filePath, exerciseList); + } catch (FileNotFoundException e) { + ui.showToUser(WorkoutSessionUi.PRINT_ERROR); + } + + while (!endWorkoutSession[0]) { + try { + if (isNew) { + workoutSessionProcessCommand(ui.getCommand("Workout Menu > New Workout Session")); + } else { + workoutSessionProcessCommand(ui.getCommand("Workout Menu > Workout Session " + index)); + } + } catch (NullPointerException e) { + ui.showToUser(WorkoutSessionUi.EMPTY_INPUT_ERROR); + } catch (InvalidCommandWordException e) { + ui.showToUser(ExceptionHandler.handleCheckedExceptions(e)); + } + } + } + + private void workoutSessionProcessCommand(String input) throws NullPointerException, InvalidCommandWordException { + String[] commParts = WorkoutSessionParser.workoutSessionParser(input.trim()); + Command command = cl.getCommand(commParts[0]); + CommandResult commandResult = command.execute(commParts, exerciseList, filePath, workoutSessionStorage, + endWorkoutSession); + if (commandResult.getFeedbackMessage().compareTo("") != 0) { + ui.showToUser(commandResult.getFeedbackMessage()); + } + } + +} diff --git a/src/test/java/diet/dietmanager/DietManagerTest.java b/src/test/java/diet/dietmanager/DietManagerTest.java new file mode 100644 index 0000000000..95b11c4134 --- /dev/null +++ b/src/test/java/diet/dietmanager/DietManagerTest.java @@ -0,0 +1,49 @@ +package diet.dietmanager; + +import exceptions.InvalidCommandWordException; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +//@@author CFZeon +public class DietManagerTest { + private static final String TEST_SAVES_FOLDER_DIET = "src/test/java/diet/dietmanager/saves/"; + private static final String TEST_DATE = "2020-04-04"; + private static final Double TEST_CALORIES = 444.0; + + @Test + void getDateTotalCalories_validInput_returnDouble() { + DietManager dietManager = new DietManager(); + LocalDate date = LocalDate.parse(TEST_DATE); + Double expected = TEST_CALORIES; + Double totalCalories = dietManager.getDateTotalCalories(TEST_SAVES_FOLDER_DIET, date); + assertEquals(expected, totalCalories); + } + + @Test + void getDateTotalCalories_wrongFolderInput_throwsAssertionError() { + DietManager dietManager = new DietManager(); + LocalDate date = LocalDate.parse(TEST_DATE); + assertThrows(AssertionError.class, () -> + dietManager.getDateTotalCalories(TEST_SAVES_FOLDER_DIET + "wrong", date)); + } + + @Test + void getDateTotalCalories_wrongDateInput_returnsZero() { + DietManager dietManager = new DietManager(); + Double expected = 0.0; + Double totalCalories = dietManager.getDateTotalCalories(TEST_SAVES_FOLDER_DIET, java.time.LocalDate.now()); + assertEquals(expected, totalCalories); + } + + @Test + void processCommand_invalidInput_throwsInvalidCommandWordException() { + DietManager dietManager = new DietManager(); + assertThrows(InvalidCommandWordException.class, () -> + dietManager.processCommand("asdf")); + } +} diff --git a/src/test/java/diet/dietmanager/DietSessionStorageTest.java b/src/test/java/diet/dietmanager/DietSessionStorageTest.java new file mode 100644 index 0000000000..4de7545bff --- /dev/null +++ b/src/test/java/diet/dietmanager/DietSessionStorageTest.java @@ -0,0 +1,58 @@ +package diet.dietmanager; + +import diet.dietsession.DietSession; +import exceptions.InvalidDateFormatException; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +//@@author CFZeon +public class DietSessionStorageTest { + + private static final String TEST_SAVES_FOLDER_DIET = "src/test/java/diet/dietmanager/saves/"; + private static final String TEST_SAVES_FOLDER_CORRUPTED_DIET = "src/test/java/diet/dietmanager/corruptedsaves/"; + private static final String TEST_SAVE_NAME = "2020-05-04 breakfast"; + + /** + * Tests readDietSession method of class DietStorage when data from file is read. + */ + @Test + void testReadDietSession_exampleDietData_returnsDietSessionInstance() { + DietStorage storage = new DietStorage(); + DietSession loadedInstance = null; + loadedInstance = storage.readDietSession(TEST_SAVES_FOLDER_DIET, "2020-11-05 breakfast.json"); + assertNotNull(loadedInstance); + } + + /** + * Tests readDietSession method of class DietStorage when corrupted data from file is read. + */ + @Test + void testReadDietSession_corruptedDietData_returnsNull() { + DietStorage storage = new DietStorage(); + DietSession loadedInstance = null; + loadedInstance = storage.readDietSession(TEST_SAVES_FOLDER_CORRUPTED_DIET, "2020-11-09 unspecified.json"); + assertNull(loadedInstance); + } + + /** + * Tests the saveDietSession by saving and reading from folder. + * + * @throws IOException if file is not read properly + * @throws InvalidDateFormatException if date format is wrong during DietSession instantiation + */ + @Test + void saveDietSession_newDietData_returnsDietSessionInstance() throws IOException, InvalidDateFormatException { + DietStorage storage = new DietStorage(); + storage.init(TEST_SAVES_FOLDER_DIET, TEST_SAVE_NAME); + DietSession savedInstance = new DietSession("breakfast", "2020-05-04", true, 1); + savedInstance.saveToFile(TEST_SAVES_FOLDER_DIET, storage, savedInstance); + DietSession loadedInstance = null; + loadedInstance = storage.readDietSession(TEST_SAVES_FOLDER_DIET, TEST_SAVE_NAME + ".json"); + assertNotNull(loadedInstance); + } +} diff --git a/src/test/java/diet/dietmanager/corruptedsaves/2020-11-09 unspecified.json b/src/test/java/diet/dietmanager/corruptedsaves/2020-11-09 unspecified.json new file mode 100644 index 0000000000..5e5cffd28f --- /dev/null +++ b/src/test/java/diet/dietmanager/corruptedsaves/2020-11-09 unspecified.json @@ -0,0 +1,16 @@ +{ + "foodList": [], + "dateInput": "09-11-2020", + "typeInput": "unspecified", + "date": { + "year": 2020 + "month": 11 + "day": 9 + }, + "isNew": true, + "index": -1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} \ No newline at end of file diff --git a/src/test/java/diet/dietmanager/saves/2020-04-04 breakfast.json b/src/test/java/diet/dietmanager/saves/2020-04-04 breakfast.json new file mode 100644 index 0000000000..8594aeaa9c --- /dev/null +++ b/src/test/java/diet/dietmanager/saves/2020-04-04 breakfast.json @@ -0,0 +1,21 @@ +{ + "foodList": [ + { + "name": "asdf", + "calories": 123.0 + } + ], + "dateInput": "04-04-2020", + "typeInput": "breakfast", + "date": { + "year": 2020, + "month": 4, + "day": 4 + }, + "isNew": true, + "index": -1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} \ No newline at end of file diff --git a/src/test/java/diet/dietmanager/saves/2020-04-04 lunch.json b/src/test/java/diet/dietmanager/saves/2020-04-04 lunch.json new file mode 100644 index 0000000000..24cb7a6c31 --- /dev/null +++ b/src/test/java/diet/dietmanager/saves/2020-04-04 lunch.json @@ -0,0 +1,21 @@ +{ + "foodList": [ + { + "name": "fdas", + "calories": 321.0 + } + ], + "dateInput": "04-04-2020", + "typeInput": "lunch", + "date": { + "year": 2020, + "month": 4, + "day": 4 + }, + "isNew": true, + "index": -1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} \ No newline at end of file diff --git a/src/test/java/diet/dietmanager/saves/2020-05-04 breakfast.json b/src/test/java/diet/dietmanager/saves/2020-05-04 breakfast.json new file mode 100644 index 0000000000..fc58ab73a2 --- /dev/null +++ b/src/test/java/diet/dietmanager/saves/2020-05-04 breakfast.json @@ -0,0 +1,16 @@ +{ + "foodList": [], + "dateInput": "2020-05-04", + "typeInput": "breakfast", + "date": { + "year": 2020, + "month": 5, + "day": 4 + }, + "isNew": true, + "index": 1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} \ No newline at end of file diff --git a/src/test/java/diet/dietmanager/saves/2020-05-05 breakfast.json b/src/test/java/diet/dietmanager/saves/2020-05-05 breakfast.json new file mode 100644 index 0000000000..a1dcdc0cb6 --- /dev/null +++ b/src/test/java/diet/dietmanager/saves/2020-05-05 breakfast.json @@ -0,0 +1,15 @@ +{ + "foodList": [], + "dateInput": "2020-05-04", + "typeInput": "breakfast", + "date": { + "year": 2020, + "month": 5, + "day": 4 + }, + "index": 1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} \ No newline at end of file diff --git a/src/test/java/diet/dietmanager/saves/2020-11-05 breakfast.json b/src/test/java/diet/dietmanager/saves/2020-11-05 breakfast.json new file mode 100644 index 0000000000..fd9940d30d --- /dev/null +++ b/src/test/java/diet/dietmanager/saves/2020-11-05 breakfast.json @@ -0,0 +1,16 @@ +{ + "foodList": [], + "dateInput": "05-11-2020", + "typeInput": "breakfast", + "date": { + "year": 2020, + "month": 11, + "day": 5 + }, + "isNew": true, + "index": -1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} \ No newline at end of file diff --git a/src/test/java/logic/commands/CommandLibTest.java b/src/test/java/logic/commands/CommandLibTest.java new file mode 100644 index 0000000000..bd04ce78d2 --- /dev/null +++ b/src/test/java/logic/commands/CommandLibTest.java @@ -0,0 +1,335 @@ +package logic.commands; + +import logic.commands.diet.dietmanager.DietSessionClear; +import logic.commands.diet.dietmanager.DietSessionCreate; +import logic.commands.diet.dietmanager.DietSessionDelete; +import logic.commands.diet.dietmanager.DietSessionEdit; +import logic.commands.diet.dietmanager.DietSessionHelp; +import logic.commands.diet.dietmanager.DietSessionList; +import logic.commands.diet.dietmanager.DietSessionWrong; +import logic.commands.diet.dietsession.FoodItemAdd; +import logic.commands.diet.dietsession.FoodItemClear; +import logic.commands.diet.dietsession.FoodItemDelete; +import logic.commands.diet.dietsession.FoodItemHelp; +import logic.commands.diet.dietsession.FoodItemList; +import logic.commands.diet.dietsession.FoodItemWrong; +import logic.commands.main.MainHelp; +import logic.commands.main.MainWrong; +import logic.commands.main.ToDiet; +import logic.commands.main.ToProfile; +import logic.commands.main.ToWorkout; +import logic.commands.profile.ProfileAdd; +import logic.commands.profile.ProfileDelete; +import logic.commands.profile.ProfileEdit; +import logic.commands.profile.ProfileEnd; +import logic.commands.profile.ProfileHelp; +import logic.commands.profile.ProfileView; +import logic.commands.profile.ProfileWrong; +import logic.commands.workout.workoutmanager.ByeWS; +import logic.commands.workout.workoutmanager.DeleteWS; +import logic.commands.workout.workoutmanager.ListWS; +import logic.commands.workout.workoutmanager.NewWS; +import logic.commands.workout.workoutmanager.WrongWS; +import logic.commands.workout.workoutsession.WorkoutSessionAdd; +import logic.commands.workout.workoutsession.WorkoutSessionDelete; +import logic.commands.workout.workoutsession.WorkoutSessionEnd; +import logic.commands.workout.workoutsession.WorkoutSessionHelp; +import logic.commands.workout.workoutsession.WorkoutSessionList; +import logic.commands.workout.workoutsession.WorkoutSessionSearch; +import logic.commands.workout.workoutsession.WorkoutSessionWrong; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static ui.CommonUi.EMPTY_STRING; + +class CommandLibTest { + + @Test + void getCommandTest_dietManagerUnrecognisedCommand_returnDietSessionWrong() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("unregconised") instanceof DietSessionWrong); + } + + @Test + void getCommandTest_listDietSession_returnListCommand() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("list") instanceof DietSessionList); + } + + @Test + void getCommandTest_deleteDietSession_returnDeleteCommand() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("delete") instanceof DietSessionDelete); + } + + @Test + void getCommandTest_newDietSession_returnNewCommand() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("new") instanceof DietSessionCreate); + } + + @Test + void getCommandTest_clearDietSession_returnClearCommand() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("clear") instanceof DietSessionClear); + } + + @Test + void getCommandTest_editDietSession_returnEditCommand() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("edit") instanceof DietSessionEdit); + } + + @Test + void getCommandTest_helpDietSession_returnHelpCommand() { + CommandLib cl = new CommandLib(); + cl.initDietManagerCl(); + assertTrue(cl.getCommand("help") instanceof DietSessionHelp); + } + + @Test + void getCommandTest_DietManagerUnrecognisedCommand_returnFoodItemWrong() { + CommandLib cl = new CommandLib(); + cl.initDietSessionCl(); + assertTrue(cl.getCommand("unregconised") instanceof FoodItemWrong); + } + + @Test + void getCommandTest_listFoodItem_returnListCommand() { + CommandLib cl = new CommandLib(); + cl.initDietSessionCl(); + assertTrue(cl.getCommand("list") instanceof FoodItemList); + } + + @Test + void getCommandTest_deleteFoodItem_returnDeleteCommand() { + CommandLib cl = new CommandLib(); + cl.initDietSessionCl(); + assertTrue(cl.getCommand("delete") instanceof FoodItemDelete); + } + + @Test + void getCommandTest_newFoodItem_returnNewCommand() { + CommandLib cl = new CommandLib(); + cl.initDietSessionCl(); + assertTrue(cl.getCommand("add") instanceof FoodItemAdd); + } + + @Test + void getCommandTest_clearFoodItem_returnClearCommand() { + CommandLib cl = new CommandLib(); + cl.initDietSessionCl(); + assertTrue(cl.getCommand("clear") instanceof FoodItemClear); + } + + @Test + void getCommandTest_helpFoodItem_returnHelpCommand() { + CommandLib cl = new CommandLib(); + cl.initDietSessionCl(); + assertTrue(cl.getCommand("help") instanceof FoodItemHelp); + } + + @Test + void getCommandTest_WorkoutManagerUnrecognisedCommand_returnNull() { + CommandLib cl = new CommandLib(); + cl.initWorkoutManagerCl(); + assertTrue(cl.getCommand("unregconised") instanceof WrongWS); + } + + @Test + void getCommandTest_listWS_returnListCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutManagerCl(); + assertTrue(cl.getCommand("list") instanceof ListWS); + } + + @Test + void getCommandTest_deleteWS_returnDeleteCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutManagerCl(); + assertTrue(cl.getCommand("delete") instanceof DeleteWS); + } + + @Test + void getCommandTest_newWS_returnNewCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutManagerCl(); + assertTrue(cl.getCommand("new") instanceof NewWS); + } + + @Test + void getCommandTest_byeWS_returnByeCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutManagerCl(); + assertTrue(cl.getCommand("end") instanceof ByeWS); + } + + //@@author yujinyang1998 + @Test + void getCommandTest_WorkoutSessionUnrecognisedCommand_returnNull() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("unregconised") instanceof WorkoutSessionWrong); + } + + @Test + void getCommandTest_workoutSessionList_returnListCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("list") instanceof WorkoutSessionList); + } + + @Test + void getCommandTest_workoutSessionDelete_returnDeleteCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("delete") instanceof WorkoutSessionDelete); + } + + @Test + void getCommandTest_workoutSessionAdd_returnAddCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("add") instanceof WorkoutSessionAdd); + } + + @Test + void getCommandTest_workoutSessionEnd_returnEndCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("end") instanceof WorkoutSessionEnd); + } + + @Test + void getCommandTest_workoutSessionSearch_returnSearchCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("search") instanceof WorkoutSessionSearch); + } + + @Test + void getCommandTest_workoutSessionHelp_returnHelpCommand() { + CommandLib cl = new CommandLib(); + cl.initWorkoutSessionCl(); + assertTrue(cl.getCommand("help") instanceof WorkoutSessionHelp); + } + + //@@author tienkhoa16 + @Test + void getCommandTest_profileSessionAddCommand_returnProfileAdd() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("add") instanceof ProfileAdd); + } + + @Test + void getCommandTest_profileSessionDeleteCommand_returnProfileDelete() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("delete") instanceof ProfileDelete); + } + + @Test + void getCommandTest_profileSessionEditCommand_returnProfileEdit() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("edit") instanceof ProfileEdit); + } + + @Test + void getCommandTest_profileSessionEndCommand_returnProfileEnd() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("end") instanceof ProfileEnd); + } + + @Test + void getCommandTest_profileSessionHelpCommand_returnProfileHelp() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("help") instanceof ProfileHelp); + } + + @Test + void getCommandTest_profileSessionViewCommand_returnProfileView() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("view") instanceof ProfileView); + } + + @Test + void getCommandTest_profileSessionUnrecognisedCommand_returnProfileWrong() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand("unregconised") instanceof ProfileWrong); + } + + @Test + void getCommandTest_profileInputNull_returnWrong() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand(null) instanceof ProfileWrong); + } + + @Test + void getCommandTest_profileInputEmpty_returnWrong() { + CommandLib cl = new CommandLib(); + cl.initProfileSessionCl(); + assertTrue(cl.getCommand(EMPTY_STRING) instanceof ProfileWrong); + } + + @Test + void getCommandTest_mainHelpCommand_returnHelp() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand("help") instanceof MainHelp); + } + + @Test + void getCommandTest_mainDietCommand_returnToDiet() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand("diet") instanceof ToDiet); + } + + @Test + void getCommandTest_mainProfileCommand_returnToProfile() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand("profile") instanceof ToProfile); + } + + @Test + void getCommandTest_mainWorkoutCommand_returnToWorkout() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand("workout") instanceof ToWorkout); + } + + @Test + void getCommandTest_mainUnrecognisedCommand_returnWrong() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand("unregconised") instanceof MainWrong); + } + + @Test + void getCommandTest_mainInputNull_returnWrong() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand(null) instanceof MainWrong); + } + + @Test + void getCommandTest_mainInputEmpty_returnWrong() { + CommandLib cl = new CommandLib(); + cl.initMainMenuCl(); + assertTrue(cl.getCommand(EMPTY_STRING) instanceof MainWrong); + } +} diff --git a/src/test/java/logic/commands/diet/dietmanager/DietSessionClearTest.java b/src/test/java/logic/commands/diet/dietmanager/DietSessionClearTest.java new file mode 100644 index 0000000000..f2e7784629 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietmanager/DietSessionClearTest.java @@ -0,0 +1,40 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.CommandResult; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; + +import java.io.ByteArrayInputStream; +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.workout.workoutmanager.WorkoutManagerUi.CLEAR_ABORTED; + +//@@author CFZeon +public class DietSessionClearTest { + private static final String EMPTY_STRING = ""; + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsNoSuchElementException() { + assertThrows(NoSuchElementException.class, () -> { + new DietSessionClear().execute(EMPTY_STRING, (DietStorage) null); + }); + } + + @Test + void testExecute_inputEmptyArguments_throwsNoSuchElementException() { + DietStorage dietStorage = new DietStorage(); + assertThrows(NoSuchElementException.class, () -> { + new DietSessionClear().execute(EMPTY_STRING, dietStorage); + }); + } + + @Test + void testExecute_inputNotYes_success() { + DietStorage dietStorage = new DietStorage(); + System.setIn(new ByteArrayInputStream("not Yes".getBytes())); + CommandResult commandResult = new DietSessionClear().execute(EMPTY_STRING, dietStorage); + assertEquals(CLEAR_ABORTED, commandResult.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/diet/dietmanager/DietSessionCreateTest.java b/src/test/java/logic/commands/diet/dietmanager/DietSessionCreateTest.java new file mode 100644 index 0000000000..fa52479319 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietmanager/DietSessionCreateTest.java @@ -0,0 +1,70 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.CommandResult; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static ui.diet.dietmanager.DietManagerUi.DIET_CREATE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_DATE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_NEW_SUCCESS; + +//@@author CFZeon +public class DietSessionCreateTest { + private static final String EMPTY_STRING = ""; + + @Test + void testExecute_inputEmptyArguments_success() { + DietStorage storage = new DietStorage(); + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionCreate().execute(EMPTY_STRING, storage); + assertEquals(DIET_NEW_SUCCESS, result.getFeedbackMessage()); + } + + @Test + void testExecute_inputOnlyDate_success() { + String dateInput = "/d 2020-05-04"; + DietStorage storage = new DietStorage(); + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionCreate().execute(dateInput, storage); + assertEquals(DIET_NEW_SUCCESS, result.getFeedbackMessage()); + } + + @Test + void testExecute_inputTag_success() { + String tagInput = "/t breakfast"; + DietStorage storage = new DietStorage(); + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionCreate().execute(tagInput, storage); + assertEquals(DIET_NEW_SUCCESS, result.getFeedbackMessage()); + } + + @Test + void testExecute_validInput_success() { + String tagInput = "/d 2020-05-04 /t breakfast"; + DietStorage storage = new DietStorage(); + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionCreate().execute(tagInput, storage); + assertEquals(DIET_NEW_SUCCESS, result.getFeedbackMessage()); + } + + @Test + void testExecute_invalidDate_returnsResultDateWrongFormat() { + DietStorage storage = new DietStorage(); + String input = "/d 2020-1234"; + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionCreate().execute(input, storage); + assertEquals(DIET_DATE_WRONG_FORMAT, result.getFeedbackMessage()); + } + + @Test + void testExecute_invalidInput_placeholder() { + DietStorage storage = new DietStorage(); + String input = "//d"; + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionCreate().execute(input, storage); + assertEquals(DIET_CREATE_WRONG_FORMAT, result.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/diet/dietmanager/DietSessionDeleteTest.java b/src/test/java/logic/commands/diet/dietmanager/DietSessionDeleteTest.java new file mode 100644 index 0000000000..16d68bcbd2 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietmanager/DietSessionDeleteTest.java @@ -0,0 +1,53 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.CommandResult; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static ui.diet.dietmanager.DietManagerUi.DIET_DELETE_SUCCESS; +import static ui.diet.dietmanager.DietManagerUi.DIET_DELETE_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_ARRAY_OUT_OF_BOUND; + +//@@author CFZeon +public class DietSessionDeleteTest { + private static String EMPTY_STRING = ""; + + @Test + void testExecute_validInput_success() { + DietStorage storage = new DietStorage(); + // to ensure there is something to delete + System.setIn(new ByteArrayInputStream("end".getBytes())); + new DietSessionCreate().execute(EMPTY_STRING, storage); + System.setIn(new ByteArrayInputStream("end".getBytes())); + String deleteInput = "1"; + CommandResult result = new DietSessionDelete().execute(deleteInput, storage); + assertEquals(DIET_DELETE_SUCCESS, result.getFeedbackMessage()); + } + + @Test + void testExecute_outOfBoundsInput_resultOutOfBounds() { + DietStorage storage = new DietStorage(); + // to ensure there is something to delete + System.setIn(new ByteArrayInputStream("end".getBytes())); + new DietSessionCreate().execute(EMPTY_STRING, storage); + System.setIn(new ByteArrayInputStream("end".getBytes())); + String deleteInput = "0"; + CommandResult result = new DietSessionDelete().execute(deleteInput, storage); + assertEquals(DIET_FILE_ARRAY_OUT_OF_BOUND, result.getFeedbackMessage()); + } + + @Test + void testExecute_wrongInput_resultDeleteWrongFormat() { + DietStorage storage = new DietStorage(); + // to ensure there is something to delete + System.setIn(new ByteArrayInputStream("end".getBytes())); + new DietSessionCreate().execute(EMPTY_STRING, storage); + System.setIn(new ByteArrayInputStream("end".getBytes())); + String deleteInput = "a"; + CommandResult result = new DietSessionDelete().execute(deleteInput, storage); + assertEquals(DIET_DELETE_WRONG_FORMAT, result.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/diet/dietmanager/DietSessionEditTest.java b/src/test/java/logic/commands/diet/dietmanager/DietSessionEditTest.java new file mode 100644 index 0000000000..ea809412a8 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietmanager/DietSessionEditTest.java @@ -0,0 +1,52 @@ +package logic.commands.diet.dietmanager; + +import logic.commands.CommandResult; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static ui.diet.dietmanager.DietManagerUi.DIET_EDIT_WRONG_FORMAT; +import static ui.diet.dietmanager.DietManagerUi.DIET_FILE_ARRAY_OUT_OF_BOUND; +import static ui.diet.dietmanager.DietManagerUi.DIET_NEW_SUCCESS; + +//@@author CFZeon +public class DietSessionEditTest { + private static String EMPTY_STRING = ""; + + @Test + void testExecute_validInput_success() { + DietStorage storage = new DietStorage(); + // to ensure there is something to edit + System.setIn(new ByteArrayInputStream("end".getBytes())); + new DietSessionCreate().execute(EMPTY_STRING, storage); + System.setIn(new ByteArrayInputStream("end".getBytes())); + String editInput = "1"; + CommandResult result = new DietSessionEdit().execute(editInput, storage); + assertEquals(DIET_NEW_SUCCESS, result.getFeedbackMessage()); + } + + @Test + void testExecute_invalidInput_resultArrayOutOfBound() { + DietStorage storage = new DietStorage(); + // to ensure there is something to edit + System.setIn(new ByteArrayInputStream("end".getBytes())); + new DietSessionCreate().execute(EMPTY_STRING, storage); + System.setIn(new ByteArrayInputStream("end".getBytes())); + String editInput = "0"; + CommandResult result = new DietSessionEdit().execute(editInput, storage); + assertEquals(DIET_FILE_ARRAY_OUT_OF_BOUND, result.getFeedbackMessage()); + } + + @Test + void testExecute_emptyInput_resultEditWrongFormat() { + DietStorage storage = new DietStorage(); + // to ensure there is something to edit + System.setIn(new ByteArrayInputStream("end".getBytes())); + new DietSessionCreate().execute(EMPTY_STRING, storage); + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new DietSessionEdit().execute(EMPTY_STRING, storage); + assertEquals(DIET_EDIT_WRONG_FORMAT, result.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/diet/dietmanager/DietSessionSearchTest.java b/src/test/java/logic/commands/diet/dietmanager/DietSessionSearchTest.java new file mode 100644 index 0000000000..16fc66a3d8 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietmanager/DietSessionSearchTest.java @@ -0,0 +1,27 @@ +package logic.commands.diet.dietmanager; + +import exceptions.InvalidDateFormatException; +import exceptions.diet.InvalidSearchDateException; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; +import static org.junit.jupiter.api.Assertions.assertThrows; + +//@@author CFZeon +public class DietSessionSearchTest { + + @Test + void testExecute_inputEndDateBeforeStartDate_ValidStorage_throwsInvalidSearchDateException() { + String input = "/s 2020-05-04 /e 2020-05-03"; + assertThrows(InvalidSearchDateException.class, () -> { + new DietSessionSearch().execute(input, (DietStorage) null); + }); + } + + @Test + void testExecute_inputInvalidDate_throwsInvalidDateFormatException() { + String input = "/s 2020-"; + assertThrows(InvalidDateFormatException.class, () -> { + new DietSessionSearch().execute(input, (DietStorage) null); + }); + } +} diff --git a/src/test/java/logic/commands/diet/dietsession/FoodItemAddTest.java b/src/test/java/logic/commands/diet/dietsession/FoodItemAddTest.java new file mode 100644 index 0000000000..23542ce177 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietsession/FoodItemAddTest.java @@ -0,0 +1,65 @@ +package logic.commands.diet.dietsession; + +import logic.commands.CommandResult; +import models.Food; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static profile.Constants.CALORIES_UPPER_BOUND; + +//@@author zsk612 +public class FoodItemAddTest { + + ArrayList foodList = new ArrayList<>(); + DietStorage storage = new DietStorage(); + FoodItemAdd foodItemAdd = new FoodItemAdd(); + + @Test + public void execute_correctFoodItem_addSuccessful() { + String input = "Noodles /c 300"; + StringBuilder userOutput = new StringBuilder(); + Food temp = new Food("Noodles", 300); + userOutput.append("Yay! You have added " + temp.toString()); + CommandResult result = foodItemAdd.execute(input, foodList, storage, -1); + assertEquals(userOutput.toString(), result.getFeedbackMessage()); + } + + @Test + public void execute_exceedMaxCalories_returnMaxCalories() { + String input = "Noodles /c 1000000000000"; + StringBuilder userOutput = new StringBuilder(); + Food temp = new Food("Noodles", CALORIES_UPPER_BOUND); + userOutput.append(DietSessionUi.MESSAGE_HIGH_CALORIES); + userOutput.append("Yay! You have added " + temp.toString()); + CommandResult result = foodItemAdd.execute(input, foodList, storage, -1); + assertEquals(userOutput.toString(), result.getFeedbackMessage()); + } + + @Test + public void execute_noFoodName_returnNoNameWarning() { + String input = "/c 300"; + String expected = DietSessionUi.MESSAGE_NO_FOOD_NAME; + CommandResult result = foodItemAdd.execute(input, foodList, storage, -1); + assertEquals(expected, result.getFeedbackMessage()); + } + + @Test + public void execute_wrongCaloriesNumber_returnNumberFormatWarning() { + String input = "Noodles /c haha"; + String expected = DietSessionUi.MESSAGE_WRONG_CALORIES; + CommandResult result = foodItemAdd.execute(input, foodList, storage, -1); + assertEquals(expected, result.getFeedbackMessage()); + } + + @Test + public void execute_negativeCaloriesNumber_returnNegativeCaloriesWarning() { + String input = "Noodles /c -100"; + String expected = DietSessionUi.MESSAGE_NEGATIVE_CALORIES; + CommandResult result = foodItemAdd.execute(input, foodList, storage, -1); + assertEquals(expected, result.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/diet/dietsession/FoodItemClearTest.java b/src/test/java/logic/commands/diet/dietsession/FoodItemClearTest.java new file mode 100644 index 0000000000..ee5a7d9fd8 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietsession/FoodItemClearTest.java @@ -0,0 +1,46 @@ +package logic.commands.diet.dietsession; + +import logic.commands.CommandResult; +import models.Food; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author zsk612 +public class FoodItemClearTest { + + ArrayList foodList = new ArrayList<>(); + DietStorage storage = new DietStorage(); + FoodItemClear foodItemClear = new FoodItemClear(); + + @Test + public void execute_clearWithYes_clearSuccessful() { + Food temp = new Food("Noodles", 300); + foodList.add(temp); + String input = ""; + String userOutput = "Alright, your food items have been cleared."; + InputStream sysInBackup = System.in; // backup System.in to restore it later + ByteArrayInputStream in = new ByteArrayInputStream("YES".getBytes()); + System.setIn(in); + CommandResult result = foodItemClear.execute(input, foodList, storage, -1); + assertEquals(userOutput, result.getFeedbackMessage()); + } + + @Test + public void execute_clearWithoutYes_deleteAborted() { + Food temp = new Food("Noodles", 300); + foodList.add(temp); + String input = ""; + String userOutput = "You have aborted clear operation."; + InputStream sysInBackup = System.in; // backup System.in to restore it later + ByteArrayInputStream in = new ByteArrayInputStream("NO".getBytes()); + System.setIn(in); + CommandResult result = foodItemClear.execute(input, foodList, storage, 1); + assertEquals(userOutput, result.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/diet/dietsession/FoodItemDeleteTest.java b/src/test/java/logic/commands/diet/dietsession/FoodItemDeleteTest.java new file mode 100644 index 0000000000..ebd066e626 --- /dev/null +++ b/src/test/java/logic/commands/diet/dietsession/FoodItemDeleteTest.java @@ -0,0 +1,49 @@ +package logic.commands.diet.dietsession; + +import logic.commands.CommandResult; +import models.Food; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; +import ui.diet.dietsession.DietSessionUi; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author zsk612 +public class FoodItemDeleteTest { + + ArrayList foodList = new ArrayList<>(); + DietStorage storage = new DietStorage(); + FoodItemDelete foodItemDelete = new FoodItemDelete(); + + @Test + public void execute_correctFoodItemIndex_deleteSuccessful() { + Food temp = new Food("Noodles", 300); + foodList.add(temp); + String input = "1"; + String userOutput = "You have deleted " + temp.toString() + " from your list!"; + CommandResult result = foodItemDelete.execute(input, foodList, storage, -1); + assertEquals(userOutput, result.getFeedbackMessage()); + } + + @Test + public void execute_wrongFoodItemIndex_returnWrongIndexWarning() { + Food temp = new Food("Noodles", 300); + foodList.add(temp); + String input = "10"; + String userOutput = DietSessionUi.MESSAGE_NO_SUCH_INDEX; + CommandResult result = foodItemDelete.execute(input, foodList, storage, -1); + assertEquals(userOutput, result.getFeedbackMessage()); + } + + @Test + public void execute_indexNotNumber_returnWrongIndexFormatWarning() { + Food temp = new Food("Noodles", 300); + foodList.add(temp); + String input = "haha"; + String userOutput = DietSessionUi.MESSAGE_DELETE_WRONG_FORMAT; + CommandResult result = foodItemDelete.execute(input, foodList, storage, -1); + assertEquals(userOutput, result.getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/main/MainEndTest.java b/src/test/java/logic/commands/main/MainEndTest.java new file mode 100644 index 0000000000..5d605e3852 --- /dev/null +++ b/src/test/java/logic/commands/main/MainEndTest.java @@ -0,0 +1,25 @@ +package logic.commands.main; + +import exceptions.EndException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class MainEndTest { + + @Test + void testExecute_inputNullArguments_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new MainEnd().execute(null); + }); + } + + @Test + void testExecute_inputEmptyArguments_ValidStorage_throwsEndException() { + assertThrows(EndException.class, () -> { + new MainEnd().execute(EMPTY_STRING); + }); + } +} diff --git a/src/test/java/logic/commands/main/MainHelpTest.java b/src/test/java/logic/commands/main/MainHelpTest.java new file mode 100644 index 0000000000..8721867e40 --- /dev/null +++ b/src/test/java/logic/commands/main/MainHelpTest.java @@ -0,0 +1,25 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.ExecutionResult; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class MainHelpTest { + + @Test + void testExecute_inputNullArguments_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new MainHelp().execute(null); + }); + } + + @Test + void testExecute_inputEmptyArguments_returnSuccess() throws SchwarzeneggerException { + assertEquals(ExecutionResult.OK, new MainHelp().execute(EMPTY_STRING).getStatus()); + } +} diff --git a/src/test/java/logic/commands/main/MainWrongTest.java b/src/test/java/logic/commands/main/MainWrongTest.java new file mode 100644 index 0000000000..992bdff51a --- /dev/null +++ b/src/test/java/logic/commands/main/MainWrongTest.java @@ -0,0 +1,25 @@ +package logic.commands.main; + +import exceptions.InvalidCommandWordException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class MainWrongTest { + + @Test + void testExecute_inputNullArguments_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new MainWrong().execute(null); + }); + } + + @Test + void testExecute_inputEmptyArguments_returnSuccess() { + assertThrows(InvalidCommandWordException.class, () -> { + new MainWrong().execute(EMPTY_STRING); + }); + } +} diff --git a/src/test/java/logic/commands/main/ToDietTest.java b/src/test/java/logic/commands/main/ToDietTest.java new file mode 100644 index 0000000000..6180a3782e --- /dev/null +++ b/src/test/java/logic/commands/main/ToDietTest.java @@ -0,0 +1,31 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class ToDietTest { + + @Test + void testExecute_inputNullArguments_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ToDiet().execute(null); + }); + } + + @Test + void testExecute_inputEmptyArguments_returnSuccess() throws SchwarzeneggerException { + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new ToDiet().execute(EMPTY_STRING); + System.setIn(System.in); + assertEquals(ExecutionResult.OK, result.getStatus()); + } +} diff --git a/src/test/java/logic/commands/main/ToProfileTest.java b/src/test/java/logic/commands/main/ToProfileTest.java new file mode 100644 index 0000000000..dd7b9881f4 --- /dev/null +++ b/src/test/java/logic/commands/main/ToProfileTest.java @@ -0,0 +1,31 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class ToProfileTest { + + @Test + void testExecute_inputNullArguments_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ToProfile().execute(null); + }); + } + + @Test + void testExecute_inputEmptyArguments_returnSuccess() throws SchwarzeneggerException { + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new ToProfile().execute(EMPTY_STRING); + System.setIn(System.in); + assertEquals(ExecutionResult.OK, result.getStatus()); + } +} diff --git a/src/test/java/logic/commands/main/ToWorkoutTest.java b/src/test/java/logic/commands/main/ToWorkoutTest.java new file mode 100644 index 0000000000..2d1503a3c6 --- /dev/null +++ b/src/test/java/logic/commands/main/ToWorkoutTest.java @@ -0,0 +1,31 @@ +package logic.commands.main; + +import exceptions.SchwarzeneggerException; +import logic.commands.CommandResult; +import logic.commands.ExecutionResult; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class ToWorkoutTest { + + @Test + void testExecute_inputNullArguments_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ToWorkout().execute(null); + }); + } + + @Test + void testExecute_inputEmptyArguments_returnSuccess() throws SchwarzeneggerException { + System.setIn(new ByteArrayInputStream("end".getBytes())); + CommandResult result = new ToWorkout().execute(EMPTY_STRING); + System.setIn(System.in); + assertEquals(ExecutionResult.OK, result.getStatus()); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileAddTest.java b/src/test/java/logic/commands/profile/ProfileAddTest.java new file mode 100644 index 0000000000..8f2d1153fb --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileAddTest.java @@ -0,0 +1,79 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidCommandFormatException; +import exceptions.profile.SavingException; +import models.Profile; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static ui.profile.ProfileUi.MESSAGE_CREATE_PROFILE_ACK; +import static ui.profile.ProfileUi.MESSAGE_PROFILE_EXIST; + +//@@author tienkhoa16 +class ProfileAddTest { + private static final Path SAMPLE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", "ProfileCommandsTest"); + private static final Profile SAMPLE_PROFILE = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + + @Test + void testExecute_noExistingProfile_inputValidArguments_ValidStorage_returnSuccess() throws SchwarzeneggerException { + String commandArgs = "/n Schwarzenegger /h 188 /w 113 /e 100 /c 2500"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(null); + + String successMsg = String.format(MESSAGE_CREATE_PROFILE_ACK, SAMPLE_PROFILE.toString()); + assertEquals(successMsg, new ProfileAdd().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputValidArguments_ValidStorage_returnFailure() + throws SchwarzeneggerException { + String commandArgs = "/n Schwarzenegger /h 188 /w 113 /e 100 /c 2500"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + assertEquals(MESSAGE_PROFILE_EXIST, new ProfileAdd().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_noExistingProfile_inputEmptyArguments_ValidStorage_throwsInvalidCommandFormatException() + throws SavingException { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(null); + + assertThrows(InvalidCommandFormatException.class, () -> { + new ProfileAdd().execute(EMPTY_STRING, storage); + }); + } + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + assertThrows(AssertionError.class, () -> { + new ProfileAdd().execute(null, storage); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileAdd().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileDeleteTest.java b/src/test/java/logic/commands/profile/ProfileDeleteTest.java new file mode 100644 index 0000000000..c0fb4a5a83 --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileDeleteTest.java @@ -0,0 +1,80 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import logic.commands.CommandResult; +import models.Profile; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.io.ByteArrayInputStream; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static ui.profile.ProfileUi.MESSAGE_DELETE_NOTHING; +import static ui.profile.ProfileUi.MESSAGE_DELETE_PROFILE; +import static ui.workout.workoutmanager.WorkoutManagerUi.CLEAR_ABORTED; + +//@@author tienkhoa16 +class ProfileDeleteTest { + private static final Path SAMPLE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", "ProfileCommandsTest"); + private static final Profile SAMPLE_PROFILE = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + assertThrows(AssertionError.class, () -> { + new ProfileDelete().execute(null, new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile)); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileDelete().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } + + @Test + void testExecute_hasExistingProfile_inputYes_returnSuccess() throws SchwarzeneggerException { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + System.setIn(new ByteArrayInputStream("YES".getBytes())); + CommandResult result = new ProfileDelete().execute(EMPTY_STRING, storage); + System.setIn(System.in); + + assertEquals(MESSAGE_DELETE_PROFILE, result.getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputDifferentFromYes_returnFailure() throws SchwarzeneggerException { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + System.setIn(new ByteArrayInputStream("some input different from YES".getBytes())); + CommandResult result = new ProfileDelete().execute(EMPTY_STRING, storage); + System.setIn(System.in); + + assertEquals(CLEAR_ABORTED, result.getFeedbackMessage()); + } + + @Test + void testExecute_noExistingProfile_returnFailure() throws SchwarzeneggerException { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(null); + + assertEquals(MESSAGE_DELETE_NOTHING, new ProfileDelete().execute(EMPTY_STRING, storage).getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileEditTest.java b/src/test/java/logic/commands/profile/ProfileEditTest.java new file mode 100644 index 0000000000..0d5b7dd3cf --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileEditTest.java @@ -0,0 +1,162 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidCommandFormatException; +import exceptions.profile.SavingException; +import models.Profile; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static profile.Constants.CALORIES_UPPER_BOUND; +import static ui.CommonUi.EMPTY_STRING; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static ui.profile.ProfileUi.MESSAGE_EDIT_NOTHING; +import static ui.profile.ProfileUi.MESSAGE_EDIT_PROFILE_ACK; +import static ui.profile.ProfileUi.MESSAGE_PROFILE_NOT_EXIST; + +//@@author tienkhoa16 +class ProfileEditTest { + private static final Path SAMPLE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", "ProfileCommandsTest"); + private static final Profile SAMPLE_PROFILE = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + assertThrows(AssertionError.class, () -> { + new ProfileEdit().execute(null, storage); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileEdit().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } + + @Test + void testExecute_noExistingProfile_inputValidArguments_ValidStorage_returnFailure() throws SchwarzeneggerException { + String commandArgs = "/n Arnold"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(null); + + assertEquals(String.format(MESSAGE_PROFILE_NOT_EXIST, COMMAND_WORD_EDIT), + new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputEmptyArguments_ValidStorage_throwsInvalidCommandFormatException() + throws SavingException { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + assertThrows(InvalidCommandFormatException.class, () -> { + new ProfileEdit().execute(EMPTY_STRING, storage); + }); + } + + @Test + void testExecute_hasExistingProfile_inputEditName_ValidStorage_returnSuccess() throws SchwarzeneggerException { + String commandArgs = "/n Arnold"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile("Arnold", EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + assertEquals(successMsg, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputEditHeight_ValidStorage_returnSuccess() throws SchwarzeneggerException { + String commandArgs = "/h 200"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile(EXAMPLE_NAME, 200, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + assertEquals(successMsg, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputEditWeight_ValidStorage_returnSuccess() throws SchwarzeneggerException { + String commandArgs = "/w 200"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 200, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + assertEquals(successMsg, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputEditExpectedWeight_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + String commandArgs = "/e 200"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, 200, EXAMPLE_CALORIES); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + assertEquals(successMsg, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputEditCaloriesToUpperBound_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + String commandArgs = "/c 200000"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, CALORIES_UPPER_BOUND); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + assertEquals(successMsg, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputEditCaloriesMoreThanUpperBound_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + String commandArgs = "/c 200001"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, CALORIES_UPPER_BOUND); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + assertEquals(successMsg, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_inputSameInformation_ValidStorage_returnFailure() + throws SchwarzeneggerException { + String commandArgs = "/n Schwarzenegger"; + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + storage.saveData(SAMPLE_PROFILE); + + assertEquals(MESSAGE_EDIT_NOTHING, new ProfileEdit().execute(commandArgs, storage).getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileEndTest.java b/src/test/java/logic/commands/profile/ProfileEndTest.java new file mode 100644 index 0000000000..5decb7e951 --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileEndTest.java @@ -0,0 +1,39 @@ +package logic.commands.profile; + +import exceptions.EndException; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class ProfileEndTest { + private static final Path SAMPLE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", "ProfileCommandsTest"); + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + assertThrows(AssertionError.class, () -> { + new ProfileEnd().execute(null, new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile)); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileEnd().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } + + @Test + void testExecute_inputValidArguments_ValidStorage_throwsEndException() { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + assertThrows(EndException.class, () -> { + new ProfileEnd().execute(EMPTY_STRING, new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile)); + }); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileHelpTest.java b/src/test/java/logic/commands/profile/ProfileHelpTest.java new file mode 100644 index 0000000000..911e8d6b5c --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileHelpTest.java @@ -0,0 +1,62 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static profile.Constants.ADD_PROFILE_FORMAT; +import static profile.Constants.EDIT_PROFILE_FORMAT; +import static seedu.duke.Constants.COMMAND_WORD_ADD; +import static seedu.duke.Constants.COMMAND_WORD_DELETE; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static seedu.duke.Constants.COMMAND_WORD_END; +import static seedu.duke.Constants.COMMAND_WORD_VIEW; +import static ui.CommonUi.EMPTY_STRING; +import static ui.CommonUi.helpFormatter; + +//@@author tienkhoa16 +class ProfileHelpTest { + private static final Path SAMPLE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", "ProfileCommandsTest"); + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + assertThrows(AssertionError.class, () -> { + new ProfileHelp().execute(null, storage); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileHelp().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } + + @Test + void testExecute_inputEmptyArguments_ValidStorage_returnHelpMessage() throws SchwarzeneggerException { + StringBuilder helpMessage = new StringBuilder(); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_ADD), ADD_PROFILE_FORMAT, + "Add your new profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_VIEW), COMMAND_WORD_VIEW, + "View your profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_EDIT), EDIT_PROFILE_FORMAT, + "Edit your existing profile. You may edit from 1 field to all fields")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_DELETE), COMMAND_WORD_DELETE, + "Delete your existing profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_END), COMMAND_WORD_END, + "Go back to Main Menu")); + + Path dataFile = Paths.get(SAMPLE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_DATA_FOLDER, dataFile); + assertEquals(helpMessage.toString().trim(), + new ProfileHelp().execute(EMPTY_STRING, storage).getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileViewTest.java b/src/test/java/logic/commands/profile/ProfileViewTest.java new file mode 100644 index 0000000000..aa59aaad89 --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileViewTest.java @@ -0,0 +1,127 @@ +package logic.commands.profile; + +import exceptions.SchwarzeneggerException; +import models.Profile; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static seedu.duke.Constants.COMMAND_WORD_VIEW; +import static ui.CommonUi.EMPTY_STRING; +import static ui.profile.ProfileUi.MESSAGE_ENOUGH_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_MORE_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_PROFILE_NOT_EXIST; +import static ui.profile.ProfileUi.MESSAGE_SET_EXPECTED_WEIGHT; +import static ui.profile.ProfileUi.MESSAGE_VIEW_PROFILE; + +//@@author tienkhoa16 +class ProfileViewTest { + private static final String SAMPLE_DIET_DATA_FOLDER = Paths.get("src", "test", "java", "saves", + "ProfileCommandsTest", "diet").toString() + "/"; + private static final Path SAMPLE_PROFILE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", + "ProfileCommandsTest"); + + private static final double EXAMPLE_NORMAL_EXPECTED_WEIGHT = 76.7; + private static final double EXAMPLE_UNDERWEIGHT_EXPECTED_WEIGHT = 40; + private static final double EXAMPLE_OBESITY_EXPECTED_WEIGHT = 100; + + private static final Profile SAMPLE_NORMAL_EXPECTED_WEIGHT_PROFILE = new Profile(EXAMPLE_NAME, + EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_NORMAL_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + private static final Profile SAMPLE_UNDERWEIGHT_EXPECTED_WEIGHT_PROFILE = new Profile(EXAMPLE_NAME, + EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_UNDERWEIGHT_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + private static final Profile SAMPLE_OBESITY_EXPECTED_WEIGHT_PROFILE = new Profile(EXAMPLE_NAME, + EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_OBESITY_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, dataFile); + assertThrows(AssertionError.class, () -> { + new ProfileView().execute(null, storage); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileView().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } + + @Test + void testExecute_noExistingProfile_inputEmptyArguments_ValidStorage_returnFailure() throws SchwarzeneggerException { + Path dataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, dataFile); + storage.saveData(null); + + assertEquals(String.format(MESSAGE_PROFILE_NOT_EXIST, COMMAND_WORD_VIEW), + new ProfileView().execute(EMPTY_STRING, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_notReachedTarget_normalWeight_inputEmptyArguments_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + Path profileDataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, profileDataFile); + storage.saveData(SAMPLE_NORMAL_EXPECTED_WEIGHT_PROFILE); + String sampleCaloriesMsg = String.format(MESSAGE_MORE_CALORIES, 2200.0); + + assertEquals(String.format(MESSAGE_VIEW_PROFILE, SAMPLE_NORMAL_EXPECTED_WEIGHT_PROFILE.toString(), + sampleCaloriesMsg, EMPTY_STRING).trim(), + new ProfileView(SAMPLE_DIET_DATA_FOLDER, LocalDate.of(2020, 11, 8)) + .execute(EMPTY_STRING, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_reachedTarget_normalWeight_inputEmptyArguments_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + Path profileDataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, profileDataFile); + storage.saveData(SAMPLE_NORMAL_EXPECTED_WEIGHT_PROFILE); + + assertEquals(String.format(MESSAGE_VIEW_PROFILE, SAMPLE_NORMAL_EXPECTED_WEIGHT_PROFILE.toString(), + MESSAGE_ENOUGH_CALORIES, EMPTY_STRING).trim(), + new ProfileView(SAMPLE_DIET_DATA_FOLDER, LocalDate.of(2020, 11, 7)) + .execute(EMPTY_STRING, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_reachedTarget_underWeight_inputEmptyArguments_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + Path profileDataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, profileDataFile); + storage.saveData(SAMPLE_UNDERWEIGHT_EXPECTED_WEIGHT_PROFILE); + + String weightTip = String.format(MESSAGE_SET_EXPECTED_WEIGHT, + EXAMPLE_NORMAL_EXPECTED_WEIGHT, EXAMPLE_NORMAL_EXPECTED_WEIGHT); + assertEquals(String.format(MESSAGE_VIEW_PROFILE, SAMPLE_UNDERWEIGHT_EXPECTED_WEIGHT_PROFILE.toString(), + MESSAGE_ENOUGH_CALORIES, weightTip).trim(), + new ProfileView(SAMPLE_DIET_DATA_FOLDER, LocalDate.of(2020, 11, 7)) + .execute(EMPTY_STRING, storage).getFeedbackMessage()); + } + + @Test + void testExecute_hasExistingProfile_reachedTarget_obesity_inputEmptyArguments_ValidStorage_returnSuccess() + throws SchwarzeneggerException { + Path profileDataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, profileDataFile); + storage.saveData(SAMPLE_OBESITY_EXPECTED_WEIGHT_PROFILE); + + String weightTip = String.format(MESSAGE_SET_EXPECTED_WEIGHT, + EXAMPLE_NORMAL_EXPECTED_WEIGHT, EXAMPLE_NORMAL_EXPECTED_WEIGHT); + assertEquals(String.format(MESSAGE_VIEW_PROFILE, SAMPLE_OBESITY_EXPECTED_WEIGHT_PROFILE.toString(), + MESSAGE_ENOUGH_CALORIES, weightTip).trim(), + new ProfileView(SAMPLE_DIET_DATA_FOLDER, LocalDate.of(2020, 11, 7)) + .execute(EMPTY_STRING, storage).getFeedbackMessage()); + } +} diff --git a/src/test/java/logic/commands/profile/ProfileWrongTest.java b/src/test/java/logic/commands/profile/ProfileWrongTest.java new file mode 100644 index 0000000000..8625b1d290 --- /dev/null +++ b/src/test/java/logic/commands/profile/ProfileWrongTest.java @@ -0,0 +1,42 @@ +package logic.commands.profile; + +import exceptions.InvalidCommandWordException; +import org.junit.jupiter.api.Test; +import storage.profile.ProfileStorage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class ProfileWrongTest { + private static final Path SAMPLE_PROFILE_DATA_FOLDER = Paths.get("src", "test", "java", "saves", + "ProfileCommandsTest"); + + @Test + void testExecute_inputNullArguments_ValidStorage_throwsAssertionError() { + Path dataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, dataFile); + assertThrows(AssertionError.class, () -> { + new ProfileWrong().execute(null, storage); + }); + } + + @Test + void testExecute_inputEmptyArguments_NullStorage_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + new ProfileWrong().execute(EMPTY_STRING, (ProfileStorage) null); + }); + } + + @Test + void testExecute_inputEmptyArguments_ValidStorage_throwsInvalidCommandWordException() { + Path dataFile = Paths.get(SAMPLE_PROFILE_DATA_FOLDER.toString(), "profileDataFile.json"); + ProfileStorage storage = new ProfileStorage(SAMPLE_PROFILE_DATA_FOLDER, dataFile); + assertThrows(InvalidCommandWordException.class, () -> { + new ProfileWrong().execute(EMPTY_STRING, storage); + }); + } +} diff --git a/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionAddTest.java b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionAddTest.java new file mode 100644 index 0000000000..64517b941a --- /dev/null +++ b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionAddTest.java @@ -0,0 +1,102 @@ +package logic.commands.workout.workoutsession; + +import exceptions.workout.workoutmanager.SchwIoException; +import logic.commands.CommandResult; +import models.ExerciseList; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; +import storage.workout.WorkoutSessionStorage; +import storage.workout.WorkOutManagerStorage; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionAddTest { + + @Test + void execute_validInput_success() throws SchwIoException { + String[] inputs = {"add", "bench", "/n", "3244", "/w", "4324"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + + CommandResult actual = workoutSessionAdd.execute(inputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("Yay! You have added bench to your list.\n" + + "\t [Repetitions: 3244 || Weight: 4324.0]"); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_invalidInput_formatError() throws SchwIoException { + String[] inputs = {"add", "bench", "3244", "/w", "4324"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + + CommandResult actual = workoutSessionAdd.execute(inputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("Wrong format, please enter in the format:\n" + + "\t add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]"); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_invalidInput_negativeFormatError() throws SchwIoException { + String[] inputs = {"add", "bench", "/n", "-3244", "/w", "4324"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + + CommandResult actual = workoutSessionAdd.execute(inputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("Wrong format, please enter in the format:\n" + + "\t add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]\n" + + "\t Please make sure [NUMBER_OF_REPETITIONS] and [WEIGHT] are non negative numbers."); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_nullParams_assert() { + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + assertThrows(AssertionError.class, () -> { + workoutSessionAdd.execute(null, null, null, null, null); + }); + } + + @Test + void execute_nullParams_assert1() throws SchwIoException { + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String[] inputs = {"add", "bench", "/n", "-3244", "/w", "4324"}; + String filePath = workOutManagerStorage.createfile(); + assertThrows(AssertionError.class, () -> { + workoutSessionAdd.execute(inputs, null, null, null, null); + }); + } + + @Test + void execute_nullParams_assert3() throws SchwIoException { + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String[] inputs = {"add", "bench", "/n", "-3244", "/w", "4324"}; + ExerciseList exerciseList = new ExerciseList(); + String filePath = workOutManagerStorage.createfile(); + assertThrows(AssertionError.class, () -> { + workoutSessionAdd.execute(inputs, exerciseList, filePath, null, null); + }); + } +} diff --git a/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionDeleteTest.java b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionDeleteTest.java new file mode 100644 index 0000000000..551fcce5e4 --- /dev/null +++ b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionDeleteTest.java @@ -0,0 +1,87 @@ +package logic.commands.workout.workoutsession; + +import exceptions.workout.workoutmanager.SchwIoException; +import logic.commands.CommandResult; +import models.ExerciseList; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; +import storage.workout.WorkOutManagerStorage; +import storage.workout.WorkoutSessionStorage; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionDeleteTest { + + @Test + void execute_validInput_success() throws SchwIoException { + String[] addInputs = {"add", "bench", "/n", "3244", "/w", "4324"}; + String[] deleteInputs = {"delete", "1"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + workoutSessionAdd.execute(addInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + WorkoutSessionDelete workoutSessionDelete = new WorkoutSessionDelete(); + CommandResult actual = workoutSessionDelete.execute(deleteInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("You have deleted bench from your list!\n" + + "\t [Repetitions: 3244 || Weight: 4324.0]"); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_invalidInput_formatError() throws SchwIoException { + String[] addInputs = {"add", "bench", "/n", "3244", "/w", "4324"}; + String[] deleteInputs = {"delete", "fsdg"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + workoutSessionAdd.execute(addInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + WorkoutSessionDelete workoutSessionDelete = new WorkoutSessionDelete(); + CommandResult actual = workoutSessionDelete.execute(deleteInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("Wrong format, please enter in the format:\n" + + "\t delete [INDEX]"); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_invalidInput_indexError() throws SchwIoException { + String[] addInputs = {"add", "bench", "/n", "3244", "/w", "4324"}; + String[] deleteInputs = {"delete", "10"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionAdd workoutSessionAdd = new WorkoutSessionAdd(); + workoutSessionAdd.execute(addInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + WorkoutSessionDelete workoutSessionDelete = new WorkoutSessionDelete(); + CommandResult actual = workoutSessionDelete.execute(deleteInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("Index does not exist. Please refer to the list."); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_nullParams_assert() { + WorkoutSessionDelete workoutSessionDelete = new WorkoutSessionDelete(); + assertThrows(AssertionError.class, () -> { + workoutSessionDelete.execute(null, null, null, null, null); + }); + } + +} \ No newline at end of file diff --git a/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionEndTest.java b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionEndTest.java new file mode 100644 index 0000000000..02326cf599 --- /dev/null +++ b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionEndTest.java @@ -0,0 +1,41 @@ +package logic.commands.workout.workoutsession; + +import exceptions.workout.workoutmanager.SchwIoException; +import logic.commands.CommandResult; +import models.ExerciseList; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; +import storage.workout.WorkOutManagerStorage; +import storage.workout.WorkoutSessionStorage; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionEndTest { + + @Test + void execute_validInput_success() throws SchwIoException { + String[] input = {"end"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionEnd workoutSessionEnd = new WorkoutSessionEnd(); + + CommandResult actual = workoutSessionEnd.execute(input, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult(""); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_nullParams_assert() { + WorkoutSessionEnd workoutSessionEnd = new WorkoutSessionEnd(); + assertThrows(AssertionError.class, () -> { + workoutSessionEnd.execute(null, null, null, null, null); + }); + } +} \ No newline at end of file diff --git a/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionHelpTest.java b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionHelpTest.java new file mode 100644 index 0000000000..7a33e7c96a --- /dev/null +++ b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionHelpTest.java @@ -0,0 +1,42 @@ +package logic.commands.workout.workoutsession; + +import exceptions.workout.workoutmanager.SchwIoException; +import logic.commands.CommandResult; +import models.ExerciseList; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; +import storage.workout.WorkOutManagerStorage; +import storage.workout.WorkoutSessionStorage; +import ui.workout.workoutsession.WorkoutSessionUi; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionHelpTest { + + @Test + void execute_validInput_success() throws SchwIoException { + String[] input = {"help"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionHelp workoutSessionHelp = new WorkoutSessionHelp(); + + CommandResult actual = workoutSessionHelp.execute(input, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult(WorkoutSessionUi.printHelp()); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_nullParams_assert() { + WorkoutSessionHelp workoutSessionHelp = new WorkoutSessionHelp(); + assertThrows(AssertionError.class, () -> { + workoutSessionHelp.execute(null, null, null, null, null); + }); + } +} \ No newline at end of file diff --git a/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionListTest.java b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionListTest.java new file mode 100644 index 0000000000..db4e04643e --- /dev/null +++ b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionListTest.java @@ -0,0 +1,41 @@ +package logic.commands.workout.workoutsession; + +import exceptions.workout.workoutmanager.SchwIoException; +import logic.commands.CommandResult; +import models.ExerciseList; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; +import storage.workout.WorkOutManagerStorage; +import storage.workout.WorkoutSessionStorage; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionListTest { + + @Test + void execute_validInput_emptyList() throws SchwIoException { + String[] input = {"list"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionList workoutSessionList = new WorkoutSessionList(); + + CommandResult actual = workoutSessionList.execute(input, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("List is empty. Please enter something."); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_nullParams_assert() { + WorkoutSessionList workoutSessionList = new WorkoutSessionList(); + assertThrows(AssertionError.class, () -> { + workoutSessionList.execute(null, null, null, null, null); + }); + } +} \ No newline at end of file diff --git a/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionSearchTest.java b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionSearchTest.java new file mode 100644 index 0000000000..f11a0bb86d --- /dev/null +++ b/src/test/java/logic/commands/workout/workoutsession/WorkoutSessionSearchTest.java @@ -0,0 +1,57 @@ +package logic.commands.workout.workoutsession; + +import exceptions.workout.workoutmanager.SchwIoException; +import logic.commands.CommandResult; +import models.ExerciseList; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; +import storage.workout.WorkOutManagerStorage; +import storage.workout.WorkoutSessionStorage; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionSearchTest { + + @Test + void execute_validInput_success() throws SchwIoException { + String[] searchInputs = {"search", "ben"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionSearch workoutSessionSearch = new WorkoutSessionSearch(); + CommandResult actual = workoutSessionSearch.execute(searchInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("No matching result has been found."); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_emptyInput_success() throws SchwIoException { + String[] searchInputs = {"search"}; + ExerciseList exerciseList = new ExerciseList(); + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + boolean[] hasEndedWorkoutSessions = {false}; + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionSearch workoutSessionSearch = new WorkoutSessionSearch(); + CommandResult actual = workoutSessionSearch.execute(searchInputs, exerciseList, filePath, workoutSessionStorage, + hasEndedWorkoutSessions); + CommandResult expected = new CommandResult("Wrong format, please enter in the format:\n" + + "\t search [NAME_OF_MOVE]"); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void execute_nullParams_assert() { + WorkoutSessionSearch workoutSessionSearch = new WorkoutSessionSearch(); + assertThrows(AssertionError.class, () -> { + workoutSessionSearch.execute(null, null, null, null, null); + }); + } +} \ No newline at end of file diff --git a/src/test/java/logic/parser/CommonParserTest.java b/src/test/java/logic/parser/CommonParserTest.java new file mode 100644 index 0000000000..df649101a6 --- /dev/null +++ b/src/test/java/logic/parser/CommonParserTest.java @@ -0,0 +1,60 @@ +package logic.parser; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static ui.CommonUi.EMPTY_STRING; + +//@@author tienkhoa16 +class CommonParserTest { + + @Test + void testParseCommand_inputTwoWords_returnNonEmptyStringInSecondElement() { + CommonParser parser = new CommonParser(); + String rawUserInput = "add /n"; + String[] expectedSplit = {"add", "/n"}; + + assertEquals(Arrays.toString(expectedSplit), + Arrays.toString(parser.parseCommand(rawUserInput))); + } + + @Test + void testParseCommand_inputThreeWords_returnNonEmptyStringInSecondElement() { + CommonParser parser = new CommonParser(); + String rawUserInput = "add /n test"; + String[] expectedSplit = {"add", "/n test"}; + + assertEquals(Arrays.toString(expectedSplit), + Arrays.toString(parser.parseCommand(rawUserInput))); + } + + @Test + void testParseCommand_inputOneWord_returnEmptyStringInSecondElement() { + CommonParser parser = new CommonParser(); + String rawUserInput = "add"; + String[] expectedSplit = {"add", EMPTY_STRING}; + + assertEquals(Arrays.toString(expectedSplit), + Arrays.toString(parser.parseCommand(rawUserInput))); + } + + @Test + void testParseCommand_inputEmptyString_throwsAssertionError() { + CommonParser parser = new CommonParser(); + + assertThrows(AssertionError.class, () -> { + parser.parseCommand(EMPTY_STRING); + }); + } + + @Test + void testParseCommand_inputNull_throwsAssertionError() { + CommonParser parser = new CommonParser(); + assertThrows(AssertionError.class, () -> { + parser.parseCommand(null); + }); + } +} diff --git a/src/test/java/logic/parser/DateParserTest.java b/src/test/java/logic/parser/DateParserTest.java new file mode 100644 index 0000000000..7fee10efde --- /dev/null +++ b/src/test/java/logic/parser/DateParserTest.java @@ -0,0 +1,43 @@ +package logic.parser; + +import exceptions.InvalidDateFormatException; +import org.junit.jupiter.api.Test; +import logic.parser.DateParser; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +class DateParserTest { + + @Test + void parseDate_correctFormat_returnParsedDate() { + LocalDateTime dt = LocalDateTime.of(2020, 10, 22, 0, 0); + + try { + String in1 = "20201022"; + assertEquals(dt, DateParser.parseDate(in1)); + + String in2 = "2020-10-22"; + assertEquals(dt, DateParser.parseDate(in2)); + + String in3 = "2020 10 22"; + assertEquals(dt, DateParser.parseDate(in3)); + } catch (InvalidDateFormatException e) { + fail(); + } + } + + @Test + void parseDate_wrongFormat_returnNull() { + assertThrows(InvalidDateFormatException.class, () -> DateParser.parseDate("2020:10:22")); + } + + @Test + void parseDate_invalidDate_throwsInvalidDateFormatException() { + assertThrows(InvalidDateFormatException.class, () -> DateParser.parseDate("20202131")); + } +} diff --git a/src/test/java/logic/parser/DietManagerParserTest.java b/src/test/java/logic/parser/DietManagerParserTest.java new file mode 100644 index 0000000000..514a0a7739 --- /dev/null +++ b/src/test/java/logic/parser/DietManagerParserTest.java @@ -0,0 +1,228 @@ +package logic.parser; + +import exceptions.InvalidDateFormatException; +import exceptions.profile.InvalidCommandFormatException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.HashMap; + +import static logic.parser.ProfileParser.extractCommandTagAndInfo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author CFZeon +public class DietManagerParserTest { + + @Test + void parseString_validInput_success() { + String input = "first second"; + DietManagerParser parser = new DietManagerParser(); + String[] expected = {"first", "second"}; + String[] result = parser.parseCommand(input); + assertTrue(Arrays.equals(expected, result)); + } + + @Test + void parseString_invalidInput_failure() { + String input = "FirstSecond"; + DietManagerParser parser = new DietManagerParser(); + String[] expected = {"First", "Second"}; + String[] result = parser.parseCommand(input); + Assertions.assertFalse(Arrays.equals(expected, result)); + } + + @Test + void extractTagAndInfo_missingSlashInput_success() { + assertThrows(InvalidCommandFormatException.class, () -> + extractCommandTagAndInfo("search", "t breakfast")); + } + + @Test + void extractTagAndInfo_multipleSlashInput_throwInvalidCommandFormatException() { + assertThrows(InvalidCommandFormatException.class, () -> + extractCommandTagAndInfo("search", "//t breakfast")); + } + + @Test + void extractTagAndInfo_wrongCommandFormatInput_throwInvalidCommandFormatException() { + assertThrows(InvalidCommandFormatException.class, () -> + extractCommandTagAndInfo("search", "j")); + } + + @Test + void extractNewDate_validDateInput_success() throws InvalidCommandFormatException, InvalidDateFormatException { + String input = "/d 2020-05-04"; + String expected = "04-05-2020"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + String extractedDate = parser.extractNewDate(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractNewDate_invalidDateInput_throwInvalidDateFormatException() throws InvalidCommandFormatException { + String input = "/d 2131-14-51"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + assertThrows(InvalidDateFormatException.class, () -> + parser.extractNewDate(parsedParams, message)); + } + + @Test + void extractNewDate_emptyDateInput_returnCurrentDate() throws InvalidCommandFormatException, + InvalidDateFormatException { + String input = "/d "; + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + String expected = dtf.format(now); + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + String extractedDate = parser.extractNewDate(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractNewDate_noDateInputDetected_returnCurrentDate() throws InvalidCommandFormatException, + InvalidDateFormatException { + String input = ""; + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + String expected = dtf.format(now); + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + String extractedDate = parser.extractNewDate(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractSearchTag_validTagInput_success() throws InvalidCommandFormatException { + String input = "/t lunch"; + String expected = "lunch"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + String extractedTag = parser.extractSearchTag(parsedParams, message); + assertEquals(expected, extractedTag); + } + + @Test + void extractSearchTag_noTagInputDetected_returnDefaultTag() throws InvalidCommandFormatException { + String input = "/s 2020-11-11 /e 2020-11-23"; + String expected = ""; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + String extractedTag = parser.extractSearchTag(parsedParams, message); + assertEquals(expected, extractedTag); + } + + @Test + void extractNewTag_validTagInput_success() throws InvalidCommandFormatException { + String input = "/t lunch"; + String expected = "lunch"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + String extractedTag = parser.extractNewTag(parsedParams, message); + assertEquals(expected, extractedTag); + } + + @Test + void extractNewTag_emptyTagInput_returnDefaultTag() throws InvalidCommandFormatException { + String input = "/t "; + String expected = "unspecified"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + String extractedTag = parser.extractNewTag(parsedParams, message); + assertEquals(expected, extractedTag); + } + + @Test + void extractNewTag_noTagInputDetected_returnDefaultTag() throws InvalidCommandFormatException { + String input = ""; + String expected = "unspecified"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("new", input); + String extractedTag = parser.extractNewTag(parsedParams, message); + assertEquals(expected, extractedTag); + } + + @Test + void extractStartDates_correctDateInput_success() + throws InvalidDateFormatException, InvalidCommandFormatException { + String input = "/s 2020-11-02"; + LocalDateTime expected = DateParser.parseDate("2020-11-02"); + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + LocalDateTime extractedDate = parser.extractStartDates(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractStartDates_noDateInputDetected_returnDefaultDate() + throws InvalidDateFormatException, InvalidCommandFormatException { + String input = "/e 2020-11-11 /t lunch"; + LocalDateTime expected = DateParser.parseDate("0001-01-01"); + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + LocalDateTime extractedDate = parser.extractStartDates(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractStartDates_invalidDateInput_throwInvalidDateFormatException() throws InvalidCommandFormatException { + String input = "/s 2131-14-51"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + assertThrows(InvalidDateFormatException.class, () -> + parser.extractStartDates(parsedParams, message)); + } + + @Test + void extractEndDates_correctDateInput_success() + throws InvalidDateFormatException, InvalidCommandFormatException { + String input = "/e 2020-11-02"; + LocalDateTime expected = DateParser.parseDate("2020-11-02"); + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + LocalDateTime extractedDate = parser.extractEndDates(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractEndDates_noDateInputDetected_returnDefaultDate() + throws InvalidDateFormatException, InvalidCommandFormatException { + String input = "/s 2020-11-11 /t lunch"; + LocalDateTime expected = DateParser.parseDate("9999-12-12"); + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + LocalDateTime extractedDate = parser.extractEndDates(parsedParams, message); + assertEquals(expected, extractedDate); + } + + @Test + void extractEndDates_invalidDateInput_throwInvalidDateFormatException() throws InvalidCommandFormatException { + String input = "/e 2131-14-51"; + StringBuilder message = new StringBuilder(); + DietManagerParser parser = new DietManagerParser(); + HashMap parsedParams = parser.extractDietManagerCommandTagAndInfo("search", input); + assertThrows(InvalidDateFormatException.class, () -> + parser.extractEndDates(parsedParams, message)); + } +} diff --git a/src/test/java/logic/parser/DietSessionParserTest.java b/src/test/java/logic/parser/DietSessionParserTest.java new file mode 100644 index 0000000000..40eac5bc45 --- /dev/null +++ b/src/test/java/logic/parser/DietSessionParserTest.java @@ -0,0 +1,31 @@ +package logic.parser; + +import logic.parser.DietSessionParser; +import exceptions.diet.NegativeCaloriesException; +import exceptions.diet.NoNameException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DietSessionParserTest { + + @Test + void processFoodName_ValidInput_Success() throws NoNameException { + String input = "Food /c 123"; + DietSessionParser parser = new DietSessionParser(); + String expected = "Food"; + String result = parser.processFoodName(input); + assertEquals(expected, result); + } + + + @Test + void processFoodCalories_ValidInput_Success() throws NegativeCaloriesException { + String input = "Food /c 123"; + DietSessionParser parser = new DietSessionParser(); + Double expected = 123.0; + double result = parser.processFoodCalories(input); + assertEquals(expected, result); + } +} diff --git a/src/test/java/logic/parser/ProfileParserTest.java b/src/test/java/logic/parser/ProfileParserTest.java new file mode 100644 index 0000000000..3d689f1fe7 --- /dev/null +++ b/src/test/java/logic/parser/ProfileParserTest.java @@ -0,0 +1,42 @@ +package logic.parser; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidCommandFormatException; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; + +import static logic.parser.ProfileParser.extractCommandTagAndInfo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +//@@author tienkhoa16 +class ProfileParserTest { + + @Test + void testExtractCommandTagAndInfo_missingSlashInput_throwInvalidCommandFormatException() { + assertThrows(InvalidCommandFormatException.class, () -> + extractCommandTagAndInfo("add", "add n Schwarzenegger")); + } + + @Test + void testExtractCommandTagAndInfo_excessiveSlashInput_throwInvalidCommandFormatException() { + assertThrows(InvalidCommandFormatException.class, () -> + extractCommandTagAndInfo("add", "add //n Schwarzenegger")); + } + + @Test + void testExtractCommandTagAndInfo_validInput_returnValidParsedParams() throws SchwarzeneggerException { + HashMap parsedParams = new HashMap<>(); + parsedParams.put("/n", "Schwarzenegger"); + parsedParams.put("/c", "2500"); + parsedParams.put("/h", "188"); + parsedParams.put("/w", "113"); + parsedParams.put("/e", "100"); + + String command = "add"; + String commandArgs = "/n Schwarzenegger /c 2500 /h 188 /w 113 /e 100"; + + assertEquals(parsedParams.toString(), extractCommandTagAndInfo(command, commandArgs).toString()); + } +} diff --git a/src/test/java/logic/parser/WorkoutManagerParserTest.java b/src/test/java/logic/parser/WorkoutManagerParserTest.java new file mode 100644 index 0000000000..d046b4e60c --- /dev/null +++ b/src/test/java/logic/parser/WorkoutManagerParserTest.java @@ -0,0 +1,270 @@ +package logic.parser; + +import exceptions.InvalidDateFormatException; +import exceptions.workout.workoutmanager.NotANumberException; +import models.PastWorkoutSessionRecord; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +class WorkoutManagerParserTest { + + private static final WorkoutManagerParser ps = WorkoutManagerParser.getInstance(); + + @Test + void parseTags_validInput_success() { + String args = "/t legs, chest"; + ArrayList result = ps.parseTags(args); + ArrayList expected = new ArrayList<>(); + expected.add("legs"); + expected.add("chest"); + assertEquals(expected, result); + } + + @Test + void parseTags_missingOrWrongIdentifierKey_empty() { + String in1 = "/tt legs chest"; + ArrayList out1 = ps.parseTags(in1); + ArrayList expected = new ArrayList<>(); + assertEquals(expected, out1); + + String in2 = "legs /t chest"; + ArrayList out2 = ps.parseTags(in2); + assertEquals(expected, out2); + + String in3 = "legs chest"; + ArrayList out3 = ps.parseTags(in3); + assertEquals(expected, out3); + } + + @Test + void parseSearchConditions_correctDateTagCondition_success() { + List tg = Arrays.asList("legs", "chest"); + ArrayList tags = new ArrayList<>(tg); + + LocalDateTime date = LocalDateTime.of(2020, + 10, + 17, + 12, + 0); + PastWorkoutSessionRecord record = new PastWorkoutSessionRecord("random", + date, date, tags); + String in1 = "/d 20201017"; + List expected1 = Arrays.asList(true); + ArrayList testResults1 = new ArrayList<>(); + ArrayList> tests = null; + try { + tests = ps.parseSearchConditions(in1); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults1.add(t.test(record)); + } + assertEquals(expected1, testResults1); + + String in2 = "/d 20201017 /t arms"; + List expected2 = Arrays.asList(false, true); + ArrayList testResults2 = new ArrayList<>(); + try { + tests = ps.parseSearchConditions(in2); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults2.add(t.test(record)); + } + assertEquals(expected2, testResults2); + + String in3 = "/d 20201017 /t legs, ch"; + List expected3 = Arrays.asList(true, true); + ArrayList testResults3 = new ArrayList<>(); + try { + tests = ps.parseSearchConditions(in3); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults3.add(t.test(record)); + } + assertEquals(expected3, testResults3); + } + + @Test + void parseSearchConditions_wrongConditionFormat_emptyChecks() { + List tg = Arrays.asList("legs", "chest"); + ArrayList tags = new ArrayList<>(tg); + + LocalDateTime date = LocalDateTime.of(2020, + 10, + 17, + 12, + 0); + PastWorkoutSessionRecord record = new PastWorkoutSessionRecord("random", + date, date, tags); + String in1 = "20201017 /d"; + List expected1 = new ArrayList<>(); + ArrayList testResults1 = new ArrayList<>(); + ArrayList> tests = null; + try { + tests = ps.parseSearchConditions(in1); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults1.add(t.test(record)); + } + assertEquals(expected1, testResults1); + + String in2 = "20201017 arms"; + List expected2 = new ArrayList<>(); + ArrayList testResults2 = new ArrayList<>(); + try { + tests = ps.parseSearchConditions(in2); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults2.add(t.test(record)); + } + assertEquals(expected2, testResults2); + } + + @Test + void parseCommandKw_correctFormat_success() { + String in1 = "list aa"; + String[] ex1 = {"list", "aa"}; + assertArrayEquals(ex1, ps.parseCommandKw(in1)); + + String in = "list /d 20201025"; + String[] ex2 = {"list", " /d 20201025"}; + assertArrayEquals(ex2, ps.parseCommandKw(in)); + } + + @Test + void parseCommandKw_empty_emptyOutput() { + String in1 = ""; + String[] ex1 = {""}; + assertArrayEquals(ex1, ps.parseCommandKw(in1)); + + } + + @Test + void parseIndex_correctInput_success() throws NotANumberException { + assertEquals(5, ps.parseIndex("5")); + } + + @Test + void parseIndex_inputNotANumber_throwNotANumberException() { + assertThrows(NotANumberException.class, () -> ps.parseIndex("5.0")); + assertThrows(NotANumberException.class, () -> ps.parseIndex("abc")); + assertThrows(NotANumberException.class, () -> ps.parseIndex("")); + assertThrows(NotANumberException.class, () -> ps.parseIndex(null)); + } + + @Test + void parseList_correctFormat_success() { + List tg = Arrays.asList("legs", "chest"); + ArrayList tags = new ArrayList<>(tg); + + LocalDateTime date = LocalDateTime.of(2020, + 10, + 17, + 12, + 0); + PastWorkoutSessionRecord record = new PastWorkoutSessionRecord("random", + date, date, tags); + String in1 = "/s 20201017"; + List expected1 = Arrays.asList(true); + ArrayList testResults1 = new ArrayList<>(); + ArrayList> tests = null; + try { + tests = ps.parseList(in1); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults1.add(t.test(record)); + } + assertEquals(expected1, testResults1); + + String in2 = "/e 20201019"; + List expected2 = Arrays.asList(true); + ArrayList testResults2 = new ArrayList<>(); + try { + tests = ps.parseList(in2); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults2.add(t.test(record)); + } + assertEquals(expected2, testResults2); + + String in3 = "/s 20201017 /e 20201019"; + List expected3 = Arrays.asList(true, true); + ArrayList testResults3 = new ArrayList<>(); + try { + tests = ps.parseList(in3); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults3.add(t.test(record)); + } + assertEquals(expected3, testResults3); + } + + @Test + void parseList_wrongDateFormat_throwInvalidDateFormatException() { + + String in1 = "/s aabb"; + assertThrows(InvalidDateFormatException.class, () -> ps.parseList(in1)); + } + + @Test + void parseList_wrongDateIdentifier_success() { + List tg = Arrays.asList("legs", "chest"); + ArrayList tags = new ArrayList<>(tg); + LocalDateTime date = LocalDateTime.of(2020, + 10, + 17, + 12, + 0); + PastWorkoutSessionRecord record = new PastWorkoutSessionRecord("random", + date, date, tags); + ArrayList> tests = null; + String in2 = "/s 20201017 /e/e"; + List expected2 = Arrays.asList(true); + ArrayList testResults2 = new ArrayList<>(); + try { + tests = ps.parseList(in2); + } catch (InvalidDateFormatException e) { + fail(); + } + + for (Predicate t : tests) { + testResults2.add(t.test(record)); + } + assertEquals(expected2, testResults2); + + } + +} \ No newline at end of file diff --git a/src/test/java/logic/parser/WorkoutSessionParserTest.java b/src/test/java/logic/parser/WorkoutSessionParserTest.java new file mode 100644 index 0000000000..f68fd06d87 --- /dev/null +++ b/src/test/java/logic/parser/WorkoutSessionParserTest.java @@ -0,0 +1,73 @@ +package logic.parser; + +import exceptions.workout.workoutsession.AddFormatException; +import exceptions.workout.workoutsession.DeleteFormatException; +import logic.parser.WorkoutSessionParser; +import models.Exercise; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author yujinyang1998 +class WorkoutSessionParserTest { + + @Test + void workoutSessionParser_validInput_success() { + String[] actual = WorkoutSessionParser.workoutSessionParser("add bench /n 3244 /w 4324"); + String[] expected = {"add", "bench", "/n", "3244", "/w", "4324"}; + assertArrayEquals(expected, actual); + } + + @Test + void workoutSessionParser_emptyString() { + String[] actual = WorkoutSessionParser.workoutSessionParser(""); + String[] expected = {""}; + assertArrayEquals(expected, actual); + } + + @Test + void addParser_validInput_success() throws AddFormatException { + String[] inputString = {"add", "", "bench", "/n", "3244", "/w", "4324"}; + Exercise actual = WorkoutSessionParser.addParser(inputString); + Exercise expected = new Exercise("bench", 3244, 4324); + assertTrue(EqualsBuilder.reflectionEquals(expected, actual)); + } + + @Test + void addParser_invalidInput_negativeRepetitions() { + String[] inputString = {"add", "", "bench", "/n", "-3244", "/w", "4324"}; + assertThrows(AddFormatException.class, () -> WorkoutSessionParser.addParser(inputString)); + } + + @Test + void addParser_invalidInput_formatError() { + String[] inputString = {"add", "", "bench", "/w", "4324"}; + assertThrows(NumberFormatException.class, () -> WorkoutSessionParser.addParser(inputString)); + } + + @Test + void deleteParser_validInput_success() throws DeleteFormatException { + String[] inputString = {"delete", "1"}; + int actual = WorkoutSessionParser.deleteParser(inputString); + int expected = 1; + assertEquals(expected, actual); + } + + @Test + void deleteParser_invalidInput_formatError() { + String[] inputString = {"delete", "fdsf"}; + assertThrows(DeleteFormatException.class, () -> WorkoutSessionParser.deleteParser(inputString)); + } + + @Test + void searchParser_validInput_success() { + String[] inputString = {"search", "test"}; + String actual = WorkoutSessionParser.searchParser(inputString); + String expected = "test"; + assertEquals(expected, actual); + } +} diff --git a/src/test/java/models/ExerciseTest.java b/src/test/java/models/ExerciseTest.java new file mode 100644 index 0000000000..ad2da47390 --- /dev/null +++ b/src/test/java/models/ExerciseTest.java @@ -0,0 +1,36 @@ +package models; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExerciseTest { + + @Test + void testToString() { + Exercise exerciseActual = new Exercise("benchpress", 432, 423.34); + String expected = "benchpress, Repetitions:432, Weight=423.34"; + assertEquals(expected, exerciseActual.toString()); + } + + @Test + void getDescription() { + Exercise exerciseActual = new Exercise("benchpress", 432, 423.34); + String expected = "benchpress"; + assertEquals(expected, exerciseActual.getDescription()); + } + + @Test + void getRepetitions() { + Exercise exerciseActual = new Exercise("benchpress", 432, 423.34); + String expected = "432"; + assertEquals(expected, exerciseActual.getRepetitions()); + } + + @Test + void getWeight() { + Exercise exerciseActual = new Exercise("benchpress", 432, 423.34); + String expected = "423.34"; + assertEquals(expected, exerciseActual.getWeight()); + } +} \ No newline at end of file diff --git a/src/test/java/models/ProfileTest.java b/src/test/java/models/ProfileTest.java new file mode 100644 index 0000000000..b525134e8f --- /dev/null +++ b/src/test/java/models/ProfileTest.java @@ -0,0 +1,131 @@ +package models; + +import org.junit.jupiter.api.Test; + +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.assertThrows; +import static ui.CommonUi.EMPTY_STRING; +import static profile.Constants.EXAMPLE_BMI; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static profile.Constants.PROFILE_STRING_REPRESENTATION; + +//@@author tienkhoa16 +class ProfileTest { + private static final Profile SAMPLE_PROFILE = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + private static final String EXAMPLE_PROFILE_STRING = String.format(PROFILE_STRING_REPRESENTATION, EXAMPLE_NAME, + EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES, EXAMPLE_BMI); + + @Test + void testGetBmiClassification_underweightInput_returnUnderweight() { + Profile testProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 54, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals("15.3 (Underweight)", testProfile.getBmiClassification()); + } + + @Test + void testGetBmiClassification_normalWeightInput_returnNormalWeight() { + Profile testProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 70, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals("19.8 (Normal Weight)", testProfile.getBmiClassification()); + } + + @Test + void testGetBmiClassification_overweightInput_returnOverweight() { + Profile testProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 100, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals("28.3 (Overweight)", testProfile.getBmiClassification()); + } + + @Test + void testGetBmiClassification_obesityClassOneInput_returnObesityClassOne() { + Profile testProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 113, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals("32.0 (Obesity Class 1)", testProfile.getBmiClassification()); + } + + @Test + void testGetBmiClassification_obesityClassTwoInput_returnObesityClassTwo() { + Profile testProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 127, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals("35.9 (Obesity Class 2)", testProfile.getBmiClassification()); + } + + @Test + void testGetBmiClassification_extremeObesityClassThreeInput_returnExtremeObesityClassThree() { + Profile testProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, 148, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals("41.9 (Extreme Obesity Class 3)", testProfile.getBmiClassification()); + } + + @Test + void testEquals_inputTwoIdenticalProfiles_returnTrue() { + Profile sampleProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_EXPECTED_WEIGHT, + EXAMPLE_CALORIES); + Profile profileToCompare = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_EXPECTED_WEIGHT, + EXAMPLE_CALORIES); + assertEquals(sampleProfile, profileToCompare); + } + + @Test + void testEquals_inputDifferentNames_returnFalse() { + Profile profileToCompare = new Profile("Duke", EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertFalse(SAMPLE_PROFILE.equals(profileToCompare)); + } + + @Test + void testEquals_inputDifferentHeights_returnFalse() { + Profile profileToCompare = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT + 1, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertNotEquals(profileToCompare, SAMPLE_PROFILE); + } + + @Test + void testEquals_inputDifferentWeights_returnFalse() { + Profile profileToCompare = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT + 1, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertNotEquals(profileToCompare, SAMPLE_PROFILE); + } + + @Test + void testEquals_inputDifferentExpectedWeights_returnFalse() { + Profile profileToCompare = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT + 1, EXAMPLE_CALORIES); + assertNotEquals(profileToCompare, SAMPLE_PROFILE); + } + + @Test + void testEquals_inputDifferentCalories_returnFalse() { + Profile profileToCompare = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES + 1); + assertNotEquals(profileToCompare, SAMPLE_PROFILE); + } + + @Test + void testEquals_inputSameProfileObject_returnTrue() { + assertEquals(SAMPLE_PROFILE, SAMPLE_PROFILE); + } + + @Test + void testEquals_inputObjectOfDifferentClass_returnFalse() { + assertNotEquals(SAMPLE_PROFILE, new StringBuilder()); + } + + @Test + void testEquals_inputNull_returnFalse() { + assertNotEquals(SAMPLE_PROFILE, null); + } + + @Test + void testToString_validProfile_returnValidProfileString() { + assertEquals(EXAMPLE_PROFILE_STRING, SAMPLE_PROFILE.toString()); + } + + @Test + void testToString_invalidProfile_throwsAssertionError() { + Profile invalidProfile = new Profile(EMPTY_STRING, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertThrows(AssertionError.class, invalidProfile::toString); + } +} diff --git a/src/test/java/profile/ProfileSessionTest.java b/src/test/java/profile/ProfileSessionTest.java new file mode 100644 index 0000000000..b9cd676f18 --- /dev/null +++ b/src/test/java/profile/ProfileSessionTest.java @@ -0,0 +1,195 @@ +package profile; + +import diet.dietsession.DietSession; +import exceptions.EndException; +import exceptions.InsufficientArgumentException; +import exceptions.InvalidCommandWordException; +import exceptions.InvalidDateFormatException; +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidCommandFormatException; +import logic.commands.CommandResult; +import models.Profile; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import storage.diet.DietStorage; +import storage.profile.ProfileStorage; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static profile.Constants.ADD_PROFILE_FORMAT; +import static profile.Constants.EDIT_PROFILE_FORMAT; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static seedu.duke.Constants.COMMAND_WORD_ADD; +import static seedu.duke.Constants.COMMAND_WORD_DELETE; +import static seedu.duke.Constants.COMMAND_WORD_EDIT; +import static seedu.duke.Constants.COMMAND_WORD_END; +import static seedu.duke.Constants.COMMAND_WORD_VIEW; +import static seedu.duke.Constants.PATH_TO_PROFILE_FILE; +import static seedu.duke.Constants.PATH_TO_PROFILE_FOLDER; +import static ui.CommonUi.EMPTY_STRING; +import static ui.CommonUi.helpFormatter; +import static ui.profile.ProfileUi.MESSAGE_CREATE_PROFILE_ACK; +import static ui.profile.ProfileUi.MESSAGE_DELETE_PROFILE; +import static ui.profile.ProfileUi.MESSAGE_EDIT_PROFILE_ACK; +import static ui.profile.ProfileUi.MESSAGE_MORE_CALORIES; +import static ui.profile.ProfileUi.MESSAGE_SET_EXPECTED_WEIGHT; +import static ui.profile.ProfileUi.MESSAGE_VIEW_PROFILE; + +//@@author tienkhoa16 +class ProfileSessionTest { + private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private static final String TEST_SAVES_FOLDER_DIET = "saves/diet/"; + private static final String TEST_SAVE_NAME = DTF.format(LocalDate.now()) + " breakfast"; + + private static final Profile SAMPLE_PROFILE = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + private static ProfileSession profileSession = ProfileSession.getInstance(PATH_TO_PROFILE_FOLDER, + PATH_TO_PROFILE_FILE); + + private static final double EXAMPLE_NORMAL_EXPECTED_WEIGHT = 76.7; + + @BeforeAll + static void initData() throws IOException, InvalidDateFormatException { + DietStorage dietStorage = new DietStorage(); + dietStorage.init(TEST_SAVES_FOLDER_DIET, TEST_SAVE_NAME); + DietSession savedInstance = new DietSession("breakfast", DTF.format(LocalDate.now()), true, 1); + savedInstance.saveToFile(TEST_SAVES_FOLDER_DIET, dietStorage, savedInstance); + + File file = new File(PATH_TO_PROFILE_FILE.toString()); + file.getParentFile().mkdirs(); + file.createNewFile(); + } + + @Test + void testProcessCommand_inputNull_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + profileSession.processCommand(null); + }); + } + + @Test + void testProcessCommand_inputEmptyString_throwsAssertionError() { + assertThrows(AssertionError.class, () -> { + profileSession.processCommand(EMPTY_STRING); + }); + } + + @Test + void testProcessCommand_inputWrongCommand_throwsInvalidCommandWordException() { + String userInput = "wrong command"; + assertThrows(InvalidCommandWordException.class, () -> { + profileSession.processCommand(userInput); + }); + } + + @Test + void testProcessCommand_inputValidAddProfileCommand_returnSuccess() throws SchwarzeneggerException { + String userInput = "add /n Schwarzenegger /h 188 /w 113 /e 100 /c 2500"; + ProfileStorage storage = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + storage.saveData(null); + + String successMsg = String.format(MESSAGE_CREATE_PROFILE_ACK, SAMPLE_PROFILE.toString()); + assertEquals(successMsg, profileSession.processCommand(userInput).getFeedbackMessage()); + } + + @Test + void testProcessCommand_inputInvalidAddProfileCommand_throwsInsufficientArgumentException() + throws SchwarzeneggerException { + String userInput = "add /n Schwarzenegger"; + ProfileStorage storage = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + storage.saveData(null); + + assertThrows(InsufficientArgumentException.class, () -> { + profileSession.processCommand(userInput); + }); + } + + @Test + void testProcessCommand_inputValidDeleteProfileCommand_returnSuccess() throws SchwarzeneggerException { + String userInput = "delete"; + ProfileStorage storage = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + storage.saveData(SAMPLE_PROFILE); + + System.setIn(new ByteArrayInputStream("YES".getBytes())); + CommandResult result = profileSession.processCommand(userInput); + System.setIn(System.in); + + assertEquals(MESSAGE_DELETE_PROFILE, result.getFeedbackMessage()); + } + + @Test + void testProcessCommand_inputValidEditProfileCommand_returnSuccess() throws SchwarzeneggerException { + String userInput = "edit /n Arnold"; + ProfileStorage storage = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + storage.saveData(SAMPLE_PROFILE); + + Profile editedProfile = new Profile("Arnold", EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + String successMsg = String.format(MESSAGE_EDIT_PROFILE_ACK, editedProfile.toString()); + + assertEquals(successMsg, profileSession.processCommand(userInput).getFeedbackMessage()); + } + + @Test + void testProcessCommand_inputInvalidEditProfileCommand_throwsInvalidCommandFormatException() + throws SchwarzeneggerException { + String userInput = "edit"; + ProfileStorage storage = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + storage.saveData(SAMPLE_PROFILE); + + assertThrows(InvalidCommandFormatException.class, () -> { + profileSession.processCommand(userInput); + }); + } + + @Test + void testProcessCommand_inputValidEndProfileCommand_throwsEndException() { + String userInput = "end"; + assertThrows(EndException.class, () -> { + profileSession.processCommand(userInput); + }); + } + + @Test + void testProcessCommand_inputValidHelpProfileCommand_returnHelpMessage() throws SchwarzeneggerException { + StringBuilder helpMessage = new StringBuilder(); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_ADD), ADD_PROFILE_FORMAT, + "Add your new profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_VIEW), COMMAND_WORD_VIEW, + "View your profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_EDIT), EDIT_PROFILE_FORMAT, + "Edit your existing profile. You may edit from 1 field to all fields")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_DELETE), COMMAND_WORD_DELETE, + "Delete your existing profile")); + helpMessage.append(helpFormatter(StringUtils.capitalize(COMMAND_WORD_END), COMMAND_WORD_END, + "Go back to Main Menu")); + String userInput = "help"; + assertEquals(helpMessage.toString().trim(), profileSession.processCommand(userInput).getFeedbackMessage()); + } + + @Test + void testProcessCommand_inputValidViewProfileCommand_returnSuccess() throws SchwarzeneggerException { + ProfileStorage profileStorage = new ProfileStorage(PATH_TO_PROFILE_FOLDER, PATH_TO_PROFILE_FILE); + profileStorage.saveData(SAMPLE_PROFILE); + + String sampleCaloriesMsg = String.format(MESSAGE_MORE_CALORIES, EXAMPLE_CALORIES); + String weightTip = String.format(MESSAGE_SET_EXPECTED_WEIGHT, + EXAMPLE_NORMAL_EXPECTED_WEIGHT, EXAMPLE_NORMAL_EXPECTED_WEIGHT); + + String userInput = "view"; + assertEquals(String.format(MESSAGE_VIEW_PROFILE, SAMPLE_PROFILE.toString(), sampleCaloriesMsg, weightTip), + profileSession.processCommand(userInput).getFeedbackMessage()); + } +} diff --git a/src/test/java/profile/UtilsTest.java b/src/test/java/profile/UtilsTest.java new file mode 100644 index 0000000000..d2a9858761 --- /dev/null +++ b/src/test/java/profile/UtilsTest.java @@ -0,0 +1,84 @@ +package profile; + +import models.Profile; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; +import static profile.Constants.HEIGHT_LOWER_BOUND; +import static profile.Constants.HEIGHT_UPPER_BOUND; +import static profile.Constants.WEIGHT_LOWER_BOUND; +import static profile.Constants.WEIGHT_UPPER_BOUND; +import static profile.Utils.checkValidName; +import static profile.Utils.checkValidProfile; + +//@@author tienkhoa16 +class UtilsTest { + + @Test + void testCheckValidProfile_validProfile_returnTrue() { + Profile validProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertTrue(checkValidProfile(validProfile)); + } + + @Test + void testCheckValidProfile_nullInput_returnFalse() { + assertFalse(checkValidProfile(null)); + } + + @Test + void testCheckValidProfile_heightLessThanLowerBound_returnFalse() { + Profile inValidProfile = new Profile(EXAMPLE_NAME, HEIGHT_LOWER_BOUND - 1, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertFalse(checkValidProfile(inValidProfile)); + } + + @Test + void testCheckValidProfile_heightGreaterThanUpperBound_returnFalse() { + Profile inValidProfile = new Profile(EXAMPLE_NAME, HEIGHT_UPPER_BOUND + 1, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertFalse(checkValidProfile(inValidProfile)); + } + + @Test + void testCheckValidProfile_weightLessThanLowerBound_returnFalse() { + Profile inValidProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, WEIGHT_LOWER_BOUND - 1, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertFalse(checkValidProfile(inValidProfile)); + } + + @Test + void testCheckValidProfile_weightGreaterThanUpperBound_returnFalse() { + Profile inValidProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, WEIGHT_UPPER_BOUND + 1, + EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertFalse(checkValidProfile(inValidProfile)); + } + + @Test + void testCheckValidProfile_negativeCalories_returnFalse() { + Profile inValidProfile = new Profile(EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, + EXAMPLE_EXPECTED_WEIGHT, -1); + assertFalse(checkValidProfile(inValidProfile)); + } + + @Test + void testCheckValidName_validInput_returnTrue() { + assertTrue(checkValidName("Schwarzenegger")); + } + + @Test + void testCheckValidName_emptyInput_returnFalse() { + assertFalse(checkValidName("")); + } + + @Test + void testCheckValidName_nullInput_returnFalse() { + assertFalse(checkValidName(null)); + } +} diff --git a/src/test/java/saves/ProfileCommandsTest/diet/2020-11-07 lunch.json b/src/test/java/saves/ProfileCommandsTest/diet/2020-11-07 lunch.json new file mode 100644 index 0000000000..61b93bc37e --- /dev/null +++ b/src/test/java/saves/ProfileCommandsTest/diet/2020-11-07 lunch.json @@ -0,0 +1,33 @@ +{ + "foodList": [ + { + "name": "fish", + "calories": 100.0 + }, + { + "name": "chicken", + "calories": 200.0 + }, + { + "name": "beef", + "calories": 200.0 + }, + { + "name": "rice", + "calories": 2000.0 + } + ], + "dateInput": "07-11-2020", + "typeInput": "lunch", + "date": { + "year": 2020, + "month": 11, + "day": 7 + }, + "isNew": true, + "index": -1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} diff --git a/src/test/java/saves/ProfileCommandsTest/diet/2020-11-08 breakfast.json b/src/test/java/saves/ProfileCommandsTest/diet/2020-11-08 breakfast.json new file mode 100644 index 0000000000..0a7b734302 --- /dev/null +++ b/src/test/java/saves/ProfileCommandsTest/diet/2020-11-08 breakfast.json @@ -0,0 +1,25 @@ +{ + "foodList": [ + { + "name": "fish", + "calories": 100.0 + }, + { + "name": "chicken", + "calories": 200.0 + } + ], + "dateInput": "08-11-2020", + "typeInput": "breakfast", + "date": { + "year": 2020, + "month": 11, + "day": 8 + }, + "isNew": true, + "index": -1, + "dietSessionUi": {}, + "storage": {}, + "parser": {}, + "endDietSession": false +} diff --git a/src/test/java/saves/ProfileCommandsTest/profileDataFile.json b/src/test/java/saves/ProfileCommandsTest/profileDataFile.json new file mode 100644 index 0000000000..7a6e58f9b4 --- /dev/null +++ b/src/test/java/saves/ProfileCommandsTest/profileDataFile.json @@ -0,0 +1,7 @@ +{ + "name": "Schwarzenegger", + "height": 188, + "weight": 113.0, + "expectedWeight": 76.7, + "calories": 2500.0 +} \ No newline at end of file diff --git a/src/test/java/saves/ProfileStorageTest/corruptedProfileData.json b/src/test/java/saves/ProfileStorageTest/corruptedProfileData.json new file mode 100644 index 0000000000..f80385ab5d --- /dev/null +++ b/src/test/java/saves/ProfileStorageTest/corruptedProfileData.json @@ -0,0 +1,4 @@ +{ + "name": "Schwarzenegger", + "age": 30, +} diff --git a/src/test/java/saves/ProfileStorageTest/exampleProfileData.json b/src/test/java/saves/ProfileStorageTest/exampleProfileData.json new file mode 100644 index 0000000000..ea3d6bbc3f --- /dev/null +++ b/src/test/java/saves/ProfileStorageTest/exampleProfileData.json @@ -0,0 +1,7 @@ +{ + "name": "Schwarzenegger", + "height": 188, + "weight": 113.0, + "expectedWeight": 100.0, + "calories": 2500 +} diff --git a/src/test/java/saves/ProfileStorageTest/invalidProfileData.json b/src/test/java/saves/ProfileStorageTest/invalidProfileData.json new file mode 100644 index 0000000000..3ed949fd5e --- /dev/null +++ b/src/test/java/saves/ProfileStorageTest/invalidProfileData.json @@ -0,0 +1,7 @@ +{ + "name": "", + "height": 188, + "weight": 113.0, + "expectedWeight": 100.0, + "calories": 2500 +} diff --git a/src/test/java/storage/profile/ProfileStorageTest.java b/src/test/java/storage/profile/ProfileStorageTest.java new file mode 100644 index 0000000000..2bf94f84d7 --- /dev/null +++ b/src/test/java/storage/profile/ProfileStorageTest.java @@ -0,0 +1,48 @@ +package storage.profile; + +import exceptions.SchwarzeneggerException; +import exceptions.profile.InvalidSaveFormatException; +import models.Profile; +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static profile.Constants.EXAMPLE_CALORIES; +import static profile.Constants.EXAMPLE_EXPECTED_WEIGHT; +import static profile.Constants.EXAMPLE_HEIGHT; +import static profile.Constants.EXAMPLE_NAME; +import static profile.Constants.EXAMPLE_WEIGHT; + +//@@author tienkhoa16 +class ProfileStorageTest { + private static final Path TEST_SAVES_FOLDER = Paths.get("src", "test", "java", "saves", "ProfileStorageTest"); + + @Test + void testDecodeProfile_exampleProfileData_returnsExampleProfileString() throws SchwarzeneggerException, + FileNotFoundException { + Path inputFilePath = Paths.get(TEST_SAVES_FOLDER.toString(), "exampleProfileData.json"); + ProfileStorage storage = new ProfileStorage(TEST_SAVES_FOLDER, inputFilePath); + Profile testProfile = storage.decodeProfile(); + Profile exampleProfile = new Profile( + EXAMPLE_NAME, EXAMPLE_HEIGHT, EXAMPLE_WEIGHT, EXAMPLE_EXPECTED_WEIGHT, EXAMPLE_CALORIES); + assertEquals(exampleProfile, testProfile); + } + + @Test + void testDecodeProfile_corruptedDataInput_throwsInvalidSaveFormatException() { + Path inputFilePath = Paths.get(TEST_SAVES_FOLDER.toString(), "corruptedProfileData.json"); + ProfileStorage storage = new ProfileStorage(TEST_SAVES_FOLDER, inputFilePath); + assertThrows(InvalidSaveFormatException.class, storage::decodeProfile); + } + + @Test + void testDecodeProfile_invalidDataInput_throwsInvalidSaveFormatException() { + Path inputFilePath = Paths.get(TEST_SAVES_FOLDER.toString(), "invalidProfileData.json"); + ProfileStorage storage = new ProfileStorage(TEST_SAVES_FOLDER, inputFilePath); + assertThrows(InvalidSaveFormatException.class, storage::decodeProfile); + } +} diff --git a/src/test/java/storage/workout/WorkoutSessionStorageTest.java b/src/test/java/storage/workout/WorkoutSessionStorageTest.java new file mode 100644 index 0000000000..56de512672 --- /dev/null +++ b/src/test/java/storage/workout/WorkoutSessionStorageTest.java @@ -0,0 +1,46 @@ +package storage.workout; + +import exceptions.workout.workoutmanager.SchwIoException; +import models.Exercise; +import models.ExerciseList; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +//@@author yujinyang1998 +class WorkoutSessionStorageTest { + + @Test + void readFileContents() throws SchwIoException, IOException { + ExerciseList actualExerciseList = new ExerciseList(); + ExerciseList expectedExerciseList = new ExerciseList(); + + WorkOutManagerStorage workOutManagerStorage = new WorkOutManagerStorage(); + String filePath = workOutManagerStorage.createfile(); + + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + + expectedExerciseList.exerciseList.add(new Exercise("ben", 547, 567)); + + workoutSessionStorage.writeToStorage(filePath, expectedExerciseList); + workoutSessionStorage.readFileContents(filePath, actualExerciseList); + assertEquals(expectedExerciseList.exerciseList.toString(), actualExerciseList.exerciseList.toString()); + } + + @Test + void writeToStorage_inputNull_assert() { + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + + assertThrows(AssertionError.class, () -> workoutSessionStorage.readFileContents(null, null)); + } + + @Test + void readFileContents_inputNull_assert() { + WorkoutSessionStorage workoutSessionStorage = new WorkoutSessionStorage(); + + assertThrows(AssertionError.class, () -> workoutSessionStorage.writeToStorage(null, null)); + } +} \ No newline at end of file diff --git a/src/test/java/ui/workout/workoutsession/WorkoutSessionUiTest.java b/src/test/java/ui/workout/workoutsession/WorkoutSessionUiTest.java new file mode 100644 index 0000000000..78a16095a9 --- /dev/null +++ b/src/test/java/ui/workout/workoutsession/WorkoutSessionUiTest.java @@ -0,0 +1,55 @@ +package ui.workout.workoutsession; + +import models.Exercise; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static ui.CommonUi.helpFormatter; + +//@@author yujinyang1998 +class WorkoutSessionUiTest { + + @Test + void saveCorruptedError() { + String filePath = "save/workout/"; + String actual = WorkoutSessionUi.saveCorruptedError(filePath); + String expected = ":( Save format in save/workout/ is invalid.\n" + + "\t File is cleared."; + assertEquals(expected, actual); + } + + @Test + void printHelp() { + String actual = WorkoutSessionUi.printHelp(); + String expected = (helpFormatter("Add", "add [NAME_OF_MOVE] /n [NUMBER_OF_REPETITIONS] /w [WEIGHT]", + "Add a new move") + + helpFormatter("List", "list", + "Show all moves in this current session") + + helpFormatter("Delete", "delete [INDEX]", + "Delete a move according to the index in the list") + + helpFormatter("Search", "search [NAME_OF_MOVE]", + "Show a list of moves that match the entered keyword") + + helpFormatter("End", "end", + "Go back to the Workout Menu")).trim(); + + assertEquals(expected, actual); + } + + @Test + void addExerciseSuccess() { + Exercise exercise = new Exercise("benchpress", 435, 432.432); + String actual = WorkoutSessionUi.addExerciseSuccess(exercise); + String expected = "Yay! You have added benchpress to your list.\n" + + "\t [Repetitions: 435 || Weight: 432.432]"; + assertEquals(expected, actual); + } + + @Test + void deleteExerciseSuccess() { + Exercise exercise = new Exercise("benchpress", 435, 432.432); + String actual = WorkoutSessionUi.deleteExerciseSuccess(exercise); + String expected = "You have deleted benchpress from your list!\n" + + "\t [Repetitions: 435 || Weight: 432.432]"; + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT deleted file mode 100644 index 892cb6cae7..0000000000 --- a/text-ui-test/EXPECTED.TXT +++ /dev/null @@ -1,9 +0,0 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt deleted file mode 100644 index f6ec2e9f95..0000000000 --- a/text-ui-test/input.txt +++ /dev/null @@ -1 +0,0 @@ -James Gosling \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat deleted file mode 100644 index 25ac7a2989..0000000000 --- a/text-ui-test/runtest.bat +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -setlocal enableextensions -pushd %~dp0 - -cd .. -call gradlew clean shadowJar - -cd build\libs -for /f "tokens=*" %%a in ( - 'dir /b *.jar' -) do ( - set jarloc=%%a -) - -java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TXT - -cd ..\..\text-ui-test - -FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh deleted file mode 100755 index 1dcbd12021..0000000000 --- a/text-ui-test/runtest.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# change to script directory -cd "${0%/*}" - -cd .. -./gradlew clean shadowJar - -cd text-ui-test - -java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT - -cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix EXPECTED-UNIX.TXT ACTUAL.TXT -diff EXPECTED-UNIX.TXT ACTUAL.TXT -if [ $? -eq 0 ] -then - echo "Test passed!" - exit 0 -else - echo "Test failed!" - exit 1 -fi