diff --git a/.gitignore b/.gitignore index 2873e189e1..0d4f3e6668 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +/florizz-out/logs +/florizz-out/data +/florizz-out/saved diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..0528ff6198 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: florizz.core.Florizz + diff --git a/README.md b/README.md index f82e2494b7..e60f15fc0d 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,8 @@ -# Duke project template +# Florizz -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. +An easy-to-use bouquet recommendation software using CLI. -## Setting up in Intellij - -Prerequisites: JDK 11 (use the exact version), update Intellij to the most recent version. - -1. **Ensure Intellij JDK 11 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 11 in a previous Intellij project. -1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -1. **Verify the 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 - > Task :processResources NO-SOURCE - > Task :classes - - > Task :Duke.main() - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - - What is your name? - ``` - Type some word and press enter to let the execution proceed to the end. - -## Build automation using Gradle - -* This project uses Gradle for build automation and dependency management. It includes a basic build script as well (i.e. the `build.gradle` file). -* If you are new to Gradle, refer to the [Gradle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/gradle.html). - -## Testing - -### I/O redirection tests - -* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script. - -### JUnit tests - -* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template. -* If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html). - -## Checkstyle - -* A sample CheckStyle rule configuration is provided in this project. -* If you are new to Checkstyle, refer to the [Checkstyle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/checkstyle.html). - -## CI using GitHub Actions - -The project uses [GitHub actions](https://github.com/features/actions) for CI. When you push a commit to this repo or PR against it, GitHub actions will run automatically to build and verify the code as updated by the commit/PR. - -## Documentation - -`/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. +Useful links: +* [User Guide](docs/UserGuide.md) +* [Developer Guide](docs/DeveloperGuide.md) +* [About Us](docs/AboutUs.md) diff --git a/build.gradle b/build.gradle index ea82051fab..0579ab08b4 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + implementation('com.github.freva:ascii-table:1.8.0') } test { @@ -29,11 +30,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("florizz.core.Florizz") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("florizz") archiveClassifier.set("") } @@ -43,4 +44,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..c8b632d174 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:-------------------:|:----------------------------------------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Ian Freda Hariyanto | [Github](https://github.com/IanFH) | [Portfolio](team/ianfh.md) +![](https://via.placeholder.com/100.png?text=Photo) | Mohamed Ijaaz | [Github](https://github.com/Ijaaz01) | [Portfolio](team/ijaaz01.md) +![](https://via.placeholder.com/100.png?text=Photo) | Jeffinson Darmawan | [Github](https://github.com/JeffinsonDarmawan) | [Portfolio](team/jeffinsondarmawan.md) +![](https://via.placeholder.com/100.png?text=Photo) | Samuel Ong | [Github](https://github.com/samuelory) | [Portfolio](team/samuelory.md) + diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..39fa872424 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,230 @@ # Developer Guide ## Acknowledgements +Florizz implemented a third-party JTextUtil ASCII-Table library to create ASCII tables for the `help`, `compare` and `info` +command. The library can be found at [ASCII-Table GitHub Page](https://github.com/freva/ascii-table). -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +## Design +Florizz consists of four components, Ui, Logic, Model and Storage. These components are all called on by the Main component, Florizz.java, which simply handles the app initialisation. -## Design & implementation +App initialisation: Initialises the various components, then begins reading inputs from the user -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +![Architecture](UML-diagrams/Samuel/Architecture.png) +`Ui`: takes in user inputs from the CLI and sends it to the Logic component. Also handles all writing of messages to the user -## Product scope -### Target user profile +`Logic`: takes in string input from Ui, and parses it into commands to be executed on variables stored in the model. Calls Ui to provide the user with feedback that the action has or has not been completed. -{Describe the target user profile} +`Model`: Handles all the cached variables, i.e. the list of Bouquets created by the user, the list of available Flowers that the user can add to their Bouquets -### Value proposition +`Storage`: Handles storing and reading from hardware, such as saving the list of bouquets created by the user so they can access it later. -{Describe the value proposition: what problem does it solve?} +## Implementation -## User Stories +### Adding/Removing Bouquets + +![AddBouquets](UML-diagrams/Samuel/AddBouquets.png) +To add a new bouquet to the list of Bouquets saved in the BouquetList variable, the user would have to type `add ` into the CLI, which is then handled by `Ui.getInput` and passed to `Parser`, which +creates a Command `Addcommand` with a new Bouquet of that name as an argument. This command is then sent back to `Florizz`, the main class, where it is then executed. + +If a bouquet of the same name already exists in BouquetList, an exception will be thrown and the user will be prompted to choose another name. This is facilitated by overriding the .equals() method in the `Bouquet` class such that +when two Bouquets are compared, .equals() returns true when they have the same name, regardless of which flowers are contained within each `Bouquet`. This decision was made mainly to facilitate `DeleteBouquetCommand`, +so we can simply call BouquetList.remove(BouquetToRemove) and the correct bouquet will be removed even if the BouquetToRemove is simply an empty `Bouquet` with the same name as the one in the list. + +After adding the `Bouquet` to `BouquetList`, the new `Bouquet` is then sent to `ui.printBouquetAdded`, where a confirmation message will be printed to the user. + +Removing Bouquets follows a similar logic, just that instead of calling `BouquetList.add(Bouquet)`, executing `DeleteBouquetCommand` calls `BouquetList.remove(Bouquet)`, removing the `Bouquet` as mentioned above. + +### Flower Information Command + +`info ` command prints information about the specified flower + +![Info Command Sequence Diagram](UML-diagrams/Ian/InfoCommandUML.png) + +Step 1: Flower information mechanism utilize the `parser` class to parse user command for a specific flower name inputted. + +Step 2: Once `parser` detect that `info` keyword is used, it will instantiate `InfoCommand` class and run its `execute()` method. + +Step 3: `InfoCommand` class will call `printFlowerInfo()` method of `Ui` class + +Step 4: `get()` of `FlowerDictionary` class will then be called in order to retrive information about the specified flower. This information will be printed by the `Ui` class + +### Flower Help Command + +`help` Shows a list of valid commands + +![Help Command Sequence Diagram](UML-diagrams/Jeffinson/Jeffinson-UML-Help.png); + +Help command utilises the `parser` class to identify the keyword `help` + +**Step 1:** User will write `help` and `HelpCommand` class will be instantiated and `execute()` method will run. + +**Step 2:** `HelpCommand` class will call `printHelpMessage()` method of `Ui` class + +**Step 3:** The list of valid commands will be printed by the `Ui` class + +### Add Flower Command + +`add /q /to ` command adds specified number of flower to a bouquet + +![Add Flower Command Diagram](UML-diagrams/Ijaaz/AddFlowerCommand.png) + +**Step 1:** Firstly the input is put into the `Parser.parse()` method to identify that it is infact an add flower command. + +**Step 2:** Then an object of type `AddFlowerCommand` is returned which contains the name, quantity of the flower to be added, as well as the target bouquet + +**Step 3:** The `execute()` method is called to execute the add flower command + +**Step 4:** The target bouquet, which is under model, is updated accordingly + +**Step 5:** A confirmation message is then sent back to the user + +### Remove Flower Command + +`remove /q /from ` removes flower(s) from a bouquet + +![Remove Command Sequence Diagram](UML-diagrams/Jeffinson/Jeffinson-UML-Remove.png) + +Removing flowers utilise the `parser` class to parse user command to identify which flower to remove, +how much to remove and from which bouquet to remove. + +**Step 1:** User will input the flower name, quantity and bouquet name into the `Parser.parser()` method, after which `RemoveFlowerCommand` +class will be instantiated and run its `execute()` method. + +**Step 2:** An object of type `RemoveFlowerCommand` is returned which contains the name, quantity of the flower to be added, and the target bouquet -|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| +**Step 3:** Within the `RemoveFlowerCommand`, `execute()` method is called to execute the removal of flower command -## Non-Functional Requirements +**Step 4:** The flowers are removed and a confirmation message is sent back to the user -{Give non-functional requirements} +### Recommend Command -## Glossary +`recommend` recommends a bouquet based on user input -* *glossary item* - Definition +![Recommend Command Sequence Diagram](UML-diagrams/Ian/RecommendCommand.png) +*simplified version of the sequence diagram* -## Instructions for manual testing +**Step 1:** User will input `recommend` into the CLI, which will be parsed by the `parser` class to identify. -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +**Step 2:** `RecommendCommand` class will be instantiated and run its `execute()` method. + +**Step 3:** Various methods will be called to get the user input. In order, the methods are `askOccasion(...)`, `askColour(...)`, `askSize(...)`, `askBouquetName(...)`, `askSize()`, `addRandomFlowers()`, `askSaveBouquet()`. + +#### askOccasion(...) + +A deeper look into the `askOccasion(...)` method + +![askOccasion Sequence Diagram](UML-diagrams/Ian/askOccasion.png) + +**Step 1:** `askOccasion(...)` method will call `Ui.askOccasion()` to get the user input. + +**Step 2:** `askOccasion(...)` method will call `Parser` to validate the format of user input. + +**Step 3:** `askOccasion(...)` method will check with `Flower` class to see if the occasion is valid. + +**Step 4:** once format is validated and the occasion inputted is valid, the method will return the occasion. + +#### askColour(...), askSize(...), askBouquetName(...), askSize(...), addRandomFlowers(...), askSaveBouquet() + +The other methods will follow a similar structure as `askOccasion(...)` method, where the user input is taken, validated, and then stored in the `RecommendCommand` class to be further processed. + +### Compare Flower Command + +`compare /vs/ ` command compares two flowers based on their flowers names. + +![Compare Flower Command Diagram](UML-diagrams/Jeffinson/Jeffinson-UML-compare.png) + +Step 1: User will input the two flower names into the `Parser.parse()` method to identify the `compare` command keyword +and the presence of the 2 flowers in the userInput to compare + +Step 2: An object of type `CompareCommand` is then returned which contains the names of the two flowers to be compared. + +Step 3: The `execute()` method is called to execute the compare command + +Step 4: Ui.printCompareFlowers() is called to print the comparison of the two flowers + +Step 5: A table is printed out to the user showing the comparison between the two flowers + +### Save Command + +`save ` command adds specified number of flower to a bouquet + +![Save Command Diagram](UML-diagrams/Ijaaz/SaveCommand.png) + +Step 1: Firstly the input is put into the `Parser.parse()` method to identify that it is infact a save command. + +Step 2: Then an object of type `SaveCommand` is returned which contains the name of the bouquet to be saved + +Step 3: The `execute()` method is called to execute the save command + +Step 4: The target bouquet is then sent to Storage to be saved locally + +Step 5: A confirmation message is then sent back to the user + +# Appendix: Requirements + +## Product scope + +### Target user profile: +- has a need of purchasing a bouquet +- prefers desktop apps over other types +- can type reasonably fast +- prefers typing to mouse interactions +- is comfortable using CLI apps + +### Value proposition: +- organise flowers faster than using a GUI driven app +- curate flowers automatically based on context given through typed text + +## User Stories +Priorities: High (must have) `* * *`, Medium (nice to have) `* *`, Low (unlikely to have) `*` + +| No | As a ... | I can ... | So that I can ... | Priority | +|-----|-----------------|--------------------------------------------------|-------------------------------------------------------------------------------|----------| +| 1 | first-time user | see an intro message detailing a command flow | easily know what commands to type | *** | +| 2 | user | add flowers into my list | easily manage the list | *** | +| 3 | user | remove flowers from my list | update my list of flowers | *** | +| 4 | user | navigate a list of flowers | easily know what flowers to add to my bouquet | *** | +| 5 | user | specify the occasion I am buying flowers for | get suggestion on appropriate flowers to buy without wasting time researching | *** | +| 6 | user | make new bouquets | make multiple bouquets for different occasion | *** | +| 7 | advanced user | see detailed information of each flower | be more knowledgeable in the flowers I buy | ** | +| 8 | user | save my bouquets to hardware | view it for a later date | ** | +| 9 | user | export my list of flowers to readable files | save and share it externally | ** | +| 10 | advanced user | mix and match flowers manually | have a customised bouquet | ** | +| 11 | user | filter flowers based on colours | add matching flowers into my bouquet | ** | +| 12 | first-time user | type `help` for usage of command | better use the app | ** | +| 13 | user | view an image of specified flower | better visualise how the bouquet will look like | ** | +| 14 | user | calculate the approximate cost of bouquets | better budget my bouquet | * | +| 15 | user | ask how to prepare bouquets | make my own bouquets from flowers I bought | * | +| 16 | user | view locations of flower shops around me | buy fresh flower conveniently | * | + +## Use cases + +(For all use cases below, the System is `Florizz` and the Actor is the `user`, unless specified otherwise) + +**Use case: Create a new bouquet named "for valentine" and add 3 stalks of rose into the bouquet** + +Main Success Scenario (MSS): +1. User creates new bouquet named "for valentine" +2. Programme responds that a new bouquet is successfully created +3. User adds 3 stalks of rose into the bouquet +4. Programme responds that the 3 stalks of rose has been successfully added +5. Programme displays the current state of "for valentine" bouquet and all the flowers inside it + +**Use case: Removing flowers from a bouquet named "for valentine"** + +Main Success Scenario (MSS): +1. User removes 5 stalks of rose from the bouquet named "for valentine" +2. Programme responds that 5 stalks of rose from the specified bouquet successfully +3. Programme displays the current state of "for valentine" bouquet and all the flowers inside it + +**Extensions** +- 1a. The bouquet named "for valentine" does not exist + - 1a. Florizz shows an error message + + Use case ends. + +- 2a. There are less than 5 stalks of rose in the specified bouquet + - 2a1. Florizz shows an error message + + Use case ends. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..ab17837558 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# Florizz -{Give product intro here} +An easy to use bouquet recommendation software using CLI Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UML-diagrams/Ian/(old)InfoCommandUML.png b/docs/UML-diagrams/Ian/(old)InfoCommandUML.png new file mode 100644 index 0000000000..87e84e320a Binary files /dev/null and b/docs/UML-diagrams/Ian/(old)InfoCommandUML.png differ diff --git a/docs/UML-diagrams/Ian/Ian-style.puml b/docs/UML-diagrams/Ian/Ian-style.puml new file mode 100644 index 0000000000..e84e525775 --- /dev/null +++ b/docs/UML-diagrams/Ian/Ian-style.puml @@ -0,0 +1,10 @@ +@startuml +'https://plantuml.com/sequence-diagram + +!define LOGIC_COLOUR #bcf5c9 +!define LOGIC_COLOUR_T1 #3dfc6a +!define MODEL_COLOUR #f5a2a2 +!define MODEL_COLOUR_T1 #fc1e1e +!define UI_COLOUR #FFDA17 +!define UI_COLOUR_T1 #FFF5C0 +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ian/InfoCommandUML.png b/docs/UML-diagrams/Ian/InfoCommandUML.png new file mode 100644 index 0000000000..abd630a30c Binary files /dev/null and b/docs/UML-diagrams/Ian/InfoCommandUML.png differ diff --git a/docs/UML-diagrams/Ian/InfoCommandUML.puml b/docs/UML-diagrams/Ian/InfoCommandUML.puml new file mode 100644 index 0000000000..225bb38e9c --- /dev/null +++ b/docs/UML-diagrams/Ian/InfoCommandUML.puml @@ -0,0 +1,52 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Ian-style.puml + +actor "user" as user +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant "a:InfoCommand" as InfoCommand LOGIC_COLOUR_T1 +participant "b:FlowerDictionary" as FlowerDictionary LOGIC_COLOUR_T1 +end box + +box Ui UI_COLOUR +participant ":UI" as Ui UI_COLOUR_T1 +end box + +[-> Florizz : execute("info rose") +activate Florizz + +Florizz -> Parser : parse("info rose") +activate Parser + +create InfoCommand +Parser -> InfoCommand : InfoCommand("rose") +activate InfoCommand + +InfoCommand --> Parser : a +deactivate InfoCommand + +Parser --> Florizz : a +deactivate Parser + +Florizz -> InfoCommand : execute() +activate InfoCommand + +InfoCommand -> Ui : printFlowerInfo("rose") +activate Ui + +Ui -> FlowerDictionary : get("rose") +activate FlowerDictionary +FlowerDictionary ---> Ui +deactivate FlowerDictionary + +Ui --> InfoCommand +deactivate Ui + +InfoCommand --> Florizz +deactivate InfoCommand + +<--Florizz +deactivate Florizz +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ian/RecommendCommand.png b/docs/UML-diagrams/Ian/RecommendCommand.png new file mode 100644 index 0000000000..692630b6df Binary files /dev/null and b/docs/UML-diagrams/Ian/RecommendCommand.png differ diff --git a/docs/UML-diagrams/Ian/askOccasion.png b/docs/UML-diagrams/Ian/askOccasion.png new file mode 100644 index 0000000000..62afd86ff5 Binary files /dev/null and b/docs/UML-diagrams/Ian/askOccasion.png differ diff --git a/docs/UML-diagrams/Ian/askOccasion.puml b/docs/UML-diagrams/Ian/askOccasion.puml new file mode 100644 index 0000000000..d768acedf6 --- /dev/null +++ b/docs/UML-diagrams/Ian/askOccasion.puml @@ -0,0 +1,33 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Ian-style.puml + +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant "a:RecommendCommand" as RecommendCommand LOGIC_COLOUR_T1 +participant "b:Flower" as Flower LOGIC_COLOUR_T1 + +box Ui UI_COLOUR +participant ":Ui" as Ui UI_COLOUR_T1 + +activate RecommendCommand + +loop !isValidFormat && !isValidOccasion +RecommendCommand -> Ui : printAskOccasion() + +activate Ui +Ui --> RecommendCommand : input +deactivate Ui + +RecommendCommand -> Parser : parseOccasion(input) +activate Parser +Parser --> RecommendCommand : isValidFormat +deactivate Parser + +RecommendCommand -> Flower : isValidOccasion() +activate Flower +Flower --> RecommendCommand :isValidOccasion +deactivate Flower + +end +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ian/command.puml b/docs/UML-diagrams/Ian/command.puml new file mode 100644 index 0000000000..1ea733e443 --- /dev/null +++ b/docs/UML-diagrams/Ian/command.puml @@ -0,0 +1,42 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Ian-style.puml + +actor "user" as user +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant "a:RecommendCommand" as RecommendCommand LOGIC_COLOUR_T1 + +[-> Florizz : execute("recommend") +activate Florizz + +Florizz -> Parser : parse("recommend") +activate Parser + +create RecommendCommand +Parser -> RecommendCommand : RecommendCommand("rose") +activate RecommendCommand + +RecommendCommand --> Parser : a +deactivate RecommendCommand + +Parser --> Florizz : a +deactivate Parser + +Florizz -> RecommendCommand : execute() +activate RecommendCommand + +RecommendCommand -> RecommendCommand: askOccasion(...) +RecommendCommand -> RecommendCommand: askColour(...) +RecommendCommand -> RecommendCommand: askBouquetName(...) +RecommendCommand -> RecommendCommand: askSize(...); +RecommendCommand -> RecommendCommand: addRandomFlowers(...) +RecommendCommand -> RecommendCommand: askSaveBouquet(...); + +RecommendCommand --> Florizz +deactivate RecommendCommand + +<--Florizz +deactivate Florizz +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ian/storage.png b/docs/UML-diagrams/Ian/storage.png new file mode 100644 index 0000000000..b3ad60e640 Binary files /dev/null and b/docs/UML-diagrams/Ian/storage.png differ diff --git a/docs/UML-diagrams/Ian/storage.puml b/docs/UML-diagrams/Ian/storage.puml new file mode 100644 index 0000000000..2179d316b5 --- /dev/null +++ b/docs/UML-diagrams/Ian/storage.puml @@ -0,0 +1,23 @@ +@startuml +package "Storage Component" { + class StorageManager { + -URL: String + +getFile(): void + +retrieve(): Data + +delete(): void + } + + class Encoder { + -bouquet: Bouquet + +encodeBouquet(:bouquet): void + } + + class Decoder { + -bouquet: Bouquet + +decodeBouquet(): Bouquet + } + + StorageManager -- Encoder + StorageManager -- Decoder +} +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ijaaz/AddFlowerCommand.png b/docs/UML-diagrams/Ijaaz/AddFlowerCommand.png new file mode 100644 index 0000000000..4b9656a51d Binary files /dev/null and b/docs/UML-diagrams/Ijaaz/AddFlowerCommand.png differ diff --git a/docs/UML-diagrams/Ijaaz/AddFlowerCommand.puml b/docs/UML-diagrams/Ijaaz/AddFlowerCommand.puml new file mode 100644 index 0000000000..eb13e23cfd --- /dev/null +++ b/docs/UML-diagrams/Ijaaz/AddFlowerCommand.puml @@ -0,0 +1,62 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include Ijaaz-style.puml + +actor "user" as user +box Ui UI_COLOUR +participant ":Ui" as Ui UI_COLOUR_T1 +end box +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant ":FuzzyLogic" as Fuzzy LOGIC_COLOUR_T1 +participant "a:AddFlowerCommand" as AddFlowerCommand LOGIC_COLOUR_T1 +end box + +box Model MODEL_COLOUR +participant ":Model" as Model MODEL_COLOUR_T1 +end box + +user -> Ui : readInput("add rose /c red /q 1 /to mum") +activate Ui + +Ui -> Florizz : execute("add rose /c red /q 1 /to mum") +activate Florizz + +Florizz -> Parser : parse("add rose /c red /q 1 /to mum") +activate Parser + +Parser -> Fuzzy : CommandHandler(""add rose /c red /q 1 /to mum") +activate Fuzzy + +Fuzzy --> Parser : {"add","Rose /c red /q 1 /to mum", null} +deactivate Fuzzy + +Parser -> Parser : handleAddFlowerCommand() + +create AddFlowerCommand +Parser -> AddFlowerCommand : AddFlowerCommand("rose", "red", 1, "mum") +activate AddFlowerCommand + +AddFlowerCommand --> Parser : a +deactivate AddFlowerCommand + +Parser --> Florizz : a +deactivate Parser + +Florizz -> AddFlowerCommand : execute() +activate AddFlowerCommand + +AddFlowerCommand -> Model : addFlower("rose", "red", 1) +activate Model + +Model --> AddFlowerCommand +deactivate Model + +AddFlowerCommand --> Ui : printAddFlowerSuccess() +Ui --> user : "Successfully added..." +deactivate Ui + +AddFlowerCommand --> Florizz : true +deactivate AddFlowerCommand +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ijaaz/Ijaaz-style.puml b/docs/UML-diagrams/Ijaaz/Ijaaz-style.puml new file mode 100644 index 0000000000..60aa103555 --- /dev/null +++ b/docs/UML-diagrams/Ijaaz/Ijaaz-style.puml @@ -0,0 +1,12 @@ + @startuml +'https://plantuml.com/sequence-diagram + +!define LOGIC_COLOUR #bcf5c9 +!define LOGIC_COLOUR_T1 #3dfc6a +!define MODEL_COLOUR #f5a2a2 +!define MODEL_COLOUR_T1 #fc1e1e +!define UI_COLOUR #FFDA17 +!define UI_COLOUR_T1 #FFF5C0 +!define STORAGE_COLOUR #9fb5f5 +!define STORAGE_COLOUR_T1 #3667f5 +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Ijaaz/SaveCommand.png b/docs/UML-diagrams/Ijaaz/SaveCommand.png new file mode 100644 index 0000000000..23f8b6165d Binary files /dev/null and b/docs/UML-diagrams/Ijaaz/SaveCommand.png differ diff --git a/docs/UML-diagrams/Ijaaz/SaveCommand.puml b/docs/UML-diagrams/Ijaaz/SaveCommand.puml new file mode 100644 index 0000000000..8bad9e3b6b --- /dev/null +++ b/docs/UML-diagrams/Ijaaz/SaveCommand.puml @@ -0,0 +1,71 @@ +@startuml +'https://plantuml.com/sequence-diagram + +!include Ijaaz-style.puml + +actor "user" as user +box Ui UI_COLOUR +participant ":Ui" as Ui UI_COLOUR_T1 +end box +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant ":FuzzyLogic" as Fuzzy LOGIC_COLOUR_T1 +participant "s:SaveCommand" as SaveCommand LOGIC_COLOUR_T1 +end box + +box Model MODEL_COLOUR +participant ":Model" as Model MODEL_COLOUR_T1 +end box + +box Storage STORAGE_COLOUR +participant ":Storage" as Storage STORAGE_COLOUR_T1 +end box + +user -> Ui : readInput("save mum") +activate Ui + +Ui -> Florizz : execute("save mum") +activate Florizz + +Florizz -> Parser : parse("save mum") +activate Parser + +Parser -> Fuzzy : CommandHandler("save mum") +activate Fuzzy + +Fuzzy --> Parser : {"save","mum", null} +deactivate Fuzzy + +create SaveCommand +Parser -> SaveCommand : SaveCommand("mum") +activate SaveCommand + +SaveCommand --> Parser : s +deactivate SaveCommand + +Parser --> Florizz : s +deactivate Parser + +Florizz -> SaveCommand : execute() +activate SaveCommand + +SaveCommand -> Model : bouquetList.get(idx) +activate Model + +Model --> SaveCommand : bouquetToSave +deactivate Model + +SaveCommand -> Storage : saveBouquet(bouquetToSave) +activate Storage + +Storage --> SaveCommand +deactivate Storage + +SaveCommand --> Ui : printSaveSuccess() +Ui --> user : "Successfully saved..." +deactivate Ui + +SaveCommand --> Florizz : true +deactivate SaveCommand +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Jeffinson/(Old)Jeffinson-UML-Remove.png b/docs/UML-diagrams/Jeffinson/(Old)Jeffinson-UML-Remove.png new file mode 100644 index 0000000000..20f53c42dc Binary files /dev/null and b/docs/UML-diagrams/Jeffinson/(Old)Jeffinson-UML-Remove.png differ diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Help.png b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Help.png new file mode 100644 index 0000000000..5fe93df596 Binary files /dev/null and b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Help.png differ diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Help.puml b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Help.puml new file mode 100644 index 0000000000..b6867e6c62 --- /dev/null +++ b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Help.puml @@ -0,0 +1,46 @@ +@startuml +!include Jeffinson-style.puml + +actor "user" as user +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant "HelpCommand" as HelpCommand LOGIC_COLOUR_T1 +end box + +box Ui UI_COLOUR +participant ":Ui" as Ui UI_COLOUR_T1 +end box + +user -> Florizz : execute("help") +activate Florizz + +Florizz -> Parser : parse("help") +activate Parser + +create HelpCommand +Parser -> HelpCommand : HelpCommand() +activate HelpCommand + +HelpCommand --> Parser : a +deactivate HelpCommand + +Parser --> Florizz : a +deactivate Parser + +Florizz -> HelpCommand : execute() +activate HelpCommand + +HelpCommand -> Ui : printHelpMessage() +activate Ui + +Ui --> HelpCommand +deactivate Ui + +HelpCommand --> Florizz +deactivate HelpCommand + +Florizz --> user : Print help message +deactivate Florizz + +@enduml diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Remove.png b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Remove.png new file mode 100644 index 0000000000..6aecd17ec0 Binary files /dev/null and b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Remove.png differ diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Remove.puml b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Remove.puml new file mode 100644 index 0000000000..d3ed4b016e --- /dev/null +++ b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-Remove.puml @@ -0,0 +1,45 @@ +@startuml +!include Jeffinson-style.puml + +actor "user" as user +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant "RemoveFlowerCommand" as RemoveFlowerCommand LOGIC_COLOUR_T1 +end box + +box Model MODEL_COLOUR +participant ":Model" as Model MODEL_COLOUR_T1 +end box + +user -> Florizz : execute("remove rose /q 1 /from mybouquet1") +activate Florizz + +Florizz -> Parser : parse("remove rose /q 1 /from mybouquet1") +activate Parser + +create RemoveFlowerCommand +Parser -> RemoveFlowerCommand : RemoveFlowerCommand("rose", 1, "mybouquet1") +activate RemoveFlowerCommand + +RemoveFlowerCommand --> Parser : a +deactivate RemoveFlowerCommand + +Parser --> Florizz : a +deactivate Parser + +Florizz -> RemoveFlowerCommand : execute() +activate RemoveFlowerCommand + +RemoveFlowerCommand -> Model : removeFlower("rose", 1) +activate Model + +Model --> RemoveFlowerCommand +deactivate Model + +RemoveFlowerCommand --> Florizz +deactivate RemoveFlowerCommand + +Florizz --> user : Print remove message and final state of bouquet +deactivate Florizz +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-UML-compare.png b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-compare.png new file mode 100644 index 0000000000..de45e3a8a4 Binary files /dev/null and b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-compare.png differ diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-UML-compare.puml b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-compare.puml new file mode 100644 index 0000000000..357c85eb08 --- /dev/null +++ b/docs/UML-diagrams/Jeffinson/Jeffinson-UML-compare.puml @@ -0,0 +1,56 @@ +@startuml +!include Jeffinson-style.puml + +actor "user" as user +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant "a:CompareCommand" as CompareCommand LOGIC_COLOUR_T1 +participant "b:FlowerDictionary" as FlowerDictionary LOGIC_COLOUR_T1 +end box + +box Ui UI_COLOUR +participant ":UI" as Ui UI_COLOUR_T1 +end box + +[-> Florizz : execute("compare rose /vs/ lily") +activate Florizz + +Florizz -> Parser : parse("compare rose /vs/ lily") +activate Parser + +create CompareCommand +Parser -> CompareCommand : CompareCommand("rose", "lily") +activate CompareCommand + +CompareCommand --> Parser : a +deactivate CompareCommand + +Parser --> Florizz : a +deactivate Parser + +Florizz -> CompareCommand : execute() +activate CompareCommand + +CompareCommand -> Ui : printCompareFlowers("rose") +activate Ui + +Ui -> FlowerDictionary : get("rose") +activate FlowerDictionary +FlowerDictionary ---> Ui +deactivate FlowerDictionary + +Ui -> FlowerDictionary : get("lily") +activate FlowerDictionary +FlowerDictionary ---> Ui +deactivate FlowerDictionary + +Ui --> CompareCommand +deactivate Ui + +CompareCommand --> Florizz +deactivate CompareCommand + +<--Florizz +deactivate Florizz +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Jeffinson/Jeffinson-style.puml b/docs/UML-diagrams/Jeffinson/Jeffinson-style.puml new file mode 100644 index 0000000000..e84e525775 --- /dev/null +++ b/docs/UML-diagrams/Jeffinson/Jeffinson-style.puml @@ -0,0 +1,10 @@ +@startuml +'https://plantuml.com/sequence-diagram + +!define LOGIC_COLOUR #bcf5c9 +!define LOGIC_COLOUR_T1 #3dfc6a +!define MODEL_COLOUR #f5a2a2 +!define MODEL_COLOUR_T1 #fc1e1e +!define UI_COLOUR #FFDA17 +!define UI_COLOUR_T1 #FFF5C0 +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Samuel/AddBouquets.png b/docs/UML-diagrams/Samuel/AddBouquets.png new file mode 100644 index 0000000000..f0ecd8ed4d Binary files /dev/null and b/docs/UML-diagrams/Samuel/AddBouquets.png differ diff --git a/docs/UML-diagrams/Samuel/AddBouquets.puml b/docs/UML-diagrams/Samuel/AddBouquets.puml new file mode 100644 index 0000000000..645342a8f1 --- /dev/null +++ b/docs/UML-diagrams/Samuel/AddBouquets.puml @@ -0,0 +1,48 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include samuel-style.puml + +actor "user" as user +participant ":Florizz" as Florizz +box Logic LOGIC_COLOUR +participant ":Parser" as Parser LOGIC_COLOUR_T1 +participant ":AddBouquetCommand" as AddBouquetCommand LOGIC_COLOUR_T1 +end box + +box Model MODEL_COLOUR +participant ":Model" as Model MODEL_COLOUR_T1 +end box + +user -> Florizz : "new valentines" +activate Florizz + +Florizz -> Parser : parse("new valentines") +activate Parser + +create AddBouquetCommand +Parser -> AddBouquetCommand : AddBouquetCommand(new Bouquet("valentines")) +activate AddBouquetCommand + +AddBouquetCommand --> Parser : AddBouquetCommand +deactivate AddBouquetCommand + +Parser --> Florizz : AddBouquetCommand +deactivate Parser + +Florizz -> AddBouquetCommand : AddBouquetCommand.execute() +activate AddBouquetCommand + +AddBouquetCommand -> Model : BouquetList.add(new Bouquet("valentines")) +activate Model + +Model --> AddBouquetCommand : +deactivate Model + +AddBouquetCommand --> Florizz : isRunning=true +deactivate AddBouquetCommand + +destroy AddBouquetCommand + +Florizz-->user : "added valentines" +deactivate Florizz +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Samuel/Architecture.png b/docs/UML-diagrams/Samuel/Architecture.png new file mode 100644 index 0000000000..d6e259763c Binary files /dev/null and b/docs/UML-diagrams/Samuel/Architecture.png differ diff --git a/docs/UML-diagrams/Samuel/Architecture.puml b/docs/UML-diagrams/Samuel/Architecture.puml new file mode 100644 index 0000000000..dcb4bae073 --- /dev/null +++ b/docs/UML-diagrams/Samuel/Architecture.puml @@ -0,0 +1,22 @@ +@startuml +[Main] +[Ui] +[Logic] +[Model] +[Storage] + +Main --> Ui +Main --> Logic +Main --> Model +Main --> Storage + +Ui -> Logic +Logic -> Ui +Model -> Logic + + + +Logic -> Model + +Model -> Storage +@enduml \ No newline at end of file diff --git a/docs/UML-diagrams/Samuel/samuel-style.puml b/docs/UML-diagrams/Samuel/samuel-style.puml new file mode 100644 index 0000000000..8fca7f17f5 --- /dev/null +++ b/docs/UML-diagrams/Samuel/samuel-style.puml @@ -0,0 +1,8 @@ +@startuml +'https://plantuml.com/sequence-diagram + +!define LOGIC_COLOUR #bcf5c9 +!define LOGIC_COLOUR_T1 #3dfc6a +!define MODEL_COLOUR #f5a2a2 +!define MODEL_COLOUR_T1 #fc1e1e +@enduml \ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..f101013cfd 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,472 @@ # User Guide +Florizz is your personal digital florist which helps people in Singapore to curate flowers to create bouquets for all +occasions. -## Introduction +## Table of Contents + +* [User Guide](#user-guide) + * [Table of Contents](#table-of-contents) + * [Features ](#features-a-namefeaturesa) + * [Viewing help: `help` ](#viewing-help-help-a-namehelpa) + * [Create a new bouquet: `new`](#create-a-new-bouquet-new) + * [Delete existing bouquet: `delete`](#delete-existing-bouquet-delete) + * [View existing bouquets: `mybouquets`](#view-existing-bouquets-mybouquets) + * [List all available flowers: `flowers`](#list-all-available-flowers-flowers) + * [View detailed info of a flower: `info`](#view-detailed-info-of-a-flower-info) + * [Add flower: `add`](#add-flower-add) + * [Remove flower: `remove`](#remove-flower-remove) + * [List occasions: `occasion`](#list-occasions-occasion) + * [Recommend A Bouquet: `recommend`](#recommend-a-bouquet-recommend) + * [Save a bouquet to device: `save`](#save-a-bouquet-to-device-save) + * [Compare two flowers: `compare`](#compare-two-flowers-compare) + * [Exit programme: `bye`](#exit-programme-bye) + * [Fuzzy Logic](#fuzzy-logic) + * [Autosave](#autosave) + -{Give a product intro} +## Features +### Viewing help: `help` +Shows a list of commands and its corresponding function -## Quick Start +Format: `help` -{Give steps to get started quickly} +Expected output: -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +``` +Here is the table showing a list of commands you can use: ++-----+------------------------------------------------+----------------------------------------+------------------------------+ +| No. | Command | Explanation | Example | ++-----+------------------------------------------------+----------------------------------------+------------------------------+ +| 1 | new | Add a bouquet | new Birthday Bouquet | +| 2 | delete | Delete a bouquets | delete Birthday Bouquet | +| 3 | mybouquets | List current saved bouquets | mybouquets | +| 4 | info | Provide information on chosen flower | info Rose | +| 5 | add /c (optional) /q | Add flower to a bouquet | add Rose /c Red /q 5 /to | +| | /to | | Birthday Bouquet | +| 6 | remove /c (optional) /q | Remove flower from a bouquet | remove Rose /c Red /q 5 | +| | /from | | /from Birthday Bouquet | +| 7 | flowers | Shows a list of flowers that can be | flowers | +| | | added into mybouquets | | +| 8 | flowers | Shows a list of flowers associated | flowers Valentines | +| | | with said occasion | | +| 9 | occasion | Shows a list of occasions associated | occasion | +| | | with available flowers | | +| 10 | save | Saves a bouquet to an external | save Birthday Bouquet | +| | | .txt file | | +| 11 | recommend | Recommends a bouquet based on the | recommend | +| | | chosen occasion and colour | | +| 12 | compare <1st flowerName> /vs/ <2nd flowerName> | Show information regarding two flowers | compare Rose /vs/ Lily | +| | | side-by-side for comparison | | +| 13 | bye | Exits the programme | bye | ++-----+------------------------------------------------+----------------------------------------+------------------------------+ +``` -## Features +### Create a new bouquet: `new` +Creates an empty bouquet to add flowers to later -{Give detailed description of each feature} +Format: `new NAME` -### Adding a todo: `todo` -Adds a new item to the list of todo items. +- Bouquet name must not already exist +- Bouquet names are not case-sensitive i.e. For Girlfriend = for girlfriend -Format: `todo n/TODO_NAME d/DEADLINE` +Examples: +`new For Girlfriend` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Expected output: +``` +Added new bouquet to list: +For Girlfriend +``` +
-Example of usage: +### Delete existing bouquet: `delete` +Deletes a bouquet from the bouquet list -`todo n/Write the rest of the User Guide d/next week` +Format: `delete ` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +- Bouquet of that name must exist in the list -## FAQ +Example: +`delete For Mother` -**Q**: How do I transfer my data to another computer? +Expected output: +``` +Deleted bouquet: +For Mother +``` -**A**: {your answer here} +### View existing bouquets: `mybouquets` +Views all the bouquets in the list -## Command Summary +Format: `mybouquets` + +Expected output: +``` +Here is the list of your saved bouquets: +1. For Girlfriend : + No flowers added so far +``` +
+ +### List all available flowers: `flowers` +List all available flowers in the database currently, also able to filter presented flowers according to occasion, +colour (coming soon) and meaning (coming soon). + +Shows at most five flowers at once, type `next` to see flowers on the next page, and `back` to view a previous page. + +Format: `flowers ` + +Example: `flowers` + +Expected output: +``` +Showing page 1/6 of all the flowers you can add: +1. Orchid (White) +2. Rose (Dark crimson) +3. Rose (Red) +4. Rose (Yellow) +5. Lily (White) +Type 'next' to go to the next page. +``` + +Example: `flowers Funeral` + +Expected output: +``` +Here is page 1/1 of all the flowers related to Funeral: +1. Rose (Dark crimson) +2. Lily (White) +3. Chrysanthemum (White) +``` + +### View detailed info of a flower: `info` + +Get detailed info (colour, meaning and related occasions) about a specific flower in the database, +will show all colours and their associated meanings (specifying colours will come soon). + +Shows at most five flowers at once, type `next` to see flowers on the next page, and `back` to view a previous page. + +Format: `info ` + +Example: `info Lily` + +Expected Output: +``` +Here is a table of information about the flower Lily: ++-----+-------------+--------+------------------+-----------+-------------+-----------+ +| No. | Flower Name | Colour | Occasion | Meaning | Type | Price ($) | ++-----+-------------+--------+------------------+-----------+-------------+-----------+ +| 1 | Lily | White | Funeral, Wedding | Innocence | MAIN_FLOWER | 2.50 | +| 2 | Lily | Orange | - | Hatred | MAIN_FLOWER | 2.50 | ++-----+-------------+--------+------------------+-----------+-------------+-----------+ +____________________________________________________________ +``` + +### Add flower: `add` + +Adds a flower into a bouquet + +Format: `add /c (optional) /q /to ` + +- Flower must exist in the database +- Quantity must be a positive integer +- Bouquet must exist in the database +- If flower has several colours available, but user did not enter a colour in the command, user will be prompted to choose a colour from the available colours +- If flower only has one colour available, user does not need to enter a colour + +Examples: +- `add Rose /c red /q 3 /to For Girlfriend` + +Expected Output: +``` +What can I do for you? +add Rose /c red /q 3 /to For Girlfriend +You have successfully added the following: + - 3 x Rose (Red) -> Bouquet: For Girlfriend +Here is the list of your saved bouquets: +1. For Girlfriend : + - 3 x Rose (Red) + Total estimated price = $6.00 +__________________________________ +``` +- `add Rose /q 3 /to For Girlfriend` + +``` +The flower you're looking for has more than one colour available, each with their own vastly different meanings. +Here is a table of information about the flower rose: ++-----+-------------+--------------+----------------------------------+----------------------------------------+-------------+-----------+ +| No. | Flower Name | Colour | Occasion | Meaning | Type | Price ($) | ++-----+-------------+--------------+----------------------------------+----------------------------------------+-------------+-----------+ +| 1 | Rose | Dark crimson | Funeral | Mourning | MAIN_FLOWER | 2.00 | +| 2 | Rose | Red | Valentines, Wedding, Mothers day | Love | MAIN_FLOWER | 2.00 | +| 3 | Rose | Yellow | - | Jealousy, Decrease of love, Infidelity | MAIN_FLOWER | 2.00 | ++-----+-------------+--------------+----------------------------------+----------------------------------------+-------------+-----------+ +____________________________________________________________ +Type the colour you want to add into the bouquet, or 'cancel' to return to the main menu. +red +You have successfully added the following: + - 3 x Rose (Red) -> Bouquet: For Girlfriend +Here is the list of your saved bouquets: +1. For Girlfriend : + - 3 x Rose (Red) + Total estimated price = $6.00 +``` +- `add daisy /q 3 /to For Girlfriend` + +``` +You have successfully added the following: + - 3 x Daisy (White) -> Bouquet: For Girlfriend +Here is the list of your saved bouquets: +1. For Girlfriend : + - 3 x Daisy (White) + - 3 x Rose (Red) + Total estimated price = $7.50 +``` + +### Remove flower: `remove` + +Removes a flower from a bouquet + +Format: `remove /c (optional) /q /from ` + +- Flower of that colour must exist in the database and the bouquet specified +- Quantity must be between 1 and the current amount of flowers in the bouquet (inclusive). +- Bouquet must exist in the database +- If multiple flowers of that colour exist in the bouquet and colour is not specified, user will be prompted to choose a colour from the ones available + +Examples: +- `remove Carnation /q 1 /from for mother` + +Expected output: +``` +The flower you're looking for has more than one colour available, each with their own vastly different meanings. +Here is a table of information about the flower carnation: ++-----+-------------+--------+-------------+---------------------------+-------------+-----------+ +| No. | Flower Name | Colour | Occasion | Meaning | Type | Price ($) | ++-----+-------------+--------+-------------+---------------------------+-------------+-----------+ +| 1 | Carnation | Pink | Mothers day | Gratitude, Love | MAIN_FLOWER | 2.00 | +| 2 | Carnation | Red | Valentines | My heart aches, Deep Love | MAIN_FLOWER | 2.00 | ++-----+-------------+--------+-------------+---------------------------+-------------+-----------+ +____________________________________________________________ +Type the colour you want to add into the bouquet, or 'cancel' to return to the main menu. +red +You have successfully removed the following: + - 1 x Carnation (Red) -> Bouquet: for mother +Here is the list of your saved bouquets: +1. For Mother : + - 1 x Carnation (Pink) + Total estimated price = $2.00 +``` +- `remove carnation /c pink /q 1 /from for mother` + +``` +You have successfully removed the following: + - 1 x Carnation (Pink) -> Bouquet: for mother +Here is the list of your saved bouquets: +1. For Mother : + No flowers added so far +``` +### List occasions: `occasion` +Shows a list of occasions that the flowers in the database are associated with. + +Format: `occasion` + +Expected output: +``` +Here are all the occasions associated with the available flowers: +- Funeral +- Wedding +- Valentines +- Mothers day +____________________________________________________________ +``` +### Recommend A Bouquet: `recommend` + +Recommends a bouquet based on the occasion and the recipient's preference. Will enter user into 'recommend mode'. Type `cancel` to exit recommend mode. + +Steps: +1. Type command: `recommend` + + Expected output: + ``` + For what occasion are you buying flowers for? + Here are all the occasions associated with the available flowers: + - Funeral + - Wedding + - Valentines + - Mothers day + ____________________________________________________________ + Type 'cancel' if you would like to exit the recommendation page + ``` + +2. Type occasion: `Funeral` + + Expected output: + ``` + What colour would you like your bouquets to be? + Here is the list of colours available for the occasion: + - Dark crimson + - White + ____________________________________________________________ + Type 'cancel' if you would like to exit the recommendation page + ``` + +3. Select colour of bouquet: `WHITE` + + Expected output: + + ``` + Great we managed to find some flowers for you! + Before we carry on what would you like to call your bouquet? + Note: please take note 'cancel' cannot be used as a bouquet name + ____________________________________________________________ + Type 'cancel' if you would like to exit the recommendation page + ``` + +4. Type bouquet name: `for grandma` + + Expected output: + ``` + What size would you like your recommended bouquet to be? + 1. Small + 2. Medium + 3. Large + ____________________________________________________________ + Type 'cancel' if you would like to exit the recommendation page + ``` + +5. Select bouquet size: `Large` + + Expected output: + ``` + Would you like to save this bouquet to your list? + Here is the full list of flowers in for grandma: + - 2 x Pittosporum + - 2 x Freesia + - 1 x Dusty Miller + - 3 x Chrysanthemum + - 1 x Pistacia + - 2 x Lily + ____________________________________________________________ + Type 'yes' to save, 'no' to discard + Type 'cancel' if you would like to exit the recommendation page + ``` + *Note: The flowers in the bouquet are randomly generated and may differ from the example* + + +6. Confirm whether you want to add the recommended bouquet to your mybouquets list: `yes` + + ``` + Added new bouquet to list: + for grandma + ____________________________________________________________ + What can I do for you? + ``` + +### Save a bouquet to device: `save` +Saves chosen bouquet, if it exists, locally to the users device + +Format: `save ` + +- Bouquet must exist before it can be saved + +Examples: +- `save moms bouquet` + +Expected Output: +``` +Successfully saved moms bouquet. You can find it at 'florizz-out/saved/moms bouquet.txt' +``` + +### Compare two flowers: `compare` +Compares the colour, occasion, meaning, type and price of flowers based on flower names + +Format: `compare /vs/ ` + +- Both flowers must exist in the database + +Examples: +- `compare Rose /vs/ Lily` + +Expected output: +``` +Here is a table of comparison between the two flowers: ++-----+-------------+--------------+----------------------------------+----------------------------------------+-------------+-----------+ +| No. | Flower Name | Colour | Occasion | Meaning | Type | Price ($) | ++-----+-------------+--------------+----------------------------------+----------------------------------------+-------------+-----------+ +| 1 | Rose | Dark crimson | Funeral | Mourning | MAIN_FLOWER | 2.00 | +| 2 | Rose | Red | Valentines, Wedding, Mothers day | Love | MAIN_FLOWER | 2.00 | +| 3 | Rose | Yellow | - | Jealousy, Decrease of love, Infidelity | MAIN_FLOWER | 2.00 | +| 4 | Lily | White | Funeral, Wedding | Innocence | MAIN_FLOWER | 2.50 | +| 5 | Lily | Orange | - | Hatred | MAIN_FLOWER | 2.50 | ++-----+-------------+--------------+----------------------------------+----------------------------------------+-------------+-----------+ +``` + +### Exit programme: `bye` + +Exits the program. + +Format: `bye` + +Expected output: +``` +Enjoy your bouquet! Thank you for using Florizz! +``` + +### Fuzzy Logic + +Florizz uses a type of fuzzy logic that rectifies typos in user input by utilising the Damerau-Levenshtein Distance +to measure the similarity between the user input and a valid command/flower/colour/occasion. + +The Damerau-Levenshtein distance measures the minimum number of single-character edits required to change one string +into another. These edits can be insertions, deletions, substitutions and transpositions of individual characters. + +When a typo is detected, Florizz will make a calculated guess as to the valid command that a user is actually referring to. + +Examples of typos that Florizz can rectify include: + +1. Typo in spelling + - `flowerz Vlentine` + - `ads Ross /c ret /q 10 /to mybouquet1` +2. Incorrect placements of whitespaces + - `c o m p are R ose /v s/ L i ly` + - `m y b ouq ue ts` +3. Absence of whitespaces + - `flowersmothersday` + - `infoDaisy` + +Note: Fuzzy Logic only works for commands, flower names, colours and occasions, and it cannot rectify typos which +combine no. 1 with no. 2 or no. 3. + +Expected output: + +``` +flowerz Vlentine +--> Your input is [flowerz] but I am guessing you mean [flowers] +--> Your input is [Vlentine] but I am guessing you mean [Valentines] +``` + +``` +ads Ross /c ret /q 10 /to mybouquet1 +--> Your input is [ads] but I am guessing you mean [add] +--> Your input is [Ross] but I am guessing you mean [Rose] +--> Your input is [ret] but I am guessing you mean [Red] +``` + +``` +c o m p are R ose /v s/ L i ly +--> Your input is [c o m p are R ose /v s/ L i ly] but I am guessing you mean [compare Rose /vs/ Lily] +``` + +``` +flowersmothersday +--> Your input is [flowersmothersday] but I am guessing you mean [flowers Mothers Day] +``` + +### Autosave +Florizz automatically backs up all your bouquet data onto your device in a `FlorizzBouquets.txt` file. +As such the user can transfer their bouquet data between devices by simply moving the text file to the `florizz-out/data/` +folder. However, editing this text file is not recommended as the format is very specific, so users +should do so at their own risk -{Give a 'cheat sheet' of commands here} -* Add todo `todo n/TODO_NAME d/DEADLINE` diff --git a/docs/team/ianfh.md b/docs/team/ianfh.md new file mode 100644 index 0000000000..3569d213a8 --- /dev/null +++ b/docs/team/ianfh.md @@ -0,0 +1,72 @@ +# Ian Freda Hariyanto - Project Portfolio Page + +## Overview +Florizz is a digital florist replacement that helps users choose appropriate +flowers to put into a bouquet for various occasions. + +## Summary of Contributions +### Code Contributed +Click [here](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=ianfh&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=IanFH&tabRepo=AY2324S2-CS2113-T11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) to view my contributions. + +### Enhancements Implemented +* **New Feature**: Add Flower Command + * Command takes in the format `add /q /to `. This command was necessary to + allow users to create bouquets with flowers of their choosing, and was a key feature of the programme. + * The command was implemented by creating a new class `AddFlowerCommand` which would take in the user input and + parse it to identify the flower, quantity and bouquet name. The command would then add the flower to the bouquet + and update the bouquet accordingly. + +* **New Feature**: Remove Flower Command + * Command takes in the format `remove /q /from `. This command was necessary to + allow users to remove flowers from their bouquets, and was a key feature of the programme. + * The command was implemented by creating a new class `RemoveFlowerCommand` which would take in the user input and + parse it to identify the flower, quantity and bouquet name. The command would then remove the flower from the bouquet + and update the bouquet accordingly. + +* **New Feature**: Recommend Command + * Command takes in the format `recommend` which will enter the user into 'recommend mode'. In this mode, the user can + input the occasion, colour, name, and size of the bouquet and the programme will recommend a bouquet for them. + * The command matches the user requirements with the flowers in `FlowerDictionary` and creates a bouquet containing some main flowers and some filler flowers. + * This command was necessary to allow beginner users who are not familiar with flowers to get a bouquet recommendation + based on the occasion they are looking for. + +* **New Feature**: Bye Command + * Command takes in the format `bye` which will exit the programme. This command was necessary to allow users to exit + the programme when they are done using it. + +* **Enhancements**: Added Regex for Parsing User Input + * The first to suggest and implemented regex for parsing user input for `add` and `remove` command. This was necessary to ensure that the user input was + correctly parsed and the correct flower, quantity and bouquet name was identified. [proof](https://github.com/nus-cs2113-AY2324S2/tp/commit/34889baaa7a024c6c8f1fef686b7ebe4493d9f44#diff-bb1b61dd1311729e01e01d2e43ccaf1648d0e8a57a97f5eafc82c1add885d247) + +* **Enhancements**: Added More Flowers to `FlowerDictionary` + * Researched main flowers and filler flowers that are available in Singapore along with their prices [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/97/files) + +* **Unused** : Configured sqlite3 database for storage of flowers and bouquets. + * This was not used in the final product as we found out DBMS was not allowed for this project. [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/92) + +### Contributions to the UG +- Added Table of Contents to the UG [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/178/files) +- Added the add flower command to the UG [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/42/files) +- Added the remove flower command to the UG [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/42/files) +- Added the recommend command to the UG [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/178/files) +- Added the bye command to the UG [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/42/files) + +### Contributions to the DG +- Responsible for the add flower command sequence diagram and descriptions [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/78/files) +- Responsible for proposed storage implementation and storage class diagram for DG draft [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/78/files) +- Responsible for recommend command sequence diagram and descriptions [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/178/files) +- Responsible for askOccasion sequence diagram and description [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/178/files) +- Responsible for Appendix of product scope, value proposition, user stories, and use cases [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/72/files) +- Updated info command, remove flower command sequence diagram based on TA's feedback [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/169/files) + +#### Diagram Added +![Add Flower Command Diagram](../UML-diagrams/Ijaaz/AddFlowerCommand.png) +![Recommend Command Sequence Diagram](../UML-diagrams/Ian/RecommendCommand.png) +![askOccasion Sequence Diagram](../UML-diagrams/Ian/askOccasion.png) +![Unused storage class diagram](../UML-diagrams/Ian/storage.png) + +### Contributions to Team-based Tasks +- Helped to set up the issue tracker for the team (i.e. milestones, tagging) + +### Review/mentoring Contributions +- Reviewed and provided feedback on PRs by other team members [proof](https://nus-cs2113-ay2324s2.github.io/dashboards/contents/tp-comments.html#19-ian-anto-ianfh-31-comments) \ No newline at end of file diff --git a/docs/team/ijaaz01.md b/docs/team/ijaaz01.md new file mode 100644 index 0000000000..d1fdd12e6a --- /dev/null +++ b/docs/team/ijaaz01.md @@ -0,0 +1,47 @@ +# Mohamed Ijaaz - Project Portfolio Page + +## Overview +Florizz is a digital florist replacement that helps users choose appropriate +flowers to put into a bouquet for various occasions. + +### Summary of Contributions +* Click [here](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=ijaaz&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=Ijaaz01&tabRepo=AY2324S2-CS2113-T11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) to view my contributions. + +* **New Feature**: Flower and FlowerDictionary class + * Added the initial version of both the Flower and FlowerDictionary class to Florizz, along with the startup() method + that has been used to add flowers into the FlowerDictionary till now. [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/19/files) + * These two classes form the backbone of our programme and as such it was important that they were implemented properly, + so that the project could carry on in the future smoothly + +* **New Feature**: Storage + * Implemented the storage functionality for Florizz. This included checking and creating the relevant output directories, + Creating the necessary output files for logging, automatically saving of all bouquets during the run time as well as recovering + said bouquets when the programme is launched again. + * This feature was necessary to ensure that using Florizz would be much more practical for both casual users and developers, + as all the necessary data is stored and organised properly. + +* **New Feature**: Save bouquet + * Implemented the `save` command which would save a bouquet of the users choosing locally, allowing them to refer to the + bouquet outside of Florizz + * The highlight of this function comes from the customized save files for every bouquet created meaning the bouquet 'mum' + will be saved to 'mum.txt', while 'dad' will be saved to 'dad.txt'. This is a quality of life feature for users as they + do not have to look through a long text file to find the specific bouquet they want + +* **New Feature**: Flowers and info command + * Implemented the initial version of both the `flowers` and `info` command for Florizz. + * Highlight of `flowers` is the option to display all flowers if an occasion was not specified by the user, or just + flowers related to a specific occasion if one was specified. + +* **Enhancements to Existing Features** + * Helped to change the logic of the `recommend` command mostly implemented by my teammate Ian, so that it did not kick + users out of 'recommend mode' when given a bad input. [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/155/files) + * Updated the `info` command to allow it to display the information for a flower even if its meaning and ocassions had + no entries. This required updating the item key for the fuzzy logic function implemented by my teammate Jeff. [proof](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/157/files) + +* **Documentation** + * **UG**: + * Added documentation for `save` and `flowers` as well as AutoSave + + * **DG**: + * Created sequence diagrams for save command and addFlower command + diff --git a/docs/team/jeffinsondarmawan.md b/docs/team/jeffinsondarmawan.md new file mode 100644 index 0000000000..317d09d380 --- /dev/null +++ b/docs/team/jeffinsondarmawan.md @@ -0,0 +1,88 @@ +# Jeffinson Darmawan - Project Portfolio Page + +## Overview +Florizz is a digital florist replacement that helps users choose appropriate +flowers to put into a bouquet for various occasions. + +### Summary of Contributions +Code Contribution: [Jeffinson Darmawan RepoSense Report](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=jeffinsondarmawan&breakdown=true) + + - **New Features:** Fuzzy Logic (Pull Request [#71](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/71), [#83](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/83), [#84](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/84), [#87](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/87), [#162](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/162)) + - What it is: + Theoretically, Fuzzy Logic is a mathematical framework that deals with uncertainty and imprecision in + decision-making. Rather than only accepting a binary value of truth (i.e. true or false), Fuzzy Logic allows variables + to have a degree of truth which gives it the sense of "Fuzziness". Practically, Fuzzy Logic is implemented in + the Florizz programme to reduce human error in typing inputs with typos. For example, take a valid command of + `info Rose` where Florizz will show information regarding that flower. Suppose that a user mistakenly input + `invo Ross` and another user mistakenly input `zjgh bfre`. Without Fuzzy Logic, both `invo Ross` and `zjgh bfre` + will be regarded as equaly invalid commands and the programme will throw an exception. However, one can make a + strong case that `invo Ross` is definitely closer to `info Rose` compared to `zjgh bfre`. + - Justification: + Fuzzy Logic helps + users by correcting their typos due to accidental human errors and increase the overall convenience of using the + programme. This feature is especially useful for users who are prone to making typos in their inputs. By implementing + Fuzzy Logic, Florizz is able to correct these typos and provide the user with the intended output thereby reducing + the effect of human errors.Imagine having to retype `adds Rose /q 1 /to Bouquet for My 3th Anniversary with My Girlfriend` + because of a small mistake of having an "s" after "add" and repeating this over and over again! + - How it works: + Fuzzy Logic in Florizz works primarily in 3 ways. Firstly, applying + `detectItem(userInput, validCommand)`, Florizz will calculate the Damerau-Levenshtein distance which is a metric + used to measure the similarity between the user input and a list of predetermined valid commands. + It calculates the minimum number of single-character edits required to change userInput to a valid command, + by counting the number of insertions, deletions, substitutions and transpositions of individual characters. For + Florizz, we use a SIMILARITY_THRESHOLD of 2, meaning that we would only match a userInput to a valid command which + has the smallest distance that is less than 2. example, the userInput: `myboquets` compared to a validCommand: + `mybouquets` has a Damerau-Levenshtein distance of one. Secondly, Florizz also recognises inputs which mistakenly + have whitespaces separating alphabets which form valid commands. For example, Florriz is able to correct `h e l p` + into `help` or `re mov e Lily /q 1 /from bouquet`into `remove Lily /q 1 /from bouquet`. Thirdly, Florizz can + also correct inputs which are mistakenly glued to each other, such as `flowersmothersday` into `flowers Mothers Day`. + +- **New Feature:** Listing Available Occasion (Pull Request [#26](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/26)) + - What it is: + `occasion` lists out all the occasions which are associated will all flowers in our database. This + allows users to have a quick look at what occasions they can choose for their flowers. Once users know what occasion + they can input `flowers ` to see all the flowers associated with the occasion of choosing. + - Justification: + This feature is especially useful for users who are unsure of what flowers to choose for a specific occasion. +- **New Feature:** Help Command (Pull Request [#16](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/16)) + - What it is: + `help` assists users in using Florizz by showing a quick list of commands that users can use. + - Justification: + This feature is especially useful for new users who are unfamiliar with the commands available in Florizz. + - Highlights: + This feature uses an ASCII table to increase readability of the list of available commands. +- **New Feature:** Compare Command + - What it is: + `compare` allows users to compare the colour, occasion, meaning, type and price of two flowers. + - Justification: + When deciding which flowers to add to a bouquet, users might find it difficult to compare between different flowers + as they need to separately call the `info` command for each flower. + Thus, this feature is especially useful for users who are deciding between two flowers and want to know the + differences between them. + - Highlights: + This feature required the implementation of a new command and the creation of a new class to handle the comparison of two flowers. + It also uses an ASCII table to increase readability of the comparison results. + +**Enhancements to Existing Features:** +- Applying appropriate exceptions to ensure valid integer inputs in all commands involving integers +(Pull Request [#166](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/166)) +- Added price information to bouquets (Pull Request [#65](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/65)) +- Improving Parser class by applying FuzzyLogic.detectItem() +(Pull Request [#71](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/71)) +- Wrote JUnit tests for `AddFlowerCommandTest`, `HelpTest`, `InfoCommandTest`, `ListOccasionCommandTest`, + `RemoveFlowerCommandTest`, `FuzzyLogicTest`, `ParserTest`, `CompareCommandTest` + and wrote additional JUnit tests for `AddBouquetTest`, + `DeleteBouquetTest` which increases classes coverage from 54% to 63% and lines coverage from 29% to 51%. + +**Documentation** +- Developer Guide +(Pull Request [#82](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/82), [#180](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/180)) + - Added UML diagrams and implementation details for `compare`, `remove` and `help` +- User Guide +(Pull Request [#106](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/106)) + - Added documentation on `compare` and `Fuzzy Logic` + - Updated documentation on `help`, `new`, `add` and `remove` + +**Tools** +- Integrated a third party JTextUtil ascii-table library to create ASCII tables for the `help`, `info` +and `compare` commands. (Link to GitHub Page: [ASCII-Table GitHub Page](https://github.com/freva/ascii-table)). \ No newline at end of file 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/samuelory.md b/docs/team/samuelory.md new file mode 100644 index 0000000000..bfcd0776b7 --- /dev/null +++ b/docs/team/samuelory.md @@ -0,0 +1,62 @@ +# Samuel Ong - Project Portfolio Page + +## Overview +Florizz is a digital florist replacement that helps users choose appropriate +flowers to put into a bouquet for various occasions. + +### Summary of Contributions +* Code contributed: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=samuelory&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=samuelory&tabRepo=AY2324S2-CS2113-T11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **New feature**: Overall OOP of the product + * Wrote the overall flow of the product, i.e. having Ui take in inputs from the user, + have them be parsed by Parser, which then creates a Command to be executed by the core Florizz class. + * By setting the overall structure of the code for my teammates, it made it easier for them to implement + their features as they just had to create the Command as well how to parse information from the users input to the Command. + + +* **New feature**: Creating reading and deleting bouquets + * What it does: Allows the user to create and delete bouquets. + * Justification: This feature allows the user to arrange their own bouquets with their own flowers, + and also delete any unnecessary bouquets. + * Highlights: This feature involved overriding the equals method of the Bouquet class for it to only + be equals if the name of the Bouquet is the same, so that removing and adding to the Bouquet ArrayList would be much easier. + + +* **New feature**: Flower pages + * What it does: When viewing a list of flowers, instead of viewing the entire list of flowers, + the user would only see "pages" of max five flowers at once. The user would then be able to type "next" or "back" + to go forward or backwards respectively. + * Justification: This would make the code more future ready, as if the list of flowers gets too long, + the user would have to scroll up to view all the flowers if it was printed in one long list, which would + be really inconvenient. + * Highlights: This implementation was challenging as it required adding a memory of what list of flowers was last + printed to the users as well as what page they were currently on. + + +* **New Feature**: Ability to choose flower colour when adding/removing flowers to Bouquets. + * What it does: When adding flowers, if there is more than one colour available for a flower that the user wants to add, + the user has to choose the correct colour to add the flower. This can be achieved in two ways, including the colour in the + original command by including the /c field after the name, or if the user omitted that field, by viewing a list of + available colours for that flower after inputting the original command and choosing which colour they meant. + * Justification: In flower language, some flowers have vastly different meanings and can convey different intentions + if you simply change the colour. As such, it is important to specify the colour of the flower in the bouquets, lest the user + accidentally convey the wrong message to their target audience. + * Highlights: This command required two separate ways to implement the same feature as the storage of bouquets to memory involved + writing the command to the file, hence it was more efficient to condense all the relevant flower information into one command. On the + other hand, having too many fields in one command makes it inconvenient for the user to add or remove flowers, as well as the fact that + many flowers only have one flower. Hence, I decided to make putting the colour in the original command optional and allow the user to decide the colour later. + + +* **Enhancement to existing features**: + * Made flower colours and occasions enums within the Flower class. (Pull request [#62](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/62)) + * Updated info command to show flowers with different meanings and occasions for different colours. (Pull request [#89](https://github.com/AY2324S2-CS2113-T11-3/tp/pull/89)) + + +* **Documentation**: + * User Guide: + * Added documentation for features `new bouquet`, `mybouquets` and `delete bouquet` + * Updated documentation for `info` and `flowers` features + * Developer Guide: + * Added implementation details for `new bouquet` feature + * Added overall architecture section \ No newline at end of file diff --git a/src/main/java/florizz/command/AddBouquetCommand.java b/src/main/java/florizz/command/AddBouquetCommand.java new file mode 100644 index 0000000000..288afd3216 --- /dev/null +++ b/src/main/java/florizz/command/AddBouquetCommand.java @@ -0,0 +1,29 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class AddBouquetCommand extends Command{ + private final Bouquet bouquetToAdd; + private final boolean enableUi; + public AddBouquetCommand(Bouquet bouquetToAdd, boolean enableUi){ + this.bouquetToAdd = bouquetToAdd; + this.enableUi = enableUi; + + } + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + if (bouquetList.contains(bouquetToAdd)){ + throw new FlorizzException("Tried to add bouquet already in list"); + } + bouquetList.add(bouquetToAdd); + if (enableUi) { + ui.printBouquetAdded(bouquetToAdd); + } + assert !bouquetList.isEmpty() : "Bouquet list should not be empty"; + return true; + } +} diff --git a/src/main/java/florizz/command/AddFlowerCommand.java b/src/main/java/florizz/command/AddFlowerCommand.java new file mode 100644 index 0000000000..f56a0f1427 --- /dev/null +++ b/src/main/java/florizz/command/AddFlowerCommand.java @@ -0,0 +1,115 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class AddFlowerCommand extends Command{ + private static Logger logger = Logger.getLogger(AddFlowerCommand.class.getName()); + private String flowerName; + private Flower.Colour colour; + private boolean hasColour = false; + private Integer quantity; + private String bouquetName; + private boolean enableUi; + + /** + * Constructs a new AddFlowerCommand with the specified parameters. + * + * @param flowerName The name of the flower to add + * @param quantity The quantity of the flower to add + * @param bouquetName The name of the bouquet to add the flower to + * @param enableUi A boolean indicating whether UI should be enabled + */ + public AddFlowerCommand(String flowerName, int quantity, String bouquetName, boolean enableUi) { + this.flowerName = flowerName; + this.quantity = quantity; + this.bouquetName = bouquetName; + this.enableUi = enableUi; + } + + /** + * Constructs a new AddFlowerCommand with the specified parameters. + * + * @param flowerName The name of the flower to add + * @param colour The color of the flower + * @param quantity The quantity of the flower to add + * @param bouquetName The name of the bouquet to add the flower to + * @param enableUi A boolean indicating whether UI should be enabled + */ + public AddFlowerCommand(String flowerName, Flower.Colour colour, + int quantity, String bouquetName, boolean enableUi) { + this.flowerName = flowerName; + this.colour = colour; + this.quantity = quantity; + this.bouquetName = bouquetName; + this.enableUi = enableUi; + this.hasColour = true; + } + + /** + * Executes the AddFlowerCommand by adding flowers to the specified bouquet. + * + * @param bouquetList The list of bouquets to search for the specified bouquet + * @param ui The user interface to interact with the user + * @return True if the command is executed successfully, false otherwise + * @throws FlorizzException If the specified bouquet is not found or if there is an issue adding the flower + */ + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + logger.entering(AddFlowerCommand.class.getName(), "execute"); + boolean doesBouquetExist = false; + Bouquet bouquetToAddFlower = new Bouquet(); + for (int i = 0; !doesBouquetExist && i < bouquetList.size(); i++) { + if (bouquetList.get(i).getBouquetName().equalsIgnoreCase(this.bouquetName)) { + bouquetToAddFlower = bouquetList.get(i); + doesBouquetExist = true; + } + } + + if (!doesBouquetExist) { + throw new FlorizzException("No such bouquet is found."); + } + + ArrayList matchingFlowers = FlowerDictionary.filterByName(flowerName); + Flower flowerToAdd; + if (hasColour){ + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchingFlowers, colour); + if (!matchedFlowerAndColour.isEmpty()){ + flowerToAdd = matchedFlowerAndColour.get(0); + bouquetToAddFlower.addFlower(flowerToAdd,this.quantity); + if (enableUi) { + ui.printAddFlowerSuccess(bouquetList, flowerToAdd.getNameAndColour(), quantity, bouquetName); + } + } else { + throw new FlorizzException("This flower does not exist in that colour. " + + "Type info to view all available colours for this flower"); + } + } else if (matchingFlowers.size() == 1){ + flowerToAdd = matchingFlowers.get(0); + bouquetToAddFlower.addFlower(matchingFlowers.get(0), this.quantity); + if (enableUi) { + ui.printAddFlowerSuccess(bouquetList, flowerToAdd.getNameAndColour(), quantity, bouquetName); + } + } else { + flowerToAdd = ui.chooseColour(matchingFlowers, flowerName); + if (!flowerToAdd.getFlowerName().isBlank()){ + bouquetToAddFlower.addFlower(flowerToAdd, this.quantity); + if (enableUi) { + ui.printAddFlowerSuccess(bouquetList, flowerToAdd.getNameAndColour(), quantity, bouquetName); + } + } else { + ui.printCancelCommand(); + } + + } + + logger.exiting(AddFlowerCommand.class.getName(), "execute"); + return true; + } +} diff --git a/src/main/java/florizz/command/BackCommand.java b/src/main/java/florizz/command/BackCommand.java new file mode 100644 index 0000000000..b1958bc5fb --- /dev/null +++ b/src/main/java/florizz/command/BackCommand.java @@ -0,0 +1,15 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class BackCommand extends Command{ + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + ui.printBackPage(); + return true; + } +} diff --git a/src/main/java/florizz/command/Command.java b/src/main/java/florizz/command/Command.java new file mode 100644 index 0000000000..b0587bd23b --- /dev/null +++ b/src/main/java/florizz/command/Command.java @@ -0,0 +1,19 @@ +package florizz.command; +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import java.util.ArrayList; + +/** + * Abstract class for all commands to inherit + */ +public abstract class Command { + /** + * Executes the command + * @param bouquetList list of bouquets to be manipulated by the command + * @param ui ui class for printing + * @return isRunning, if false program will terminate + * @throws FlorizzException catch all exception class + */ + public abstract boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException; +} diff --git a/src/main/java/florizz/command/CompareCommand.java b/src/main/java/florizz/command/CompareCommand.java new file mode 100644 index 0000000000..4ba9b06af4 --- /dev/null +++ b/src/main/java/florizz/command/CompareCommand.java @@ -0,0 +1,49 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.ArrayList; + +public class CompareCommand extends Command { + + private String firstFlowerName; + private String secondFlowerName; + + /** + * Constructor for the CompareCommand class. + * + * @param firstFlowerName The name of the first flower to compare + * @param secondFlowerName The name of the second flower to compare + */ + public CompareCommand(String firstFlowerName, String secondFlowerName) { + this.firstFlowerName = firstFlowerName; + this.secondFlowerName = secondFlowerName; + } + + /** + * Executes the CompareCommand by comparing two flowers. + * + * @param bouquetList The list of bouquets to search for the specified bouquet + * @param ui The user interface to interact with the user + * @return True if the command is executed successfully, false otherwise + */ + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + if (firstFlowerName.equals(secondFlowerName)) { + throw new FlorizzException("Unable to compare a flower with itself. Please input different flowers"); + } + + ArrayList firstFilteredFlowers = FlowerDictionary.filterByName(firstFlowerName); + ArrayList secondFilteredFlowers = FlowerDictionary.filterByName(secondFlowerName); + + if (firstFilteredFlowers.isEmpty() || secondFilteredFlowers.isEmpty()) { + throw new FlorizzException("Flower does not exist, type 'flowers' for a list of flowers"); + } + + ui.printCompareFlowers(firstFilteredFlowers, secondFilteredFlowers); + return true; + } +} diff --git a/src/main/java/florizz/command/DeleteBouquetCommand.java b/src/main/java/florizz/command/DeleteBouquetCommand.java new file mode 100644 index 0000000000..84dc313de9 --- /dev/null +++ b/src/main/java/florizz/command/DeleteBouquetCommand.java @@ -0,0 +1,25 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class DeleteBouquetCommand extends Command{ + private final Bouquet bouquetToDelete; + public DeleteBouquetCommand(Bouquet bouquetToDelete){ + this.bouquetToDelete = bouquetToDelete; + } + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + if (!bouquetList.contains(bouquetToDelete)){ + throw new FlorizzException("Could not find bouquet to delete"); + } + int initialListSize = bouquetList.size(); + bouquetList.remove(bouquetToDelete); + ui.printBouquetDeleted(bouquetToDelete); + assert bouquetList.size() < initialListSize : "Size of bouquet list did not go down"; + return true; + } +} diff --git a/src/main/java/florizz/command/ExitCommand.java b/src/main/java/florizz/command/ExitCommand.java new file mode 100644 index 0000000000..ba33276529 --- /dev/null +++ b/src/main/java/florizz/command/ExitCommand.java @@ -0,0 +1,19 @@ +package florizz.command; + +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class ExitCommand extends Command { + private Logger logger = Logger.getLogger(ExitCommand.class.getName()); + + @Override + public boolean execute(ArrayList bouquetList, Ui ui) { + logger.entering(ExitCommand.class.getName(), "execute"); + ui.printExitMessage(); + logger.exiting(ExitCommand.class.getName(), "execute"); + return false; + } +} diff --git a/src/main/java/florizz/command/FlowerCommand.java b/src/main/java/florizz/command/FlowerCommand.java new file mode 100644 index 0000000000..3479b624be --- /dev/null +++ b/src/main/java/florizz/command/FlowerCommand.java @@ -0,0 +1,33 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.ArrayList; + +public class FlowerCommand extends Command { + private String occasion; + + public FlowerCommand(String occasion) { + this.occasion = occasion; + } + + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + if (occasion.isBlank()) { + ui.printAllDictFlowerName(1); + return true; + } + try { + Flower.Occasion occasionEnum = Flower.stringToOccasion(occasion); + ui.printFilteredFlowers(FlowerDictionary.filterByOccasion(occasionEnum),occasion, 1); + return true; + + } catch (IllegalArgumentException error){ + throw new FlorizzException("This occasion does not exist. Type 'occasion' to get a list of occasions"); + } + } +} diff --git a/src/main/java/florizz/command/HelpCommand.java b/src/main/java/florizz/command/HelpCommand.java new file mode 100644 index 0000000000..9988369272 --- /dev/null +++ b/src/main/java/florizz/command/HelpCommand.java @@ -0,0 +1,13 @@ +package florizz.command; +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class HelpCommand extends Command{ + @Override + public boolean execute(ArrayList bouquetList, Ui ui) { + ui.printHelpMessage(); + return true; + } +} diff --git a/src/main/java/florizz/command/InfoCommand.java b/src/main/java/florizz/command/InfoCommand.java new file mode 100644 index 0000000000..7bde343326 --- /dev/null +++ b/src/main/java/florizz/command/InfoCommand.java @@ -0,0 +1,35 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.ArrayList; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class InfoCommand extends Command{ + private static final Logger logger = Logger.getLogger(InfoCommand.class.getName()); + private ConsoleHandler ch = new ConsoleHandler(); + private String flowerName; + + public InfoCommand(String flowerName) { + this.flowerName = flowerName; + logger.addHandler(ch); + } + + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + ArrayList filteredFlowers = FlowerDictionary.filterByName(flowerName); + if (filteredFlowers.isEmpty()){ + logger.log(Level.WARNING, "FLOWER DOES NOT EXIST"); + throw new FlorizzException("Flower does not exist, type 'flowers' for a list of flowers"); + } else{ + ui.printFlowerInfo(filteredFlowers, flowerName, 1); + } + return true; + } +} diff --git a/src/main/java/florizz/command/ListBouquetCommand.java b/src/main/java/florizz/command/ListBouquetCommand.java new file mode 100644 index 0000000000..e258ab6dba --- /dev/null +++ b/src/main/java/florizz/command/ListBouquetCommand.java @@ -0,0 +1,14 @@ +package florizz.command; + +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class ListBouquetCommand extends Command{ + @Override + public boolean execute(ArrayList bouquetList, Ui ui) { + ui.printAllBouquets(bouquetList); + return true; + } +} diff --git a/src/main/java/florizz/command/ListOccasionCommand.java b/src/main/java/florizz/command/ListOccasionCommand.java new file mode 100644 index 0000000000..0c09ce42b9 --- /dev/null +++ b/src/main/java/florizz/command/ListOccasionCommand.java @@ -0,0 +1,13 @@ +package florizz.command; + +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class ListOccasionCommand extends Command{ + public boolean execute(ArrayList bouquetList, Ui ui) { + ui.printAllOccasions(); + return true; + } +} diff --git a/src/main/java/florizz/command/NextCommand.java b/src/main/java/florizz/command/NextCommand.java new file mode 100644 index 0000000000..f62426de39 --- /dev/null +++ b/src/main/java/florizz/command/NextCommand.java @@ -0,0 +1,16 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; + +import java.util.ArrayList; + +public class NextCommand extends Command{ + + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + ui.printNextPage(); + return true; + } +} diff --git a/src/main/java/florizz/command/RecommendCommand.java b/src/main/java/florizz/command/RecommendCommand.java new file mode 100644 index 0000000000..e4d50bebea --- /dev/null +++ b/src/main/java/florizz/command/RecommendCommand.java @@ -0,0 +1,246 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Parser; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class RecommendCommand extends Command{ + private static final int SMALL_BOUQUET_FLOWER_COUNT = 1; + private static final int MEDIUM_BOUQUET_FLOWER_COUNT = 3; + private static final int LARGE_BOUQUET_FLOWER_COUNT = 5; + private static final int SMALL_BOUQUET_FILLER_COUNT = 2; + private static final int MEDIUM_BOUQUET_FILLER_COUNT = 4; + private static final int LARGE_BOUQUET_FILLER_COUNT = 6; + private final Logger logger = Logger.getLogger(RecommendCommand.class.getName()); + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + logger.entering(RecommendCommand.class.getName(), "execute"); + // ask for occasion + Flower.Occasion occasion = askOccasion(ui); + + // eligible flowers based on occasion + ArrayList eligibleFlowers = FlowerDictionary.filterByOccasion(occasion); + + // ask for colour preference + Flower.Colour colour = askColour(ui, eligibleFlowers); + + // eligible flowers based on colour + eligibleFlowers = FlowerDictionary.filterByColour(eligibleFlowers, colour); + + // check if there are any flowers available + if (eligibleFlowers.isEmpty()) { + throw new FlorizzException("No flowers available for this occasion and colour"); + } + + // ask for bouquetName + String bouquetName = askBouquetName(ui, bouquetList); + + // ask for size + String size = askSize(ui); + + // create bouquet with occasion and colour + Bouquet recommendedBouquet = new Bouquet(bouquetName); + + // randomly add 3 flowers to bouquet + addRandomFlowers(eligibleFlowers, recommendedBouquet, size, colour); + + // ask if they want to save bouquet to array + askSaveBouquet(ui, bouquetList, recommendedBouquet); + + logger.exiting(RecommendCommand.class.getName(), "execute"); + return true; + } + + /** + * Gets the name the user wants to make for the recommended bouquet and makes sure the name does not already exist + * @param ui Ui to take input and print messages + * @param bouquetList List that contains all bouquets + * @return The chosen valid bouquetName + */ + public String askBouquetName(Ui ui, ArrayList bouquetList) throws FlorizzException { + boolean isValidName = false; + String bouquetName = "placeHolder"; + ui.printAskBouquetName(); + while (!isValidName) { + bouquetName = ui.getInput(); + Parser.checkRecommendExitCondition(bouquetName); + if (bouquetList.contains(new Bouquet(bouquetName))) { + System.out.println("Sorry a bouquet with this name already exists, please enter another name"); + } else { + isValidName = true; + } + } + return bouquetName; + } + + /** + * Adds random flowers to the bouquet + * @param eligibleFlowers list of flowers to choose from + * @param recommendedBouquet bouquet to add flowers to + */ + public void addRandomFlowers(ArrayList eligibleFlowers, + Bouquet recommendedBouquet, String size, Flower.Colour colour) + throws FlorizzException { + logger.entering(RecommendCommand.class.getName(), "addRandomFlowers"); + + int flowerCount = 0; + int fillerCount = 0; + switch (size) { + case "small": + flowerCount = SMALL_BOUQUET_FLOWER_COUNT; + fillerCount = SMALL_BOUQUET_FILLER_COUNT; + break; + case "medium": + flowerCount = MEDIUM_BOUQUET_FLOWER_COUNT; + fillerCount = MEDIUM_BOUQUET_FILLER_COUNT; + break; + case "large": + flowerCount = LARGE_BOUQUET_FLOWER_COUNT; + fillerCount = LARGE_BOUQUET_FILLER_COUNT; + break; + default: + // this should not happen at any case since size is validated before this + assert false : "Invalid size"; + throw new FlorizzException("Invalid size"); + } + + // generate random combination of flowers from eligible flowers totaling to 5 + for (int i = 0; i < flowerCount; i++) { + int randomIndex = (int) (Math.random() * eligibleFlowers.size()); + recommendedBouquet.addFlower(eligibleFlowers.get(randomIndex), 1); + } + + // get all fillers + ArrayList fillers = FlowerDictionary.getFlowersByType(Flower.Type.FILLER_FLOWER); + for (int i = 0; i < fillerCount; i++) { + int randomIndex = (int) (Math.random() * fillers.size()); + // filler colour must be green or same as main flower + boolean isSameColour = fillers.get(randomIndex).getColour().equalsIgnoreCase(colour.toString()); + boolean isGreen = fillers.get(randomIndex).getColour().equalsIgnoreCase("green"); + if (isSameColour || isGreen) { + recommendedBouquet.addFlower(fillers.get(randomIndex), 1); + } else { + i--; + } + } + + + logger.exiting(RecommendCommand.class.getName(), "addRandomFlowers"); + } + + /** + * Asks user for occasion + * @return Occasion enum + */ + public Flower.Occasion askOccasion(Ui ui) throws FlorizzException { + logger.entering(RecommendCommand.class.getName(), "askOccasion"); + boolean isValidFormat = false; + boolean isValidOccasion = false; + String occasionInput = "placeholder"; + ui.printAskOccasion(); + while (!(isValidFormat && isValidOccasion)) { + occasionInput = ui.getInput(); + Parser.checkRecommendExitCondition(occasionInput); + isValidFormat = Parser.parseOccasion(occasionInput); + isValidOccasion = Flower.isValidOccasion(occasionInput); + // check if occasion is in our dictionary + if (!isValidOccasion) { + System.out.println("This occasion does not exist."); + Ui.printBreakLine(); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + } + } + + logger.exiting(RecommendCommand.class.getName(), "askOccasion"); + // convert string to Occasion enum + return Flower.stringToOccasion(occasionInput); + } + + /** + * Asks user for colour + * @param eligibleFlowers list of flowers to choose from + * @return Colour enum + */ + private Flower.Colour askColour(Ui ui, ArrayList eligibleFlowers) throws FlorizzException{ + assert !eligibleFlowers.isEmpty() : "Eligible flowers should not be empty"; + logger.entering(RecommendCommand.class.getName(), "askColour"); + String colourInput = "placeHolder"; + boolean isValidFormat = false; + boolean isValidColour = false; + ui.printAskColour(eligibleFlowers); + while (!(isValidColour && isValidFormat)) { + colourInput = ui.getInput().trim(); + Parser.checkRecommendExitCondition(colourInput); + isValidFormat = Parser.parseColour(colourInput); + isValidColour = Flower.isValidColour(colourInput); + + // check if colour is in our dictionary + if (!isValidColour) { + System.out.println("This colour does not exist."); + Ui.printBreakLine(); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + } + } + + logger.exiting(RecommendCommand.class.getName(), "askColour"); + return Flower.stringToColour(colourInput); + } + + /** + * Asks user if they want to save the bouquet + * @param ui Ui to take input and print messages + * @param bouquetList List that contains all bouquets + * @param recommendedBouquet Bouquet to be saved + */ + private void askSaveBouquet(Ui ui, ArrayList bouquetList, + Bouquet recommendedBouquet) { + logger.entering(RecommendCommand.class.getName(), "askSaveBouquet"); + String saveInput = "placeHolder"; + boolean isValidFormat = false; + boolean isValidInput = false; + while (!(isValidInput && isValidFormat)) { + saveInput = ui.printAskSaveBouquet(recommendedBouquet).toLowerCase(); + isValidFormat = Parser.parseSaveBouquet(saveInput); + isValidInput = (saveInput.equalsIgnoreCase("yes") || saveInput.equalsIgnoreCase("no")); + } + + if (saveInput.equalsIgnoreCase("yes")) { + bouquetList.add(recommendedBouquet); + ui.printBouquetAdded(recommendedBouquet); + assert !bouquetList.isEmpty() : "Bouquet list should not be empty"; + } + + if (saveInput.equalsIgnoreCase("no")) { + ui.printBouquetNotAdded(recommendedBouquet); + } + + logger.exiting(RecommendCommand.class.getName(), "askSaveBouquet"); + } + + /** + * Asks user for size of bouquet + * @param ui Ui to take input and print messages + * @return size of bouquet in lower case string + */ + private String askSize(Ui ui) { + logger.entering(RecommendCommand.class.getName(), "askSize"); + String sizeInput = "placeHolder"; + boolean isValidFormat = false; + boolean isValidInput = false; + while (!(isValidInput && isValidFormat)) { + sizeInput = ui.printAskSize().toLowerCase(); + isValidFormat = Parser.parseSize(sizeInput); + isValidInput = (sizeInput.equalsIgnoreCase("small") || + sizeInput.equalsIgnoreCase("medium") || + sizeInput.equalsIgnoreCase("large")); + } + logger.exiting(RecommendCommand.class.getName(), "askSize"); + return sizeInput.toLowerCase(); + } +} diff --git a/src/main/java/florizz/command/RemoveFlowerCommand.java b/src/main/java/florizz/command/RemoveFlowerCommand.java new file mode 100644 index 0000000000..6a48e8fd01 --- /dev/null +++ b/src/main/java/florizz/command/RemoveFlowerCommand.java @@ -0,0 +1,85 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.ArrayList; +import java.util.logging.Logger; + +public class RemoveFlowerCommand extends Command { + private static Logger logger = Logger.getLogger(RemoveFlowerCommand.class.getName()); + private String flowerName; + private Integer quantity; + private String bouquetName; + private Flower.Colour colour; + private boolean hasColour = false; + + public RemoveFlowerCommand(String flowerName, int quantity, String bouquetName) { + this.flowerName = flowerName; + this.quantity = quantity; + this.bouquetName = bouquetName; + } + + public RemoveFlowerCommand(String flowerName, Flower.Colour colour, int quantity, String bouquetName) { + this.flowerName = flowerName; + this.quantity = quantity; + this.bouquetName = bouquetName; + this.colour = colour; + hasColour = true; + } + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + logger.entering(RemoveFlowerCommand.class.getName(), "execute"); + assert !bouquetList.isEmpty() : "Bouquet list should not be empty"; + boolean doesBouquetExist = false; + Bouquet bouquetToRemoveFlower = new Bouquet(); + for (int i = 0; !doesBouquetExist && i < bouquetList.size(); i++) { + if (bouquetList.get(i).getBouquetName().equalsIgnoreCase(this.bouquetName)) { + bouquetToRemoveFlower = bouquetList.get(i); + doesBouquetExist = true; + } + } + + if (!doesBouquetExist) { + throw new FlorizzException("ERROR: No such bouquet is found." + System.lineSeparator() + + "Create a bouquet by using 'new `"); + } + + ArrayList matchingFlowers = FlowerDictionary.filterByName( + bouquetToRemoveFlower.getFlowerList(), flowerName); + Flower flowerToRemove; + if (hasColour){ + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchingFlowers, colour); + if (!matchedFlowerAndColour.isEmpty()){ + flowerToRemove = matchedFlowerAndColour.get(0); + bouquetToRemoveFlower.removeFlower(flowerToRemove,this.quantity); + ui.printRemoveFlowerSuccess(bouquetList, flowerToRemove.getNameAndColour(), quantity, bouquetName); + } else { + throw new FlorizzException("This bouquet does not contain that colour" + + "Type mybouquets to view all your bouquets."); + } + } else if (matchingFlowers.size()==1){ + flowerToRemove = matchingFlowers.get(0); + bouquetToRemoveFlower.removeFlower(flowerToRemove, this.quantity); + ui.printRemoveFlowerSuccess(bouquetList, flowerToRemove.getNameAndColour(), quantity, bouquetName); + + } else { + flowerToRemove = ui.chooseColour(matchingFlowers, flowerName); + if (!flowerToRemove.getFlowerName().isBlank()){ + bouquetToRemoveFlower.removeFlower(flowerToRemove, this.quantity); + ui.printRemoveFlowerSuccess(bouquetList, flowerToRemove.getNameAndColour(), quantity, bouquetName); + } else { + ui.printCancelCommand(); + } + + } + + logger.exiting(RemoveFlowerCommand.class.getName(), "execute"); + return true; + } + + +} diff --git a/src/main/java/florizz/command/SaveCommand.java b/src/main/java/florizz/command/SaveCommand.java new file mode 100644 index 0000000000..b1a3064ebe --- /dev/null +++ b/src/main/java/florizz/command/SaveCommand.java @@ -0,0 +1,55 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.storage.Storage; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; + +public class SaveCommand extends Command{ + + private final String bouquetName; + private final String savePath; + private final File saveFile; + + public SaveCommand(String bouquetName) { + this.bouquetName = bouquetName.trim(); + this.savePath = "./florizz-out/saved/" + bouquetName.trim() + ".txt"; + this.saveFile = new File(savePath); + } + + /** + * Executes command to save a bouquet externally + * @param bouquetList list of bouquets to be manipulated by the command + * @param ui ui class for printing + * @return True if command is successfully executed + * @throws FlorizzException If the bouquet does not exist + */ + @Override + public boolean execute(ArrayList bouquetList, Ui ui) throws FlorizzException { + assert !bouquetName.isEmpty() : "bouquet name cannot be empty"; + int bouquetIdx = bouquetList.indexOf(new Bouquet(bouquetName)); + if (bouquetIdx == -1) { + throw new FlorizzException("This bouquet does not exist. Create it by typing 'new '"); + } + Storage storage = new Storage(); + try { + Bouquet bouquetToAdd = bouquetList.get(bouquetIdx); + if (!saveFile.exists()) { + Files.createFile(Paths.get(savePath)); + } + FileWriter bouquetSaver = new FileWriter(savePath); + storage.saveBouquet(bouquetToAdd, bouquetSaver); + bouquetSaver.close(); + } catch (IOException e) { + System.out.println("File not found"); + } + ui.printSaveSuccess(bouquetName); + return true; + } +} diff --git a/src/main/java/florizz/core/Florizz.java b/src/main/java/florizz/core/Florizz.java new file mode 100644 index 0000000000..7d7e204940 --- /dev/null +++ b/src/main/java/florizz/core/Florizz.java @@ -0,0 +1,68 @@ +package florizz.core; + +import florizz.command.Command; +import florizz.logging.MyFormatter; +import florizz.storage.Storage; +import florizz.objects.Bouquet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.Level; + +public class Florizz { + private static Logger logger = Logger.getLogger(Florizz.class.getName()); + + /** + * Main entry-point for the java.florizz.core.Florizz application. + */ + public static void main(String[] args) { + // Adds flowers to the dict (temporary use) + FlowerDictionary.startup(); + + // Adds occasions to the dict (temporary use) + + ArrayList tempBouquetList = new ArrayList<>(); + boolean isRunning = true; + Ui ui = new Ui(); + ui.printIntroMessage(); + + // Set up logger + + LogManager.getLogManager().reset(); + Storage storage = new Storage(); + storage.tryReadStoredBouquets(tempBouquetList); + logger.setLevel(Level.ALL); + + // Set up console handler + ConsoleHandler ch = new ConsoleHandler(); + ch.setLevel(Level.SEVERE); + logger.addHandler(ch); + + // Set up file handler + try { + FileHandler fh = new FileHandler("./florizz-out/logs/FlorizzLogger.xml", 2000, 1); + fh.setFormatter(new MyFormatter()); + fh.setLevel(Level.ALL); + logger.addHandler(fh); + } catch (IOException e) { + ui.printIOError(); + } + + while (isRunning) { + logger.log(Level.INFO, "Entered isRunning while loop in Florizz.java"); + try { + System.out.println("What can I do for you?"); + String input = ui.getInput(); + Command command = Parser.parse(input.trim(), true); + isRunning = command.execute(tempBouquetList, ui); + storage.trySaveAllBouquets(tempBouquetList); + } catch (FlorizzException error) { + ui.printError(error); + } + } + } +} diff --git a/src/main/java/florizz/core/FlorizzException.java b/src/main/java/florizz/core/FlorizzException.java new file mode 100644 index 0000000000..ff1792f0d9 --- /dev/null +++ b/src/main/java/florizz/core/FlorizzException.java @@ -0,0 +1,10 @@ +package florizz.core; + +public class FlorizzException extends Exception { + public String errorMessage; + + public FlorizzException(String errorMessage){ + this.errorMessage = errorMessage; + } + +} diff --git a/src/main/java/florizz/core/FlowerDictionary.java b/src/main/java/florizz/core/FlowerDictionary.java new file mode 100644 index 0000000000..2a7183e35f --- /dev/null +++ b/src/main/java/florizz/core/FlowerDictionary.java @@ -0,0 +1,195 @@ +package florizz.core; + + +import java.util.ArrayList; +import java.util.Arrays; + +import florizz.objects.Flower; + +/** + * A class that contains a dictionary of preset flowers + */ +public class FlowerDictionary { + private static final ArrayList flowerDict = new ArrayList(); + /** + * Adds a new flower to the flower dictionary + * + * @param name Name of flower to be added + * @param colour Colour of flower to be added + * @param occasions Occasions that the flower can be bought for + */ + private static void add(String name, String colour, String[] occasions, + Double price, String[] meanings, Flower.Type type) { + + Flower.Colour colourEnum = Flower.Colour.valueOf(colour.replaceAll(" ", "_").toUpperCase()); + ArrayList occasionsArrayList = new ArrayList<>(); + for (String occasion : occasions) { + occasionsArrayList.add(Flower.Occasion.valueOf(occasion.replaceAll(" ", "_").toUpperCase())); + } + ArrayList meaningsArrayList = new ArrayList<>(Arrays.asList(meanings)); + flowerDict.add(new Flower(name, colourEnum, occasionsArrayList, price, meaningsArrayList, type)); + } + + + /** + * Adds flowers to the dictionary when florizz starts up (temporary) + */ + public static void startup() { + add("Orchid", "White", new String[]{"Wedding"}, 10.00, + new String[]{"Innocence", "Respect", "Beauty"}, Flower.Type.MAIN_FLOWER); + add("Rose", "Dark Crimson", new String[]{"Funeral"}, 2.00, + new String[]{"Mourning"}, Flower.Type.MAIN_FLOWER); + add("Rose", "Red", new String[]{"Valentines", "Wedding", "Mothers Day"}, 2.00, + new String[]{"Love"}, Flower.Type.MAIN_FLOWER); + add("Rose", "Yellow", new String[]{}, 2.00, + new String[]{"Jealousy, Decrease of love, Infidelity"}, Flower.Type.MAIN_FLOWER); + add("Lily", "White", new String[]{"Funeral", "Wedding"}, 2.50, + new String[]{"Innocence"}, Flower.Type.MAIN_FLOWER); + add("Lily", "Orange", new String[]{}, 2.50, + new String[]{"Hatred"}, Flower.Type.MAIN_FLOWER); + add("Daisy", "White", new String[]{"Valentines"}, 0.50, + new String[]{"Innocence"}, Flower.Type.MAIN_FLOWER); + add("Chrysanthemum", "White", new String[]{"Funeral"}, 1.00, + new String[]{"Love", "Loyalty", "Innocence"}, Flower.Type.MAIN_FLOWER); + add("Hydrangea", "Blue", new String[]{"Wedding"}, 9.00, + new String[]{"Forgiveness", "Gratitude"}, Flower.Type.MAIN_FLOWER); + add("Carnation", "Pink", new String[]{"Mothers Day"}, 2.00, + new String[]{"Gratitude", "Love"}, Flower.Type.MAIN_FLOWER); + add("Carnation", "Red", new String[]{"Valentines"}, 2.00, + new String[]{"My heart aches", "Deep Love"}, Flower.Type.MAIN_FLOWER); + // [Fillers have yet to be implemented] + add("Baby Breath", "White", new String[]{"Wedding", "Valentines", "Mothers Day"}, 1.00, + new String[]{"Innocence", "Kindness", "Care", "Humble"}, Flower.Type.MAIN_FLOWER); + + add("Eucalyptus", "Green", new String[]{"Wedding"}, 1.5, + new String[]{"Love", "Kindness"}, Flower.Type.MAIN_FLOWER); + add("Dusty Miller", "Green", new String[]{}, 1.5, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Pistacia", "Green", new String[]{}, 1.5, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Pittosporum", "Green", new String[]{}, 1.5, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Chamomile", "White", new String[]{}, 1.9, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Astilbe", "Pink", new String[]{}, 2.8, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Hypericum", "Red", new String[]{}, 2.0, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Freesia", "White", new String[]{}, 1.9, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Helichrysum", "Yellow", new String[]{}, 1.5, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Limonium", "Red", new String[]{}, 1.8, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Limonium", "Dark Crimson", new String[]{}, 1.8, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Limonium Perezii", "Purple", new String[]{}, 1.8, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Statice", "Blue", new String[]{}, 1.5, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Statice", "Purple", new String[]{}, 1.5, new String[]{}, Flower.Type.FILLER_FLOWER); + add("Rice Flower", "Pink", new String[]{}, 1.8, new String[]{}, Flower.Type.FILLER_FLOWER); + } + + /** + * Returns the size of flower dictionary + * + * @return The size as an int + */ + public static int size() { + return flowerDict.size(); + } + + /** + * Gets the flower at the selected index in the flower dictionary + * + * @param i Index to get flower from + * @return The flower object at index i + */ + public static Flower get(int i) { + return flowerDict.get(i); + } + + /** + * Gets a list of flowers that suit that occasion + * + * @param occasion occasion to filter flowers by + * @return an ArrayList of flowers to be printed by ui + */ + public static ArrayList filterByOccasion(Flower.Occasion occasion) { + ArrayList filteredFlowers = new ArrayList<>(); + for (Flower flower : flowerDict) { + if (flower.getOccasion().contains(occasion)) { + filteredFlowers.add(flower); + } + } + return filteredFlowers; + } + + /** + * Gets a list of flowers that contain the name search + * + * @param name name of Flowers to filter by + * @return an ArrayList of Flowers to be printed by ui + */ + public static ArrayList filterByName(String name) throws FlorizzException { + ArrayList filteredFlowers = new ArrayList<>(); + for (Flower flower : flowerDict) { + if (flower.getFlowerName().toLowerCase().contains(name.toLowerCase())) { + filteredFlowers.add(flower); + } + } + if (filteredFlowers.isEmpty()) { + throw new FlorizzException("Flower name is unidentified."); + } + return filteredFlowers; + } + + public static ArrayList filterByName(ArrayList listOfFlowers, String name) throws FlorizzException { + ArrayList filteredFlowers = new ArrayList<>(); + for (Flower flower : listOfFlowers) { + if (flower.getFlowerName().toLowerCase().contains(name.toLowerCase())) { + filteredFlowers.add(flower); + } + } + if (filteredFlowers.isEmpty()) { + throw new FlorizzException("Flower name is unidentified."); + } + return filteredFlowers; + } + + /** + * Gets a list of flowers that contain the colour search + * + * @param colour colour of Flowers to filter by + * @return an ArrayList of Flowers to be printed by ui + */ + public static ArrayList filterByColour(Flower.Colour colour) { + ArrayList filteredFlowers = new ArrayList<>(); + for (Flower flower : flowerDict) { + if (flower.getColour().equals(colour)) { + filteredFlowers.add(flower); + } + } + return filteredFlowers; + } + + /** + * Gets a list of flowers that contains the colour based on the list of flowers inputted + * + * @param listOfFlowers list of flowers to be filtered + * @param colour colour of Flowers to filter by + * @return an ArrayList of Flowers to be printed by ui + */ + public static ArrayList filterByColour(ArrayList listOfFlowers, Flower.Colour colour) { + ArrayList filteredFlowers = new ArrayList<>(); + for (Flower flower : listOfFlowers) { + if (flower.getColour().equalsIgnoreCase(Flower.colourToString(colour))) { + filteredFlowers.add(flower); + } + } + return filteredFlowers; + } + + public static ArrayList getAllFlowers(){ + return flowerDict; + } + + public static ArrayList getFlowersByType(Flower.Type type) { + ArrayList filteredFlowers = new ArrayList<>(); + for (Flower flower : flowerDict) { + if (flower.getType().equals(type)) { + filteredFlowers.add(flower); + } + } + return filteredFlowers; + } +} diff --git a/src/main/java/florizz/core/FuzzyLogic.java b/src/main/java/florizz/core/FuzzyLogic.java new file mode 100644 index 0000000000..edcf9c8030 --- /dev/null +++ b/src/main/java/florizz/core/FuzzyLogic.java @@ -0,0 +1,401 @@ +package florizz.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Provides functionality for detecting the closest command based on user input, + * by computing Levenshtein Distance between strings; + * removing unnecessary whitespaces within command word (e.g. h e l p, flo wer s); + * and splitting inputs with the correct words but wrong format (e.g. infoRose, deletebouquetname), + * for the purpose of reducing the impact human error and improving programme flow. + */ +public class FuzzyLogic { + + private static final Map ITEMS = new HashMap<>(); + private static final int SIMILARITY_THRESHOLD = 2; + private static final Logger logger = Logger.getLogger(FuzzyLogic.class.getName()); + + static { + ITEMS.put("save", "Command: Save a bouquet to device"); + ITEMS.put("new", "Command: Add a new bouquet"); + ITEMS.put("delete", "Command: Delete a bouquet"); + ITEMS.put("mybouquets", "Command: List all bouquets"); + ITEMS.put("info", "Command: Get information about a flower"); + ITEMS.put("bye", "Command: Exits the programme"); + ITEMS.put("flowers", "Command: List all flowers"); + ITEMS.put("remove","Command: Remove flower(s) from a bouquet"); + ITEMS.put("occasion", "Command: List flowers of specified occasion"); + ITEMS.put("add", "Command: Add flower(s) to a bouquet"); + ITEMS.put("help", "Command: List available commands"); + ITEMS.put("recommend", "Command: Get a recommended bouquet"); + ITEMS.put("next", "Command: Goes to next page"); + ITEMS.put("back", "Command: Goes to previous page"); + ITEMS.put("compare", "Command: Compare two flowers"); + ITEMS.put("Orchid", "Flower"); + ITEMS.put("Rose", "Flower"); + ITEMS.put("Lily", "Flower"); + ITEMS.put("Daisy", "Flower"); + ITEMS.put("Baby Breath", "Flower"); + ITEMS.put("Chrysanthemum", "Flower"); + ITEMS.put("Hydrangea", "Flower"); + ITEMS.put("Carnation", "Flower"); + ITEMS.put("Wedding", "Occasion"); + ITEMS.put("Valentines", "Occasion"); + ITEMS.put("Mothers Day", "Occasion"); + ITEMS.put("Funeral", "Occasion"); + ITEMS.put("Eucalyptus", "Flower"); + ITEMS.put("Dusty Miller", "Flower"); + ITEMS.put("Pistacia", "Flower"); + ITEMS.put("Pittosporum", "Flower"); + ITEMS.put("Chamomile", "Flower"); + ITEMS.put("Astilbe", "Flower"); + ITEMS.put("Hypericum", "Flower"); + ITEMS.put("Freesia", "Flower"); + ITEMS.put("Helichrysum", "Flower"); + ITEMS.put("Limonium", "Flower"); + ITEMS.put("Limonium Perezii", "Flower"); + ITEMS.put("Statice", "Flower"); + ITEMS.put("Rice Flower", "Flower"); + ITEMS.put("Red", "Colour"); + ITEMS.put("Yellow", "Colour"); + ITEMS.put("White", "Colour"); + ITEMS.put("Blue", "Colour"); + ITEMS.put("Pink", "Colour"); + ITEMS.put("Green", "Colour"); + ITEMS.put("Purple", "Colour"); + ITEMS.put("Dark Crimson", "Colour"); + } + + /** + * Detects the closest predefined command/item/occasion based on user input. + * + * @param userInput The user input to be matched with predefined command/item/occasion. + * @return The closest matching command/item/occasion. + * @throws FlorizzException if the input is null or no matching command/item/occasion is found. + */ + protected static String detectItem(String userInput) throws FlorizzException { + if (userInput == null) { + throw new FlorizzException("Input cannot be null"); + } + + if (userInput.length() == 1) { + throw new FlorizzException("No command/flower/occasion/colour matches the input: " + userInput); + } + + String bestMatch = null; + int bestDistance = Integer.MAX_VALUE; + Ui uiFuzzy = new Ui(); + + // Iterate over predefined commands + for (String item : ITEMS.keySet()) { + int distance = computeDLDistance(item.toLowerCase(), userInput.toLowerCase()); + if (distance < bestDistance) { + bestDistance = distance; + bestMatch = item; + } + } + + // If the best match is within threshold, return it; otherwise, return null + if (bestDistance <= SIMILARITY_THRESHOLD && bestDistance != 0) { + logger.log(Level.INFO, "--> Detected input: [{0}]", bestMatch); + uiFuzzy.printFuzzyInputDetection(userInput, bestMatch); + return bestMatch; + } else if (bestDistance == 0) { + return bestMatch; + } else { + logger.log(Level.SEVERE, "No command/flower/occasion/colour matches the input: {0}", userInput); + throw new FlorizzException("No command/flower/occasion/colour matches the input: " + userInput); + } + } + + /** + * Computes the Damerau-Levenshtein distance which is a metric used to measure the similarity between two strings. + * It calculates the minimum number of single-character edits required to change one string into another. + * These edits can be insertions, deletions, substitutions and transpositions of individual characters. + * + * @param item The first string. + * @param userInput The second string. + * @return The Damerau-Levenshtein distance between the two strings. + * @throws FlorizzException if the loop tries to access elements beyond the array bounds. + */ + protected static int computeDLDistance(String item, String userInput) throws FlorizzException { + assert item != null && userInput != null : "Strings cannot be null"; + + int itemLength = item.length(); + int inputLength = userInput.length(); + + int[] previousRow = new int[inputLength + 1]; + int[] currentRow = new int[inputLength + 1]; + + // Initialize the first row + for (int j = 0; j <= inputLength; j++) { + previousRow[j] = j; + } + + // Calculate the Damerau-Levenshtein distance + for (int i = 1; i <= itemLength; i++) { + currentRow[0] = i; + + for (int j = 1; j <= inputLength; j++) { + if (i > itemLength || j > inputLength) { + throw new FlorizzException("Fuzzy Logic: Accessing element outside of array bounds"); + } + + int substitutionCost = (item.charAt(i - 1) == userInput.charAt(j - 1)) ? 0 : 1; + currentRow[j] = Math.min(Math.min( + previousRow[j] + 1, // deletion + currentRow[j - 1] + 1), // insertion + previousRow[j - 1] + substitutionCost); // substitution + + // Check for transpositions + if (i > 1 && j > 1 && item.charAt(i - 1) == userInput.charAt(j - 2) + && item.charAt(i - 2) == userInput.charAt(j - 1)) { + currentRow[j] = Math.min(currentRow[j], previousRow[j - 2] + substitutionCost); + } + } + + // Swap rows + int[] tempRow = previousRow; + previousRow = currentRow; + currentRow = tempRow; + } + + // Return the Damerau-Levenshtein distance + assert previousRow[inputLength] >= 0 : "Levenshtein distance cannot be negative"; + return previousRow[inputLength]; + } + + /** + * Processes the user input command and returns the input string at the correct format. + * + * + * @param userInput The user input command string. + * @return The corrected input string after processing. + * @throws FlorizzException if the input is null or empty. + */ + protected static String processCommand(String userInput) throws FlorizzException { + if (userInput == null) { + throw new FlorizzException("Input cannot be empty."); + } + String correctedInput; + String trimmedInput = userInput.trim(); + String[] words = trimmedInput.split(" "); + int firstWhitespace = trimmedInput.indexOf(" "); + if ((firstWhitespace != -1) + && (userInput.startsWith("delete") + || userInput.startsWith("save") + || userInput.startsWith("new") + || userInput.startsWith("remove") + || userInput.startsWith("add") + || userInput.startsWith("info") + || (userInput.startsWith("compare") && words.length == 4 || words.length == 5) + || userInput.startsWith("flowers"))) { + String[] arguments = new String[2]; + arguments[0] = userInput.substring(0,firstWhitespace).toLowerCase(); + arguments[1] = userInput.substring(firstWhitespace).trim(); + correctedInput = detectItem(arguments[0]) + " " + arguments[1]; + } else if ((firstWhitespace == -1) + && userInput.equals("help") + || userInput.equals("mybouquets") + || userInput.equals("flowers") + || userInput.equals("recommend") + || userInput.equals("bye") + || userInput.equals("occasion") + || userInput.equals("back") + || userInput.equals("next")) { + correctedInput = userInput; + } else { + correctedInput = splitAndMergeInput(userInput); + } + return correctedInput; + } + + /** + * Splits and merges the user input command into an input string with the correct format. + * + * @param userInput The user input command string. + * @return The corrected input string after splitting and merging. + * @throws FlorizzException if there is an issue processing the input. + */ + protected static String splitAndMergeInput(String userInput) throws FlorizzException { + String correctedInput; + String mergedInput = mergeInput(userInput); + String splitMergedInput = splitInput(mergedInput); + String[] arguments = splitMergedInput.split(" "); + String bouquetName; + String removeArgument; + String addArgument; + Ui uiFuzzy = new Ui(); + + if (arguments[0] == null) { + throw new FlorizzException("Input cannot be empty."); + } + + if (arguments.length == 1 && arguments[0] + .matches("(mybouquets|flowers|occasion|recommend|bye|help|back|next)")) { + correctedInput = arguments[0]; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(info|flowers)")) { + if (arguments.length == 1 && arguments[0].matches("(info)")) { + throw new FlorizzException("Incorrect format for " + arguments[0] + ". Correct format: " + + arguments[0] + " "); + } + if (arguments.length == 1 && arguments[0].matches("(flowers)")) { + throw new FlorizzException("Incorrect format for " + arguments[0] + ". Correct format: " + + arguments[0] + " "); + } + if (Objects.equals(arguments[1].toLowerCase(), "mothersday")) { + arguments[1] = "Mothers Day"; + } + correctedInput = arguments[0] + " " + arguments[1]; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(new)")) { + if (arguments.length == 1) { + throw new FlorizzException("Incorrect format for new. Correct format: new "); + } + bouquetName = userInput.replaceFirst("n", "") + .replaceFirst("e", "") + .replaceFirst("w", "") + .strip(); + correctedInput = "new " + bouquetName; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(delete)")) { + if (arguments.length == 1) { + throw new FlorizzException("Incorrect format for delete. Correct format: delete "); + } + bouquetName = userInput.replaceFirst("d", "") + .replaceFirst("e", "") + .replaceFirst("l", "") + .replaceFirst("e", "") + .replaceFirst("t", "") + .replaceFirst("e", "") + .strip(); + correctedInput = "delete " + bouquetName; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(save)")) { + if (arguments.length == 1) { + throw new FlorizzException("Incorrect format for save. Correct format: save "); + } + bouquetName = userInput.replaceFirst("s", "") + .replaceFirst("a", "") + .replaceFirst("v", "") + .replaceFirst("e", "") + .strip(); + correctedInput = "save " + bouquetName; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(remove)")) { + if (arguments.length == 1) { + throw new FlorizzException("Incorrect format for remove. Correct format: remove " + + " /c /q /from "); + } + removeArgument = userInput.replaceFirst("r", "") + .replaceFirst("e", "") + .replaceFirst("m", "") + .replaceFirst("o", "") + .replaceFirst("v", "") + .replaceFirst("e", "") + .strip(); + correctedInput = "remove " + removeArgument; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(add)")) { + if (arguments.length == 1) { + throw new FlorizzException("Incorrect format for add. Correct format: add " + + " /c /q /to "); + } + addArgument = userInput.replaceFirst("a", "") + .replaceFirst("d", "") + .replaceFirst("d", "") + .strip(); + correctedInput = "add " + addArgument; + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else if (arguments[0].matches("(compare)")) { + if (arguments.length == 1) { + throw new FlorizzException("Incorrect format for compare. Correct format: compare " + + " /vs/ "); + } + String[] flowerNames; + if (arguments[1].contains("/vs/")) { + flowerNames = arguments[1].split("/vs/"); + } else { + throw new FlorizzException("Incorrect format for compare. Correct format: compare " + + " /vs/ "); + } + + if (flowerNames.length != 2 || flowerNames[0].length() <= 2 || flowerNames[1].length() <= 2) { + throw new FlorizzException("Please input 2 flowers to compare."); + } + correctedInput = "compare " + flowerNames[0].strip() + " /vs/ " + flowerNames[1].strip(); + uiFuzzy.printFuzzyInputDetection(userInput, correctedInput); + } else { + correctedInput = userInput; + } + return correctedInput; + } + + /** + * Separates the input string into a command and an argument. + * If the input string matches a predefined command pattern, it corrects the input by adding a space between the + * command and the argument. + * Supported commands include: info, delete, flowers, new, add, remove, save. + * If the input does not match any predefined command pattern, it returns the original input string unchanged. + * + * @param userInput The input string to be separated and corrected. + * @return correctedInput The input with a space between the command and the argument if applicable. + * @throws FlorizzException if the input string is null. + */ + protected static String splitInput(String userInput) throws FlorizzException { + if (userInput == null) { + throw new FlorizzException("Input cannot be empty."); + } + + String correctedInput = ""; + if (userInput.matches("(info)[a-zA-Z].*")) { + String argumentInfo = userInput.replaceFirst("(info)", ""); + correctedInput = "info " + argumentInfo; + } else if (userInput.matches("(delete).+")) { + String argumentDelete = userInput.replaceFirst("delete", ""); + correctedInput = "delete " + argumentDelete; + } else if (userInput.matches("(flowers)[a-zA-Z].*")) { + String argumentFlowers = userInput.replaceFirst("(flowers)", ""); + correctedInput = "flowers " + argumentFlowers; + } else if (userInput.matches("(new).+")) { + String argumentNew = userInput.replaceFirst("new", ""); + correctedInput = "new " + argumentNew; + } else if (userInput.matches("(add)[a-zA-Z].+")) { + String argumentAdd = userInput.replaceFirst("add", ""); + correctedInput = "add " + argumentAdd; + } else if (userInput.matches("(remove)[a-zA-Z].+")) { + String argumentRemove = userInput.replaceFirst("remove", ""); + correctedInput = "remove " + argumentRemove; + } else if (userInput.matches("(save)[a-zA-Z].+")) { + String argumentSave = userInput.replaceFirst("save", ""); + correctedInput = "save " + argumentSave; + } else if (userInput.matches("(compare)[a-zA-Z].+")) { + String argumentCompare = userInput.replaceFirst("compare", ""); + correctedInput = "compare " + argumentCompare; + } + + if (correctedInput.isEmpty()) { + correctedInput = userInput; + } + return correctedInput; + } + + /** + * Merges the user input command by removing all whitespace characters. + * + * @param userInput The user input command string. + * @return The merged input string with whitespace removed. + * @throws FlorizzException if the input is null. + */ + protected static String mergeInput(String userInput) throws FlorizzException { + if (userInput == null) { + throw new FlorizzException("Input cannot be empty."); + } + return userInput.replaceAll("\\s", ""); + } +} diff --git a/src/main/java/florizz/core/Parser.java b/src/main/java/florizz/core/Parser.java new file mode 100644 index 0000000000..323670051f --- /dev/null +++ b/src/main/java/florizz/core/Parser.java @@ -0,0 +1,460 @@ +package florizz.core; + +import florizz.command.AddBouquetCommand; +import florizz.command.AddFlowerCommand; +import florizz.command.BackCommand; +import florizz.command.Command; +import florizz.command.CompareCommand; +import florizz.command.DeleteBouquetCommand; +import florizz.command.ExitCommand; +import florizz.command.FlowerCommand; +import florizz.command.HelpCommand; +import florizz.command.InfoCommand; +import florizz.command.ListBouquetCommand; +import florizz.command.ListOccasionCommand; +import florizz.command.NextCommand; +import florizz.command.RecommendCommand; +import florizz.command.RemoveFlowerCommand; +import florizz.command.SaveCommand; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.util.logging.Level; +import java.util.logging.Logger; + + + +/** + * Parses user input and generates appropriate Command objects. + */ +public class Parser { + private static Logger logger = Logger.getLogger(Florizz.class.getName()); + // prefixes to parse input + private static final String COLOUR = "/c"; + private static final String QUANTITY = "/q"; + private static final String ADD_FLOWER_PREFIX = "/to"; + private static final String REMOVE_FLOWER_PREFIX = "/from"; + + // regex + private static final String ADD_FLOWER_REGEX = "(.+)/q(\\s*)(-?\\d+)(\\s*)/to(.+)"; + + private static final String ADD_FLOWER_AND_COLOUR_REGEX = "(.+)/c(\\s*)(.+)(\\s*)/q(\\s*)(-?\\d+)(\\s*)/to(.+)"; + private static final String REMOVE_FLOWER_REGEX = "(.+)/q(\\s*)(-?\\d+)(\\s*)/from(.+)"; + private static final String REMOVE_FLOWER_AND_COLOUR_REGEX + = "(.+)/c(\\s*)(.+)(\\s*)/q(\\s*)(-?\\d+)(\\s*)/from(.+)"; + private static final String PARSE_OCCASION_REGEX = "^\\s*[A-Za-z]+(?:\\s+[A-Za-z]+)?\\s*$"; + private static final String PARSE_COLOUR_REGEX = "^\\s*[A-Za-z]+(?:\\s+[A-Za-z]+)?\\s*$"; + private static final String SAVE_BOUQUET_REGEX = "^\\s*(yes|no)\\s*$"; + private static final String PARSE_SIZE_REGEX = "^\\s*[A-Za-z]+\\s*$"; + + public static Command parse (String input, boolean enableUi) throws FlorizzException { + logger.entering("Parser", "parse"); + Command command = null; + + try { + String[] decodedInput = commandHandler(input); + switch (decodedInput[0]) { + case ("mybouquets"): + command = new ListBouquetCommand(); + break; + case ("new"): + command = handleAddBouquet(decodedInput[1], enableUi); + break; + case ("delete"): + command = handleDeleteBouquet(decodedInput[1]); + break; + case ("bye"): + command = new ExitCommand(); + break; + case ("help"): + command = new HelpCommand(); + break; + case ("flowers"): + command = handleFlowerCommand(decodedInput); + break; + case ("info"): + command = handleInfoCommand(decodedInput[1]); + break; + case ("next"): + command = new NextCommand(); + break; + case ("back"): + command = new BackCommand(); + break; + case ("occasion"): + command = new ListOccasionCommand(); + break; + case ("add"): + command = handleAddFlower(decodedInput[1], enableUi); + break; + case ("remove"): + command = handleRemoveFlower(decodedInput[1]); + break; + case ("recommend"): + command = new RecommendCommand(); + break; + case ("save"): + command = new SaveCommand(decodedInput[1]); + break; + case ("compare"): + command = handleCompareCommand(decodedInput[1], decodedInput[2]); + break; + default: + throw new FlorizzException("Unidentified input, type help to get a list of all commands!"); + } + logger.log(Level.INFO, "Command parsed successfully"); + } catch (FlorizzException ex) { + logger.log(Level.INFO, "Exception occurred while parsing command: " + ex.errorMessage, ex); + throw ex; + } finally { + logger.exiting("Parser", "parse"); + } + return command; + } + + + /** + * Splits input into command and arguments (if any). Handles capitalisation, whitespaces and small typos. + * + * @param input The user input where command is to be extracted + * @return String[] output; output[0] = item ; output[1] = argument(s) + */ + public static String[] commandHandler(String input) throws FlorizzException { + String[] outputs = new String[3]; + String trimmedInput = input.trim(); + String processedInput = FuzzyLogic.processCommand(trimmedInput); + int firstWhitespace = processedInput.indexOf(" "); + + if (firstWhitespace != -1) { + outputs[0] = FuzzyLogic.detectItem(processedInput.substring(0,firstWhitespace).toLowerCase()); + switch (outputs[0]) { + case ("save"): // Fallthrough + case ("delete"): // Fallthrough + case ("new"): + outputs[1] = processedInput.substring(firstWhitespace).trim(); + break; + case ("remove"): // Fallthrough + case ("add"): + String[] arguments = new String[2]; + String trimmedArgument = processedInput.substring(firstWhitespace).trim(); + int firstSlash = trimmedArgument.indexOf("/"); + if (firstSlash < 0 && outputs[0].equals("remove")) { + throw new FlorizzException("Incorrect usage of remove." + + " Correct format: remove " + + "/c (optional) " + + "/q /from "); + } else if (firstSlash < 0) { // add + throw new FlorizzException("Incorrect usage of add." + + " Correct format: add " + + "/c (optional) " + + "/q /to "); + } + arguments[0] = FuzzyLogic.detectItem(trimmedArgument.substring(0,firstSlash).trim()); + arguments[1] = trimmedArgument.substring(firstSlash).trim(); + outputs[1] = arguments[0] + " " + arguments[1]; + break; + case ("compare"): + outputs[1] = processedInput.substring(firstWhitespace).trim(); + String[] flowers = outputs[1].split(" /vs/ "); + if (flowers.length != 2) { + throw new FlorizzException("Incorrect usage of compare." + + " Correct format: compare /vs/ "); + } + outputs[1] = FuzzyLogic.detectItem(flowers[0]); + outputs[2] = FuzzyLogic.detectItem(flowers[1]); + break; + default: + outputs[1] = FuzzyLogic.detectItem(processedInput.substring(firstWhitespace).trim()); + break; + } + } else { + outputs[0] = FuzzyLogic.detectItem(processedInput.toLowerCase()); + } + if (firstWhitespace == -1 && (outputs[0].equals("save"))) { + throw new FlorizzException("Please specify which bouquet you are saving!"); + } + + return outputs; + } + + /** + * Handles the parsing and creation of a CompareCommand object based on user input. + * + * @param firstFlowerName The name of the first flower to compare. + * @param secondFlowerName The name of the second flower to compare. + * @return A CompareCommand object corresponding to the parsed input. + * @throws FlorizzException If the input does not contain the required flower names. + */ + private static CompareCommand handleCompareCommand(String firstFlowerName, String secondFlowerName) + throws FlorizzException { + if (firstFlowerName == null || secondFlowerName == null) { + throw new FlorizzException("Please specify two flowers to compare!"); + } + return new CompareCommand(firstFlowerName, secondFlowerName); + } + + /** + * remove prefix from an input string + * e.g. "/to For Mom" -> " For Mom" + * + * @param input The argument + * @param prefix The keyword that identifies the argument + * @return input with prefix removed + */ + public static String removePrefix(String input, String prefix) { + return input.replace(prefix, ""); + } + + /** + * Handles the parsing and creation of an AddBouquetCommand object based on user input. + * @param input The user input to be parsed. + * @return An AddBouquetCommand object corresponding to the parsed input. + * @throws FlorizzException If the input does not contain the required bouquet information. + */ + private static AddBouquetCommand handleAddBouquet(String input, boolean enableUi) throws FlorizzException{ + if (input == null){ + throw new FlorizzException("Did not include bouquet to add"); + } + String newBouquetName = input.trim(); + return new AddBouquetCommand(new Bouquet(newBouquetName), enableUi); + } + + /** + * Handles the parsing and creation of a DeleteBouquetCommand object based on user input. + * @param input The user input to be parsed. + * @return A DeleteBouquetCommand object corresponding to the parsed input. + * @throws FlorizzException If the input does not contain the required bouquet information. + */ + private static DeleteBouquetCommand handleDeleteBouquet(String input) throws FlorizzException{ + if (input == null){ + throw new FlorizzException("Did not include bouquet to delete"); + } + String bouquetToDelete = input.trim(); + return new DeleteBouquetCommand(new Bouquet(bouquetToDelete)); + } + + /** + * Handles the parsing and creation of a FlowerCommand object based on user input. + * @param input The user input to be parsed. + * @return A FlowerCommand object corresponding to the parsed input. + */ + private static FlowerCommand handleFlowerCommand(String[] input) { + String occasion = (input[1] == null) ? " " : input[1].trim(); + return new FlowerCommand(occasion); + } + + /** + * Handles the parsing and creation of an AddFlowerCommand object based on user input. + * @param argument The user input to be parsed. + * @return An AddFlowerCommand object corresponding to the parsed input. + * @throws FlorizzException If the input does not match the required format. + */ + private static AddFlowerCommand handleAddFlower(String argument, boolean enableUi) throws FlorizzException { + boolean includeColour = false; + if (argument == null) { + throw new FlorizzException("No argument detected! " + + "Please use the correct format of 'add /q /to "); + } + + if (!argument.matches(ADD_FLOWER_REGEX)) { + throw new FlorizzException("Incorrect format detected! " + + "Please use the correct format of 'add /q /to "); + } + + if (argument.matches(ADD_FLOWER_AND_COLOUR_REGEX)){ + includeColour = true; + } + // [WARNING] might need to check for extra slash k + + int prefixIndex = argument.indexOf(ADD_FLOWER_PREFIX); + int quantityIndex = argument.indexOf(QUANTITY); + + String flowerName = argument.substring(0,quantityIndex).trim().toLowerCase(); + String quantityString = removePrefix(argument.substring(quantityIndex, prefixIndex), QUANTITY).trim(); + // [WARNING] might need to check if it's a valid integer + int quantity; + try { + quantity = Integer.parseInt(quantityString); + } catch (NumberFormatException error) { + throw new FlorizzException("Invalid number inputted, please enter a sensible number next time!"); + } + if (quantity < 0) { + throw new FlorizzException("Flowers are physical objects which cannot be negative!"); + } + String bouquetName = removePrefix(argument.substring(prefixIndex), ADD_FLOWER_PREFIX).trim(); + if (includeColour) { + int colourIndex = argument.indexOf(COLOUR); + try { + flowerName = argument.substring(0,colourIndex).trim(); + String colourString = FuzzyLogic.detectItem( + removePrefix(argument.substring(colourIndex, quantityIndex), COLOUR).trim()); + Flower.Colour colourToAdd = Flower.stringToColour(colourString); + return new AddFlowerCommand(flowerName, colourToAdd, quantity, bouquetName, enableUi); + } catch ( IllegalArgumentException error) { + throw new FlorizzException("Tried to add a non recognised colour" + + "Type 'flowers' to view all the currently available flowers and their colours."); + } + } + return new AddFlowerCommand(flowerName, quantity, bouquetName, enableUi); + } + + /** + * Handles the parsing and creation of a RemoveFlowerCommand object based on user input. + * @param argument The user input to be parsed. + * @return A RemoveFlowerCommand object corresponding to the parsed input. + * @throws FlorizzException If the input does not match the required format. + */ + private static RemoveFlowerCommand handleRemoveFlower(String argument) throws FlorizzException { + boolean includeColour = false; + if (argument == null) { + throw new FlorizzException("No argument detected! " + + "Please use the correct format of 'remove /q /from "); + } + + if (!argument.matches(REMOVE_FLOWER_REGEX)) { + throw new FlorizzException("Incorrect format detected! " + + "Please use the correct format of 'remove /q /from "); + } + if (argument.matches(REMOVE_FLOWER_AND_COLOUR_REGEX)){ + includeColour = true; + } + // [WARNING] might need to check for extra slash k + + int prefixIndex = argument.indexOf(REMOVE_FLOWER_PREFIX); + int quantityIndex = argument.indexOf(QUANTITY); + + String flowerName = argument.substring(0, quantityIndex).trim().toLowerCase(); + String quantityString = removePrefix(argument.substring(quantityIndex, prefixIndex), QUANTITY).trim(); + // [WARNING] might need to check if it's a valid integer + int quantity; + try { + quantity = Integer.parseInt(quantityString); + } catch(NumberFormatException error) { + throw new FlorizzException("Invalid number inputted, please enter a sensible number next time!"); + } + if (quantity < 0) { + throw new FlorizzException("Flowers are physical objects which cannot be negative!"); + } + String bouquetName = removePrefix(argument.substring(prefixIndex), REMOVE_FLOWER_PREFIX).trim(); + if (includeColour){ + int colourIndex = argument.indexOf(COLOUR); + try{ + flowerName = argument.substring(0,colourIndex).trim(); + String colourString = removePrefix(argument.substring(colourIndex, quantityIndex), COLOUR).trim(); + Flower.Colour colourToAdd = Flower.stringToColour(colourString); + return new RemoveFlowerCommand(flowerName, colourToAdd, quantity, bouquetName); + } catch(IllegalArgumentException error){ + throw new FlorizzException("Tried to add a non recognised colour" + + "Type 'flowers' to view all the currently available flowers and their colours."); + } + } + return new RemoveFlowerCommand(flowerName, quantity, bouquetName); + } + + /** + * Handles the parsing and creation of an InfoCommand object based on user input. + * @param flowerName The user input to be parsed. + * @return An InfoCommand object corresponding to the parsed input. + */ + private static InfoCommand handleInfoCommand(String flowerName) throws FlorizzException { + if (flowerName == null) { + throw new FlorizzException("Please specify flower name to retrieve info from."); + } + return new InfoCommand(flowerName); + } + + /** + * Parses the occasion from the user input. + * @param argument The user input to be parsed. + * @return The parsed occasion. + */ + public static boolean parseOccasion(String argument) throws FlorizzException { + if (argument == null || argument.isEmpty()) { + System.out.println("No argument detected! " + + "Please input an occasion"); + return false; + } + + if (!argument.matches(PARSE_OCCASION_REGEX)) { + System.out.println("Incorrect format detected! " + + "Please input a single occasion"); + return false; + } + + return true; + } + + /** + * Parses the colour from the user input. + * @param argument The user input to be parsed. + * @return The parsed colour String + */ + public static boolean parseColour(String argument) { + if (argument == null || argument.isEmpty()) { + System.out.println("No argument detected! " + + "Please input a colour"); + return false; + } + + if (!argument.matches(PARSE_COLOUR_REGEX)) { + System.out.println("Incorrect format detected! " + + "Please input a single colour"); + return false; + } + + return true; + } + + /** + * Parses the user input to save a bouquet. + * @param argument The user input to be parsed. + * @return The parsed save bouquet String + */ + public static boolean parseSaveBouquet(String argument) { + if (argument == null || argument.isEmpty()) { + System.out.println("No argument detected! " + + "Please input a bouquet name to save"); + return false; + } + + if (!argument.matches(SAVE_BOUQUET_REGEX)) { + System.out.println("Incorrect format detected! " + + "Please input a yes or a no"); + return false; + } + + return true; + } + + /** + * Checks if the user has entered the exit word for the recommend page + * @param input Input from the user + * @throws FlorizzException Thrown when the user has entered the exit word + */ + public static void checkRecommendExitCondition(String input) throws FlorizzException{ + if (input.equalsIgnoreCase("cancel")) { + throw new FlorizzException("Leaving recommend"); + } + } + + /** + * Parses the size from the user input. + * @param argument the user input to be parsed. + * @return whether it is a valid size format + */ + public static boolean parseSize(String argument) { + if (argument == null || argument.isEmpty()) { + System.out.println("No argument detected! " + + "Please input a size of either small, medium or large"); + return false; + } + + if (!argument.matches(PARSE_SIZE_REGEX)) { + System.out.println("Incorrect format detected! " + + "Please input a single size"); + return false; + } + + return true; + } +} diff --git a/src/main/java/florizz/core/Ui.java b/src/main/java/florizz/core/Ui.java new file mode 100644 index 0000000000..b8c0877415 --- /dev/null +++ b/src/main/java/florizz/core/Ui.java @@ -0,0 +1,626 @@ +package florizz.core; + +import com.github.freva.asciitable.AsciiTable; +import com.github.freva.asciitable.Column; +import com.github.freva.asciitable.HorizontalAlign; +import com.github.freva.asciitable.OverflowBehaviour; +import florizz.objects.Bouquet; +import florizz.objects.Flower; +import florizz.objects.TableData; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Scanner; + +public class Ui { + private static ArrayList lastShownList = new ArrayList<>(); + private static int lastPageNo = 0; + private static final int PAGE_SIZE = 5; + private static final Scanner inputScanner = new Scanner (System.in); + + private static String lastCommand = ""; + /** + * Prints the introductory message. + */ + public void printIntroMessage(){ + String logo = "\n" + + " __ _ _\n" + + " / _| | (_)\n" + + " | |_| | ___ _ __ _ ________\n" + + " | _| |/ _ \\| '__| |_ /_ /\n" + + " | | | | (_) | | | |/ / / /\n" + + " |_| |_|\\___/|_| |_/___/___|\n" + + "\n"; + System.out.println("Hello from\n" + logo); + System.out.println("Type `help`, to view a table of valid commands!"); + } + + /** + * Prints a break line. + */ + public static void printBreakLine(){ + System.out.println(("____________________________________________________________")); + } + + /** + * Gets input from the user typing commands into the CLI. + * @return Returns the user input as one String. + */ + public String getInput(){ + try{ + return inputScanner.nextLine(); + } catch(NoSuchElementException | IllegalStateException error){ + return "bye"; + } + } + + /** + * Prints the message indicating a new bouquet has been added. + * @param bouquetAdded The bouquet that has been added. + */ + public void printBouquetAdded(Bouquet bouquetAdded) { + lastCommand = "OTHERS"; + System.out.println("Added new bouquet to list:\n" + bouquetAdded); + printBreakLine(); + } + + public void printBouquetNotAdded(Bouquet bouquetDiscarded) { + System.out.println("Discarded the bouquet: " + bouquetDiscarded); + printBreakLine(); + } + + /** + * Prints the message indicating a bouquet has been deleted. + * @param bouquetDeleted The bouquet that has been deleted. + */ + public void printBouquetDeleted(Bouquet bouquetDeleted){ + lastCommand = "OTHERS"; + System.out.println("Deleted bouquet:\n" + bouquetDeleted); + printBreakLine(); + + } + + /** + * Prints all saved bouquets with their flowers and total estimated price. + * @param bouquetList The list of saved bouquets. + */ + public void printAllBouquets(ArrayList bouquetList){ + lastCommand = "BOUQUETS"; + System.out.println("Here is the list of your saved bouquets:"); + int i = 1; + double totalPrice; + for (Bouquet bouquet : bouquetList){ + System.out.println(i++ + ". " + bouquet + " :"); + HashMap flowerHashMap = bouquet.getFlowerHashMap(); + totalPrice = 0; + if (!flowerHashMap.isEmpty() ) { + for (Flower j : flowerHashMap.keySet()) { + if (flowerHashMap.get(j) != 0) { + System.out.println(" - " + flowerHashMap.get(j) + " x " + j.getNameAndColour()); + totalPrice += (flowerHashMap.get(j) * j.getPrice()); + } + } + System.out.println(" Total estimated price = $" + String.format("%.2f", (double) totalPrice)); + } else { + System.out.println(" No flowers added so far"); + } + + } + printBreakLine(); + } + + /** + * Prints all flowers in a bouquet. + * @param bouquet The bouquet to print. + */ + public void printFullBouquet(Bouquet bouquet) { + System.out.println("Here is the full list of flowers in " + bouquet.getBouquetName() + ":"); + HashMap flowerHashMap = bouquet.getFlowerHashMap(); + if (!flowerHashMap.isEmpty()) { + for (Flower j : flowerHashMap.keySet()) { + System.out.println(" - " + flowerHashMap.get(j) + " x " + j.getFlowerName()); + } + } else { + System.out.println(" No flowers added so far"); + } + printBreakLine(); + } + + /** + * print all available commands + */ + public void printHelpMessage() { + lastCommand = "OTHERS"; + System.out.println("Here is the table showing a list of commands you can use:"); + List tableData = Arrays.asList( + new TableData(1, "new " + , "Add a bouquet" + , "new Birthday Bouquet"), + new TableData(2, "delete " + , "Delete a bouquets" + , "delete Birthday Bouquet"), + new TableData(3, "mybouquets" + , "List current saved bouquets" + , "mybouquets"), + new TableData(4, "info " + , "Provide information on chosen flower" + , "info Rose"), + new TableData(5, "add /c (optional) /q /to " + , "Add flower to a bouquet" + , "add Rose /c Red /q 5 /to Birthday Bouquet"), + new TableData(6, "remove /c (optional) /q /from " + , "Remove flower from a bouquet" + , "remove Rose /c Red /q 5 /from Birthday Bouquet"), + new TableData(7, "flowers" + , "Shows a list of flowers that can be added into mybouquets" + , "flowers"), + new TableData(8, "flowers " + , "Shows a list of flowers associated with said occasion" + , "flowers Valentines"), + new TableData(9, "occasion" + , "Shows a list of occasions associated with available flowers" + , "occasion"), + new TableData(10, "save " + , "Saves a bouquet to an external .txt file" + , "save Birthday Bouquet"), + new TableData(11, "recommend" + , "Recommends a bouquet based on the chosen occasion and colour" + , "recommend"), + new TableData(12, "compare <1st flowerName> /vs/ <2nd flowerName>" + , "Show information regarding two flowers side-by-side for comparison" + , "compare Rose /vs/ Lily"), + new TableData(13, "bye" + , "Exits the programme" + , "bye") + ); + System.out.println(AsciiTable.getTable(AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS, tableData, Arrays.asList( + new Column().header("No.").dataAlign(HorizontalAlign.CENTER) + .maxWidth(5, OverflowBehaviour.NEWLINE) + .with((TableData data) -> Integer.toString(data.getId())), + new Column().header("Command").dataAlign(HorizontalAlign.LEFT) + .maxWidth(48, OverflowBehaviour.NEWLINE) + .with(TableData::getCommand), + new Column().header("Explanation").dataAlign(HorizontalAlign.LEFT) + .maxWidth(40, OverflowBehaviour.NEWLINE) + .with(TableData::getExplanation), + new Column().header("Example").dataAlign(HorizontalAlign.LEFT) + .maxWidth(30, OverflowBehaviour.NEWLINE) + .with(TableData::getExample)))); + printBreakLine(); + } + + /** + * Prints error message thrown by Florizz Exception. + * + * @param error Error message + */ + public void printError(FlorizzException error){ + System.out.println(error.errorMessage); + printBreakLine(); + } + + /** + * Prints exit message. + */ + public void printExitMessage() { + System.out.println("Enjoy your bouquet! Thank you for using Florizz!"); + printBreakLine(); + } + + /** + * Prints a list of flowers with optional additional information. + * + * @param needsInfo True if additional information about the flowers is required, false otherwise + */ + private void printFlowerList(boolean needsInfo){ + int maxPages = (int) Math.ceil((double)lastShownList.size() / PAGE_SIZE); + for (int i = (lastPageNo-1)*PAGE_SIZE; i < Math.min(lastPageNo*PAGE_SIZE, lastShownList.size()); i++) { + if (needsInfo) { + System.out.println(i+1 + ". " + lastShownList.get(i)); + } else { + System.out.println(i + 1 + ". " + lastShownList.get(i).getNameAndColour()); + } + } + if (lastPageNo < maxPages && lastPageNo > 1){ + System.out.println("Type 'next' to go to the next page, or 'back' to go to the previous page."); + } else if (lastPageNo < maxPages){ + System.out.println("Type 'next' to go to the next page."); + } else if (lastPageNo > 1){ + System.out.println("Type 'back' to go to the previous page."); + } + printBreakLine(); + } + /** + * Prints flowers in the dictionary + */ + public void printAllDictFlowerName(int pageNo) { + lastShownList = FlowerDictionary.getAllFlowers(); + lastPageNo = pageNo; + lastCommand = "ALL_FLOWERS"; + int maxPages = (int) Math.ceil((double)lastShownList.size() / PAGE_SIZE); + System.out.println("Showing page " + pageNo + "/" + maxPages + + " of all the flowers you can add:"); + printFlowerList(false); + } + + /** + * Prints filtered flowers based on a given filter. + * @param flowers The list of flowers to filter. + * @param filter The filter to apply to the flowers. + */ + public void printFilteredFlowers(ArrayList flowers, String filter, int pageNo){ + lastShownList = flowers; + lastPageNo = pageNo; + lastCommand = "FILTERED_FLOWERS " + filter; + int maxPages = (int) Math.ceil((double)lastShownList.size() / PAGE_SIZE); + System.out.println("Here is page " + lastPageNo + "/" + maxPages + + " of all the flowers related to " + filter + ":"); + printFlowerList(false); + } + + /** + * Prints name, colour, occasion and price information about a specific flower. + * @param targetFlower The name of the flower the user searched for. + * @param flowers The list of flowers that contain that name. + */ + public void printFlowerInfo(ArrayList flowers, String targetFlower, int pageNo) throws FlorizzException { + List tableData = new ArrayList<>(List.of()); + int id = 1; + + for (Flower flower : flowers) { + tableData.add(new TableData(id, flower.getFlowerName(), flower.getColour(), flower.tableOccasionToString(), + flower.tableMeaningToString(), String.format("%.2f", flower.getPrice()) + , flower.getType().toString())); + id++; + } + System.out.println("Here is a table of information about the flower " + targetFlower + ":"); + try { + printFlowersTable(tableData); + printBreakLine(); + } catch (FlorizzException error){ + printError(error); + } + } + + /** + * Prints the next page of flower information based on the last command executed. + * + * @throws FlorizzException If there are no more pages to display or if there is no list of flowers to view. + */ + public void printNextPage() throws FlorizzException{ + switch (lastCommand.split(" ")[0]) { + case ("ALL_FLOWERS"): + if (lastPageNo*PAGE_SIZE >= lastShownList.size()){ + throw new FlorizzException("There is no next page, type 'back' to go to the previous page"); + } + printAllDictFlowerName(lastPageNo+1); + break; + case ("FILTERED_FLOWERS"): + if (lastPageNo*PAGE_SIZE >= lastShownList.size()){ + throw new FlorizzException("There is no next page, type 'back' to go to the previous page"); + } + printFilteredFlowers(lastShownList, lastCommand.split(" ")[1],lastPageNo+1); + break; + case ("INFO_FLOWERS"): + if (lastPageNo*PAGE_SIZE >= lastShownList.size()){ + throw new FlorizzException("There is no next page, type 'back' to go to the previous page"); + } + printFlowerInfo(lastShownList, lastCommand.split(" ")[1], lastPageNo+1); + break; + default: + throw new FlorizzException("There is no list of flowers to view. " + + "Type 'flowers' to view a list of all flowers."); + } + } + + /** + * Prints the previous page of flower information based on the last command executed. + * + * @throws FlorizzException If there are no previous pages to display or if there is no list of flowers to view. + */ + public void printBackPage() throws FlorizzException{ + switch (lastCommand.split(" ")[0]) { + case ("ALL_FLOWERS"): + if (lastPageNo == 1){ + throw new FlorizzException("There is no previous page, type 'next' to go to the next page"); + } + printAllDictFlowerName(lastPageNo-1); + break; + case ("FILTERED_FLOWERS"): + if (lastPageNo == 1){ + throw new FlorizzException("There is no previous page, type 'next' to go to the next page"); + } + printFilteredFlowers(lastShownList, lastCommand.split(" ")[1],lastPageNo-1); + break; + case ("INFO_FLOWERS"): + if (lastPageNo == 1){ + throw new FlorizzException("There is no previous page, type 'next' to go to the next page"); + } + printFlowerInfo(lastShownList, lastCommand.split(" ")[1], lastPageNo-1); + break; + default: + throw new FlorizzException("There is no list of flowers to view. " + + "Type 'flowers' to view a list of all flowers."); + } + } + + /** + * Prints all possible occasions the user can query + */ + public void printAllOccasions() { + lastCommand = "OTHERS"; + System.out.println("Here are all the occasions associated with the available flowers:"); + for (Flower.Occasion occasion : Flower.Occasion.values()){ + System.out.println("- " + Flower.occasionToString(occasion)); + } + + printBreakLine(); + } + + /** + * print ui if flower added successfully + * + * @param bouquetList The list of bouquets. + * @param flowerName The name of the flower added. + * @param quantity The quantity of the flower added. + * @param bouquetName The name of the bouquet to which the flower was added. + */ + public void printAddFlowerSuccess(ArrayList bouquetList, + String flowerName, Integer quantity, String bouquetName) { + lastCommand = "OTHERS"; + System.out.println("You have successfully added the following:" + System.lineSeparator() + + " - " + quantity + " x " + flowerName + " -> Bouquet: " + bouquetName); + printAllBouquets(bouquetList); + } + + /** + * print ui if flower removed successfully + * + * @param bouquetList The list of bouquets. + * @param flowerName The name of the flower removed. + * @param quantity The quantity of the flower removed. + * @param bouquetName The name of the bouquet from which the flower was removed. + */ + public void printRemoveFlowerSuccess(ArrayList bouquetList, + String flowerName, Integer quantity, String bouquetName) { + lastCommand = "OTHERS"; + System.out.println("You have successfully removed the following:" + System.lineSeparator() + + " - " + quantity + " x " + flowerName + " -> Bouquet: " + bouquetName); + printAllBouquets(bouquetList); + } + + /** + * print ui if flower removed can't be found + * + * @param bouquetList The list of bouquets. + * @param flowerName The name of the flower that couldn't be found. + * @param bouquetName The name of the bouquet in which the flower couldn't be found. + */ + public void printRemoveFlowerUnsuccessful(ArrayList bouquetList, String flowerName, String bouquetName) { + lastCommand = "OTHERS"; + System.out.println(flowerName + " cannot be found in bouquet: " + bouquetName); + printAllBouquets(bouquetList); + } + + /** + * Prints a message to the UI indicating a fuzzy input detection. + * + * @param userInput The user's input. + * @param bestMatch The best matching suggestion for the user's input. + */ + public void printFuzzyInputDetection (String userInput, String bestMatch) { + System.out.println("--> Your input is [" + userInput + + "] but I am guessing you mean [" + bestMatch + "]"); + } + + /** + * print if IOException is caught in a try catch block + */ + public void printIOError() { + System.out.println("ERROR: IO Error Encountered Xd"); + } + + /** + * ask user for occasion input + */ + public void printAskOccasion() { + System.out.println("For what occasion are you buying flowers for?"); + this.printAllOccasions(); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + } + + /** + * Prints prompt to ask user to input bouquet name for recommended bouquet + */ + public void printAskBouquetName() { + System.out.println("Great we managed to find some flowers for you!"); + System.out.println("Before we carry on what would you like to call your bouquet?"); + System.out.println("Note: please take note 'cancel' cannot be used as a bouquet name"); + printBreakLine(); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + } + + /** + * Asks user for colour input + * @param eligibleFlowers List of flowers that are eligible for the occasion + */ + public void printAskColour(ArrayList eligibleFlowers) { + System.out.println("What colour would you like your bouquets to be?"); + + // print all available colours in a given array list + System.out.println("Here is the list of colours available for the occasion:"); + // remove duplicate colours in eligible flowers + ArrayList colourList = new ArrayList<>(); + for (Flower flower : eligibleFlowers) { + if(!colourList.contains(flower.getColour())){ + colourList.add(flower.getColour()); + } + } + for (String colour : colourList){ + System.out.println("- " + colour); + } + printBreakLine(); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + } + + /** + * Prints a prompt asking the user if they would like to save a recommended bouquet to their list. + * Prompts the user to input 'yes' to save the bouquet or 'no' to discard it. + * + * @param recommendedBouquet The recommended bouquet to be saved + * @return The user's input ('yes' or 'no') + */ + public String printAskSaveBouquet(Bouquet recommendedBouquet) { + System.out.println("Would you like to save this bouquet to your list?"); + printFullBouquet(recommendedBouquet); + System.out.println("Type 'yes' to save, 'no' to discard"); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + return inputScanner.nextLine(); + } + + /** + * Prints out to user when bouquet has been successfully saved + * @param bouquetName Bouquet to save externally + */ + public void printSaveSuccess(String bouquetName) { + System.out.println("Successfully saved " + bouquetName + ". You can find it at 'florizz-out/saved/" + + bouquetName + ".txt'"); + printBreakLine(); + } + + /** + * Asks the user to choose a colour of a flower from a list of the flowers. + * @param flowers The list of flowers where the user can choose the colour from + * @param flowerName The name of the flower that the user is trying to choose its colour from + * @return Flower the specific Flower with the correct colour. Is blank if user chose to cancel the command instead + */ + public Flower chooseColour(ArrayList flowers, String flowerName) throws FlorizzException { + printGetFlowerColour(flowers, flowerName); + + while (true){ + String colourInput = getInput().trim().toLowerCase(); + try { + switch (colourInput) { + case "back": + printBackPage(); + break; + case "next": + printNextPage(); + break; + case "cancel": + return new Flower(); + default: + Flower.Colour chosenColour = Flower.stringToColour(colourInput); + ArrayList chosenFlowerList = FlowerDictionary.filterByColour(flowers, chosenColour); + if (!chosenFlowerList.isEmpty()){ + return chosenFlowerList.get(0); + } else { + throw new FlorizzException("This flower is not available in this colour, " + + "try typing a colour shown above!"); + } + + } + } catch(FlorizzException error){ + printError(error); + } catch(IllegalArgumentException error){ + System.out.println("Unrecognised input, type a colour that's available for this flower, " + + "or 'cancel' to go back to the main menu."); + } + } + } + + /** + * Prints information about the available colors of a flower and prompts the user to choose the color. + * Displays information about the flower, including its name and available colors. + * Prompts the user to input the desired color or 'cancel' to return to the main menu. + * + * @param flowers The list of flowers containing the flower with multiple colors + * @param flowerName The name of the flower with multiple colors + */ + public void printGetFlowerColour(ArrayList flowers, String flowerName) throws FlorizzException { + System.out.println("The flower you're looking for has more than one colour available, " + + "each with their own vastly different meanings."); + printFlowerInfo(flowers, flowerName, 1); + System.out.println("Type the colour you want to add into the bouquet, or 'cancel' to return to the main menu."); + } + + + /** + * Prints a table comparing two flowers. + */ + public void printCompareFlowers(ArrayList firstFilteredFlowers, ArrayList secondFilteredFlowers) + throws FlorizzException { + List tableData = new ArrayList<>(List.of()); + int id = 1; + + for (Flower flower : firstFilteredFlowers) { + tableData.add(new TableData(id, flower.getFlowerName(), flower.getColour(), flower.tableOccasionToString(), + flower.tableMeaningToString(), String.format("%.2f", flower.getPrice()) + , flower.getType().toString())); + id++; + } + for (Flower flower : secondFilteredFlowers) { + tableData.add(new TableData(id, flower.getFlowerName(), flower.getColour(), flower.tableOccasionToString(), + flower.tableMeaningToString(), String.format("%.2f", flower.getPrice()) + , flower.getType().toString())); + id++; + } + printBreakLine(); + System.out.println("Here is a table of comparison between the two flowers:"); + printFlowersTable(tableData); + printBreakLine(); + } + + /** + * Prints a table of flowers. + * + * @param tableData The list of flowers to be printed in a table + * @throws FlorizzException If there are no flowers to display + */ + protected void printFlowersTable(List tableData) throws FlorizzException{ + if (tableData.isEmpty()){ + throw new FlorizzException("No flowers to display."); + } + System.out.println(AsciiTable.getTable(AsciiTable.BASIC_ASCII_NO_DATA_SEPARATORS, tableData, Arrays.asList( + new Column().header("No.").dataAlign(HorizontalAlign.CENTER) + .with((TableData data) -> Integer.toString(data.getId())), + new Column().header("Flower Name").dataAlign(HorizontalAlign.LEFT) + .with(TableData::getFlowerName), + new Column().header("Colour").dataAlign(HorizontalAlign.LEFT) + .with(TableData::getFlowerColor), + new Column().header("Occasion").dataAlign(HorizontalAlign.LEFT) + .with(TableData::getFlowerOccasion), + new Column().header("Meaning").dataAlign(HorizontalAlign.LEFT) + .with(TableData::getFlowerMeaning), + new Column().header("Type").dataAlign(HorizontalAlign.LEFT) + .with(TableData::getType), + new Column().header("Price ($)").dataAlign(HorizontalAlign.RIGHT) + .with(TableData::getFlowerPrice)))); + } + + /** + * Prints a message indicating that the command has been canceled and the user is returning to the main menu. + */ + public void printCancelCommand(){ + System.out.println("Canceled command, returning to main menu."); + } + + /** + * Prints text to ask user for size of bouquet + * @return raw string of user input + */ + public String printAskSize() { + System.out.println("What size would you like your recommended bouquet to be?" + System.lineSeparator() + + "1. Small" + System.lineSeparator() + + "2. Medium" + System.lineSeparator() + + "3. Large"); + printBreakLine(); + System.out.println("Type 'cancel' if you would like to exit the recommendation page"); + return inputScanner.nextLine().trim(); + } + +} + diff --git a/src/main/java/florizz/logging/MyFilter.java b/src/main/java/florizz/logging/MyFilter.java new file mode 100644 index 0000000000..c33d3d8311 --- /dev/null +++ b/src/main/java/florizz/logging/MyFilter.java @@ -0,0 +1,25 @@ +package florizz.logging; + +import java.util.logging.Filter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class MyFilter implements Filter { + + /** + * Determine what level should a handler take + * Code taken from "https://www.digitalocean.com/community/tutorials/logger-in-java-logging-example" + * + * @param log a LogRecord + * @return + */ + @Override + public boolean isLoggable(LogRecord log) { + //don't log CONFIG logs in file + if (log.getLevel() == Level.CONFIG) { + return false; + } + return true; + } + +} diff --git a/src/main/java/florizz/logging/MyFormatter.java b/src/main/java/florizz/logging/MyFormatter.java new file mode 100644 index 0000000000..7bd1f16456 --- /dev/null +++ b/src/main/java/florizz/logging/MyFormatter.java @@ -0,0 +1,24 @@ +package florizz.logging; + +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +public class MyFormatter extends Formatter { + + /** + * Better format logging output + * Code taken from "https://www.digitalocean.com/community/tutorials/logger-in-java-logging-example" + * + * @param record the log record to be formatted. + * @return + */ + @Override + public String format(LogRecord record) { + return record.getThreadID()+"::"+record.getSourceClassName()+"::" + +record.getSourceMethodName()+"::" + +new Date(record.getMillis())+"::" + +record.getMessage()+"\n"; + } + +} diff --git a/src/main/java/florizz/objects/Bouquet.java b/src/main/java/florizz/objects/Bouquet.java new file mode 100644 index 0000000000..961d1364a5 --- /dev/null +++ b/src/main/java/florizz/objects/Bouquet.java @@ -0,0 +1,154 @@ +package florizz.objects; + + + +import florizz.core.FlorizzException; + +import java.util.ArrayList; +import java.util.HashMap; + +import java.util.Objects; + +public class Bouquet { + private String bouquetName; + private HashMap flowerHashMap; + + public Bouquet() { + this(""); + } + public Bouquet(String bouquetName){ + this.bouquetName = bouquetName; + this.flowerHashMap = new HashMap(); + } + @Override + public String toString() { + return bouquetName; + } + + public HashMap getFlowerHashMap() { + return flowerHashMap; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + /* Check if o is an instance of Complex or not + "null instanceof [type]" also returns false */ + if (!(obj instanceof Bouquet)) { + return false; + } + + // typecast o to Complex so that we can compare data members + Bouquet c = (Bouquet) obj; + + // Compare the data members and return accordingly + return Objects.equals(c.bouquetName.toUpperCase(), this.bouquetName.toUpperCase()); + } + + /** + * Get total quantity of flowers in the bouquet + * @return int total quantity of flowers + */ + public int totalNumberOfFlowers() { + int quantity = 0; + for (Flower flower : flowerHashMap.keySet()) { + quantity += flowerHashMap.get(flower); + } + return quantity; + } + + /** + * Checks if a flower exists in the bouquet. + * + * @param flowerName the flower to check for its existence + * @return true if the flower exists in the bouquet, false otherwise + */ + public boolean doesFlowerExist(Flower flowerName) { + if (flowerHashMap.get(flowerName) == null) { + return false; + } + return true; + } + + /** + * Adds flowers to the bouquet. + * + * @param flowerName Ihe name of the flower to add + * @param quantity Ihe quantity of the flower to add + * @throws FlorizzException If the quantity of flowers to add exceeds the maximum allowed value + */ + public void addFlower(Flower flowerName, Integer quantity) throws FlorizzException { + if (doesFlowerExist(flowerName)) { + Integer currentQuantity = getFlowerQuantity(flowerName); + int newQuantity = currentQuantity + quantity; + if ((quantity > 0 && currentQuantity > Integer.MAX_VALUE - quantity) || newQuantity < 0) { + throw new FlorizzException("You have added too much flowers!"); + } + flowerHashMap.replace(flowerName, newQuantity); + } else { + flowerHashMap.put(flowerName,quantity); + } + } + + /** + * Removes flowers from the bouquet. + * + * @param flowerName The name of the flower to remove + * @param quantity The quantity of the flower to remove + * @return True if the flower was successfully removed, false otherwise + * @throws FlorizzException If the specified quantity is <= 0 or if it exceeds the current quantity of flowers + */ + public boolean removeFlower(Flower flowerName, Integer quantity) throws FlorizzException { + // if flower already in bouquet + if (doesFlowerExist(flowerName)) { + Integer currentQuantity = getFlowerQuantity(flowerName); + int newQuantity = currentQuantity - quantity; + if (quantity <= 0) { + throw new FlorizzException("Please input a quantity between 1 and the " + + "current amount of flowers (inclusive)."); + } + if (newQuantity < 0) { + System.out.println("Tried to remove more than the quantity available, quantity set to 0"); + newQuantity = 0; + } + if (newQuantity == 0) { + flowerHashMap.remove(flowerName); + } + flowerHashMap.replace(flowerName, newQuantity); + return true; + } + return false; + } + + /** + * Get quantity of a flower. If flower does not exist in bouquet, return 0 + * + * @param flowerName + * @return + */ + public Integer getFlowerQuantity(Flower flowerName) { + if (flowerHashMap.get(flowerName) == null) { + return 0; + } + return flowerHashMap.get(flowerName); + } + + /** + * Gets bouquet name + * @return + */ + public String getBouquetName() { + return this.bouquetName; + } + + public void setName(String newName) { + this.bouquetName = newName; + } + + public ArrayList getFlowerList (){ + return new ArrayList (flowerHashMap.keySet()); + } +} diff --git a/src/main/java/florizz/objects/Filler.java b/src/main/java/florizz/objects/Filler.java new file mode 100644 index 0000000000..6051d276e2 --- /dev/null +++ b/src/main/java/florizz/objects/Filler.java @@ -0,0 +1,4 @@ +package florizz.objects; + +public class Filler extends Flower{ +} diff --git a/src/main/java/florizz/objects/Flower.java b/src/main/java/florizz/objects/Flower.java new file mode 100644 index 0000000000..e2f663a711 --- /dev/null +++ b/src/main/java/florizz/objects/Flower.java @@ -0,0 +1,304 @@ +package florizz.objects; + + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Represents a flower with its name, colour, occasions, and price. + */ +public class Flower { + private String name = ""; + private ArrayList occasions = null; + private Colour colour = null; + private Double price; + private ArrayList meanings = null; + private Type type = null; + + + /** + * Enumerates different colours a flower can have. + */ + public enum Colour { + WHITE, BLUE, RED, PINK, DARK_CRIMSON,GREEN, YELLOW, PURPLE, ORANGE + } + + /** + * Enumerates different occasions for which a flower can be used. + */ + public enum Occasion { + FUNERAL, WEDDING, VALENTINES, MOTHERS_DAY + } + + public enum Type { + MAIN_FLOWER, FILLER_FLOWER + } + + /** + * Default constructor for the Flower class. + */ + public Flower() { + this.name = ""; + } + + /** + * Constructs a Flower object with specified parameters. + * @param name The name of the flower. + * @param colour The colour of the flower. + * @param occasion The occasion(s) associated with the flower. + * @param price The price of the flower. + */ + public Flower(String name, Colour colour, ArrayList occasion, Double price, ArrayList meanings) { + this.name = name; + this.occasions = occasion; + this.colour = colour; + this.price = price; + this.meanings = meanings; + } + + /** + * Constructs a Flower object with specified parameters. + * @param name The name of the flower. + * @param colour The colour of the flower. + * @param occasion The occasion associated with the flower. + * @param price The price of the flower. + * @param meanings The meanings associated with the flower. + * @param type The type of the flower. + */ + public Flower(String name, Colour colour, ArrayList occasion, + Double price, ArrayList meanings, Type type){ + this.name = name; + this.occasions = occasion; + this.colour = colour; + this.price = price; + this.meanings = meanings; + this.type = type; + } + + /** + * Constructs a Flower object with specified parameters. + * @param name The name of the flower. + * @param colour The colour of the flower. + * @param occasion The occasion associated with the flower. + * @param price The price of the flower. + */ + public Flower(String name, Colour colour, Occasion occasion, Double price) { + this.name = name; + this.occasions = new ArrayList(); + occasions.add(occasion); + this.colour = colour; + this.price = price; + } + + /** + * check if argument inputted is a valid occasion + * @param argument + * @return boolean true if occasion is valid + */ + public static Boolean isValidOccasion(String argument) { + for (Flower.Occasion occasion : Flower.Occasion.values()) { + if (Flower.occasionToString(occasion).equalsIgnoreCase(argument)) { + return true; + } + } + return false; + } + + /** + * check if argument inputted is a valid colour + * @param argument + * @return boolean true if colour is valid + */ + public static Boolean isValidColour(String argument) { + for (Flower.Colour colour : Flower.Colour.values()) { + if (Flower.colourToString(colour).equalsIgnoreCase(argument)) { + return true; + } + } + return false; + } + + /** + * Converts a string representation of an occasion to an Occasion enum value. + * @param ocassionString The string representation of an occasion. + * @return The Occasion enum value corresponding to the string. + */ + public static Occasion stringToOccasion(String ocassionString){ + assert ocassionString != null; + return Occasion.valueOf(ocassionString.replaceAll(" ", "_").toUpperCase()); + } + + /** + * Converts an Occasion enum value to a string representation. + * @param occasion The Occasion enum value. + * @return The string representation of the occasion. + */ + public static String occasionToString(Occasion occasion){ + // check if occasion is valid + return occasion.toString().charAt(0) + + occasion.toString().replaceAll("_", " ").toLowerCase().substring(1); + } + + /** + * Converts a string representation of a colour to a Colour enum value. + * @param colourString The string representation of a colour. + * @return The Colour enum value corresponding to the string. + */ + public static Colour stringToColour(String colourString){ + return Colour.valueOf(colourString.replaceAll(" ", "_").toUpperCase()); + } + + public static String colourToString(Colour colour){ + return colour.toString().charAt(0) + + colour.toString().replaceAll("_", " ").toLowerCase().substring(1); + } + /** + * Gets the name of the flower. + * @return The name of the flower. + */ + public String getFlowerName() { + return name; + } + + /** + * Gets the name and colour of the flower. + * @return The name and colour of the flower. + */ + public String getNameAndColour() { + return name + " (" + colour.toString().charAt(0) + + colour.toString().replaceAll("_", " " ).substring(1).toLowerCase() + + ")"; + } + + /** + * Gets the colour of the flower. + * @return The colour of the flower in lower case. + */ + public String getColour (){ + return colourToString(colour); + } + + /** + * Gets the occasion(s) associated with the flower. + * @return The occasion(s) associated with the flower. + */ + public ArrayList getOccasion() { + return occasions; + } + + /** + * Gets the price of the flower. + * @return The price of the flower. + */ + public Double getPrice () { + return price; + } + + public ArrayList getMeanings() { + return meanings; + } + /** + * Gets type of flower in enum Type + * @return Type of flower + */ + public Flower.Type getType() { + return type; + } + + /** + * Generates a string representation of the Flower object. + * @return A string representation of the Flower object. + */ + @Override + public String toString() { + String finalOccasion; + String finalMeaning; + StringBuilder occasionsString = new StringBuilder("Occasions: "); + StringBuilder meaningsString = new StringBuilder("Meanings: "); + for (Occasion occasion : occasions){ + occasionsString.append(occasionToString(occasion)); + occasionsString.append(", "); + } + + for (String meaning : meanings){ + meaningsString.append(meaning); + meaningsString.append(", "); + } + // Check if occasionString was updated + if (!(occasionsString.toString().equals("Occasions: "))) { + finalOccasion = occasionsString.substring(0,occasionsString.lastIndexOf(",")); + } else { + finalOccasion = occasionsString.toString(); + } + // Check if meaning String was updated + if (!(meaningsString.toString().equals("Meanings: "))) { + finalMeaning = meaningsString.substring(0,meaningsString.lastIndexOf(",")); + } else { + finalMeaning = meaningsString.toString(); + } + + return ("Name: " + name + "\n" + + "Colour: " + colourToString(colour) + "\n" + + finalOccasion + "\n" + + "Price: $" + String.format("%.2f", price) + "\n" + + finalMeaning); + } + + /** + * Generates a string representation of the occasion of the Flower object for the table. + * @return A string representation of the occasion of the Flower object for the table. + */ + public String tableOccasionToString() { + String finalOccasion; + StringBuilder occasionsString = new StringBuilder(); + for (Occasion occasion : occasions){ + occasionsString.append(occasionToString(occasion)); + occasionsString.append(", "); + } + if (!(occasionsString.toString().isEmpty())) { + finalOccasion = occasionsString.substring(0,occasionsString.lastIndexOf(",")); + } else { + finalOccasion = "-"; + } + return finalOccasion; + } + + /** + * Generates a string representation of the meaning of the Flower object for the table. + * @return A string representation of the meaning of the Flower object for the table. + */ + public String tableMeaningToString() { + String finalMeaning; + StringBuilder meaningsString = new StringBuilder(); + for (String meaning : meanings){ + meaningsString.append(meaning); + meaningsString.append(", "); + } + if (!(meaningsString.toString().isEmpty())) { + finalMeaning = meaningsString.substring(0,meaningsString.lastIndexOf(",")); + } else { + finalMeaning = "-"; + } + return finalMeaning; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + /* Check if o is an instance of Complex or not + "null instanceof [type]" also returns false */ + if (!(obj instanceof Flower)) { + return false; + } + + // typecast o to Complex so that we can compare data members + Flower c = (Flower) obj; + + // Compare the data members and return accordingly + return (Objects.equals(c.name.toUpperCase(), this.name.toUpperCase()) && + Objects.equals(c.colour, this.colour)); + } +} diff --git a/src/main/java/florizz/objects/TableData.java b/src/main/java/florizz/objects/TableData.java new file mode 100644 index 0000000000..ad1f824441 --- /dev/null +++ b/src/main/java/florizz/objects/TableData.java @@ -0,0 +1,71 @@ +package florizz.objects; + +public class TableData { + private int id; + private String command; + private String explanation; + private String example; + private String flowerName; + private String flowerOccasion; + private String flowerPrice; + private String flowerColor; + private String flowerMeaning; + private String type; + + public TableData(int id, String command, String explanation, String example) { + this.id = id; + this.command = command; + this.explanation = explanation; + this.example = example; + } + + public TableData(int id, String flowerName, String flowerColor, String flowerOccasion, + String flowerMeaning, String flowerPrice, String type) { + this.id = id; + this.flowerName = flowerName; + this.flowerOccasion = flowerOccasion; + this.flowerColor = flowerColor; + this.flowerMeaning = flowerMeaning; + this.flowerPrice = flowerPrice; + this.type = type; + } + + public int getId() { + return id; + } + + public String getCommand() { + return command; + } + + public String getExplanation() { + return explanation; + } + + public String getExample() { + return example; + } + + public String getFlowerName() { + return flowerName; + } + + public String getFlowerOccasion() { + return flowerOccasion; + } + + public String getFlowerPrice() { + return flowerPrice; + } + + public String getFlowerColor() { + return flowerColor; + } + + public String getFlowerMeaning() { + return flowerMeaning; + } + public String getType() { + return type; + } +} diff --git a/src/main/java/florizz/storage/Storage.java b/src/main/java/florizz/storage/Storage.java new file mode 100644 index 0000000000..2d8ab27663 --- /dev/null +++ b/src/main/java/florizz/storage/Storage.java @@ -0,0 +1,131 @@ +package florizz.storage; + +import florizz.command.Command; +import florizz.core.FlorizzException; +import florizz.core.Parser; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Map; +import java.util.Scanner; + +/** + * Class that deals with creating directories if they do not exist for storage and + * writing to txt files to store the data from run time + */ +public class Storage { + private File bouquetStorage; + private final String storagePath = "./florizz-out/data/FlorizzBouquets.txt"; + + + public Storage() { + this.bouquetStorage = new File(storagePath); + if (!bouquetStorage.exists()) { + try { + Files.createDirectories(Paths.get("./florizz-out/data/")); + Files.createDirectory(Paths.get("./florizz-out/logs/")); + Files.createDirectory(Paths.get("./florizz-out/saved/")); + Files.createFile(Paths.get(storagePath)); + } catch (IOException e) { + System.out.println("File not created"); + } + } + } + + /** + * Tries to save all bouquets to a txt file + * catches an exception if it is unable to do so + * @param bouquetList list of bouquets to try and save + */ + public void trySaveAllBouquets(ArrayList bouquetList) { + try { + FileWriter bouquetStorageWriter = new FileWriter(storagePath); + saveAllBouquets(bouquetStorageWriter, bouquetList); + bouquetStorageWriter.close(); + } catch (IOException e) { + System.out.println("File does not exist"); + } + } + + /** + * Saves all bouquets in the bouquetList to a txt file + * text is formatted beforehand to allow the bouquets to be added back next time florizz is run + * @param bouquetStorageWriter A FileWriter that writes into the storage txt file + * @param bouquetList An ArrayList that contains all the bouquets added during run time + * @throws IOException Thrown when the file to write to does not exist + */ + public void saveAllBouquets(FileWriter bouquetStorageWriter, ArrayList bouquetList) throws IOException { + for (Bouquet bouquet : bouquetList) { + String bouquetName = bouquet.getBouquetName(); + bouquetStorageWriter.write("new " + bouquetName + "\n"); + HashMap tempHashMap = bouquet.getFlowerHashMap(); + for (Map.Entry k : tempHashMap.entrySet()) { + Flower flower = k.getKey(); + Integer quantity = k.getValue(); + bouquetStorageWriter.write("add " + flower.getFlowerName() + " /c " + flower.getColour() + + " /q " + quantity + " /to " + bouquetName + "\n"); + } + } + } + + /** + * Saves a bouquet to a txt file + * @param bouquet Target bouquet to be added to txt file + * @param bouquetStorageWriter FileWriter that writes to a txt file + * @throws IOException Thrown when the target file to write to does not exist + */ + public void saveBouquet(Bouquet bouquet, FileWriter bouquetStorageWriter) throws IOException{ + String bouquetName = bouquet.getBouquetName(); + HashMap tempHashMap = bouquet.getFlowerHashMap(); + double totalPrice = 0; + bouquetStorageWriter.write(bouquetName + " :\n"); + for (Map.Entry k : tempHashMap.entrySet()) { + Flower flower = k.getKey(); + Integer quantity = k.getValue(); + bouquetStorageWriter.write(" - " + quantity + " x " + flower.getFlowerName() + "\n"); + totalPrice += (flower.getPrice() * quantity); + } + bouquetStorageWriter.write(" Total estimated price = $" + String.format("%.2f", (double) totalPrice)); + } + + /** + * Attempts to read stored bouquets in txt file + * @param bouquetList List of bouquets to be populated at the start of the programme + */ + public void tryReadStoredBouquets(ArrayList bouquetList) { + try { + readStoredBouquets(bouquetList); + } catch (IOException e) { + System.out.println("File not found"); + } catch (FlorizzException fe) { + System.out.println(""); + } + } + + /** + * Reads from txt file to populate bouquet list with previously made bouquets + * @param bouquetList Empty bouquet list to be populated + * @throws IOException Thrown when the txt file cannot be found + * @throws FlorizzException Thrown when an invalid command is given + */ + public void readStoredBouquets(ArrayList bouquetList) throws IOException, FlorizzException { + Scanner in = new Scanner(bouquetStorage); + Command command; + boolean isRunning; + Ui ui = new Ui(); + while (in.hasNext()) { + String input = in.nextLine(); + command = Parser.parse(input, false); + isRunning = command.execute(bouquetList, ui); + } + in.close(); + } +} diff --git a/src/main/java/florizz/unused/StorageManager.java b/src/main/java/florizz/unused/StorageManager.java new file mode 100644 index 0000000000..c0744c603b --- /dev/null +++ b/src/main/java/florizz/unused/StorageManager.java @@ -0,0 +1,37 @@ +package florizz.unused; + +import florizz.core.FlorizzException; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.ResultSet; + +//@@ author IanFH-unused +public class StorageManager { + private final String url = "jdbc:sqlite:flowers.db"; + private Connection connection; + private Statement statement; + + public void loadDatabase() throws FlorizzException { + try { + this.connection = DriverManager.getConnection(url); + Statement statement = connection.createStatement(); + } catch (SQLException e) { + throw new FlorizzException("ERROR: unable to make connection with database"); + } + } + + public void printAll() throws FlorizzException { + try { + ResultSet rs = statement.executeQuery("select * from flower"); + while(rs.next()) { + System.out.println("name = " + rs.getString("name")); + } + } catch (SQLException e) { + throw new FlorizzException("ERROR: unable to query all"); + } + } + +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/test/java/florizz/command/AddBouquetTest.java b/src/test/java/florizz/command/AddBouquetTest.java new file mode 100644 index 0000000000..7ab34f5905 --- /dev/null +++ b/src/test/java/florizz/command/AddBouquetTest.java @@ -0,0 +1,73 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AddBouquetTest { + @Test + void testAddCommandExecute1() { + Bouquet testBouquet = new Bouquet("for testing"); // Two-word bouquetName + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + ArrayList testList = new ArrayList<>(); + controlList.add(testBouquet); + Command testAddBouquetCommand = new AddBouquetCommand(testBouquet, true); + try { + assertTrue(testAddBouquetCommand.execute (testList, ui)); + } catch (FlorizzException error){ + ui.printError(error); + } + assertEquals(controlList, testList); + } + + @Test + void testAddCommandExecute2() { + Bouquet testBouquet = new Bouquet("add"); // bouquetName that is also a command name + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + ArrayList testList = new ArrayList<>(); + controlList.add(testBouquet); + Command testAddBouquetCommand = new AddBouquetCommand(testBouquet, true); + try { + assertTrue(testAddBouquetCommand.execute (testList, ui)); + } catch (FlorizzException error){ + ui.printError(error); + } + assertEquals(controlList, testList); + } + + @Test + void testAddCommandExecute3() { + Bouquet testBouquet = new Bouquet("abc"); // One word bouquetName + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + ArrayList testList = new ArrayList<>(); + controlList.add(testBouquet); + Command testAddBouquetCommand = new AddBouquetCommand(testBouquet, true); + try { + assertTrue(testAddBouquetCommand.execute (testList, ui)); + } catch (FlorizzException error){ + ui.printError(error); + } + assertEquals(controlList, testList); + } + + @Test + void testAddCommandExecuteException() { + Bouquet testBouquet = new Bouquet(""); + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + controlList.add(testBouquet); + Command testAddBouquetCommand = new AddBouquetCommand(testBouquet, true); + + assertThrows(FlorizzException.class, () -> testAddBouquetCommand.execute(controlList, ui)); + } +} diff --git a/src/test/java/florizz/command/AddFlowerCommandTest.java b/src/test/java/florizz/command/AddFlowerCommandTest.java new file mode 100644 index 0000000000..194ad49af1 --- /dev/null +++ b/src/test/java/florizz/command/AddFlowerCommandTest.java @@ -0,0 +1,207 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class AddFlowerCommandTest { + + @BeforeAll + static void setup() { + FlowerDictionary.startup(); // Initialize the FlowerDictionary with test data + } + + @Test + void testAddFlowerExecuteChrysanthemumWithColour() throws FlorizzException { + boolean enableUi = true; + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 10; + String flowerName = "Chrysanthemum"; + Flower.Colour flowerColour = Flower.Colour.WHITE; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToAdd = matchedFlowerAndColour.get(0); + Command testAddFlower = new AddFlowerCommand (testFlowerToAdd.getFlowerName() + , quantity, testBouquet.getBouquetName(), enableUi); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToAdd, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testAddFlowerExecuteChrysanthemumNoColour() throws FlorizzException { + boolean enableUi = true; + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 10; + String flowerName = "Chrysanthemum"; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + Flower testFlowerToAdd = matchedFlower.get(0); + Command testAddFlower = new AddFlowerCommand (testFlowerToAdd.getFlowerName() + , quantity, testBouquet.getBouquetName(), enableUi); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToAdd, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testAddFlowerExecuteBabyBreath() throws FlorizzException { + boolean enableUi = true; + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 10; + String flowerName = "Baby Breath"; + Flower.Colour flowerColour = Flower.Colour.WHITE; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToAdd = matchedFlowerAndColour.get(0); + Command testAddFlower = new AddFlowerCommand (testFlowerToAdd.getFlowerName(), flowerColour + , quantity, testBouquet.getBouquetName(), enableUi); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToAdd, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testAddFlowerExecuteLily() throws FlorizzException { + boolean enableUi = true; + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 10; + String flowerName = "Lily"; + Flower.Colour flowerColour = Flower.Colour.WHITE; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToAdd = matchedFlowerAndColour.get(0); + Command testAddFlower = new AddFlowerCommand (testFlowerToAdd.getFlowerName(), flowerColour + , quantity, testBouquet.getBouquetName(), enableUi); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToAdd, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testAddFlowerExecuteDarkCrimsonRose() throws FlorizzException { + boolean enableUi = true; + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 3; + String flowerName = "Rose"; + Flower.Colour flowerColour = Flower.Colour.DARK_CRIMSON; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToAdd = matchedFlowerAndColour.get(0); + Command testAddFlower = new AddFlowerCommand (testFlowerToAdd.getFlowerName(), flowerColour + , quantity, testBouquet.getBouquetName(), enableUi); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToAdd, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testAddFlowerExecuteRedRose() throws FlorizzException { + boolean enableUi = true; + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 3; + String flowerName = "Rose"; + Flower.Colour flowerColour = Flower.Colour.RED; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToAdd = matchedFlowerAndColour.get(0); + Command testAddFlower = new AddFlowerCommand (testFlowerToAdd.getFlowerName(), flowerColour + , quantity, testBouquet.getBouquetName(), enableUi); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToAdd, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } +} diff --git a/src/test/java/florizz/command/CompareCommandTest.java b/src/test/java/florizz/command/CompareCommandTest.java new file mode 100644 index 0000000000..1f7d7cab6f --- /dev/null +++ b/src/test/java/florizz/command/CompareCommandTest.java @@ -0,0 +1,50 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CompareCommandTest { + + @Test + void testCompareExecute() { // Comparing two different flowers + CompareCommand testCompareCommand = new CompareCommand("Rose", "Lily"); + ArrayList testList = new ArrayList<>(); + Ui ui = new Ui(); + try { + assertTrue(testCompareCommand.execute(testList, ui)); + } catch (FlorizzException error) { + ui.printError(error); + } + } + + @Test + void testCompareException1() { // Comparing the same flower + ArrayList testList = new ArrayList<>(); + Ui ui = new Ui(); + CompareCommand testCompareCommand2 = new CompareCommand("Rose", "Rose"); + assertThrows(FlorizzException.class, () -> testCompareCommand2.execute(testList, ui)); + } + + @Test + void testCompareException2() { // Comparing a flower that does not exist + ArrayList testList = new ArrayList<>(); + Ui ui = new Ui(); + CompareCommand testCompareCommand3 = new CompareCommand("Rose", "Tulip"); + assertThrows(FlorizzException.class, () -> testCompareCommand3.execute(testList, ui)); + } + + @Test + void testCompareException3() { // Comparing empty flower names + ArrayList testList = new ArrayList<>(); + Ui ui = new Ui(); + CompareCommand testCompareCommand4 = new CompareCommand("", ""); + assertThrows(FlorizzException.class, () -> testCompareCommand4.execute(testList, ui)); + } +} diff --git a/src/test/java/florizz/command/DeleteBouquetTest.java b/src/test/java/florizz/command/DeleteBouquetTest.java new file mode 100644 index 0000000000..87de384149 --- /dev/null +++ b/src/test/java/florizz/command/DeleteBouquetTest.java @@ -0,0 +1,62 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DeleteBouquetTest { + @Test + void testDeleteCommandExecute1() { + Bouquet testBouquet = new Bouquet("for testing"); // Two-word bouquetName + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + ArrayList testList = new ArrayList<>(); + controlList.add(testBouquet); + testList.add(testBouquet); + Command testDeleteBouquetCommand = new DeleteBouquetCommand(testBouquet); + controlList.remove(testBouquet); + try { + assertTrue(testDeleteBouquetCommand.execute(testList, ui)); + } catch(FlorizzException error){ + ui.printError(error); + } + assertEquals(controlList, testList); + } + + @Test + void testDeleteCommandExecute2() { + Bouquet testBouquet = new Bouquet("mybouquets"); // bouquetName which is also a valid command name + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + ArrayList testList = new ArrayList<>(); + controlList.add(testBouquet); + testList.add(testBouquet); + Command testDeleteBouquetCommand = new DeleteBouquetCommand(testBouquet); + controlList.remove(testBouquet); + try { + assertTrue(testDeleteBouquetCommand.execute(testList, ui)); + } catch(FlorizzException error){ + ui.printError(error); + } + assertEquals(controlList, testList); + } + + @Test + void testDeleteCommandExecuteException(){ + Bouquet testBouquet = new Bouquet("myBouquet"); // One-word bouquetName + Ui ui = new Ui(); + ArrayList controlList = new ArrayList<>(); + controlList.add(testBouquet); + Command testDeleteBouquetCommand = new DeleteBouquetCommand(testBouquet); + controlList.remove(testBouquet); + + assertThrows(FlorizzException.class, () -> testDeleteBouquetCommand.execute(controlList, ui)); + } +} diff --git a/src/test/java/florizz/command/ExitTest.java b/src/test/java/florizz/command/ExitTest.java new file mode 100644 index 0000000000..5432990e05 --- /dev/null +++ b/src/test/java/florizz/command/ExitTest.java @@ -0,0 +1,20 @@ +package florizz.command; + +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class ExitTest { + @Test + void execute_exitCommand_false() { + // next two line might not be necessary + ArrayList tempBouquetList = new ArrayList<>(); + Ui ui = new Ui(); + ExitCommand exitCommand = new ExitCommand(); + assertFalse(exitCommand.execute(tempBouquetList, ui)); + } +} diff --git a/src/test/java/florizz/command/HelpTest.java b/src/test/java/florizz/command/HelpTest.java new file mode 100644 index 0000000000..069626ccd8 --- /dev/null +++ b/src/test/java/florizz/command/HelpTest.java @@ -0,0 +1,21 @@ +package florizz.command; + + + +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HelpTest { + @Test + void executeHelpCommandTrue(){ + ArrayList tempBouquetList = new ArrayList<>(); + Ui ui = new Ui(); + HelpCommand helpCommand = new HelpCommand(); + assertTrue(helpCommand.execute(tempBouquetList, ui)); + } +} diff --git a/src/test/java/florizz/command/InfoCommandTest.java b/src/test/java/florizz/command/InfoCommandTest.java new file mode 100644 index 0000000000..c5de6db9eb --- /dev/null +++ b/src/test/java/florizz/command/InfoCommandTest.java @@ -0,0 +1,112 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class InfoCommandTest { + + @BeforeAll + static void setup() { + FlowerDictionary.startup(); // Initialize the FlowerDictionary with test data + } + @Test + void testInfoCommandRose() { + String flowerName = "Rose"; + ArrayList bouquetList = new ArrayList<>(); + Ui ui = new Ui(); + InfoCommand infoCommand = new InfoCommand(flowerName); + + boolean result = false; + try { + result = infoCommand.execute(bouquetList, ui); + } catch (FlorizzException e) { + fail("Unexpected Florizz Exception thrown: " + e.getMessage()); + } + + assertTrue(result, "Expected execute method to return true indicating successful execution"); + } + + @Test + void testInfoCommandLily() { + String flowerName = "Lily"; + ArrayList bouquetList = new ArrayList<>(); + Ui ui = new Ui(); + InfoCommand infoCommand = new InfoCommand(flowerName); + + boolean result = false; + try { + result = infoCommand.execute(bouquetList, ui); + } catch (FlorizzException e) { + fail("Unexpected Florizz Exception thrown: " + e.getMessage()); + } + assertTrue(result, "Expected execute method to return true indicating successful execution"); + } + + @Test + void testInfoCommandOrchid() { + String flowerName = "Orchid"; + ArrayList bouquetList = new ArrayList<>(); + Ui ui = new Ui(); + InfoCommand infoCommand = new InfoCommand(flowerName); + + boolean result = false; + try { + result = infoCommand.execute(bouquetList, ui); + } catch (FlorizzException e) { + fail("Unexpected Florizz Exception thrown: " + e.getMessage()); + } + assertTrue(result, "Expected execute method to return true indicating successful execution"); + } + + @Test + void testInfoCommandBabyBreath() { + String flowerName = "Baby Breath"; + ArrayList bouquetList = new ArrayList<>(); + Ui ui = new Ui(); + InfoCommand infoCommand = new InfoCommand(flowerName); + + boolean result = false; + try { + result = infoCommand.execute(bouquetList, ui); + } catch (FlorizzException e) { + fail("Unexpected Florizz Exception thrown: " + e.getMessage()); + } + assertTrue(result, "Expected execute method to return true indicating successful execution"); + } + + @Test + void testInfoCommandDaisy() { + String flowerName = "Daisy"; + ArrayList bouquetList = new ArrayList<>(); + Ui ui = new Ui(); + InfoCommand infoCommand = new InfoCommand(flowerName); + + boolean result = false; + try { + result = infoCommand.execute(bouquetList, ui); + } catch (FlorizzException e) { + fail("Unexpected Florizz Exception thrown: " + e.getMessage()); + } + assertTrue(result, "Expected execute method to return true indicating successful execution"); + } + + @Test + void testInfoCommandException() { + String flowerName = "Non-existent Flower"; + ArrayList bouquetList = new ArrayList<>(); + Ui ui = new Ui(); + InfoCommand infoCommand = new InfoCommand(flowerName); + + assertThrows(FlorizzException.class, () -> infoCommand.execute(bouquetList, ui)); + } +} diff --git a/src/test/java/florizz/command/ListOccasionCommandTest.java b/src/test/java/florizz/command/ListOccasionCommandTest.java new file mode 100644 index 0000000000..4f241b4e89 --- /dev/null +++ b/src/test/java/florizz/command/ListOccasionCommandTest.java @@ -0,0 +1,20 @@ +package florizz.command; + +import florizz.core.Ui; +import florizz.objects.Bouquet; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ListOccasionCommandTest { + + @Test + void executeListOccasionTest() { + ArrayList tempBouquetList = new ArrayList<>(); + Ui ui = new Ui(); + ListOccasionCommand occasionCommand = new ListOccasionCommand(); + assertTrue(occasionCommand.execute(tempBouquetList, ui)); + } +} diff --git a/src/test/java/florizz/command/RecommendTest.java b/src/test/java/florizz/command/RecommendTest.java new file mode 100644 index 0000000000..22494a3598 --- /dev/null +++ b/src/test/java/florizz/command/RecommendTest.java @@ -0,0 +1,173 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static florizz.objects.Flower.colourToString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RecommendTest { + private static final int SMALL_BOUQUET_FLOWER_COUNT = 1; + private static final int MEDIUM_BOUQUET_FLOWER_COUNT = 3; + private static final int LARGE_BOUQUET_FLOWER_COUNT = 5; + private static final int SMALL_BOUQUET_FILLER_COUNT = 2; + private static final int MEDIUM_BOUQUET_FILLER_COUNT = 4; + private static final int LARGE_BOUQUET_FILLER_COUNT = 6; + @Test + void testRecommendCommand_addRandomFlowersSmallValentines() { + Ui ui = new Ui(); + FlowerDictionary.startup(); + Flower.Occasion occasion = Flower.Occasion.VALENTINES; + ArrayList eligibleFlowersByOccasion = FlowerDictionary.filterByOccasion(occasion); + Bouquet recommendedBouquet = new Bouquet("TestBouquet"); + // NOTE: testSize must be in lowercase. In actual case, + // validation will be done before addRandomFlower is called. + String testSize = "small"; + Flower.Colour testColour = Flower.Colour.WHITE; + ArrayList eligibleFlowersByOccasionAndColour = + FlowerDictionary.filterByColour(eligibleFlowersByOccasion, testColour); + RecommendCommand testCommand = new RecommendCommand(); + try { + testCommand.addRandomFlowers(eligibleFlowersByOccasionAndColour, recommendedBouquet, testSize, testColour); + } catch (FlorizzException e) { + ui.printError(e); + } + + // check if all flowers in recommendedBouquet have the correct colour + for (Flower flower : recommendedBouquet.getFlowerList()) { + assertTrue(flower.getColour().equalsIgnoreCase(testColour.toString()) + || flower.getColour().equalsIgnoreCase("green")); + } + + // check if the number of flowers in recommendedBouquet is correct + assertEquals(SMALL_BOUQUET_FLOWER_COUNT + SMALL_BOUQUET_FILLER_COUNT, + recommendedBouquet.totalNumberOfFlowers()); + + // check if the number of main flowers is correct + int mainFlowerCount = 0; + for (Flower flower : recommendedBouquet.getFlowerList()) { + if (flower.getType() == Flower.Type.MAIN_FLOWER) { + mainFlowerCount += recommendedBouquet.getFlowerHashMap().get(flower); + } + } + assertEquals(SMALL_BOUQUET_FLOWER_COUNT, mainFlowerCount); + + // check if the number of filler flowers is correct + int fillerFlowerCount = 0; + for (Flower flower : recommendedBouquet.getFlowerList()) { + if (flower.getType() == Flower.Type.FILLER_FLOWER) { + fillerFlowerCount += recommendedBouquet.getFlowerHashMap().get(flower); + } + } + assertEquals(SMALL_BOUQUET_FILLER_COUNT, fillerFlowerCount); + } + + @Test + void testRecommendCommand_addRandomFlowersMediumMothersDay() { + Ui ui = new Ui(); + FlowerDictionary.startup(); + Flower.Occasion occasion = Flower.Occasion.MOTHERS_DAY; + ArrayList eligibleFlowersByOccasion = FlowerDictionary.filterByOccasion(occasion); + Bouquet recommendedBouquet = new Bouquet("TestBouquet"); + String testSize = "medium"; + Flower.Colour testColour = Flower.Colour.PINK; + ArrayList eligibleFlowersByOccasionAndColour = + FlowerDictionary.filterByColour(eligibleFlowersByOccasion, testColour); + RecommendCommand testCommand = new RecommendCommand(); + try { + testCommand.addRandomFlowers(eligibleFlowersByOccasionAndColour, recommendedBouquet, testSize, testColour); + } catch (FlorizzException e) { + ui.printError(e); + } + + ArrayList test = new ArrayList<>(); + test.add(recommendedBouquet); + ui.printAllBouquets(test); + + // check if all flowers in recommendedBouquet have the correct colour + for (Flower flower : recommendedBouquet.getFlowerList()) { + assertTrue(flower.getColour().equalsIgnoreCase(testColour.toString()) + || flower.getColour().equalsIgnoreCase("green")); + } + + // check if the number of flowers in recommendedBouquet is correct + assertEquals(MEDIUM_BOUQUET_FLOWER_COUNT + MEDIUM_BOUQUET_FILLER_COUNT, + recommendedBouquet.totalNumberOfFlowers()); + + // check if the number of main flowers is correct + int mainFlowerCount = 0; + for (Flower flower : recommendedBouquet.getFlowerList()) { + if (flower.getType() == Flower.Type.MAIN_FLOWER) { + mainFlowerCount += recommendedBouquet.getFlowerHashMap().get(flower); + } + } + assertEquals(MEDIUM_BOUQUET_FLOWER_COUNT, mainFlowerCount); + + // check if the number of filler flowers is correct + int fillerFlowerCount = 0; + for (Flower flower : recommendedBouquet.getFlowerList()) { + if (flower.getType() == Flower.Type.FILLER_FLOWER) { + fillerFlowerCount += recommendedBouquet.getFlowerHashMap().get(flower); + } + } + assertEquals(MEDIUM_BOUQUET_FILLER_COUNT, fillerFlowerCount); + } + + @Test + void testRecommendCommand_addRandomFlowersLargeFuneral() { + Ui ui = new Ui(); + FlowerDictionary.startup(); + Flower.Occasion occasion = Flower.Occasion.FUNERAL; + ArrayList eligibleFlowersByOccasion = FlowerDictionary.filterByOccasion(occasion); + Bouquet recommendedBouquet = new Bouquet("TestBouquet"); + String testSize = "large"; + Flower.Colour testColour = Flower.Colour.DARK_CRIMSON; + ArrayList eligibleFlowersByOccasionAndColour = + FlowerDictionary.filterByColour(eligibleFlowersByOccasion, testColour); + RecommendCommand testCommand = new RecommendCommand(); + try { + testCommand.addRandomFlowers(eligibleFlowersByOccasionAndColour, recommendedBouquet, testSize, testColour); + } catch (FlorizzException e) { + ui.printError(e); + } + + ArrayList test = new ArrayList<>(); + test.add(recommendedBouquet); + ui.printAllBouquets(test); + + // check if all flowers in recommendedBouquet have the correct colour + for (Flower flower : recommendedBouquet.getFlowerList()) { + assertTrue(flower.getColour().equalsIgnoreCase(colourToString(testColour)) + || flower.getColour().equalsIgnoreCase("green")); + } + + // check if the number of flowers in recommendedBouquet is correct + assertEquals(LARGE_BOUQUET_FLOWER_COUNT + LARGE_BOUQUET_FILLER_COUNT, + recommendedBouquet.totalNumberOfFlowers()); + + // check if the number of main flowers is correct + int mainFlowerCount = 0; + for (Flower flower : recommendedBouquet.getFlowerList()) { + if (flower.getType() == Flower.Type.MAIN_FLOWER) { + mainFlowerCount += recommendedBouquet.getFlowerHashMap().get(flower); + } + } + assertEquals(LARGE_BOUQUET_FLOWER_COUNT, mainFlowerCount); + + // check if the number of filler flowers is correct + int fillerFlowerCount = 0; + for (Flower flower : recommendedBouquet.getFlowerList()) { + if (flower.getType() == Flower.Type.FILLER_FLOWER) { + fillerFlowerCount += recommendedBouquet.getFlowerHashMap().get(flower); + } + } + assertEquals(LARGE_BOUQUET_FILLER_COUNT, fillerFlowerCount); + } +} diff --git a/src/test/java/florizz/command/RemoveFlowerCommandTest.java b/src/test/java/florizz/command/RemoveFlowerCommandTest.java new file mode 100644 index 0000000000..3d01a29d61 --- /dev/null +++ b/src/test/java/florizz/command/RemoveFlowerCommandTest.java @@ -0,0 +1,174 @@ +package florizz.command; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import florizz.objects.Bouquet; +import florizz.objects.Flower; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class RemoveFlowerCommandTest { + @BeforeAll + static void setup() { + FlowerDictionary.startup(); // Initialize the FlowerDictionary with test data + } + + @Test + void testRemoveFlowerExecuteChrysanthemumWithColour() throws FlorizzException { + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 5; + String flowerName = "Chrysanthemum"; + Flower.Colour flowerColour = Flower.Colour.WHITE; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToRemove = matchedFlowerAndColour.get(0); + testBouquet.addFlower(testFlowerToRemove, quantity); + Command testAddFlower = new RemoveFlowerCommand (testFlowerToRemove.getFlowerName(), flowerColour + , quantity, testBouquet.getBouquetName()); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToRemove, quantity); + controlBouquet.removeFlower(testFlowerToRemove, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testRemoveFlowerExecuteBabyBreathWithNoColour() throws FlorizzException { + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 5; + String flowerName = "Baby Breath"; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + Flower testFlowerToRemove = matchedFlower.get(0); + testBouquet.addFlower(testFlowerToRemove, quantity); + Command testAddFlower = new RemoveFlowerCommand (testFlowerToRemove.getFlowerName() + , quantity, testBouquet.getBouquetName()); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToRemove, quantity); + controlBouquet.removeFlower(testFlowerToRemove, quantity); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + @Test + void testRemoveFlowerExecuteRedRose() throws FlorizzException { + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 5; + int quantityToRemove = 2; + String flowerName = "Rose"; + Flower.Colour flowerColour = Flower.Colour.RED; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToRemove = matchedFlowerAndColour.get(0); + testBouquet.addFlower(testFlowerToRemove, quantity); + + int existingQuantity = 10; + String existingFlowerName = "Lily"; + Flower.Colour existingFlowerColour = Flower.Colour.WHITE; + + ArrayList matchedExistingFlower = FlowerDictionary.filterByName(existingFlowerName); + ArrayList matchedExistingFlowerAndColour = + FlowerDictionary.filterByColour(matchedExistingFlower, existingFlowerColour); + Flower testExistingFlowerToRemove = matchedExistingFlowerAndColour.get(0); + testBouquet.addFlower(testExistingFlowerToRemove,existingQuantity); + + Command testAddFlower = new RemoveFlowerCommand (testFlowerToRemove.getFlowerName(), flowerColour + , quantityToRemove, testBouquet.getBouquetName()); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToRemove, quantity); + controlBouquet.addFlower(testExistingFlowerToRemove,existingQuantity); + controlBouquet.removeFlower(testFlowerToRemove, quantityToRemove); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + + @Test + void testRemoveFlowerExecuteDaisy() throws FlorizzException { + Ui ui = new Ui(); + + Bouquet testBouquet = new Bouquet("Test Bouquet"); + int quantity = 10; + int quantityToRemove = 20; + String flowerName = "Daisy"; + Flower.Colour flowerColour = Flower.Colour.WHITE; + + ArrayList testList = new ArrayList<>(); + ArrayList matchedFlower = FlowerDictionary.filterByName(flowerName); + ArrayList matchedFlowerAndColour = FlowerDictionary.filterByColour(matchedFlower, flowerColour); + Flower testFlowerToRemove = matchedFlowerAndColour.get(0); + testBouquet.addFlower(testFlowerToRemove, quantity); + + int existingQuantity = 10; + String existingFlowerName = "Lily"; + Flower.Colour existingFlowerColour = Flower.Colour.WHITE; + + ArrayList matchedExistingFlower = FlowerDictionary.filterByName(existingFlowerName); + ArrayList matchedExistingFlowerAndColour = + FlowerDictionary.filterByColour(matchedExistingFlower, existingFlowerColour); + Flower testExistingFlowerToRemove = matchedExistingFlowerAndColour.get(0); + testBouquet.addFlower(testExistingFlowerToRemove,existingQuantity); + + Command testAddFlower = new RemoveFlowerCommand (testFlowerToRemove.getFlowerName(), flowerColour + , quantityToRemove, testBouquet.getBouquetName()); + testList.add(testBouquet); + + try { + assertTrue(testAddFlower.execute(testList, ui)); + } catch (FlorizzException error) { + fail("Unexpected Florizz Exception thrown: " + error.getMessage()); + } + + // Control Bouquet + Bouquet controlBouquet = new Bouquet("Control Bouquet"); + controlBouquet.addFlower(testFlowerToRemove, quantity); + controlBouquet.addFlower(testExistingFlowerToRemove,existingQuantity); + controlBouquet.removeFlower(testFlowerToRemove, quantityToRemove); + + assertEquals(controlBouquet.getFlowerHashMap(), testBouquet.getFlowerHashMap()); + } + +} diff --git a/src/test/java/florizz/core/FlowerDictionaryTest.java b/src/test/java/florizz/core/FlowerDictionaryTest.java new file mode 100644 index 0000000000..4e85090908 --- /dev/null +++ b/src/test/java/florizz/core/FlowerDictionaryTest.java @@ -0,0 +1,62 @@ +package florizz.core; + +import florizz.objects.Flower; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FlowerDictionaryTest { + @Test + void testFilterByOccasion() { + FlowerDictionary.startup(); + ArrayList filteredFlowers = new ArrayList<>(); + filteredFlowers = FlowerDictionary.filterByOccasion(Flower.Occasion.FUNERAL); + // check if all flowers in filteredFlowers have the correct occasion + for (Flower flower : filteredFlowers) { + assertTrue(flower.getOccasion().contains(Flower.Occasion.FUNERAL)); + } + } + + @Test + void testFilterByName() { + FlowerDictionary.startup(); + Ui ui = new Ui(); + ArrayList filteredFlowers = new ArrayList<>(); + try { + filteredFlowers = FlowerDictionary.filterByName("Rose"); + } catch (FlorizzException e) { + ui.printError(e); + } + // check if all flowers in filteredFlowers have the correct name + for (Flower flower : filteredFlowers) { + assertEquals("Rose", flower.getFlowerName()); + } + } + + @Test + void testFilterByColour() { + FlowerDictionary.startup(); + ArrayList filteredFlowers = new ArrayList<>(); + filteredFlowers = FlowerDictionary.filterByColour(Flower.Colour.RED); + // check if all flowers in filteredFlowers have the correct colour + for (Flower flower : filteredFlowers) { + assertEquals(Flower.Colour.RED, flower.getColour()); + } + } + + @Test + void testFilterByColourWithList() { + FlowerDictionary.startup(); + ArrayList filteredFlowers = new ArrayList<>(); + ArrayList listOfFlowers = new ArrayList<>(); + listOfFlowers = FlowerDictionary.filterByOccasion(Flower.Occasion.FUNERAL); + filteredFlowers = FlowerDictionary.filterByColour(listOfFlowers, Flower.Colour.WHITE); + // check if all flowers in filteredFlowers have the correct colour + for (Flower flower : filteredFlowers) { + assertEquals(Flower.Colour.WHITE.toString(), flower.getColour().toUpperCase()); + } + } +} diff --git a/src/test/java/florizz/core/FuzzyLogicTest.java b/src/test/java/florizz/core/FuzzyLogicTest.java new file mode 100644 index 0000000000..b5eeaabb0f --- /dev/null +++ b/src/test/java/florizz/core/FuzzyLogicTest.java @@ -0,0 +1,281 @@ +package florizz.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FuzzyLogicTest { + + @Test + void testComputeDLDistance() throws FlorizzException { // Two-word command input + String userInput = "invo"; + String validCommand = "info"; + int distance = FuzzyLogic.computeDLDistance(userInput, validCommand); + assertEquals(1, distance); + } + + @Test + void testComputeDLDistance2() throws FlorizzException { // Two-word command input + String userInput = "rimof"; + String validCommand = "remove"; + int distance = FuzzyLogic.computeDLDistance(userInput, validCommand); + assertEquals(3, distance); + } + + @Test + void testComputeDLDistance3() throws FlorizzException { // Two-word command input + String userInput = "mybouquets"; + String validCommand = "mybouquets"; + int distance = FuzzyLogic.computeDLDistance(userInput, validCommand); + assertEquals(0, distance); + } + + @Test + void testDetectItemInfo() throws FlorizzException { // Two-word command input + String userInput = "invo"; + String decodedInput = FuzzyLogic.detectItem(userInput); + assertEquals("info", decodedInput); + } + + @Test + void testDetectItemOccasion() throws FlorizzException { // One-word command input + String userInput = "okasion"; + String decodedInput = FuzzyLogic.detectItem(userInput); + assertEquals("occasion", decodedInput); + } + + @Test + void testDetectItemEucalyptus() throws FlorizzException { // Flower + String userInput = "eucalitus"; + String decodedInput = FuzzyLogic.detectItem(userInput); + assertEquals("Eucalyptus", decodedInput); + } + + @Test + void testProcessCommand1() throws FlorizzException { // input starts with a valid command and input length != 1 + String userInput = "saves bouquetName"; + String processedInput = FuzzyLogic.processCommand(userInput); + assertEquals("save bouquetName", processedInput); + } + + @Test + void testProcessCommand2() throws FlorizzException { // input starts with a valid command and input length == 1 + String userInput = "recommended"; + String processedInput = FuzzyLogic.processCommand(userInput); + assertEquals("recommended", processedInput); + } + + @Test + void testProcessCommand3() throws FlorizzException { // processInput returns the original input with invalid command + String userInput = "invo Ross"; + String processedInput = FuzzyLogic.processCommand(userInput); + assertEquals("invo Ross", processedInput); + } + + @Test + void testSplitAndMergeInputAdd1() throws FlorizzException { + String userInput = "addRose /q 10 /from bouquet"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("add Rose /q 10 /from bouquet", splitMergedInput); + } + + @Test + void testSplitAndMergeInputAdd2() throws FlorizzException { + String userInput = "a d d Rose /q 10 /from bouquet"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("add Rose /q 10 /from bouquet", splitMergedInput); + } + + @Test + void testSplitAndMergeInputRemove1() throws FlorizzException { + String userInput = "removeRose /q 10 /from bouquet"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("remove Rose /q 10 /from bouquet", splitMergedInput); + } + + @Test + void testSplitAndMergeInputRemove2() throws FlorizzException { + String userInput = "r e m ove Rose /q 10 /from bouquet"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("remove Rose /q 10 /from bouquet", splitMergedInput); + } + + @Test + void testSplitAndMergeInputSave1() throws FlorizzException { + String userInput = "savebouquet123"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("save bouquet123", splitMergedInput); + } + + @Test + void testSplitAndMergeInputSave2() throws FlorizzException { + String userInput = "s av e bouquet123"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("save bouquet123", splitMergedInput); + } + + @Test + void testSplitAndMergeInputDelete1() throws FlorizzException { + String userInput = "deletebouquet"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("delete bouquet", splitMergedInput); + } + + @Test + void testSplitAndMergeInputDelete2() throws FlorizzException { + String userInput = "d e l e t e bouquet"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("delete bouquet", splitMergedInput); + } + + @Test + void testSplitAndMergeInputNew1() throws FlorizzException { + String userInput = "newmybouquets"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("new mybouquets", splitMergedInput); + } + + @Test + void testSplitAndMergeInputNew2() throws FlorizzException { + String userInput = "n ew mybouquets"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("new mybouquets", splitMergedInput); + } + + @Test + void testSplitAndMergeInputFlowers1() throws FlorizzException { + String userInput = "flowersMothers Day"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("flowers Mothers Day", splitMergedInput); + } + + @Test + void testSplitAndMergeInputFlowers2() throws FlorizzException { + String userInput = "fl owe r s Mothers Day"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("flowers Mothers Day", splitMergedInput); + } + + @Test + void testSplitAndMergeInputInfo1() throws FlorizzException { + String userInput = "infoRose"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("info Rose", splitMergedInput); + } + + @Test + void testSplitAndMergeInputInfo2() throws FlorizzException { + String userInput = "i n f o Rose"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("info Rose", splitMergedInput); + } + + @Test + void testSplitAndMergeInputMyBouquets() throws FlorizzException { + String userInput = "m y bou quets"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("mybouquets", splitMergedInput); + } + + @Test + void testSplitAndMergeInputFlowers() throws FlorizzException { + String userInput = "f low ers"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("flowers", splitMergedInput); + } + + @Test + void testSplitAndMergeInputOccasion() throws FlorizzException { + String userInput = "o cc asi on"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("occasion", splitMergedInput); + } + + @Test + void testSplitAndMergeInputRecommend() throws FlorizzException { + String userInput = "re commen d"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("recommend", splitMergedInput); + } + + @Test + void testSplitAndMergeInputByw() throws FlorizzException { + String userInput = "b y e"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("bye", splitMergedInput); + } + + @Test + void testSplitAndMergeInputHelp() throws FlorizzException { + String userInput = "h elp"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("help", splitMergedInput); + } + + @Test + void testSplitAndMergeInputBack() throws FlorizzException { + String userInput = "b a c k"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("back", splitMergedInput); + } + + @Test + void testSplitAndMergeInputNext() throws FlorizzException { + String userInput = "ne xt"; + String splitMergedInput = FuzzyLogic.splitAndMergeInput(userInput); + assertEquals("next", splitMergedInput); + } + + @Test + void testSplitInputDelete() throws FlorizzException { + String userInput = "deleteBouquet For Girlfriend"; + String splitInput = FuzzyLogic.splitInput(userInput); + assertEquals("delete Bouquet For Girlfriend", splitInput); + } + @Test + void testSplitInputFlowers() throws FlorizzException { + String userInput = "flowersMothers Day"; + String splitInput = FuzzyLogic.splitInput(userInput); + assertEquals("flowers Mothers Day", splitInput); + } + @Test + void testSplitInputNew() throws FlorizzException { + String userInput = "newmother's day bouquet"; + String splitInput = FuzzyLogic.splitInput(userInput); + assertEquals("new mother's day bouquet", splitInput); + } + + @Test + void testSplitInputAdd() throws FlorizzException { + String userInput = "addLily /q 1 /to bouquetName"; + String splitInput = FuzzyLogic.splitInput(userInput); + assertEquals("add Lily /q 1 /to bouquetName", splitInput); + } + + @Test + void testSplitInputInfo() throws FlorizzException { + String userInput = "infoRose"; + String splitInput = FuzzyLogic.splitInput(userInput); + assertEquals("info Rose", splitInput); + } + + @Test // Test case for null input + void testMergeInputNullInput() { + assertThrows(FlorizzException.class, () -> FuzzyLogic.mergeInput(null)); + } + + @Test // Test case for input with spaces + void testMergeInputWithSpaces() throws FlorizzException { + String userInput = "Hello World"; + String mergedInput = FuzzyLogic.mergeInput(userInput); + assertEquals("HelloWorld", mergedInput); + } + + @Test // Test case for input without spaces + void testMergeInputNoSpaces() throws FlorizzException { + String userInput = "NoSpacesHere"; + String mergedInput = FuzzyLogic.mergeInput(userInput); + assertEquals("NoSpacesHere", mergedInput); + } +} diff --git a/src/test/java/florizz/core/ParserTest.java b/src/test/java/florizz/core/ParserTest.java new file mode 100644 index 0000000000..36306ef1dc --- /dev/null +++ b/src/test/java/florizz/core/ParserTest.java @@ -0,0 +1,15 @@ +package florizz.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ParserTest { + @Test + void testParse() throws FlorizzException { + String userInput = "Gibberish Input"; + boolean enableUi = true; + + assertThrows(FlorizzException.class, () -> Parser.parse(userInput, enableUi)); + } +} diff --git a/src/test/java/florizz/objects/BouquetTest.java b/src/test/java/florizz/objects/BouquetTest.java new file mode 100644 index 0000000000..0b60692d83 --- /dev/null +++ b/src/test/java/florizz/objects/BouquetTest.java @@ -0,0 +1,43 @@ +package florizz.objects; + +import florizz.core.FlorizzException; +import florizz.core.FlowerDictionary; +import florizz.core.Ui; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class BouquetTest { + @Test + void testToString() { + Bouquet testBouquet = new Bouquet("For Mom"); + assertEquals("For Mom", testBouquet.toString()); + } + + @Test + void testEquals(){ + Bouquet testBouquet1 = new Bouquet ("For Mom"); + Bouquet testBouquet2 = new Bouquet ("For Mom"); + Bouquet testBouquet3 = new Bouquet ("For GF"); + assertEquals(testBouquet1, testBouquet2); + assertNotEquals(testBouquet1, testBouquet3); + } + + @Test + void testBouquetSize() { + int quantity = 3; + FlowerDictionary.startup(); + Ui ui = new Ui(); + Bouquet testBouquet = new Bouquet(); + Flower flowerToAdd = FlowerDictionary.get(1); + try { + testBouquet.addFlower(flowerToAdd, quantity); + } catch (FlorizzException e) { + ui.printError(e); + } + + // check for size + assertEquals(quantity, testBouquet.totalNumberOfFlowers()); + } +} diff --git a/src/test/java/florizz/objects/FlowerTest.java b/src/test/java/florizz/objects/FlowerTest.java new file mode 100644 index 0000000000..586ff1fd1d --- /dev/null +++ b/src/test/java/florizz/objects/FlowerTest.java @@ -0,0 +1,15 @@ +package florizz.objects; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FlowerTest { + + @Test + void testGetName() { + Flower flower = new Flower("Orchid", Flower.Colour.WHITE, Flower.Occasion.WEDDING, 10.00); + assertEquals("Orchid",flower.getFlowerName()); + } + +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} diff --git a/text-ui-test/EXPECTED-UNIX.TXT b/text-ui-test/EXPECTED-UNIX.TXT new file mode 100644 index 0000000000..75494afd42 --- /dev/null +++ b/text-ui-test/EXPECTED-UNIX.TXT @@ -0,0 +1,23 @@ +Hello from + + __ _ _ + / _| | (_) + | |_| | ___ _ __ _ ________ + | _| |/ _ \| '__| |_ /_ / + | | | | (_) | | | |/ / / / + |_| |_|\___/|_| |_/___/___| + + +Type `help`, to view a table of valid commands! +What can I do for you? +Added new bouquet to list: +test +____________________________________________________________ +What can I do for you? +Here is the list of your saved bouquets: +1. test : + No flowers added so far +____________________________________________________________ +What can I do for you? +Enjoy your bouquet! Thank you for using Florizz! +____________________________________________________________ diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..75494afd42 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,23 @@ Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| -What is your name? -Hello James Gosling + __ _ _ + / _| | (_) + | |_| | ___ _ __ _ ________ + | _| |/ _ \| '__| |_ /_ / + | | | | (_) | | | |/ / / / + |_| |_|\___/|_| |_/___/___| + + +Type `help`, to view a table of valid commands! +What can I do for you? +Added new bouquet to list: +test +____________________________________________________________ +What can I do for you? +Here is the list of your saved bouquets: +1. test : + No flowers added so far +____________________________________________________________ +What can I do for you? +Enjoy your bouquet! Thank you for using Florizz! +____________________________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..95dbd950c7 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,3 @@ -James Gosling \ No newline at end of file +new test +mybouquets +bye \ No newline at end of file