diff --git a/build.gradle b/build.gradle index b0c5528fb5..3e9c783171 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ checkstyle { toolVersion = '8.23' } -run{ +run { standardInput = System.in + enableAssertions = true; } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..f6b6d2b5b1 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,11 @@ # About us -Display | Name | Github Profile | Portfolio +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) +![image](https://user-images.githubusercontent.com/62021897/140710285-5c0a50ae-40b5-485c-a97b-d310cead7151.png) | Woo Bo Tuan | [Bobowoo2468](https://github.com/Bobowoo2468) | [Portfolio](team/bobowoo2468.md) +![Sam_Potrait](https://user-images.githubusercontent.com/62021897/140722740-fcad3644-2516-452c-aff0-dad4e41af303.jpg) | Wong Tze Shan Samantha | [swongts](https://github.com/swongts) | [Portfolio](team/swongts.md) +![image](https://user-images.githubusercontent.com/79963329/140712676-0fa1f0ec-8fb9-43b4-b2e0-59ab7e31e678.jpg)| Yip Wayne | [YipWayne](https://github.com/YipWayne) | [Portfolio](team/YipWayne.md) +![](https://via.placeholder.com/100.png?text=Photo) | Zhou Chengxu | [Demonshaha](https://github.com/Demonshaha) | [Portfolio](team/Demonshaha.md) + + + diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..2643bb5d15 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,729 @@ # Developer Guide -## Acknowledgements +## Table of Contents -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- [Getting Started](#getting-started) + - [Recommended software (for optimal compatability)](#recommended-software-for-optimal-compatibility) + - [Setting up this project in your computer](#setting-up-this-project-in-your-computer) -## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +- [Design & Implementation](#design--implementation) + - [Architecture](#architecture) + - [Parser](#parser-component) + - [Ui component](#ui-component) + - [Command component](#command-component) + - [Add feature](#add-feature) + - [Cut feature](#cut-feature) + - [List feature](#list-feature) + - [Find feature](#find-feature) + - [Sort feature](#sort-feature) + - [Storage component](#storage-component) -## Product scope +- [Appendix A: Product Scope](#appendix-a-product-scope) + - [Target user profile](#target-user-profile) + - [Value proposition](#value-proposition) +- [Appendix B: User Stories](#appendix-b-user-stories) +- [Appendix C: Non-Functional Requirements](#appendix-c-non-functional-requirements) +- [Appendix D: Glossary](#appendix-d-glossary) +- [Appendix E: Instructions for manual testing](#appendix-e-instructions-for-manual-testing) + +
+ +## Getting Started + +
+ +### Recommended software (for optimal compatibility) + +* GitHub +* Sourcetree (for version control) +* IntelliJ IDEA (IDE) +* Amazon Coretto + +### Setting up this project in your computer + +1. On Github, fork this repo by clicking on the fork button + + +2. In Sourcetree, clone the fork into your computer + 1. Open a new tab + 2. Select the clone button at the top of the menu + 3. Key in the details of this repository + + +3. Configure the JDK: Follow the guide [Project Configuration/ SDKs](https://www.jetbrains.com/help/idea/sdk.html) + to ensure that Intellij is configured to use JDK 11. + + +4. Import the project as a Gradle project: Follow the + guide [Intellij IDEA: Importing a Gradle project](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) + + +5. Verify the setup: + 1. Run the seedu.duke.TourPlanner and try a few commands. + 2. Run the tests to ensure they all pass. + +
+ +## Design & Implementation + +
+ +## Architecture + +
+ +The diagram below shows the high-level design of TourPlanner: + +![Architecture Class Diagram](https://user-images.githubusercontent.com/62021897/140712865-752a92c6-da93-42a2-9d12-66f4182df56c.png) + +Below is an overview of the main components, and how they interact with each other. + +**Main components** + +```TourPlanner``` acts as the main class. It is responsible for: + +* At app launch: Initialises the components in the correct sequence, and connects them up with each other. +* While app is running: Executes commands from the user. +* At shut down: Shuts down the components and invokes clean-up methods where necessary. + +The rest of the app consists of the following components: + +* ```Ui```: The UI of the application. +* ```Parser```: Deciphers user input and returns the appropriate command to `TourPlanner`. +* ```Command```: The different types of commands that can possibly be executed. +* ```ObjectList```: Holds data in different arrays, based on their type. Namely there are four types of ObjectLists: + * `ClientList` + * `TourList` + * `FlightList` + * `ClientPackageList` +* ```Storage```: Reads data from, and writes data to, the hard disk. + +
+ +**Interaction of components** + +The diagram below shows how the components interact with each other if the user inputs the command ```list -c```: + +![ListClientCommand](https://user-images.githubusercontent.com/70316271/140695121-080a0611-4c22-4399-b460-eb5b0454e445.png) + +
+ +## Parser component + +
+ +**API: `Parser.java`** + +The Sequence Diagram below illustrates the flow of parsing a command: + +Here is a class diagram of Parser component: + +![Parser](https://user-images.githubusercontent.com/70316271/140693617-31b988e6-90ff-46b5-8af1-8921ca1ef3fa.png) + +The flow of **parse** in `Parser` is as follows: + +**Step 1.** `Parser` splits the user input String to identify user's **Command** (add, list, cut etc.). + +**Step 2.** Depending on the Command, `Parser` calls `parseXYZ()` method respective to the `XYZCommand` +(`XYZ` is a placeholder for the specific command e.g. `AddClientCommand`) given. + +**Step 3.** `Parser` creates an `XYZCommand` object which Parser returns as a `Command` object. + +**Step 4.** `XYZCommand` is returned to `TourPlanner` to be executed. + +
+ +### Example of parse mechanism - `ParseAdd` + +Given below is an example scenario, outlining how parseAdd behaves at each step. + +**Step 1.** `Parser` first identifies the identifier for add, namely `-c` (add client), `-t` (add tour), `-f` (add +flight), `-p` (add package), by splitting the identifier and the rest of the string by spaces. + +``` +-t JPN /n Japan Tour /p 1500 --> [-t, JPN, /n, Japan Tour, /p 1500] +``` + +**Step 2.** Next, `Parser` checks if all prefixes for data fields e.g. name, price, id are present. + +**Step 3.** To sort the values for addition, the prefixes and their corresponding indexes are stored as key-value pairs +into a TreeMap. A TreeMap helps to sort the pairs by the natural ordering of the keys. +:information_source: See [TreeMap](#appendix-d-glossary). + +``` +^JPN ^/n Japan Tour ^/p 1500 --> [(0, null), (4, /n), (18, /p)] (sorted) +``` + +**Step 4.** The prefix and its index will facilitate splitting the string into substrings, containing both the prefix +and the value corresponding to the prefix. + +``` +-t JPN /n Japan Tour /p 1500 --> [JPN, /n Japan Tour, /p 1500] +``` + +**Step 5.** Given the prefix, the array index that the value will be inserted into is predetermined. + +``` +[JPN, /n Japan Tour, /p 1500] --> [JPN, Japan Tour, 1500] +``` + +**Step 6.** The value is extracted from the substring by removing the prefix, and inserted into an array. The array is +used as an argument for the *Object* constructor. + +
+ +## UI Component + +
+ +**API: `Ui.java`** + +The Ui component is the means by which Command(s) can receive inputs from the user, as well as display information to +them, all through the console terminal. + +The diagram below shows the class diagram of the Ui component, in relation with other major components: + +![Ui Class Diagram](https://user-images.githubusercontent.com/62021897/140712493-2054f2c9-6f58-4353-88ad-1570707ff3ef.png) + +
+ +After the user typed in an input into the console terminal and presses 'Enter': + +* the ```Ui``` reads the input typed in by the user on the console terminal. +* the ```Parser``` class parses the read input and calls the appropriate ```Command```. (see the ```Parser``` + and ```Command``` sections for more information) +* the called ```Command``` calls a function in the ```Ui``` to print the appropriate information onto the console + terminal. + +
+ +## Command component + +
+ +**API: `Command.java`** + +Here's a (partial) class diagram of Command component: + +![Command class (2)](https://user-images.githubusercontent.com/70316271/140637570-e6a9f453-ea88-46a8-8f3c-e1913e7e938d.png) + +:information_source: `XYZ` in this diagram is a placeholder for the specific data type (`Client`, `Flight`, `Tour` +, `ClientPackage`). A similar workflow applies for these classes depending on the availability of the command for the +specific data type. + +How the `Command` works: + +**Step 1.** Based on the user input, `Parser` returns one of the subclasses of `Command` to `TourPlanner`. + +**Step 2.** `TourPlanner` calls `Command.execute()`. + +**Step 3.** Corresponding command uses associate classes, `XYZList` (`XYZ` is a placeholder for the specific data type +e.g. `Client`) and `Ui` to carry out its function. + +
+ +### Add feature + +The add feature is facilitated mainly by `Parser`, and returns an `AddCommand` object. When `AddCommand` is executed, +the values corresponding to their fields are added. + +After receiving the command from the user by `readCommand` of `Ui`, the command is parsed by `Parser`. The `Parser` +first determines the command type by separating the command from the subsequent arguments. After determining the +command, `Parser` executes the specific methods to sense-make the arguments, in response to the specific command. + +Here are sample add command inputs that can be parsed. + +Example add commands: + +* `add -c c001 /n Bo Tuan /cn 91234567 /m bobotea@gmail.com` +* `add -f SQ-JPN1 /d Japan /r Singapore /dd 29/10/21 13:00 /rd 5/11/21 02:00` +* `add -t t001 /n AustralianRomance /p 1500` +* `add -p p001 /c c001 /f SQ-JPN1 /t t001` + +In general, there is a sequence of steps when any of the 4 `add` commands are called. + +Here is an example usage of `add -c` to add client "c001": + +**Step 1.** User inputs `add -c c001 [ARGS...]`. This command is passed to `parse()` method in the `Parser` class. + +**Step 2.** Based on the user input, `parse()` identifies that it is of type `add` command and calls `ParseAdd()`. + +**Step 3** `ParseAdd()` executes three instructions: + +* Extract values from user's input and creates a `values` Array +* Handle exceptions to the `values` Array, determined by the data type identifier (e.g. `-c`, `-t`) +* Instantiates a `Client`/`Tour`/`Flight`/`ClientPackage` *Object*, determined by the same data type identifier. +* Returns an `AddXYZCommand` based on the same data type identifier, passing in the *Object* as an argument for adding. + +Depending on the type of add command being called, these command types will be returned: + +* `add -c`: `AddClientCommand` +* `add -f`: `AddFlightCommand` +* `add -t`: `AddTourCommand` +* `add -p`: `AddClientPackageCommand` + +The following 2 sections focuses on add for the specific classes. + +
+ +#### Adding a client package + +Given below is an example usage of `add -p p001 ARGS...` to add client package "p001". + +Here is a (partial) sequence diagram of above user input: + +![AddClientPackage Sequence Diagram](https://user-images.githubusercontent.com/62021897/140713338-a743133a-516b-47e3-88b6-e9b5408b226c.png) + +**Step 1.** Parser creates a `values` array, upon extracting values from user's input. +Creates `AddClientPackageCommand(values)`, determined by the datatype identifier `-p`. Returns the created `Command` +subclass to `TourPlanner`. + +**Step 2.** Then, `execute()` method in `AddClientCommand` is called. `getClientPackageById("p001")` is called, which +finds the `ClientPackage` based on the `CLIENT_PACKAGE_ID` "p001". + +**Step 3.** If the `CLIENT_PACKAGE_ID` "p001" already exists, an error message is returned. + +**Step 4.** Else, the `CLIENT_PACKAGE_ID` "p001" does not exist, `execute()` calls `createClientPackage` which finds the +specific `Client`, `Tour` and `Flight` objects to be added to the `ClientPackage` + +**Step 5.** Then, `add` in `ClientPackageList` is called, to add the specific `ClientPackage` into `ClientPackageList`. + +**Step 5.** `execute()` calls `showAdd` in `Ui`, which prints out the Object, `ClientPackage` that was added. + +
+ +#### Adding a particular client / tour / flight + +Given below is an example usage of `add -t t001 ARGS...` to add tour with the respective arguments. + +Here is a (partial) sequence diagram for above user input: + +![AddCommand](https://user-images.githubusercontent.com/62021897/140713091-70c5b626-ec86-4122-a2c9-6b0f47b0a564.png) + +**Step 1.** `Parser` parses the input and instantiates `AddTourCommand`. It then returns it to `TourPlanner`. + +**Step 2.** Then, `execute()` method in `AddTourCommand` is called. `getTourById("t001")` is called, which finds +the `Tour` based on the `TOUR_ID` "t001". + +**Step 3.** If the `TOUR_ID` "t001" already exists, an error message is returned. + +**Step 4.** Else, the `TOUR_ID` "t001" does not exist, `execute()` calls `add` in `TourList`, to add the specific client +package and its arguments into the `TourList`. + +**Step 5.** `execute()` calls `showAdd` in `Ui`, which prints out the Object, `Tour` that was added. + + +
+ +### Cut feature + +The `cut` feature is used to remove an entry from the `ObjectList` (for `Client`, `Tour`, `Flight` and `ClientPackage`), +where `Parser` determines the `ObjectList` to remove from. + +It implements these following types of `cut` commands: + +* `cut -c CLIENT_ID` +* `cut -t TOUR_ID` +* `cut -f FLIGHT_ID` +* `cut -p CLIENTPACKAGE_ID` + +In general, there is a sequence of steps when any of the 4 `cut` commands are called. + +Here is an example usage of `cut -c` to delete client with `CLIENT_ID` of "c001": + +**Step 1.** After adding a few client packages to the database, user inputs `cut -c c001`. This command is passed +to `parse()` method in the `Parser` class. + +**Step 2.** Based on the user input, `parse()` identifies that it is of type `cut` command and calls `ParseCut()`. +`ParseCut()` will then create `CutClientCommand("c001")` based on the prefix `-c`. Returns the created `Command` +subclass to `TourPlanner`. + +Depending on the type of cut command being called, these command types will be returned: + +* `cut -c`: `CutClientCommand` +* `cut -f`: `CutFlightCommand` +* `cut -t`: `CutTourCommand` +* `cut -p`: `CutClientPackageCommand` + +The following 2 sections focuses on cut for the specific classes. + +
+ +#### Cutting a particular client package + +Given below is an example usage of `cut -p p001` to deletes client package with `CLIENTPACKAGE_ID` of "p001". + +Here is a (partial) sequence diagram of above user input: + +![CutClientPackageCommand](https://user-images.githubusercontent.com/70316271/140637935-a64ba82f-a9d1-4439-ad04-157ad0ecaad3.png) + +**Step 1.** `Parser` creates `CutClientPackageCommand("p001")` and returns it to `TourPlanner`. + +**Step 2.** Then, `execute()` method in `CutClientPackageCommand` calls `getClientPackageById("p001")` +which finds the `ClientPackage` based on the `CLIENTPACKAGE_ID` "p001". + +**Step 3.** The `ClientPackage` is removed from the `ClientPackageList`. + +
+ +#### Cutting a particular client / tour / flight + +Given below is an example usage of `cut -c c001` to delete client with `CLIENT_ID` of "c001" +and all corresponding client packages that contain deleted client. + +Here is a (partial) sequence diagram for above user input: + +![CutClientCommand](https://user-images.githubusercontent.com/70316271/140637934-08e0a09e-bf63-4f16-b84f-faa7a852b2d3.png) + +**Step 1.** `Parser` creates `CutClientCommand("c001")` and returns it to `TourPlanner`. + +**Step 2.** Then, `execute()` method in `CutClientCommand` calls `getClientById("c001")` +which finds the `Client` based on the `CLIENT_ID` "c001". + +**Step 3.** The `Client` is removed from the `ClientList`. + +**Step 4.** Next, `getClientPackageByClient("c001")` returns an `ArrayList` of `ClientPackages` that contains +the `CLient` "c001". :information_source: See [ArrayList](#appendix-d-glossary). + +**Step 5.** Loops through the `ClientPackages` and deletes them from `ClientPackageList`. + +*:information_source: `Tour` and `Flight` works in the same way respectively with `TourList` and `FlightList` + +
+ +### List feature + +The `list` feature is used to display all entries in `ObjectList` (`Client`, `Tour`, `Flight` and `ClientPackage`), +where `Parser` determines the `ObjectList` to list from. + +It implements these following types of list commands: + +* `list -c`: Lists all existing clients and their contact numbers +* `list -t`: Lists all existing tours +* `list -f`: Lists all existing flights +* `list -p`: Lists all clients and their corresponding tours and flights + +Given below is an example usage of command `list -c` that lists all `Client` objects in `ClientList`: + +Here is a (partial) sequence diagram for the above user input: +![ListClientCommand](https://user-images.githubusercontent.com/70316271/140637940-066bab37-d9dd-4efa-b0ee-e681fcd5b139.png) + +**Step 1**: After adding a few clients to the database, user inputs `list -c`. This command is passed to `parse()` +method in the `Parser` class. + +**Step 2**: Based on the user input, `parse()` identifies that it is of type `list` command and calls `ParseList()`. +`ParseList()` will then return `ListClientCommand` based on the prefix `-c`. + +**Step 3**: Then, `execute()` method in `ListClientCommand` is called, where it loops through the current `clientList` +and prints out all client names and their details. + +Depending on the type of list command being called, these command types will be returned: + +* `list -c`: `ListClientCommand` +* `list -f`: `ListFlightCommand` +* `list -t`: `ListTourCommand` +* `list -p`: `ListClientPackageCommand` + +
+ +### Find feature + +The ```find``` feature is to be used to query for a particular client, tour or flight, providing extensive information +about it. It is facilitated by the ```parse``` function in the ```Parser``` class, and returns a FindCommand object. +When FindCommand is executed, it queries the corresponding `ObjectList` and returns the matching entries. + +It implements these following types of find commands: + +* `find -c CLIENT_NAME`: returns clients that contain `CLIENT_NAME` in their name and client packages they are + subscribed to. +* `find -t TOUR_ID`: returns tours that match the `TOUR_ID` and tours subscribed to the tour. +* `find -f FLIGHT_ID`: returns flights that match the `FLIGHT_ID` and clients subscribed to the flight. + +
+ +In general, there is a sequence of steps when any of the 3 `find` commands are called. + +Firstly, assume that in previous sessions, commands were executed to add clients, tours, flights and packages to +the ```ClientList```, ```TourList```, ```FlightList``` and ```PackageList``` respectively. In particular, these specific +commands were executed. + +* ```add -c c001 /n Bo Tuan /cn 93338333 /m borangutuan@mail.com``` +* ```add -t JPN /n Japan Tour /p 1500``` +* ```add -f SQ-JPN /d JPN /r SG /dd 20/10/2021 18:00 /rd 21/10/2021 03:00``` +* ```add -p p001 /c c001 /t JPN /f SQ-JPN``` + +Here is an example usage of `find -c bo` to find a client with name "Bo Tuan": +**Step 1**: The user executes ```find -c bo``` to query if a client named "Bo Tuan" exists in the ClientList. +The ```parse``` function in the ```Parser``` class takes the command, and calls `parseFind()` +based on (```find```) in the input. ```parseFind()``` determines which type of Object is to be queried for and +```FindClientCommand()``` is created with the parameter ```bo``` based on prefix `-c`. + +Depending on the type of find command being called, these command types will be returned: + +* ```find -c```: ```FindClientCommand``` +* ```find -t```: ```FindTourCommand``` +* ```find -f```: ```FindFlightCommand``` + +The following 3 sections focuses on find for the specific classes. + +
+ +#### Finding a particular client + +Given below is an example usage of `find -c bo`. + +Here is a (partial) sequence diagram for the above user input: + +![FindClient Sequence Diagram](https://user-images.githubusercontent.com/62021897/140740252-019e87c0-7793-4203-9cc2-df2834f71b0e.png) + +**Step 1** `Parser` creates `FindClientCommand("bo")` and returns it to `TourPlanner`. + +**Step 2** The ```FindClientCommand``` iterates through each ```Client``` object in the ```ClientList```. For +every ```Client```, the ```getName()``` function is called to retrieve the name attribute of the Client. The name +attribute is then converted to lower case for comparison with the substring. If the name attribute is contains the +substring```bo```, the ```Client``` object is printed onto the console terminal. + +**Step 3** In addition, the ```FindClientCommand``` iterates through each ```ClientPackage``` object in +the ```ClientPackageList```. For every ```ClientPackage```, the ```getClient()``` function is called to retrieve the +client attribute of the ClientPackage. If the client attribute is equals to the same ```Client``` object that was found +in Step 2, the respective client package will be printed onto the console terminal. + +
+ +#### Finding a particular tour + +Given below is an example usage of `find -t JPN`. + +Here is a (partial) sequence diagram for the above user input: + +![FindTour Sequence Diagram](https://user-images.githubusercontent.com/62021897/140713906-dd0524f5-53d2-4409-819b-05a467adee53.png) + +**Step 1**: `Parser` creates `FindTourCommand("JPN")` and returns it to `TourPlanner`. + +**Step 2**: The ```FindTourCommand``` is executed and iterates through each ```Tour``` object in the ```TourList```. For +every ```Tour```, the ```getCode()``` function is called to retrieve the code attribute of the Tour. If the tour +attribute is equals to ```JPN```, the ```Tour``` object is printed onto the console terminal. + +**Step 3**: In addition, the ```FindTourCommand``` iterates through each ```ClientPackage``` object in +the ```ClientPackageList```. For every ```ClientPackage```, the ```getTour()``` function is called to retrieve the tour +attribute of the ClientPackage. If the tour attribute is equals to the same ```Tour``` object that was found in Step 2, +the client attribute of that same ClientPackage will be retrieved using the ```getClient()``` function. + +**Step 4**: The client's name attribute is then retrieved using the ```getName()``` function in the Client class. The +name is printed onto the console terminal under "Subscribed Clients". Once all ```ClientPackage``` objects are iterated +through in the ```ClientPackageList```, the total number of subscribed clients will be printed on the console terminal. + +
+ +#### Finding a particular flight + +Given below is an example usage of `find -f SQ-JPN`. + +Here is a (partial) sequence diagram for the above user input: + +![FindFlight Sequence Diagram](https://user-images.githubusercontent.com/62021897/140713762-85084399-97e1-47ee-a228-51e6c743d0d1.png) + +**Step 1**: `Parser` creates `FindFlightCommand("SQ-JPN")` and returns it to `TourPlanner`. + +**Step 2**: The ```FindFlightCommand``` is executed and iterates through each ```Flight``` object in +the ```FlightList```. For every ```Flight```, the ```getCode()``` function is called to retrieve the code attribute of +the Tour. If the tour attribute is equals to ```SQ-JPN```, the ```Flight``` object is printed onto the console terminal. + +**Step 3**: In addition, the ```FindFlightCommand``` iterates through each ```ClientPackage``` object in +the ```ClientPackageList```. For every ```ClientPackage```, the ```getFlight()``` function is called to retrieve the +tour attribute of the ClientPackage. If the tour attribute is equals to the same ```Flight``` object that was found in +Step 2, the client attribute of that same ClientPackage will be retrieved using the ```getClient()``` function. + +**Step 4**: The client's name attribute is then retrieved using the ```getName()``` function in the Client class. The +name is printed onto the console terminal under "Passengers". Once all ```ClientPackage``` objects are iterated through +in the ```ClientPackageList```, the total number of passengers will be printed on the console terminal. + +
+ +#### Design Considerations + +* Alternative: only iterate through the ```ClientPackageList```. + * Pros: fast querying time. + * Cons: If the client/tour/flight is not in any package, none of their information can be accessed, including their + contact number. + +
+ +### Sort feature + +The `sort` feature is used to sort the `ObjectList` (for `Client`, `Tour`, `Flight` and `ClientPackage`) and list it, +where `Parser` determines the `ObjectList` and criteria to sort for. + +It implements these following types of `sort` commands: + +* Client + * `sort -c /n`: Sorts alphabetically by client name + * `sort -c /id`: Sorts alphabetically by client id +* Flight + * `sort -f /d`: Sorts in ascending order of date and time for departure flight + * `sort -f /r`: Sorts in ascending order of date and time for return flight + * `sort -f /id`: Sorts alphabetically by flight id +* Tour + * `sort -t /id`: Sorts alphabetically by tour id + * `sort -t /p`: Sorts in ascending order of tour price + +Given below is an example usage of `sort -c /id`: + +Here is a (partial) sequence diagram of the above user input: + +![SortClientCommand](https://user-images.githubusercontent.com/70316271/140637939-1ff5b961-31fa-4afa-b834-316066362ffd.png) + +**Step 1**: After adding a few clients to the database, user inputs `sort -c /id`. This command is passed to `parse()` +method in the `Parser` class. + +**Step 2**: Based on the user input, `parse()` identifies that it is of type `sort` command and calls `ParseSort()`. +`ParseSort()` will then return `SortClientCommand` based on the prefix `-c`. + +**Step 3**: Then, `execute()` method in `SortClientCommand` is called, where it gets the sorted `ArrayList` of +`clientIds`. :information_source: See [ArrayList](#appendix-d-glossary). + +**Step 4** In `UI`, `showSortedClientById()` is called, with `clientIds` passed in. The method iterates through all the +client IDs. For each iteration, finds the corresponding `Client` with `getClientById()` and prints out the `Client` +and their details. + +Depending on the type of sort command being called, these command types will be returned: + +* `sort -c`: `SortClientCommand` +* `sort -f`: `SortFlightCommand` +* `sort -t`: `SortTourCommand` + +
+ +## Storage Component + +
+ +**API: `ClientPackageStorage.java` `ClientStorage.java` `FlightStorage.java` `TourStorage.java`** + +Shown below is the class diagram for TourPlanner's Storage Component: + +![Storage](https://user-images.githubusercontent.com/62021897/140651207-73bd0de8-cb0b-469f-9cdd-c9bbc26b8efc.png) + +
+ +The Storage component consists of: + +1. `ClientPackageStorage.java`: Reading and saving files which record all clientpackages. +2. `ClientStorage.java`: Reading and saving files which record all clients. +3. `FlightStorage.java`: Reading and saving files which record all flights. +4. `TourStorage.java`: Reading and saving files which record all tours. + +To add on Storage component is designed to access only the following folder: + +1. `data/` + +
+ +## Appendix A: Product scope + +
+ ### Target user profile -{Describe the target user profile} +* Tour company employees that need to manage a significant amount of tour data +* Prefer desktop apps over other types +* Can type fast +* Is reasonably comfortable using CLI apps ### Value proposition -{Describe the value proposition: what problem does it solve?} +* Manage tour information faster than typical mouse /GUI driven apps + +
+ +## Appendix B: User Stories -## User Stories +
-|Version| As a ... | I want to ... | So that I can ...| +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` + +|Priority| 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| +|`* * *`|new user|see usage instructions|refer to them when I forget how to use the application| +|`* * *`|user|add a new entry of specific data type| add data into the current database| +|`* * *`|user|delete an existing entry of specific data type |remove outdated data from the current database| +|`* *`|user with large amounts of data|find an existing entry of specific data type |locate a specific entry easily| +|`* *`|user with large amounts of data|sort existing entries of specific data type |make smarter recommendations to clients based on their preferences| +|`* *`|user|check number of clients subscribed to a tour / flight|check the popularity, vacancy of certain tours / flights| + +:information_source: 'specific data type' refers to either clients, tours, flights or client packages. + +
+ +## Appendix C: Non-Functional Requirements + +
+ +* Should work on any mainstream OS as long as it has Java 11 or above installed. +* Should be able to hold up to 1000 entries without a noticeable sluggishness in performance for typical usage. +* A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be + able to accomplish most of the tasks faster using commands than using the mouse. + +
+ +## Appendix D: Glossary + +
+ +**ClientPackage:** serves as a link between the client, tour and flight, helps to keep track of the tours (and flights) +they are subscribed to. `ClientPackage` class stores the actual `Client`, `Flight` and `Tour` objects. + +**ArrayList:** acts as a dynamic array, where items can be easily added and removed. + +**TreeMap:** sorts and stores key-value pairs. The key-value pairs are sorted according to the natural ordering of its +keys, or by a Comparator provided at map creation time. + +**Subclass:** To say class `A` is a subclass of class `B` would mean that class `A` inherits from class `B`. A subclass +inherits attributes and methods from the parent class. + +**Exceptions:** is any event that interrupts the normal flow of program execution, due to an unwanted event which cannot +be controlled by developers + +
+ +## Appendix E: Instructions for manual testing + +
+ +Given below are instructions to test the app manually. + +### Launch and shutdown + +1. Initial launch + 1. Download the jar file and copy into an empty folder. + 2. Run the command ```java -jar TourPlanner.jar``` in a command window to start the program. + + * Expected: Applications shows welcome messages and allows you to type in commands. + +### Adding and viewing data + +1. Add `Client` to database + * Test case: `add -c c001 /n Bo Tuan /cn 93338333 /m borangutuan@mail.com` -## Non-Functional Requirements + 1. Input the test case to the command window. + 2. Input `list -c`. -{Give non-functional requirements} + * Expected: `Client` with id "c001", name "Bo Tuan", contact "93338333", mail "borangutuan@mail.com" + is displayed in the list of clients. + * :information_source: tests can be repeated with `Tour`, `Flight` and `ClientPackage` with the corresponding data + fields. -## Glossary +### Cutting data -* *glossary item* - Definition +1. Cut `Client` from database + * Test case: `cut -c c001` after calling `add -c c001 /n Bo Tuan /cn 93338333 /m borangutuan@mail.com` -## Instructions for manual testing + 1. Input the test case to the command window. + 2. Input `list -c`. -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} + * Expected: `Client` with id "c001", name "Bo Tuan", contact "93338333", mail "borangutuan@mail.com" + is no longer displayed in the list of clients. + * :information_source: tests can be repeated with `Tour`, `Flight` and `ClientPackage` with the corresponding data + fields. diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..e2556e39ae 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,8 @@ -# Duke +# TourPlanner -{Give product intro here} +If you are a tour agency who require customised data management solutions, **TourPlanner** is the program for you! +TourPlanner has an intuitive and easy-to-use CLI interface, one can easily add to, and update the database with client, flight, and tour details! +What are you waiting for? Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..5b5f5392a7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,960 @@ -# User Guide +# **TourPlanner User Guide** -## Introduction +
+TourPlanner is a desktop application meant for employees of travel agencies. Its main purpose is to manage clients, +flights, accommodations and client packages data, optimized for use via a Command Line Interface (CLI). If you can type +fast, this application can allow one to access relevant travel information faster than traditional GUI applications. -{Give a product intro} +
-## Quick Start +## Table of Contents -{Give steps to get started quickly} +### Taking Off - Introducing TourPlanner -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +- [User Guide - How to Use](#user-guide---how-to-use) +- [Quick Start](#quick-start) +- [Introduction to Data Types](#introduction-to-data-types) +- [Introduction to Data Fields](#introduction-to-data-fields) -## Features +### On Top Of The World - Features of TourPlanner -{Give detailed description of each feature} +#### Add/Cut Data Entries -### Adding a todo: `todo` -Adds a new item to the list of todo items. +- [Adding Data Types](#heavy_plus_sign-adding-data-types--add) + - [Adding Clients](#adding-clients-into-database-add--c) + - [Adding Tours](#adding-tours-into-database-add--t) + - [Adding Flights](#adding-flights-into-database-add--f) + - [Adding Client Packages](#adding-clientpackage-into-database-add--p) +- [Cutting Data Types](#scissors-cutting-data-types--cut) + - [Cutting Client Packages](#cut-client-package) + - [Cutting Clients/Tours/Flights](#cut-client--tour--flight) -Format: `todo n/TODO_NAME d/DEADLINE` +#### Querying the Database -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +- [Listing Data Types](#mag_right-listing-data-types-list) +- [Finding Data Types](#finding-data-types-find) + - [Finding Clients](#find-client) + - [Finding Tour/Flights](#find-tour--flight) +- [Sorting Data Types](#chart_with_upwards_trend-sorting-data-types-sort) + - [Sorting Clients](#sort-client) + - [Sorting Tours](#sort-tour) + - [Sorting Flights](#sort-flights) -Example of usage: +#### Miscellaneous -`todo n/Write the rest of the User Guide d/next week` +- [Viewing help](#viewing-helphelp) +- [Exiting TourPlanner](#exit-application-bye) -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +### Landing Gracefully - Summary -## FAQ +- [General Command Information](#general-command-information) +- [Supporting Command Information](#supporting-command-information) -**Q**: How do I transfer my data to another computer? +
-**A**: {your answer here} +## **User Guide - How to Use** -## Command Summary +
-{Give a 'cheat sheet' of commands here} +Like our application, TourPlanner's User Guide is **intuitive** to use, but **comprehensive** as well. Any doubts you +might have when using the application, we got you covered. -* Add todo `todo n/TODO_NAME d/DEADLINE` +**Navigate** across the User Guide with the [**Table of Contents**](#table-of-contents). Just click on the hyperlinks +associated with your query, and you will be redirected to the specific section that addresses your query. + +The contents in this user guide follows you through the runtime of the program, and is sequenced chronologically. + +If you require help with your set-up, look under the [**Taking off**](#taking-off---introducing-tourplanner) section. + +Details on how to use the features are given under the +[**On Top of The World**](#on-top-of-the-world---features-of-tourplanner) section. + +Lastly, for a summary of all the commands and prefixes, look under the +[**Landing Gracefully**](#landing-gracefully---summary) section. + +Happy to have you onboard TourPlanner! + +
+ +## **Quick Start** ## + +
+ +1. Ensure you have Java 11 installed in your Computer. + +2. Download the latest TourPlanner.jar from [here](https://github.com/AY2122S1-CS2113T-F11-3/tp/releases/tag/A-JarV2.1). + +3. Copy the file to the folder you want to use as the home folder for your database. + +4. Open a command window in that folder + +5. Run the command ```java -jar TourPlanner.jar``` to start the program + +6. Type the command in the command box and press Enter to execute + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Introduction to Data Types ## + +
+ +Throughout the user guide, you may observe that many commands have a +```[DATA_TYPE]``` parameter. This parameter is to be specified right after declaring the command to use. + +There are 4 data types that are stored in TourPlanner: + +* ```-c``` Clients +* ```-t``` Tours +* ```-f``` Flights +* ```-p``` Client Package + +:information_source: Client Package contains the client along with the tour and flight they have opted for. + +Examples of data types in commands: + +* ```add -t JPN /n Japan Basic Tour /p 1500 ``` calls for a tour to be added. +* ```list -p ``` calls for all available client packages to be listed out. + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Introduction to Data Fields + +
+ +Throughout the user guide, you may observe that many commands have a ```[DATA_FIELDS]``` parameter. +The ```[DATA_FIELDS]``` parameter represents the compulsory information fields that serves to describe the command in +more detail. + +Data fields can be viewed as supporting documents to the main command to be executed. + +One data field is represented in the following format: ```/PREFIX INFO``` + +These fields are *mandatory*. Let's say a client is added without his **name**, or **contact details**. Having these +empty fields reduces the utility of the program, since certain meaningful operations cannot be performed. Case in point, +one will not be able to query the client by name. + +These fields paint a complete picture of the different data types. Intuitively, clients have *key information fields* +such as **name**, **contact number** and **email**. + +Examples of data fields in commands: + +* Add client: + * ```/n NAME``` + * ```/cn CONTACT_NUMBER``` + * ```/m EMAIL``` + + +* Sort tours: + * ```/p``` - sorts tours by *price* + * ```/n``` - sorts tours by *name* + +*Note: TourPlanner does not support the use of special characters or numbers larger than 2 billion.* + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Viewing help:``help`` + +
+ +Shows a message with the link to this user guide. + +
+ +Format: ```help``` + +The following output will be shown: + +``` +add: Add information of all data types into the database. +Prefixes can be input in any order. +Add client: add -c CLIENT_ID /n NAME /cn CONTACT_NUM /m EMAIL +Add flight: add -f FLIGHT_ID /d DEPART_DESTINATION /r RETURN_DESTINATION +/dd DEPARTURE_DATETIME /rd RETURN_DATETIME +Add tour: add -t TOUR_ID /n DEPART_DESTINATION /p TOUR_PRICE +Add client package: add -p PACKAGE_ID /c CLIENT_ID /t TOUR_ID /f FLIGHT_ID + +list: Shows a list of all available entries of a specific data type, along with their respective fields. +List client: list -c +List flight: list -f +List tour: list -t +List client package: list -p + +cut: Deletes entry of a certain data type and all client packages corresponding to the entry. +Cut client: cut -c CLIENT_ID +Cut flight: cut -f FLIGHT_ID +Cut tour: cut -t TOUR_ID +Cut client package: cut -p PACKAGE_ID + +find: Finds specific entry of data type, returns the entry and other relevant information. +Find client: find -c CLIENT_NAME +Find flight: find -f FLIGHT_ID +Find tour: find -t TOUR_ID + +sort: Sorts entries in the data type based on the criteria. +Sort client: +Sort by id: sort -c /id +Sort by name: sort -c /n +Sort flight: +Sort by id: sort -f /id +Sort by departure date: sort -f /d +Sort by return date: sort -f /r +Sort tour: +Sort by id: sort -t /id +Sort by name: sort -t /n +Sort by price: sort -t /p + +bye: Exits the program. +``` + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Adding / Cutting Data Types + +
+ +:information_source:Please refer to Introduction to Data Types and Introduction to Data Fields on the +purpose and syntax of ```DATA_TYPES``` and ```DATA_FIELDS```. + +
+ +## :heavy_plus_sign: Adding data types: ```add``` + +You are able to add information of all data types into the database, specified by mandatory fields to enter for each +entry. + +Format: ```add [DATA_TYPE] [DATA_FIELDS]``` + +
+ +### Adding clients into database: ```add -c``` + +Format: ```add [DATA_TYPE] [CLIENT_ID] [DATA_FIELDS]``` + +These are your existing or potential customers. In this industry of tour planning, your livelihoods depend on them. + +Mandatory data fields: + +* Client ID - ```CLIENT_ID``` +* Client name - ```/n NAME``` +* Client contact number - ```/cn CONTACT_NUMBER``` +* Client email address - ```/m EMAIL``` + +:exclamation: Note that the given **contact number** should ideally contain only eight numbers from _0 to 9_. If there +are extra inputs, TourPlanner will return a **warning** message. + +Example: + +* `add -c c001 /n Bo Tuan /cn 91234567 /m bobotea@gmail.com` + +Adds *Bo Tuan*, contact number *91234567* and email +*bobotea@gmail.com*, as client ID *c001*. + +* `add -c c002 /n Wayne /m winnie@gmail.com /cn 92468024` + +Adds *Wayne*, contact number *92468024* and email +*winnie@gmail.com*, as client ID *c002*. + +
+ +### Adding flights into database: ```add -f``` + +Format: ```add [DATA_TYPE] [FLIGHT_ID] [DATA_FIELDS]``` + +Mandatory data fields: + +* Flight ID - ```FLIGHT_ID``` +* Flight departure destination - ```/d DEPARTURE_DESTINATION``` +* Flight return destination - ```/r RETURN_DESTINATION``` +* Flight departure date and time - ```/dd DEPARTURE_TIME``` +* Flight return date and time - ```/rd RETURN_TIME``` + +:exclamation: Note that the given date and times should be of the format: ```d/M/yy HH:mm```. TourPlanner will +**reject** any entry that violates this format rule. + +Example: + +* `add -f SQ-JPN1 /d Japan /r Singapore /dd 29/10/21 13:00 /rd 5/11/21 02:00` + +Add flights from _Singapore_ to _Japan_ +and back, departing from Singapore at _1pm, 29 Oct 2021_ and returning to Singapore at _5 Nov 2021, 2am_. Stored in the +database as ID: _SQ-JPN1_. + +* `add -f SQ-KOR1 /d Korea /dd 2/5/22 11:00 /r Singapore /rd 15/5/22 23:00` + +Add flights from _Singapore_ to _Korea_ +and back, departing from Korea at _11am, 2 May 2022_ and returning to Singapore at _15 May 2022, 11pm_. Stored in the +database as ID: _SQ-KOR1_. + +:exclamation: Note that the given **date-times** should be logically coherent +(i.e. arrival at destination should be before return). TourPlanner will reject erroneous inputs with an **error** +message. + +
+ +### Adding tours into database: ```add -t``` + +Format: ```add [DATA_TYPE] [TOUR_ID] [DATA_FIELDS]``` + +Mandatory data fields: + +* Tour ID - ```TOUR_ID``` +* Tour name - ```/n TOUR_NAME``` +* Tour price - ```/p TOUR_PRICE``` + +:exclamation: Note that the given **price** should be a *positive numerical value* (up to 2 decimal places). + +Example: + +* `add -t t001 /n AustralianRomance /p 1500` + +Adds _AustralianRomance_, which costs _$1500_, as tour _t001_. + +* `add -t t002 /p 2300 /n KoreanWonderland` + +Adds _KoreanWonderland_, which costs _$2300_, as tour _t002_. + +
+ +### Adding ClientPackage into database: ```add -p``` + +ClientPackage acts as a master list of details for each client, to have an overview of the client upon querying. Each +package represents one client, with his or her respective tour and flight details. + +Format: ```add [DATA_TYPE] [PACKAGE_ID] [DATA_FIELDS]``` + +Mandatory data fields: + +* Package ID - ```PACKAGE_ID``` +* Client ID - ```/c CLIENT_ID``` +* Flight ID - ```/f FLIGHT_ID``` +* Tour ID - ```/t TOUR_ID``` + +:exclamation: Note that the respective client, flight and tour must **exist** in order for package to be added. + +Example: + +* `add -p p001 /c c001 /f SQ-JPN1 /t t001` + +Adds Client _c001_, Flight _SQ-JPN1_, and Tour _t001_ into an overall package _p001_. + +* `add -p p002 /c c002 /t t002 /f SQ-KOR1 ` + +Adds Client _c002_, Flight _SQ-KOR1_, and Tour _t002_ into an overall package _p002_. + +
+ +[**Return to Table of Contents**](#table-of-contents) + +
+ +## :scissors: Cutting data types: ```cut``` + +Deletes entry of a certain data type. + +
+ +### Cut Client Package + +Deletes the client package from the list of packages. + +Format: `cut -p DATA_ID` + +* Deletes the client package with specified DATA_ID. + +
+Examples: + +* `cut -p p001` deletes client package with id 'p001', where 'p001' contains client 'c001', tour 'JPN1' and flight ' + SQ-JPN' + +An output of this format will be shown: + +``` +Client has been deleted: +Package ID: p001 + +Client: +Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com + +Tour: +Name: Japan Basic Tour +Code: JPN1 +Price per pax: $1500.00 + +Flight: +Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/21 18:00 +Return Flight: SG, 21/10/21 03:00 +``` + +
+ +### Cut Client / Tour / Flight + +Format: `cut [DATA_TYPE] DATA_ID` + +* Deletes the entry of DATA_TYPE with specified DATA_ID. +* Deletes all client packages that contains the specific entry + +
+Examples: + +* `cut -c c001` deletes client with id 'c001', and all client packages that contains client 'c001'. + +An output of this format will be shown: + +Deleting client: + +``` +Client has been deleted: +Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com +``` + +Deleting all related client packages: + +``` +Client Package has been deleted: +Package ID: p001 + +Client: +Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com + +Tour: +Name: Japan Basic Tour +Code: JPN1 +Price per pax: $1500.00 + +Flight: +Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/21 18:00 +Return Flight: SG, 21/10/21 03:00 +``` + +
+ +* `cut -t KOR` deletes tour with id 'KOR', and all client packages that contains tour 'KOR'. + +An output of this format will be shown: + +Deleting tour: + +``` +Tour has been deleted: +Name: Korea Cultural Tour +Code: KOR +Price per pax: $3000.00 +``` + +Deleting all related client packages: + +``` +Client Package has been deleted: +Package ID: p001 + +Client: +Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com + +Tour: +Name: Korea Cultural Tour +Code: KOR +Price per pax: $3000.00 + +Flight: +Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/21 18:00 +Return Flight: SG, 21/10/21 03:00 +``` + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## **Querying Data Types** + +
+ +The user is also able to view all entries of a specific data type, as well as find specific entrie(s) based their codes. + +
+ +:information_source: Please refer to Introduction to Data Types on the syntax of ```[DATA_TYPE]``` + +
+ +## :mag_right: Listing data types: ```list``` + +Shows a list of all available entries of a specific data type, along with their respective fields. + +Format: ```list [DATA_TYPE]``` + +
+Examples: + +* ```list -c``` lists out all available client entries. + +An output of this format will be shown: + +``` +Here is a list of all clients: +1. Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com + +2. Client ID: c002 +Name: Betty +Contact Number: 12223444 +Email: betty@mail.com + +Total Clients: 2 +``` + +
+ +* ```list -f``` lists out all available flight entries. + +An output of this format will be shown: + +``` +Here is a list of all flights: +1. Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/2021 18:00 +Return Flight: SG, 21/10/2021 03:00 + +2. Flight ID: SQ-KOR +Departure Flight: KOR, 23/10/2021 18:00 +Return Flight: SG, 30/10/2021 03:00 + +Total Flights: 2 +``` + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Finding data types: ```find``` + +### Find client + +Finds specific client(s) based on a particular substring. It will return client(s) if their name contains the substring. +Note that the substring is case-insensitive. In addition, it will return the found clients' subscribed packages. + +
+ +Format: ```find -c [SUBSTRING]``` + +Examples: + +* ```find -c Adam``` finds clients that contain 'Adam' in their name. + +An output of this format will be shown: + +``` +This is the client(s) that matches your search +1. Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com + +Package ID: p123 + +Client: +Client ID: c001 +Name: Adam +Contact Number: 93338333 +Email: adam@mail.com + +Tour: +Name: australiaromance +Code: aus1369 +Price per pax: $1300.00 + +Flight: +Flight ID: MSIA-KOR1 +Departure Flight: MSIA, 29/10/21 12:00 +Return Flight: KOR, 24/6/21 02:00 +``` + +In addition, ```find -c ad``` will yield the same results, since "ad" is contained in "Adam". +
+ +### Find tour / flight + +Finds a specific entry based on a particular code. In addition, for tours and flights, it will show the names of the +subscriber(s) / passenger(s) who are assigned to them respectively. + +
+ +Format: ```find [DATA_TYPE] [ID]``` + +Examples: + +* ```find -t JPN``` finds a particular tour with code 'JPN'. It also shows the clients who are subscribed to said tour. + +An output of this format will be shown: + +``` +This is the tour that matches your search +Name: Japan Basic Tour +Code: JPN +Price per pax: $1500.00 + + +Subscribed Clients: +Adam + +Total Subscribed Clients: 1 +``` + +
+ +* ```find -f SQ-JPN``` finds a particular flight with code 'SQ-JPN'. It also shows the clients who are passengers to + said flight. + +An output of this format will be shown: + +``` +This is the flight that matches your search +Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/2021 18:00 +Return Flight: SG, 21/10/2021 03:00 + + +Passengers: +Betty + +Total Passengers: 1 +``` + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## :chart_with_upwards_trend: Sorting data types: ```sort``` + +Sort a specific data type based on a particular ```[FILTER]```. It will return all client(s) in ascending alphabetical +order. the possible values of ```[FILTER]``` varies between data types. + +Format: ```sort [DATA_TYPE] [FILTER]``` + +
+ +### Sort client + +The possible values of ```[FILTER]``` are: + +* ```/n``` to sort by client name +* ```/id``` to sort by client id + +
+ +Examples: + +* ```sort -c /n``` sorts the clients by client name alphabetically. + +An output of this format will be shown: + +``` +Sorted by client name alphabetically +1. Client ID: c001 +Name: Bo Tuan +Contact Number: 93338333 +Email: borangutuan@mail.com + +2. Client ID: c004 +Name: ChengXu +Contact Number: 10101010 +Email: demonshaha@mail.com + +3. Client ID: c002 +Name: Sam +Contact Number: 12223444 +Email: sam@mail.com + +4. Client ID: c003 +Name: Wayne +Contact Number: 56667888 +Email: wendy@mail.com +``` + +
+ +* ```sort -c /id``` sorts the clients by id alphabetically. + +An output of this format will be shown: + +``` +orted by client id alphabetically +1. Client ID: c001 +Name: Bo Tuan +Contact Number: 93338333 +Email: borangutuan@mail.com + +2. Client ID: c002 +Name: Sam +Contact Number: 12223444 +Email: sam@mail.com + +3. Client ID: c003 +Name: Wayne +Contact Number: 56667888 +Email: wendy@mail.com + +4. Client ID: c004 +Name: ChengXu +Contact Number: 10101010 +``` + +
+ +### Sort tour + +Sort tour(s) based on a particular ```[FILTER]```. It will return all tour(s) in ascending alphabetical order. + +The possible values of ```[FILTER]``` are: + +* ```/id``` to sort by tour id +* ```/p``` to sort by price + +
+ +Examples: + +* ```sort -t /id``` sorts the tours by tour id alphabetically. + +An output of this format will be shown: + +``` +Sorted by tour id alphabetically +1. Name: Japan Basic Tour +Code: JPN1 +Price per pax: $1500.00 + +2. Name: Japan Food Tour +Code: JPN2 +Price per pax: $4000.00 + +3. Name: Korea Cultural Tour +Code: KOR +Price per pax: $3000.00 + +4. Name: Zimbabwe Tour +Code: ZWM +Price per pax: $1700.00 +``` + +
+ +* ```sort -t /n``` sorts the tours by tour name alphabetically. + +``` +Sorted by tour name alphabetically +1. Name: Japan Basic Tour +Code: JPN1 +Price per pax: $1500.00 + +2. Name: Japan Food Tour +Code: JPN2 +Price per pax: $4000.00 + +3. Name: Korea Cultural Tour +Code: KOR +Price per pax: $3000.00 + +4. Name: Zimbabwe Tour +Code: ZWM +Price per pax: $1700.00 +``` + +
+ +* ```sort -t /p``` sorts the tours by price in ascending order. + +An output of this format will be shown: + +``` +Sorted by tour id alphabetically +1. Name: Japan Basic Tour +Code: JPN1 +Price per pax: $1500.00 + +2. Name: Zimbabwe Tour +Code: ZWM +Price per pax: $1700.00 + +3. Name: Korea Cultural Tour +Code: KOR +Price per pax: $3000.00 + +4. Name: Japan Food Tour +Code: JPN2 +Price per pax: $4000.00 +``` + +
+ +### Sort flights + +Sort flight(s) based on a particular ```[FILTER]```. It will return all flight(s) in ascending alphabetical order. + +The possible values of ```[FILTER]``` are: + +* ```/id``` to sort by flight id +* ```/d``` to sort by departing flight times +* ```/r``` to sort by returning flight times + +
+ +Examples: + +* ```sort -f /id``` sorts the flights by flight id alphabetically. + +An output of this format will be shown: + +``` +Sorted by departing flight times +Sorted by flight id alphabetically +1. Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/21 18:00 +Return Flight: SG, 21/10/21 03:00 + +2. Flight ID: SQ-KOR +Departure Flight: KOR, 23/10/21 08:00 +Return Flight: SG, 30/11/21 03:00 + +3. Flight ID: SQ-ZWM +Departure Flight: ZWM, 5/11/21 09:00 +Return Flight: SG, 7/11/21 15:00 +``` + +
+ +* ```sort -f /d``` sorts the flights by departing returning flight times. + +An output of this format will be shown: + +``` +Sorted by departing flight times +1. Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/21 18:00 +Return Flight: SG, 21/10/21 03:00 + +2. Flight ID: SQ-KOR +Departure Flight: KOR, 23/10/21 08:00 +Return Flight: SG, 30/11/21 03:00 + +3. Flight ID: SQ-ZWM +Departure Flight: ZWM, 5/11/21 09:00 +Return Flight: SG, 7/11/21 15:00 +``` + +
+ +* ```sort -f /r``` sorts the flights by ascending returning flight times. + +An output of this format will be shown: + +``` +Sorted by returning flight times +1. Flight ID: SQ-JPN +Departure Flight: JPN, 20/10/21 18:00 +Return Flight: SG, 21/10/21 03:00 + +2. Flight ID: SQ-ZWM +Departure Flight: ZWM, 5/11/21 09:00 +Return Flight: SG, 7/11/21 15:00 + +3. Flight ID: SQ-KOR +Departure Flight: KOR, 23/10/21 08:00 +Return Flight: SG, 30/11/21 03:00 +``` + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Exit application: ```bye``` + +
+ +Exits the application. + +
+ +Format: ```bye``` + +The following output will be shown: + +```Thanks for using TourPlanner. Goodbye!``` + +
+ +[**Return to Table of Contents**](#table-of-contents) + +## Saving Data + +
+ +In TourPlanner, all data (clients, tours, flights, client packages) are saved in the hard disk +ONLY after the user exits the application with the ```bye``` command. + +
+ +## **Command Summary** + +
+ +### **General Command Information** + +Command | Format +------------ | ------------- +```help``` | ```help``` +```add``` | ```add [DATA_TYPE] [ID] [DATA_FIELDS] ``` +```cut``` | ```cut [DATA_TYPE] [ID]``` +```list``` | ```list [DATA_TYPE]``` +```find``` | Clients: ```find [DATA_TYPE] [SUBSTRING]```
Tour / Flights: ```find [DATA_TYPE] [ID]``` +```sort``` | ```sort [DATA_TYPE] [FILTER]``` +```bye``` | ```bye``` + +
+ +### **Supporting Command Information** + +Command | Data Type | Data Fields +------------ | ------------- |------------- +```help``` | - | - +```add``` Clients | `-c` | `/id`, `/n`, `/cn`, `/m` +```add``` Flights | `-f` |`/id`, `/d`, `/r`, `/dd`, `/rd` +```add``` Tours | `-t` |`/id`, `/n`, `/p` +```add``` Packages | `-p` |`/id`, `/c`, `/f`, `/t` +```cut``` | `-c`
`-f`
`-t`
`-p`| - +```list``` | `-c`
`-f`
`-t`
`-p` | - +```find``` | `-c`
`-f`
`-t` | - +```sort``` Clients | `-c` | `/id`, `/n` +```sort``` Flights | `-f` |`/id`,`/d`, `/r` +```sort``` Tours | `-t` |`/id`,`/n`, `/p` +```bye``` | - | - + +
+ +[**Return to Table of Contents**](#table-of-contents) diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..ce1b35c5da --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,2 @@ +theme: jekyll-theme-time-machine +gems: ["jemoji"] diff --git a/docs/team/Demonshaha.md b/docs/team/Demonshaha.md new file mode 100644 index 0000000000..d9b76fef29 --- /dev/null +++ b/docs/team/Demonshaha.md @@ -0,0 +1,55 @@ +# Zhou Chengxu - Project Portfolio Page + +## Overview - TourPlanner + +TourPlanner is a data management solution, for tour agencies. The user interacts with it using a CLI, to update a +database with flights, tours, clients, and combine them into packages. + +It is written in Java, and has about 7kLoC. + +Given below are the contributions to the project. + +* Feature: **Storage** - Added 4 class files to let the program be able to load and save the files to data folder(Because my vpn cannot work, bobowoo2468 helped + me to push) + + * What it does: Allows the program to load the clients, tours, flights, and clientpackages that user added before, and save the data they add every time. + + * Justification: This function is essential, because Touragencies need to save all the data they have added in, without the storage function, this program + cannot save the data users have added every time the user close the program, all of the data will disappear. + + * Highlight: In order to make the program convinient for the user, the clients, tours, flights, clientpakcages will be stored to 4 different files in data folder. + +* Feature: **Add's exception** --Add exceptions to add commantd which help to check human error + + * What it does: Help to check if the information input by the user is correct. + Including: + 1. Email checking + 2. Contact number checking + 3. Price checking + 4. Departure date and return date checking + + * Justification: This can help to check human errors which can improve the effciency of users and this is one of the advantage of our program. For example, if the employee + of the touragencies input the departure time and return time reversely, the program can warning him or her. + + * Highlights: For date checking, it implements java.data class to check the if the departure date and return dates are illegal and if they are reverse. + +* Task: **Storage** methods and code structure +* Task: **Modify clientlist, tourlist, fightlist, clientpackagelist classes** in order to implement storage +* Task: **Modify** toString methods + +
+ +Code contributed: : [Reposense link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=demonshaha&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=Demonshaha&tabRepo=AY2122S1-CS2113T-F11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +
+ +* Documentation + * User Guide: + * Added sort commands guideline + + * Developers Guide: + * Added implementation details for Storage + +* Coummunity: + * Reviewed and approved PRs frequently + * Give suggestions on codes. diff --git a/docs/team/YipWayne.md b/docs/team/YipWayne.md new file mode 100644 index 0000000000..6087e367fd --- /dev/null +++ b/docs/team/YipWayne.md @@ -0,0 +1,64 @@ +# Yip Wayne - Project Portfolio Page + +## Overview - TourPlanner + +TourPlanner is a data management solution, for tour agencies. The user interacts with it using a CLI, to update a +database with flights, tours, clients, and combine them into packages. + +It is written in Java, and has about 7 kLoC. + +Given below are my contributions to the project. + +* Feature: **LIST** - Added the ability to **list** clients, flights, tours and client in the database. + * What it does: Allows the user to list a certain data type by typing into the CLI input, in the format of: + `list [DATA_TYPE]` + + * Justification: This is the functionality that is core to the program, as it allows the travel agency employee to view all the clients, + tours, flights and client packages at a quick glance. + + * Highlights: The list feature required the elements of a certain data type to be printed incrementally. Since each data + type has its own attributes, a custom method to print each type of data had to be used. + +* Feature: **FIND** - Added the ability to **find** clients, flights, tours and client in the database. + * What it does: Allows the user to find a certain data type by typing into the CLI input, in the format of: + `list [DATA_TYPE] [SUBSTRING]` for clients, and `list [DATA_TYPE] [ID]` for the other tours and flights. + + * Justification: This is the functionality that is core to the program. In the event that there is a lot of data within the database, this + function allows the travel agency employee to quickly find the specific entry that they are looking for. + + * Highlights: The find feature was different between the data types. Hence, separate implementations were required. + + * Highlights: For finding tours and flights, it also printed out the relevant clients that subscribed to them. This implementation + was challenging as required interaction with other object classes, such as the ClientPackage class. + +* Task: General implementation of Ui class +* Task: General implementation of Tour and TourList classes +* Task: Implementation of list and find-related Parser exception functions + + +* Code contributed: : [Reposense link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=YipWayne&tabRepo=AY2122S1-CS2113T-F11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&reverseAuthorshipOrder=true) + + +* Project Management: + * Created general idea and structure for this project. + * Created issues and milestones for Milestone v1.0 and some of 2.0 + * Approved and reviewed PRs frequently. + + +* Documentation: + * User Guide: + * Added documentation for the features: `list` amd `find` + * Added `Quick Start` and `Introduction to Data Types` + * Added `Command summary` table for UG + * Did clean-up, make sure structure was consistent among members + + * Developers Guide: + * Added Table of Contents (TOC) and navigation for DG + * Added `Architecture` section along with relevant UML diagrams + * Added documentation for the feature `find`, along with relevant UML diagrams + * Addeed `Getting Started` and `User Stories` section + + +* Community: + * Reviewed and approved PRs frequently + * Made suggestions for the other teams in the class during tutorial diff --git a/docs/team/bobowoo2468.md b/docs/team/bobowoo2468.md new file mode 100644 index 0000000000..a02000a9b9 --- /dev/null +++ b/docs/team/bobowoo2468.md @@ -0,0 +1,66 @@ +# Woo Bo Tuan - Project Portfolio Page + +## Overview - TourPlanner + +TourPlanner is a data management solution, for tour agencies. The user interacts with it using a CLI, to update a +database with flights, tours, clients, and combine them into packages. + +It is written in Java, and has about 7 kLoC. + +Given below are the contributions to the project. + +* Feature: **ADD** - Added the ability to **add** clients, flights, tours into the database. + * What it does: Allows the user to add by typing into the CLI input, in the format of: + `add -[IDENTIFIER] /PREFIX1 DATA1 /PREFIX2 DATA2 ...` + + * Justification: This is the functionality that is core to the program, as it allows the input of data into + database. As such, many exceptions have to be handled, along with catchers for erroneous inputs. + + * Highlights: The add feature implemented (including parsing of user input) allows for greater flexibility during + input. The prefixes can be arranged in any order during adding, and additional white spaces in the command will be + trimmed. + + * Highlights: The exception handling for add is rather extensive. First, erroneous inputs such as missing + identifier, missing prefixes (hence data fields), duplicated prefixes, missing data fields will all be flagged as + an error. Erroneous or illogical entries will either be flagged out as a **WARNING** or an **ERROR**, depending on + its severity. + +* Feature: **SORT** - Added the ability to **sort** clients, flights, tours in the database. + * What it does: Allows the user to sort, in the format of: + `sort -[IDENTIFIER] /FILTER` + + * Justification: This is an additional functionality for tour agency planners to be able to perform meaningful + operations with data. For example, sorting by ascending price can allow tour agency planners to have a + side-by-side comparison of the budget of tours, from the least expensive to the most expensive tours. + + * Highlights: Sorting is mostly implemented with using the natural sorting of String or Float. Sorting for local date-time requires + the need for a defined `Comparator`. The program has allowed for sorting regardless of duplicates in the data. + +* Task: General **Parser** methods and code structure +* Task: **Refactor code** to follow OOP guidelines +* Task: **Linking Storage Class** to Main, and ObjectLists + +* Code + contributed: [Reposense link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=bobowoo2468&tabRepo=AY2122S1-CS2113T-F11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&reverseAuthorshipOrder=true) + +* Project Management: + * Managed releases for v2.0 on GitHub + * Created the *developers* team and *team repo*, including settings for GitHub workflow + * Set-up GHPages for `docs` (containing UG and DG) + * Managed *issues*, *assigned* work and *tracked* completion for Milestone v2.0 + * Approved and reviewed PRs frequently. + +* Documentation: + * User Guide: + * Added Table of Contents (TOC) and navigation for UG + * Added documentation for the features: `add` + * Added `How-to-use` for UG + * Added `Supporting Command Information` table for UG + + * Developers Guide: + * Added implementation details for the `add` feature + * Added implementation details for the `Parser` feature + +* Community: + * Reviewed and approved PRs frequently + * Made suggestions for the other teams in the class during tutorial 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/swongts.md b/docs/team/swongts.md new file mode 100644 index 0000000000..0cb4d4b1d0 --- /dev/null +++ b/docs/team/swongts.md @@ -0,0 +1,53 @@ +# Samantha Wong - Project Portfolio Page + +## Overview: TourPlanner +**TourPlanner** is a desktop application meant for employees of travel agencies. +Its main purpose is to manage clients, flights, accommodations and client packages data, optimized for use via a Command Line Interface (CLI). + +It is written in Java, and has about 7 kLoC. + +### Summary of Contributions +**Code contributed**: [RepoSense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=F11&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=swongts&tabRepo=AY2122S1-CS2113T-F11-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +**Enhancements implemented**: +* `ClientPackage` and `ClientPackageList` classes + * What it does: Stores the related `Client`, `Flight` and `Tour` into one `ClientPackage`. + * Justification: This feature is crucial to TourPlanner and serves as a link between these classes. + * Highlights: To make it easier to use and more efficient for commands like `find` and `cut` to use, +`ClientPackage` stores the actual `Client`, `Flight` and `Tour` objects instead of simply using a reference String id. + * Highlights: Created methods to get an ArrayList of `ClientPackage` based on the `Client` / `Flight` / `Tour` which +were used across a few features. + +* `AddClientPackageCommand` + * What it does: Allows user to create the `ClientPackage` using the client, flight and tour id. + * Highlights: The command searches for the ids with the respective `ObjectList` to find the +`Object` (`Client`, `Tour`, `Flight`) to store into `ClientPackage`. + +* `CutCommand` for `Client`, `Tour`, `Flight` and `ClientPackage` + * What it does: Deletes the specific `Object` from its respective `ObjectList`. + * Justification: Crucial feature for the application to be functional. + * Highlights: When the `Client` / `Tour` / `Flight` is deleted, the corresponding `ClientPackage` which contains the specific +`Object` must also be deleted from the `ClientPackageList`. This is so that it wouldn't be incoherent that the `ClientPackage` +contains an `Object` which no longer exists. + +* `HelpCommand` + * What it does: Lists all available commands. + * Justification: Allows user to have an overview of how to use the commands conveniently. + +**Contributions to documentation**: +* User guide: + * Added `cut` feature +* Developer guide: + * Added diagrams and explanations for `cut`, `sort`, `list` feature and `Command` class + * Added Appendix D and E + +**Contributions to team-based tasks**: +* Manage issue tracker: In charge of issue tracker for `v2.1`, helped with labels for `v1.0`. +* Release management: In charge of release for `v1.0`. +* General code enhancement: Helped look out for code quality, and refactor other's codes to ensure SLAP +(e.g. `TourPlanner`). + +**Review/mentoring contributions**: +* Major bug fixes to ClientPackageStorage as the client packages weren't loading properly from storage. +* Frequently reviewed and approved pull requests. +* Helped to proof-read Developer's Guide and link everyone's parts to ensure it sounds coherent. \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..3c45a3b725 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.TourPlanner + diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/seedu/duke/Parser.java b/src/main/java/seedu/duke/Parser.java new file mode 100644 index 0000000000..afb4b34d0a --- /dev/null +++ b/src/main/java/seedu/duke/Parser.java @@ -0,0 +1,756 @@ +package seedu.duke; + +import seedu.duke.commands.ByeCommand; +import seedu.duke.commands.Command; +import seedu.duke.commands.HelpCommand; +import seedu.duke.commands.clientpackages.AddClientPackageCommand; +import seedu.duke.commands.clientpackages.CutClientPackageCommand; +import seedu.duke.commands.clientpackages.ListClientPackageCommand; +import seedu.duke.commands.clients.AddClientCommand; +import seedu.duke.commands.clients.CutClientCommand; +import seedu.duke.commands.clients.FindClientCommand; +import seedu.duke.commands.clients.ListClientCommand; +import seedu.duke.commands.clients.SortClientCommand; +import seedu.duke.commands.flights.AddFlightCommand; +import seedu.duke.commands.flights.CutFlightCommand; +import seedu.duke.commands.flights.FindFlightCommand; +import seedu.duke.commands.flights.ListFlightCommand; +import seedu.duke.commands.flights.SortFlightCommand; +import seedu.duke.commands.tours.AddTourCommand; +import seedu.duke.commands.tours.CutTourCommand; +import seedu.duke.commands.tours.FindTourCommand; +import seedu.duke.commands.tours.ListTourCommand; +import seedu.duke.commands.tours.SortTourCommand; +import seedu.duke.data.Client; +import seedu.duke.data.Flight; +import seedu.duke.data.Tour; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Sense-makes the inputs given and distributes the information to other parts of the program. + */ +public class Parser { + + private static final String CLIENT_IDENTIFIER = "-c"; + private static final String TOUR_IDENTIFIER = "-t"; + private static final String FLIGHT_IDENTIFIER = "-f"; + private static final String PACKAGE_IDENTIFIER = "-p"; + + private static final String PACKAGE_CLIENT_PREFIX = "/c"; + private static final String PACKAGE_TOUR_PREFIX = "/t"; + private static final String PACKAGE_FLIGHT_PREFIX = "/f"; + + private static final String FLIGHT_DEPARTURE_PREFIX = "/d"; + private static final String FLIGHT_RETURN_PREFIX = "/r"; + private static final String FLIGHT_DEPARTURE_DATE_PREFIX = "/dd"; + private static final String FLIGHT_RETURN_DATE_PREFIX = "/rd"; + public static final String TOUR_NAME_PREFIX = "/n"; + public static final String TOUR_PRICE_PREFIX = "/p"; + + public static final String CLIENT_NAME_PREFIX = "/n"; + public static final String CLIENT_CONTACT_NUMBER_PREFIX = "/cn"; + public static final String CLIENT_EMAIL_PREFIX = "/m"; + + public static final String ERROR_INVALID_INPUT = "ERROR: TourPlanner cannot understand the command! " + + "Please enter a valid command.\n" + + "Type 'help' to view a list of commands available for use."; + public static final String ERROR_MISSING_IDENTIFIER = + "ERROR: Missing/wrong command filter used! Please enter a command with this format: COMMAND -FILTER DATA \n" + + "Example: find -t TOUR_ID"; + public static final String ERROR_DUPLICATE_PREFIXES = "ERROR: TourPlanner has detected duplicate prefixes!"; + public static final String ERROR_MISSING_PREFIXES + = "ERROR: TourPlanner has detected missing/wrong prefixes! Did you miss out some fields?"; + public static final String ERROR_MISSING_ID = "Missing id that you wish to cut from! Please try again."; + public static final String ERROR_MISSING_FIELDS = "ERROR: TourPlanner has detected empty fields! " + + "Please enter all fields!"; + public static final String ERROR_FLIGHT_TIME_FORMAT = "ERROR: TourPlanner detected wrong date-time entry " + + "formatting! \n Please input your date-times with the following format: d/M/yy HH:mm"; + public static final String ERROR_FLIGHT_TIME_INVERT = "ERROR: TourPlanner detected erroneous flight time entry! \n"; + public static final String ERROR_PRICE_FORMAT = "ERROR: TourPlanner has detected erroneous price entry! " + + "Only include numbers (includes decimal)/one decimal point!"; + + public static final String WARNING_EXTRA_INPUT = "WARNING: Extra input! Refrain from doing so."; + public static final String WARNING_EMAIL_FORMAT_WRONG = "WARNING: TourPlanner has detected possible " + + "erroneous email! \n"; + public static final String WARNING_CONTACT_NUMBER_WRONG = + "WARNING: TourPlanner has detected possible erroneous contact number! (characters other than numbers) \n"; + public static final String WARNING_SHORT_CONTACT_NUMBER = + "WARNING: TourPlanner detected that the given contact number is too short (< 8)! \n"; + private static final String WARNING_LONG_CONTACT_NUMBER = + "WARNING: TourPlanner detected that the given contact number is too long (> 8)! \n"; + private static final String WARNING_PRICE_TOO_MANY_DECIMAL = "WARNING: TourPlanner has detected " + + "erroneous price entry! Price has too many decimal places!"; + + public static final int IDENTIFIER_INDEX = 0; + public static final int ARGS_INDEX = 1; + public static final int COMMAND_INDEX = 0; + public static final int PARAMS_INDEX = 1; + public static final int MAX_VALUE_ARRAY_SIZE = 5; + + //Client has three prefixes: /n, /cn, /m + private static final int CLIENT_PREFIX_NUM = 4; + + //Flight has four prefixes: /d, /r, /dd, /rd + private static final int FLIGHT_PREFIX_NUM = 5; + + //Tour has three prefixes: /n, /p + private static final int TOUR_PREFIX_NUM = 3; + + // Package has four prefixes: /c, /f, /t + private static final int PACKAGE_PREFIX_NUM = 4; + + public static final String EMPTY_STRING = ""; + + /** + * Parses user's input into command to execute. + * + * @param input full user's input string + * @return the command parsed from user's input + * @throws TourPlannerException if there are missing fields, duplicated prefixes, missing prefixes and other general + * parsing errors + */ + public static Command parse(String input) throws TourPlannerException { + String[] commandAndParams = splitCommandString(input); + String command = commandAndParams[COMMAND_INDEX]; + String params = commandAndParams[PARAMS_INDEX]; + + switch (command) { + case "add": + return parseAdd(params); + case "bye": + return parseBye(params); + case "cut": + return parseCut(params); + case "find": + return parseFind(params); + case "help": + return parseHelp(params); + case "list": + return parseList(params); + case "sort": + return parseSort(params); + default: + throw new TourPlannerException(ERROR_INVALID_INPUT); + } + } + + /** + * Separates user input into 2 based on the separator. + * If the value of the String after the separator is null, + * the second value of the String array will be returned as "". + * + * @param input full user's input string + * @return the array containing command and argument/params strings + */ + private static String[] splitCommandString(String input) { + String[] split = input.trim().split(" ", 2); + return split.length == 2 ? split : new String[]{split[0], ""}; + } + + /** + * Extracts the indexes for prefixes and put into a map that sorts the list by the natural ordering of the keys. + * + * @param argString full user's argument string + * @return the treemap with prefix index as the key and the corresponding prefix as the value + * @throws TourPlannerException if there are missing fields or missing prefixes + */ + private static TreeMap extractPrefixIndexes(String argString, String identifier) + throws TourPlannerException { + + List prefixes = generatePrefixesFromIdentifier(identifier); + if (!containAllPrefixes(argString, prefixes)) { + throw new TourPlannerException(ERROR_MISSING_PREFIXES); + } + + TreeMap prefixIndexes = new TreeMap<>(); + prefixIndexes.put(0, ""); + prefixes.forEach((prefix) -> { + int prefixIndex = argString.indexOf(prefix); + prefixIndexes.put(prefixIndex, prefix); + }); + + int expectedNumberOfPrefixIndexes = generateExpectedNumberOfPrefixIndexes(identifier); + boolean hasUniquePrefixIndexes = prefixIndexes.size() == expectedNumberOfPrefixIndexes; + + if (!hasUniquePrefixIndexes) { + throw new TourPlannerException(ERROR_MISSING_FIELDS); + } + return prefixIndexes; + } + + /** + * Returns the number of prefixes for the specific data type, determined by the identifier. + * + * @param identifier specific identifier to determine specific data type (client, flight, tour, package) + * @return the number of prefixes determined by the identifier + */ + private static int generateExpectedNumberOfPrefixIndexes(String identifier) { + int numberOfPrefixIndexes = 0; + switch (identifier) { + case CLIENT_IDENTIFIER: + numberOfPrefixIndexes = CLIENT_PREFIX_NUM; + break; + case PACKAGE_IDENTIFIER: + numberOfPrefixIndexes = PACKAGE_PREFIX_NUM; + break; + case TOUR_IDENTIFIER: + numberOfPrefixIndexes = TOUR_PREFIX_NUM; + break; + case FLIGHT_IDENTIFIER: + numberOfPrefixIndexes = FLIGHT_PREFIX_NUM; + break; + default: + break; + } + return numberOfPrefixIndexes; + } + + /** + * Extract values from user's input command in a sorted fashion. + * + * @param prefixIndexes the treemap with prefix index as the key and the corresponding prefix as the value + * @param argString full user's argument string + * @param identifier specific identifier to determine specific data type (client, flight, tour, package) + * @return the array containing all extracted values in a sorted fashion + * @throws TourPlannerException if there are duplicate prefixes found + */ + private static ArrayList extractValuesIntoArray(TreeMap prefixIndexes, + String argString, String identifier) + throws TourPlannerException { + ArrayList extractedValues = new ArrayList<>(); + initialiseArrayList(extractedValues); + ArrayList indexes = new ArrayList<>(); + ArrayList prefixes = new ArrayList<>(); + for (Map.Entry prefixIndex : prefixIndexes.entrySet()) { + indexes.add(prefixIndex.getKey()); + prefixes.add(prefixIndex.getValue()); + } + + for (int i = 0; i < indexes.size() - 1; i++) { + int previousIndex = indexes.get(i); + int nextIndex = indexes.get(i + 1); + String prefix = prefixes.get(i); + String value = extractValue(argString, prefix, previousIndex, nextIndex, identifier); + int inputIndex = obtainArrayIndex(prefix, identifier); + extractedValues.set(inputIndex, value); + } + + String finalPrefix = prefixes.get(indexes.size() - 1); + int finalIndex = indexes.get(indexes.size() - 1); + + int inputIndex = obtainArrayIndex(finalPrefix, identifier); + String value = extractValue(argString, finalPrefix, finalIndex, argString.length(), identifier); + extractedValues.set(inputIndex, value); + return extractedValues; + } + + /** + * Initialise an empty array list of size 5 - max array size for values. + * + * @param extractedValues empty extracted values array to be initialised with empty string values + */ + private static void initialiseArrayList(ArrayList extractedValues) { + for (int i = 0; i < MAX_VALUE_ARRAY_SIZE; i++) { + extractedValues.add(""); + } + } + + /** + * Extract value from a substring of the user's argument string, according to prefix. + * + * @param argString full user's argument string + * @param prefix prefix of value to be extracted + * @param startIndex start index of substring + * @param endIndex end index of substring + * @param identifier specific identifier to determine specific data type (client, flight, tour, package) + * @return value corresponding to prefix given + * @throws TourPlannerException if there are duplicate prefixes found + */ + private static String extractValue(String argString, String prefix, int startIndex, int endIndex, String identifier) + throws TourPlannerException { + List prefixes = generatePrefixesFromIdentifier(identifier); + String unformattedSubstring = argString.substring(startIndex, endIndex).trim(); + String value = unformattedSubstring.replaceFirst(prefix, "").trim(); + + if (value.equals(EMPTY_STRING)) { + throw new TourPlannerException(ERROR_MISSING_FIELDS); + } + for (String pf : prefixes) { + boolean hasDuplicatePrefix = value.contains(pf); + if (hasDuplicatePrefix) { + throw new TourPlannerException(ERROR_DUPLICATE_PREFIXES); + } + } + return value; + } + + /** + * Return the list of prefixes for each data type. + * + * @param identifier specific identifier to determine specific data type (client, flight, tour, package) + * @return the list of prefixes, as specified by the identifier + * @throws TourPlannerException if the identifier is invalid + */ + private static List generatePrefixesFromIdentifier(String identifier) throws TourPlannerException { + List prefixes; + switch (identifier) { + case CLIENT_IDENTIFIER: + prefixes = Arrays.asList(CLIENT_NAME_PREFIX, CLIENT_CONTACT_NUMBER_PREFIX, CLIENT_EMAIL_PREFIX); + break; + case TOUR_IDENTIFIER: + prefixes = Arrays.asList(TOUR_NAME_PREFIX, TOUR_PRICE_PREFIX); + break; + case FLIGHT_IDENTIFIER: + prefixes = Arrays.asList(FLIGHT_DEPARTURE_PREFIX, FLIGHT_RETURN_PREFIX, + FLIGHT_DEPARTURE_DATE_PREFIX, FLIGHT_RETURN_DATE_PREFIX); + break; + case PACKAGE_IDENTIFIER: + prefixes = Arrays.asList(PACKAGE_CLIENT_PREFIX, PACKAGE_TOUR_PREFIX, PACKAGE_FLIGHT_PREFIX); + break; + default: + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + return prefixes; + } + + /** + * Obtains array index that corresponds to the prefix given. + * + * @param prefix prefix of value extracted + * @return array index of values according to prefix + */ + private static int obtainArrayIndex(String prefix, String identifier) { + int index; + + switch (identifier) { + case CLIENT_IDENTIFIER: + index = obtainClientArrayIndex(prefix); + break; + case TOUR_IDENTIFIER: + index = obtainTourArrayIndex(prefix); + break; + case FLIGHT_IDENTIFIER: + index = obtainFlightArrayIndex(prefix); + break; + case PACKAGE_IDENTIFIER: + index = obtainPackageArrayIndex(prefix); + break; + default: + index = 0; + break; + } + return index; + } + + /** + * Returns a value's insertion index into the ClientPackage input array, based on the given prefix. + * ClientPackage array: (Package ID, Client id, Tour id, Flight id) + * + * @param prefix prefix of value extracted + * @return respective insertion index + */ + private static int obtainPackageArrayIndex(String prefix) { + int index; + + switch (prefix) { + case PACKAGE_CLIENT_PREFIX: + index = 1; + break; + case PACKAGE_TOUR_PREFIX: + index = 2; + break; + case PACKAGE_FLIGHT_PREFIX: + index = 3; + break; + default: + index = 0; + break; + } + return index; + } + + /** + * Returns a value's insertion index into the Flight input array, based on the given prefix. + * Flight array: (ID, Departure destination, Return destination, Departure Date,Return Date) + * + * @param prefix prefix of value extracted + * @return respective insertion index + */ + private static int obtainFlightArrayIndex(String prefix) { + int index; + + switch (prefix) { + case FLIGHT_DEPARTURE_PREFIX: + index = 1; + break; + case FLIGHT_RETURN_PREFIX: + index = 2; + break; + case FLIGHT_DEPARTURE_DATE_PREFIX: + index = 3; + break; + case FLIGHT_RETURN_DATE_PREFIX: + index = 4; + break; + default: + index = 0; + break; + } + return index; + } + + /** + * Returns a value's insertion index into the Tour input array, based on the given prefix. + * Tour array: (ID, Name, Price) + * + * @param prefix prefix of value extracted + * @return respective insertion index + */ + private static int obtainTourArrayIndex(String prefix) { + int index; + + switch (prefix) { + case TOUR_NAME_PREFIX: + index = 1; + break; + case TOUR_PRICE_PREFIX: + index = 2; + break; + default: + index = 0; + break; + } + return index; + } + + /** + * Returns a value's insertion index into the Client input array, based on the given prefix. + * Client array: (ID, Name, Contact Number, eMail) + * + * @param prefix prefix of value extracted + * @return respective insertion index + */ + private static int obtainClientArrayIndex(String prefix) { + int index; + + switch (prefix) { + case CLIENT_NAME_PREFIX: + index = 1; + break; + case CLIENT_CONTACT_NUMBER_PREFIX: + index = 2; + break; + case CLIENT_EMAIL_PREFIX: + index = 3; + break; + default: + index = 0; + break; + } + return index; + } + + /** + * Returns true if all prefixes are present in add command's argument string. + * + * @param argString full user's argument string + * @return true if all prefixes are present in add command's argument string + */ + private static boolean containAllPrefixes(String argString, List prefixList) { + + String[] splitBySpaces = argString.trim().split("\\s+"); + String[] prefixes = prefixList.toArray(new String[0]); + for (String prefix : prefixes) { + boolean containPrefix = false; + for (String substring : splitBySpaces) { + if (prefix.equals(substring)) { + containPrefix = true; + break; + } + } + if (!containPrefix) { + return false; + } + } + return true; + } + + /** + * Handles all exceptions regarding inputs for add tour command. + * Throws TourPlannerException when there are duplicate decimal points (.), or additional characters in price + * that prevent parsing. + * Prints a warning if price has too many divisions, i.e. decimal places + * + * @param values the array of values extracted from user's input after parsing + * @throws TourPlannerException if there are duplicate decimal points, additional characters in price + */ + private static void handleTourException(String[] values) throws TourPlannerException { + String price = values[2]; + int decimalCount = (int) price.chars().filter(ch -> ch == '.').count(); + String priceAfterParseString = price.replaceAll("[^0-9.]", ""); + boolean containsAdditionalDecimalPoints = decimalCount > 1; + if (containsAdditionalDecimalPoints) { + throw new TourPlannerException(ERROR_PRICE_FORMAT); + } + + boolean containsAdditionalCharacters = !price.equals(priceAfterParseString); + + if (containsAdditionalCharacters) { + throw new TourPlannerException(ERROR_PRICE_FORMAT); + } + + if (decimalCount == 1) { + int decimalIndex = price.indexOf("."); + int numberOfDecimalPlaces = price.length() - decimalIndex - 1; + + if (numberOfDecimalPlaces > 2) { + System.out.println(WARNING_PRICE_TOO_MANY_DECIMAL); + } + } + } + + /** + * Handles all exceptions regarding inputs for add flight command. + * + * @param values the array of values extracted from user's input after parsing + * @throws TourPlannerException if there are logical errors with date-time input (i.e. date-time of flight to + * destination location is after date-time of return flight) + * or date-time parsing/format errors + */ + private static void handleFlightException(String[] values) throws TourPlannerException { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yy HH:mm"); + LocalDateTime start; + LocalDateTime end; + String startDateString = values[3]; + String endDateString = values[4]; + + try { + start = LocalDateTime.parse(startDateString, formatter); + end = LocalDateTime.parse(endDateString, formatter); + } catch (DateTimeParseException e) { + throw new TourPlannerException(ERROR_FLIGHT_TIME_FORMAT); + } + if (end.isBefore(start) || end.equals(start)) { + throw new TourPlannerException(ERROR_FLIGHT_TIME_INVERT); + } + } + + /** + * Handles all exceptions regarding inputs for add client command. + * Prints warnings for erroneous contact number (inclusive of characters other than numbers, length != 8) + * Also prints warnings for erroneous email (lacking/more than one '@' symbol or period '.', that are commonly + * found in emails). + * + * @param values the array of values extracted from user's input after parsing + */ + private static void handleClientException(String[] values) { + String contactNum = values[2]; + String email = values[3]; + int adSymbolCount = (int) email.chars().filter(ch -> ch == '@').count(); + int periodCount = (int) email.chars().filter(ch -> ch == '.').count(); + + if (adSymbolCount != 1 || periodCount != 1) { + System.out.println(WARNING_EMAIL_FORMAT_WRONG); + } + + boolean isWrongContactNumber = false; + int contactNumberLength = contactNum.length(); + for (int i = 0; i < contactNumberLength; i++) { + char ch = contactNum.charAt(i); + if (!(ch <= '9' && ch >= '0')) { + isWrongContactNumber = true; + break; + } + } + if (isWrongContactNumber) { + System.out.println(WARNING_CONTACT_NUMBER_WRONG); + } else if (contactNumberLength < 8) { + System.out.println(WARNING_SHORT_CONTACT_NUMBER); + } else if (contactNumberLength > 8) { + System.out.println(WARNING_LONG_CONTACT_NUMBER); + } + } + + /** + * Parses arguments with respect to the add command. + * Extract values from user's input and passes it as an argument to construct Client/Flight/Tour object + * depending on the identifier. + * Passes the created object to the specific AddCommand, determined by the identifier. + * + * @param params full user's argument string + * @return the specific AddCommand object determined by the command's identifier + * @throws TourPlannerException if there are missing fields,duplicated or missing prefixes + */ + private static Command parseAdd(String params) throws TourPlannerException { + String[] identifierAndArgs = splitCommandString(params); + String identifier = identifierAndArgs[IDENTIFIER_INDEX]; + String args = identifierAndArgs[ARGS_INDEX]; + + TreeMap prefixIndexes = extractPrefixIndexes(args, identifier); + ArrayList valuesList = extractValuesIntoArray(prefixIndexes, args, identifier); + String[] values = valuesList.toArray(new String[0]); + + switch (identifier) { + case CLIENT_IDENTIFIER: + handleClientException(values); + Client client = new Client(values); + return new AddClientCommand(client); + case FLIGHT_IDENTIFIER: + handleFlightException(values); + Flight flight = new Flight(values); + return new AddFlightCommand(flight); + case TOUR_IDENTIFIER: + handleTourException(values); + Tour tour = new Tour(values); + return new AddTourCommand(tour); + case PACKAGE_IDENTIFIER: + return new AddClientPackageCommand(values); + default: + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + } + + /** + * Parses arguments with respect to the bye command. + * + * @param params full user's argument string + * @return ByeCommand object + */ + private static Command parseBye(String params) { + if (!params.equals(EMPTY_STRING)) { + System.out.println(WARNING_EXTRA_INPUT); + } + return new ByeCommand(); + } + + /** + * Parses user input, identifies data type to be cutting from based on the command filter, + * and returns the corresponding subclass of Command. + * + * @param params user input excluding "cut" + * @return command corresponding to the data type of the command filter + * @throws TourPlannerException if command filter is wrong/missing or id to cut is wrong/missing + */ + private static Command parseCut(String params) throws TourPlannerException { + String[] identifierAndArgs = splitCommandString(params); + String identifier = identifierAndArgs[IDENTIFIER_INDEX]; + String args = identifierAndArgs[ARGS_INDEX]; + + switch (identifier) { + case CLIENT_IDENTIFIER: + handleCutException(args); + return new CutClientCommand(args); + case TOUR_IDENTIFIER: + handleCutException(args); + return new CutTourCommand(args); + case FLIGHT_IDENTIFIER: + handleCutException(args); + return new CutFlightCommand(args); + case PACKAGE_IDENTIFIER: + handleCutException(args); + return new CutClientPackageCommand(args); + default: + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + } + + private static void handleCutException(String args) throws TourPlannerException { + if (args.equals(EMPTY_STRING)) { + throw new TourPlannerException(ERROR_MISSING_ID); + } + } + + /** + * Parses the parameters given to determine which FindXYZCommand to be called for. + * + * @param params full user's argument string + * @return a FindXYZCommand of a specific data type (client, tour, flight, client package) + * @throws TourPlannerException if there are missing fields, duplicated or missing prefixes + */ + private static Command parseFind(String params) throws TourPlannerException { + String[] prefixSuffix = params.split(" ", 2); + if (prefixSuffix.length < 2) { + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + String prefix = prefixSuffix[IDENTIFIER_INDEX]; + String suffix = prefixSuffix[ARGS_INDEX]; + switch (prefix) { + case CLIENT_IDENTIFIER: + return new FindClientCommand(suffix); + case TOUR_IDENTIFIER: + return new FindTourCommand(suffix); + case FLIGHT_IDENTIFIER: + return new FindFlightCommand(suffix); + default: + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + } + + /** + * Parses arguments with respect to the help command. + * + * @param params full user's argument string + * @return HelpCommand object + */ + private static Command parseHelp(String params) { + if (!params.equals(EMPTY_STRING)) { + System.out.println(WARNING_EXTRA_INPUT); + } + return new HelpCommand(); + } + + /** + * Parses the parameters given to determine which ListXYZCommand to be called for. + * + * @param params full user's argument string + * @return a ListXYZCommand of a specific data type (client, tour, flight, client package) + * @throws TourPlannerException if there are missing fields, duplicated or missing prefixes + */ + private static Command parseList(String params) throws TourPlannerException { + switch (params) { + case CLIENT_IDENTIFIER: + return new ListClientCommand(); + case TOUR_IDENTIFIER: + return new ListTourCommand(); + case FLIGHT_IDENTIFIER: + return new ListFlightCommand(); + case PACKAGE_IDENTIFIER: + return new ListClientPackageCommand(); + default: + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + } + + /** + * Parses arguments with respect to the sort command. + * + * @param params full user's argument string + * @return the specific SortCommand object determined by the command's identifier + * @throws TourPlannerException if there are missing fields,duplicated or missing prefixes + */ + private static Command parseSort(String params) throws TourPlannerException { + String[] identifierAndFilter = splitCommandString(params); + String identifier = identifierAndFilter[IDENTIFIER_INDEX]; + String filter = identifierAndFilter[ARGS_INDEX]; + switch (identifier) { + case TOUR_IDENTIFIER: + return new SortTourCommand(filter); + case CLIENT_IDENTIFIER: + return new SortClientCommand(filter); + case FLIGHT_IDENTIFIER: + return new SortFlightCommand(filter); + default: + throw new TourPlannerException(ERROR_MISSING_IDENTIFIER); + } + } +} + + diff --git a/src/main/java/seedu/duke/TourPlanner.java b/src/main/java/seedu/duke/TourPlanner.java new file mode 100644 index 0000000000..ae138ed6fd --- /dev/null +++ b/src/main/java/seedu/duke/TourPlanner.java @@ -0,0 +1,157 @@ +package seedu.duke; + +import seedu.duke.commands.Command; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; +import seedu.duke.storage.ClientPackageStorage; +import seedu.duke.storage.ClientStorage; +import seedu.duke.storage.FlightStorage; +import seedu.duke.storage.TourStorage; + +import java.io.IOException; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * Main entry-point of the TourPlanner application. + * Initialises the application and starts interaction with application user. + */ +public class TourPlanner { + private static Ui ui; + private static ClientList clients; + private static TourList tours; + private static FlightList flights; + private static ClientPackageList clientPackages; + + private static ClientPackageStorage clientPackageStorage; + private static ClientStorage clientStorage; + private static TourStorage tourStorage; + private static FlightStorage flightStorage; + + private static final Logger logr = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + + public TourPlanner() { + } + + /** + * Main method of TourPlanner. + * Initialises Ui and ClientList objects. + * Reads, parses and executes command from user's input until exit condition is met. + * + * @param args not used + */ + public static void main(String[] args) { + loadLogger(); + loadStorage(); + ui.showWelcome(); + runCommandLoopUntilExitCommand(); + } + + /** + * Sets up logger for TourPlanner. + */ + private static void loadLogger() { + LogManager.getLogManager().reset(); + logr.setLevel(Level.ALL); + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.OFF); + logr.addHandler(consoleHandler); + + try { + FileHandler fileHandler = new FileHandler("TourPlannerLogger.log"); + fileHandler.setLevel(Level.ALL); + fileHandler.setFormatter(new SimpleFormatter()); + logr.addHandler(fileHandler); + } catch (IOException e) { + logr.log(Level.SEVERE, "File logger not working", e); + } + } + + /** + * Populates the ClientList, TourList, FlightList and ClientPackageList from previously stored files. + */ + private static void loadStorage() { + try { + initialize(); + loadClientTourFlight(); + loadClientPackage(); + } catch (TourPlannerException e) { + ui.showFileError(); + System.exit(0); + } + } + + private static void initialize() throws TourPlannerException { + ui = new Ui(); + clientPackageStorage = new ClientPackageStorage(); + clientStorage = new ClientStorage(); + tourStorage = new TourStorage(); + flightStorage = new FlightStorage(); + } + + /** + * Populates the ClientList, TourList, FlightList from previously stored files. + * + * @throws TourPlannerException if there are IOException, FileNotFoundException for the storage file + */ + private static void loadClientTourFlight() throws TourPlannerException { + clientStorage.loadFile(); + clients = clientStorage.getClients(); + tourStorage.loadFile(); + tours = tourStorage.getTours(); + flightStorage.loadFile(); + flights = flightStorage.getFlights(); + } + + /** + * Populates ClientPackageList from previously stored files. + * + * @throws TourPlannerException if there are IOException, FileNotFoundException for the storage file + */ + private static void loadClientPackage() throws TourPlannerException { + clientPackageStorage.loadFile(clients, tours, flights, ui); + clientPackages = clientPackageStorage.getClientPackages(); + } + + /** + * Loop that reads command from Ui, parses command and executes command. + * Loop ends when ByeCommand is called and isExit is set to true. + */ + private static void runCommandLoopUntilExitCommand() { + boolean isExit = false; + while (!isExit) { + String command = ui.readCommand(); + try { + Command specificCommand = Parser.parse(command); + specificCommand.setData(clients, flights, tours, clientPackages, ui); + specificCommand.execute(); + isExit = specificCommand.isExit(); + } catch (NullPointerException e) { + logr.log(Level.SEVERE, e.getMessage()); + } catch (TourPlannerException e) { + ui.show(e.getMessage()); + logr.log(Level.WARNING, e.getMessage()); + } finally { + ui.showLine(); + saveToStorage(); + } + } + } + + /** + * Saves the ClientList, FlightList, TourList and ClientPackageList to text files in the data folder. + */ + private static void saveToStorage() { + clientStorage.saveFile(); + flightStorage.saveFile(); + tourStorage.saveFile(); + clientPackageStorage.saveFile(); + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/TourPlannerException.java b/src/main/java/seedu/duke/TourPlannerException.java new file mode 100644 index 0000000000..9c41210752 --- /dev/null +++ b/src/main/java/seedu/duke/TourPlannerException.java @@ -0,0 +1,16 @@ +package seedu.duke; + +/** + * Represents a generalised error thrown when there are erroneous/out-of-bounds/missing inputs. + */ +public class TourPlannerException extends Exception { + /** + * Class constructor for TourPlannerException. + * Returns an error message to the user to inform user of error. + * + * @param errorMessage the customised error message corresponding to the error made + */ + public TourPlannerException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/seedu/duke/Ui.java b/src/main/java/seedu/duke/Ui.java new file mode 100644 index 0000000000..f63dbe2a56 --- /dev/null +++ b/src/main/java/seedu/duke/Ui.java @@ -0,0 +1,580 @@ +package seedu.duke; + +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; + + +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Text UI of the application. + */ +public class Ui { + public static final String ADD_CLIENT_MESSAGE = "Client has been added:"; + public static final String ADD_FLIGHT_MESSAGE = "Flight has been added:"; + public static final String ADD_TOUR_MESSAGE = "Tour has been added:"; + public static final String ADD_CLIENT_PACKAGE_MESSAGE = "Client package has been added:"; + + public static final String CUT_CLIENT_MESSAGE = "Client has been deleted:"; + public static final String CUT_FLIGHT_MESSAGE = "Flight has been deleted:"; + public static final String CUT_TOUR_MESSAGE = "Tour has been deleted:"; + public static final String CUT_CLIENT_PACKAGE_MESSAGE = "Client package has been deleted:"; + + public static final String LIST_NO_MESSAGE = "I'm sorry, there seems to be no "; + public static final String LIST_MESSAGE = "Here is a list of all "; + public static final String FIND_FAIL_MESSAGE_LEFT = "I'm sorry, there seems to be no "; + public static final String FIND_SUCCESS_MESSAGE_LEFT = "This is the "; + public static final String FIND_MESSAGE_RIGHT = "that matches your search"; + public static final String BYE_MESSAGE = "Thanks for using TourPlanner. Goodbye!"; + + public static final String SORT_TOUR_ID_MESSAGE = "Sorted by tour id alphabetically"; + public static final String SORT_TOUR_NAME_MESSAGE = "Sorted by tour name alphabetically"; + public static final String SORT_TOUR_PRICE_MESSAGE = "Sorted by price in ascending order"; + public static final String SORT_CLIENT_ID_MESSAGE = "Sorted by client id alphabetically"; + public static final String SORT_CLIENT_NAME_MESSAGE = "Sorted by client name alphabetically"; + public static final String SORT_FLIGHT_BY_DEPARTURE_MESSAGE = "Sorted by departing flight times"; + public static final String SORT_FLIGHT_BY_ARRIVAL_MESSAGE = "Sorted by returning flight times"; + public static final String SORT_FLIGHT_ID_MESSAGE = "Sorted by flight id alphabetically"; + + private static final String FILE_CORRUPT = "TourPlanner can't read your corrupted file.\n" + + "Please delete your 'data' folder and try again."; + + private static final Scanner in = new Scanner(System.in); + + /** + * Empty Ui class constructor. + */ + public Ui() { + } + + /** + * Shows a divider to the user. + */ + public void showLine() { + show("____________________________________________________________"); + } + + /** + * Shows a welcome message to the user. + */ + public void showWelcome() { + showLine(); + String logo = " _____ _____ _\n" + + " |_ _| ___ _ _ ___ | _ || | ___ _____ _____ ___ ___\n" + + " | | | || || || _|| ___|| || \\ | _ || _ || -_|| _|\n" + + " | | | | || || || | | | | || | || | | || | | || |_ | |\n" + + " |_| |___||____||_| |_| |_||___|||_| |_||_| |_||___||_|\n"; + String greet = "Hello, Welcome to TourPlanner!\n" + + "What can I do for you?"; + show(logo); + show(greet); + showLine(); + } + + /** + * Shows a help message to the user, which includes all available commands in TourPlanner. + */ + public void showHelp() { + String add = "add: Add information of all data types into the database.\n" + + "Prefixes can be input in any order.\n" + + " Add client: add -c CLIENT_ID /n NAME /cn CONTACT_NUM /m EMAIL\n" + + " Add flight: add -f FLIGHT_ID /d DEPART_DESTINATION /r RETURN_DESTINATION\n" + + " /dd DEPARTURE_DATETIME /rd RETURN_DATETIME\n" + + " Add tour: add -t TOUR_ID /n DEPART_DESTINATION /p TOUR_PRICE\n" + + " Add client package: add -p PACKAGE_ID /c CLIENT_ID /t TOUR_ID /f FLIGHT_ID\n\n"; + String list = "list: Shows a list of all available entries of a specific data type, along with their " + + "respective fields.\n" + + " List client: list -c\n" + + " List flight: list -f\n" + + " List tour: list -t\n" + + " List client package: list -p\n\n"; + String cut = "cut: Deletes entry of a certain data type and all client packages corresponding to the entry.\n" + + " Cut client: cut -c CLIENT_ID\n" + + " Cut flight: cut -f FLIGHT_ID\n" + + " Cut tour: cut -t TOUR_ID\n" + + " Cut client package: cut -p PACKAGE_ID\n\n"; + String find = "find: Finds specific entry of data type, returns the entry and other relevant information.\n" + + " Find client: find -c CLIENT_NAME\n" + + " Find flight: find -f FLIGHT_ID\n" + + " Find tour: find -t TOUR_ID\n\n"; + String sort = "sort: Sorts entries in the data type based on the criteria.\n " + + " Sort client:\n" + + " Sort by id: sort -c /id\n" + + " Sort by name: sort -c /n\n" + + " Sort flight:\n" + + " Sort by id: sort -f /id\n" + + " Sort by departure date: sort -f /d\n" + + " Sort by return date: sort -f /r\n" + + " Sort tour:\n" + + " Sort by id: sort -t /id\n" + + " Sort by name: sort -t /n\n" + + " Sort by price: sort -t /p\n\n"; + String bye = "bye: Exits the program."; + show(add + list + cut + find + sort + bye); + } + + /** + * Prompts for user's input and read's the text entered by the user. + * + * @return full input entered by the user + */ + public String readCommand() { + return in.nextLine(); + } + + /** + * Outputs to the CLI the text to show the user. + * Creates a newline as well. + * + * @param textToShow the output text intended for the user + */ + public void show(String textToShow) { + System.out.print(textToShow + System.lineSeparator()); + } + + /** + * Ui response to cut command. + * Checks the type of object before showing a customised message for deleting the specific object. + * + * @param object the object that was cut + */ + public void showCut(Object object) { + if (object instanceof Client) { + show(CUT_CLIENT_MESSAGE + "\n" + object); + } else if (object instanceof Flight) { + show(CUT_FLIGHT_MESSAGE + "\n" + object); + } else if (object instanceof Tour) { + show(CUT_TOUR_MESSAGE + "\n" + object); + } else if (object instanceof ClientPackage) { + show(CUT_CLIENT_PACKAGE_MESSAGE + "\n" + object); + } + } + + /** + * Ui response to add command. + * Checks the type of object before showing a customised message for deleting the specific object. + * + * @param object the object that was cut + */ + public void showAdd(Object object) { + if (object instanceof Client) { + show(ADD_CLIENT_MESSAGE + "\n" + object); + } else if (object instanceof Flight) { + show(ADD_FLIGHT_MESSAGE + "\n" + object); + } else if (object instanceof Tour) { + show(ADD_TOUR_MESSAGE + "\n" + object); + } else if (object instanceof ClientPackage) { + show(ADD_CLIENT_PACKAGE_MESSAGE + "\n" + object); + } + } + + /** + * Ui response to ListClientCommand. + * Shows all clients in the clientList on the terminal. + * + * @param clients the ClientList from which the clients are obtained from. + */ + public void showListClient(ClientList clients) { + int count = clients.getClientCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "clients"); + return; + } + show(LIST_MESSAGE + "clients:"); + for (int i = 1; i <= count; i++) { + Client currClient = clients.getClientByIndex(i - 1); + show(i + ". " + currClient + "\n"); + } + show("Total Clients: " + count); + } + + /** + * Ui response to FindClientCommand. + * Shows specific client(s) based on a susbtring, as well as the client packages they are a part of. + * + * @param clients the ClientList from which the clients are obtained from. + * @param clientPackages the ClientPackageList from which the client packages of the client are obtained from. + * @param substring the substring used to obtain the specific client(s). + */ + public void showFindClient(ClientList clients, ClientPackageList clientPackages, String substring) { + String lowercaseName = substring.toLowerCase(); + int foundClients = 0; + int count = clients.getClientCount(); + for (int i = 0; i < count; i++) { + Client currClient = clients.getClientByIndex(i); + if (currClient.getName().toLowerCase().contains(lowercaseName)) { + if (foundClients == 0) { + show(FIND_SUCCESS_MESSAGE_LEFT + "client(s) " + FIND_MESSAGE_RIGHT); + } + show((foundClients + 1) + ". " + currClient + "\n"); + showFlightTourOfClient(currClient, clientPackages); + foundClients++; + } + } + if (foundClients == 0) { + show(FIND_FAIL_MESSAGE_LEFT + "client(s) " + FIND_MESSAGE_RIGHT); + } + } + + /** + * Shows clientPackages that a certain client is a part of. + * + * @param currClient the client which the shown clientPackages must contain. + * @param clientPackages the ClientPackageList from which the client packages of the client are obtained from. + */ + private void showFlightTourOfClient(Client currClient, ClientPackageList clientPackages) { + ArrayList clientPackagesWithClient; + clientPackagesWithClient = clientPackages.getClientPackageByClient(currClient); + for (ClientPackage clientPackage : clientPackagesWithClient) { + System.out.println(clientPackage + "\n"); + } + } + + /** + * Ui response to ListTourCommand. + * Shows all tours in the tourList on the terminal. + * + * @param tours the TourList from which the tours are obtained from. + */ + public void showListTour(TourList tours) { + int count = tours.getTourCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "tours"); + return; + } + show(LIST_MESSAGE + "tours:"); + for (int i = 1; i <= count; i++) { + Tour currTour = tours.getTourByIndex(i - 1); + show(i + ". " + currTour + "\n"); + } + show("Total Tours: " + count); + } + + /** + * Ui response to FindTourCommand. + * Shows specific tour based on an ID, as well as the subscribed clients for said tour. + * + * @param tours the TourList from which the tours are obtained from. + * @param clientPackages the ClientPackageList from which the subscribers of the tour are obtained from. + * @param id the ID used to find the specific tour. + */ + public void showFindTour(TourList tours, ClientPackageList clientPackages, String id) + throws TourPlannerException { + Tour foundTour = tours.getTourById(id); + if (foundTour != null) { + show(FIND_SUCCESS_MESSAGE_LEFT + "tour " + FIND_MESSAGE_RIGHT); + show(foundTour + "\n" + "\n"); + int subbedClients = 0; + int count = clientPackages.getClientPackageCount(); + show("Subscribed Clients:"); + for (int i = 0; i < count; i++) { + Tour currTour = clientPackages.getClientPackageByIndex(i).getTour(); + if (currTour.equals(foundTour)) { + String currClientName = clientPackages.getClientPackageByIndex(i).getClient().getName(); + String currClientId = clientPackages.getClientPackageByIndex(i).getClient().getId(); + show((subbedClients + 1) + ". " + currClientName + " (ID: " + currClientId + ")"); + subbedClients++; + } + } + show("\n" + "Total Subscribed Clients: " + subbedClients); + } else { + show(FIND_FAIL_MESSAGE_LEFT + "tours " + FIND_MESSAGE_RIGHT); + } + } + + private boolean checkEmptyTours(TourList tours) { + int count = tours.getTourCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "tours"); + return true; + } + return false; + } + + /** + * Ui response to sort tour by id. + * + * @param tours the current list of tours in the database + * @param sortedTourIds the list of sorted tour codes/ids (by alphabetical order) + * @throws TourPlannerException if there is no tours that can be found given the tour code + */ + public void showSortedTourById(TourList tours, ArrayList sortedTourIds) + throws TourPlannerException { + if (checkEmptyTours(tours)) { + return; + } + show(SORT_TOUR_ID_MESSAGE); + int listIndex = 1; + for (String tourId : sortedTourIds) { + Tour currTour = tours.getTourById(tourId); + show(listIndex + ". " + currTour + System.lineSeparator()); + listIndex++; + } + } + + /** + * Ui response to sort tour by price. + * + * @param tours the current list of tours in the database + * @param sortedTourPrices the list of sorted tour prices (by ascending order) + * @throws TourPlannerException if there is no tours that can be found given the tour price + */ + public void showSortedTourByPrice(TourList tours, ArrayList sortedTourPrices) throws TourPlannerException { + if (checkEmptyTours(tours)) { + return; + } + tours.initTempArray(); + show(SORT_TOUR_PRICE_MESSAGE); + int listIndex = 1; + for (Float tourPrice : sortedTourPrices) { + Tour currTour = tours.getTourByPrice(tourPrice); + show(listIndex + ". " + currTour + System.lineSeparator()); + listIndex++; + } + } + + /** + * Ui response to sort tour by name. + * + * @param tours the current list of tours in the database + * @param sortedTourNames the list of sorted tour names (by alphabetical order) + * @throws TourPlannerException if there is no tours that can be found given the tour name + */ + public void showSortedTourByName(TourList tours, ArrayList sortedTourNames) throws TourPlannerException { + if (checkEmptyTours(tours)) { + return; + } + tours.initTempArray(); + show(SORT_TOUR_NAME_MESSAGE); + int listIndex = 1; + for (String tourName : sortedTourNames) { + Tour currTour = tours.getTourByName(tourName); + show(listIndex + ". " + currTour + System.lineSeparator()); + listIndex++; + } + } + + private boolean checkEmptyClients(ClientList clients) { + int count = clients.getClientCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "flights"); + return true; + } + return false; + } + + /** + * Ui response to sort client by id. + * + * @param clients the current list of clients in the database + * @param sortedClientIds the list of sorted client IDs (by alphabetical order) + * @throws TourPlannerException if there is no clients that can be found given the client ID + */ + public void showSortedClientById(ClientList clients, ArrayList sortedClientIds) + throws TourPlannerException { + if (checkEmptyClients(clients)) { + return; + } + show(SORT_CLIENT_ID_MESSAGE); + int listIndex = 1; + for (String clientId : sortedClientIds) { + Client currClient = clients.getClientById(clientId); + show(listIndex + ". " + currClient + System.lineSeparator()); + listIndex++; + } + } + + /** + * Ui response to sort client by name. + * + * @param clients the current list of clients in the database + * @param sortedClientNames the list of sorted client names (by alphabetical order) + * @throws TourPlannerException if there is no clients that can be found given the client name + */ + public void showSortedClientByName(ClientList clients, ArrayList sortedClientNames) + throws TourPlannerException { + if (checkEmptyClients(clients)) { + return; + } + clients.initTempArray(); + show(SORT_CLIENT_NAME_MESSAGE); + int listIndex = 1; + for (String clientName : sortedClientNames) { + Client currClient = clients.getClientByName(clientName); + show(listIndex + ". " + currClient + System.lineSeparator()); + listIndex++; + } + } + + private boolean checkEmptyFlights(FlightList flights) { + int count = flights.getFlightCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "flights"); + return true; + } + return false; + } + + /** + * Ui response to sort flight by ID. + * + * @param flights the current list of flights in the database + * @param sortedIds the list of sorted flight IDs (by alphabetical order) + * @throws TourPlannerException if there is no flights that can be found given the flight ID + */ + public void showSortedFlightById(FlightList flights, ArrayList sortedIds) + throws TourPlannerException { + if (checkEmptyFlights(flights)) { + return; + } + show(SORT_FLIGHT_ID_MESSAGE); + int listIndex = 1; + for (String flightId : sortedIds) { + Flight currFlight = flights.getFlightById(flightId); + show(listIndex + ". " + currFlight + System.lineSeparator()); + listIndex++; + } + } + + /** + * Ui response to sort flight by return date. + * + * @param flights the current list of flights in the database + * @param sortedFlightByArriveDates the list of sorted return dates (by natural order of time) + * @throws TourPlannerException if there is no flights that can be found given the return date + */ + public void showSortedFlightByReturn(FlightList flights, ArrayList sortedFlightByArriveDates) + throws TourPlannerException { + if (checkEmptyFlights(flights)) { + return; + } + show(SORT_FLIGHT_BY_ARRIVAL_MESSAGE); + flights.initTempArray(); + int listIndex = 1; + for (String flightArriveDate : sortedFlightByArriveDates) { + Flight currFlight = flights.getFlightByReturnDate(flightArriveDate); + show(listIndex + ". " + currFlight + System.lineSeparator()); + listIndex++; + } + } + + /** + * Ui response to sort flight by depart date. + * + * @param flights the current list of flights in the database + * @param sortedFlightByDepartureDates the list of sorted departure dates (by natural order of time) + * @throws TourPlannerException if there is no flights that can be found given the departure date + */ + public void showSortedFlightByDeparture(FlightList flights, ArrayList sortedFlightByDepartureDates) + throws TourPlannerException { + if (checkEmptyFlights(flights)) { + return; + } + show(SORT_FLIGHT_BY_DEPARTURE_MESSAGE); + flights.initTempArray(); + int listIndex = 1; + for (String flightDepartDate : sortedFlightByDepartureDates) { + Flight currFlight = flights.getFlightByDepartDate(flightDepartDate); + show(listIndex + ". " + currFlight + System.lineSeparator()); + listIndex++; + } + } + + /** + * Ui response to ListFlightCommand. + * Shows all flights in the flightList on the terminal. + * + * @param flights the FlightList from which the flights are obtained from. + */ + public void showListFlight(FlightList flights) { + int count = flights.getFlightCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "flights"); + return; + } + show(LIST_MESSAGE + "flights:"); + for (int i = 1; i <= count; i++) { + Flight currFlight = flights.getFlightByIndex(i - 1); + show(i + ". " + currFlight + "\n"); + } + show("Total Flights: " + count); + } + + + /** + * Ui response to FindFlightCommand. + * Shows specific flight based on an ID, as well as the passengers for said flight. + * + * @param flights the FlightList from which the flights are obtained from. + * @param clientPackages the ClientPackageList from which the passengers for the flight are obtained from. + * @param id the ID used to find the specific flight. + */ + public void showFindFlight(FlightList flights, ClientPackageList clientPackages, String id) + throws TourPlannerException { + Flight foundFlight = flights.getFlightById(id); + if (foundFlight != null) { + show(FIND_SUCCESS_MESSAGE_LEFT + "flight " + FIND_MESSAGE_RIGHT); + show(foundFlight + "\n" + "\n"); + int passengers = 0; + int count = clientPackages.getClientPackageCount(); + show("Passengers:"); + for (int i = 0; i < count; i++) { + Flight currFlight = clientPackages.getClientPackageByIndex(i).getFlight(); + if (currFlight.equals(foundFlight)) { + String currClientName = clientPackages.getClientPackageByIndex(i).getClient().getName(); + String currClientId = clientPackages.getClientPackageByIndex(i).getClient().getId(); + show((passengers + 1) + ". " + currClientName + " (ID: " + currClientId + ")"); + passengers++; + } + } + show("\n" + "Total Passengers: " + passengers); + } else { + show(FIND_FAIL_MESSAGE_LEFT + "flights " + FIND_MESSAGE_RIGHT); + } + } + + /** + * Ui response to ListClientPackageCommand. + * Shows all client packages in the clientPackageList on the terminal. + * + * @param clientPackages the ClientPackageList from which the client packages are obtained from. + */ + public void showListClientPackage(ClientPackageList clientPackages) { + int count = clientPackages.getClientPackageCount(); + if (count == 0) { + show(LIST_NO_MESSAGE + "packages"); + return; + } + show(LIST_MESSAGE + "packages:"); + for (int i = 0; i < count; i++) { + ClientPackage currPackage = clientPackages.getClientPackageByIndex(i); + show((i + 1) + ". " + currPackage + "\n" + "\n"); + } + show("Total Packages: " + count); + } + + /** + * Shows an exit message to the user to acknowledge exiting the application. + */ + public void showBye() { + show(BYE_MESSAGE); + showLine(); + } + + /** + * Shows error message for corrupted storage file. + */ + public void showFileError() { + showLine(); + show(FILE_CORRUPT); + showLine(); + } +} + diff --git a/src/main/java/seedu/duke/commands/ByeCommand.java b/src/main/java/seedu/duke/commands/ByeCommand.java new file mode 100644 index 0000000000..6dc3113f35 --- /dev/null +++ b/src/main/java/seedu/duke/commands/ByeCommand.java @@ -0,0 +1,23 @@ +package seedu.duke.commands; + +/** + * Acknowledge that the user is exiting the application. + */ +public class ByeCommand extends Command { + + + @Override + public void execute() { + ui.showBye(); + } + + /** + * Sets the exit condition to true since ByeCommand is initialised. + * + * @return true + */ + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/seedu/duke/commands/Command.java b/src/main/java/seedu/duke/commands/Command.java new file mode 100644 index 0000000000..6ee95417d3 --- /dev/null +++ b/src/main/java/seedu/duke/commands/Command.java @@ -0,0 +1,51 @@ +package seedu.duke.commands; + +import seedu.duke.Ui; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +/** + * Abstract executable command. + */ +public abstract class Command { + protected ClientList clients; + protected FlightList flights; + protected TourList tours; + protected ClientPackageList clientPackages; + protected Ui ui; + + /** + * Executes the specific command depending on the command constructed. + */ + public abstract void execute(); + + /** + * Setter for ClientList, FlightList, TourList, ClientPackageList and Ui + * for subclasses of Command to access when executing their command. + * + * @param clients list of all clients + * @param flights list of all flights + * @param tours list of all tours + * @param clientPackages list of all client packages + * @param ui user interface in charge of reading user input and showing messages to user + */ + public void setData(ClientList clients, FlightList flights, TourList tours, + ClientPackageList clientPackages, Ui ui) { + this.clients = clients; + this.flights = flights; + this.tours = tours; + this.clientPackages = clientPackages; + this.ui = ui; + } + + /** + * Function that controls the exit condition of the loop in TourPlanner class. + * + * @return the exit condition from the loop + */ + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/seedu/duke/commands/HelpCommand.java b/src/main/java/seedu/duke/commands/HelpCommand.java new file mode 100644 index 0000000000..45303bae6d --- /dev/null +++ b/src/main/java/seedu/duke/commands/HelpCommand.java @@ -0,0 +1,7 @@ +package seedu.duke.commands; + +public class HelpCommand extends Command { + public void execute() { + ui.showHelp(); + } +} diff --git a/src/main/java/seedu/duke/commands/clientpackages/AddClientPackageCommand.java b/src/main/java/seedu/duke/commands/clientpackages/AddClientPackageCommand.java new file mode 100644 index 0000000000..3952ae15a6 --- /dev/null +++ b/src/main/java/seedu/duke/commands/clientpackages/AddClientPackageCommand.java @@ -0,0 +1,124 @@ +package seedu.duke.commands.clientpackages; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.Flight; +import seedu.duke.data.Tour; + +import java.util.ArrayList; + +public class AddClientPackageCommand extends Command { + public static final String ERROR_CLIENT_PACKAGE_ID_EXISTS = + "ERROR: Package ID already exists. Please try another Package ID."; + public static final String ERROR_CLIENT_PACKAGE_SAME_FIELDS = + "ERROR: Package with same fields already exists with different ID."; + + private ClientPackage clientPackage; + private String[] rawClientPackage; + + /** + * Class constructor of AddClientPackage, used to add a ClientPackage to ClientPackageList. + * + * @param rawClientPackage the array of values of the client package, ordered in this manner: + * package id, client id, tour id and flight id + */ + public AddClientPackageCommand(String[] rawClientPackage) { + this.rawClientPackage = rawClientPackage; + } + + /** + * Executes the addition of a ClientPackage to a ClientPackageList. + * If given client package ID already exists, the client package will not be added. + */ + @Override + public void execute() { + try { + createClientPackage(); + ArrayList checkClientPackage = clientPackages.getClientPackages(); + for (ClientPackage currClientPackage : checkClientPackage) { + boolean sameId = currClientPackage.getId().equals(clientPackage.getId()); + if (sameId) { + System.out.println(ERROR_CLIENT_PACKAGE_ID_EXISTS); + return; + } + boolean sameClient = currClientPackage.getClient().equals(clientPackage.getClient()); + boolean sameTour = currClientPackage.getTour().equals(clientPackage.getTour()); + boolean sameFlight = currClientPackage.getFlight().equals(clientPackage.getFlight()); + if (sameClient && sameTour && sameFlight) { + System.out.println(ERROR_CLIENT_PACKAGE_SAME_FIELDS); + return; + } + } + if (!clientPackage.getClient().equals(null) && !clientPackage.getTour().equals(null) + && !clientPackage.getFlight().equals(null)) { + clientPackages.add(clientPackage); + ui.showAdd(clientPackage); + } + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } + + /** + * Executes the addition of a ClientPackage to a ClientPackageList for Storage class. + * If given client package ID already exists, the client package will not be added. + */ + public void executeStorage() { + try { + createClientPackage(); + ArrayList checkClientPackage = clientPackages.getClientPackages(); + for (ClientPackage currClientPackage : checkClientPackage) { + boolean sameId = currClientPackage.getId().equals(clientPackage.getId()); + if (sameId) { + System.out.println(ERROR_CLIENT_PACKAGE_ID_EXISTS); + return; + } + boolean sameClient = currClientPackage.getClient().equals(clientPackage.getClient()); + boolean sameTour = currClientPackage.getTour().equals(clientPackage.getTour()); + boolean sameFlight = currClientPackage.getFlight().equals(clientPackage.getFlight()); + if (sameClient && sameTour && sameFlight) { + System.out.println(ERROR_CLIENT_PACKAGE_SAME_FIELDS); + return; + } + } + clientPackages.add(clientPackage); + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } + + /** + * Getter for the clientPackage to be added to clientPackages. + * + * @return Client Package that will be added to the ClientPackageList + */ + public ClientPackage getClientPackage() { + return clientPackage; + } + + private void createClientPackage() throws TourPlannerException { + String clientPackageId = rawClientPackage[0]; + String clientId = rawClientPackage[1]; + String tourId = rawClientPackage[2]; + String flightId = rawClientPackage[3]; + Client client = extractClient(clientId); + Tour tour = extractTour(tourId); + Flight flight = extractFlight(flightId); + clientPackage = new ClientPackage(clientPackageId, client, tour, flight); + } + + private Client extractClient(String clientId) throws TourPlannerException { + return clients.getClientById(clientId); + } + + private Tour extractTour(String tourId) throws TourPlannerException { + return tours.getTourById(tourId); + } + + private Flight extractFlight(String flightId) throws TourPlannerException { + return flights.getFlightById(flightId); + } + +} diff --git a/src/main/java/seedu/duke/commands/clientpackages/CutClientPackageCommand.java b/src/main/java/seedu/duke/commands/clientpackages/CutClientPackageCommand.java new file mode 100644 index 0000000000..f10232907f --- /dev/null +++ b/src/main/java/seedu/duke/commands/clientpackages/CutClientPackageCommand.java @@ -0,0 +1,39 @@ +package seedu.duke.commands.clientpackages; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientPackage; + +public class CutClientPackageCommand extends Command { + private final String clientPackageId; + private ClientPackage clientPackage; + + /** + * Class constructor for CutClientPackageCommand. + * + * @param clientPackageId ID of to-be-deleted clientPackage in the clientPackage list + */ + public CutClientPackageCommand(String clientPackageId) { + this.clientPackageId = clientPackageId; + } + + /** + * Executes the deletion of a specific clientPackage from clientPackage list, according to the clientPackageId. + * If client package id cannot be found, nothing will be deleted from the list. + */ + @Override + public void execute() { + try { + this.clientPackage = clientPackages.getClientPackageById(clientPackageId); + int newClientPackageCount = clientPackages.getClientPackageCount() - 1; + ui.showCut(clientPackage); + clientPackages.cut(clientPackage); + assert newClientPackageCount == clientPackages.getClientPackageCount(); + assert newClientPackageCount >= 0; + } catch (IndexOutOfBoundsException e) { + System.out.println("INVALID: Index out of bounds"); + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/commands/clientpackages/ListClientPackageCommand.java b/src/main/java/seedu/duke/commands/clientpackages/ListClientPackageCommand.java new file mode 100644 index 0000000000..fb669da7e2 --- /dev/null +++ b/src/main/java/seedu/duke/commands/clientpackages/ListClientPackageCommand.java @@ -0,0 +1,15 @@ +package seedu.duke.commands.clientpackages; + +import seedu.duke.commands.Command; + +/** + * Lists all client packages in the database. + */ +public class ListClientPackageCommand extends Command { + /** + * Executes the listing of client packages to the terminal. + */ + public void execute() { + ui.showListClientPackage(clientPackages); + } +} diff --git a/src/main/java/seedu/duke/commands/clients/AddClientCommand.java b/src/main/java/seedu/duke/commands/clients/AddClientCommand.java new file mode 100644 index 0000000000..1a2586aab5 --- /dev/null +++ b/src/main/java/seedu/duke/commands/clients/AddClientCommand.java @@ -0,0 +1,57 @@ +package seedu.duke.commands.clients; + +import seedu.duke.commands.Command; +import seedu.duke.data.Client; + +/** + * Adds a client into the database. + */ +public class AddClientCommand extends Command { + private final Client client; + + + /** + * Class constructor for AddClientCommand. + * + * @param client client to be added + */ + public AddClientCommand(Client client) { + this.client = client; + } + + /** + * Returns the client object that was added. + * + * @return the added client object + */ + public Client getClient() { + return client; + } + + /** + * Executes the addition of client to client list. + * If given client ID already exists, the client will not be added. + */ + @Override + public void execute() { + + int count = clients.getClientCount(); + for (int i = 0; i < count; i++) { + Client currClient = clients.getClientByIndex(i); + boolean sameId = currClient.getId().equals(client.getId()); + if (sameId) { + System.out.println("ERROR: Client ID already exists. Please try another client code."); + return; + } + boolean sameName = currClient.getName().equals(client.getName()); + boolean sameContactNum = currClient.getContactNum().equals(client.getContactNum()); + boolean sameEmail = currClient.getEmail().equals(client.getEmail()); + if (sameName && sameContactNum && sameEmail) { + System.out.println("ERROR: Client with same fields already exists with different ID."); + return; + } + } + clients.add(client); + ui.showAdd(client); + } +} diff --git a/src/main/java/seedu/duke/commands/clients/CutClientCommand.java b/src/main/java/seedu/duke/commands/clients/CutClientCommand.java new file mode 100644 index 0000000000..631d121fbf --- /dev/null +++ b/src/main/java/seedu/duke/commands/clients/CutClientCommand.java @@ -0,0 +1,64 @@ +package seedu.duke.commands.clients; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientPackage; + +import java.util.ArrayList; + +public class CutClientCommand extends Command { + private final String clientId; + private Client client; + + /** + * Class constructor for CutClientCommand. + * + * @param clientId ID of to-be-deleted client in the client list + */ + public CutClientCommand(String clientId) { + this.clientId = clientId; + } + + /** + * Executes deletion of the specific client and related client packages, according to the clientId. + */ + @Override + public void execute() { + try { + cutClient(); + cutClientPackage(); + } catch (IndexOutOfBoundsException e1) { + System.out.println("INVALID: Index out of bounds"); + } catch (TourPlannerException e2) { + System.out.println(e2.getMessage()); + } + } + + /** + * Executes deletion of the specific client from the client list. + * + * @throws TourPlannerException if client cannot be found based on the client id + */ + private void cutClient() throws TourPlannerException { + this.client = clients.getClientById(clientId); + final int newClientCount = clients.getClientCount() - 1; + ui.showCut(client); + ui.showLine(); + clients.cut(client); + assert newClientCount == clients.getClientCount(); + assert newClientCount >= 0; + } + + /** + * Executes deletion of the client packages containing the specific client from the client package list. + */ + private void cutClientPackage() { + ArrayList clientPackagesWithClient = clientPackages.getClientPackageByClient(client); + for (ClientPackage clientPackage: clientPackagesWithClient) { + System.out.println(); + ui.showCut(clientPackage); + clientPackages.cut(clientPackage); + } + } +} diff --git a/src/main/java/seedu/duke/commands/clients/FindClientCommand.java b/src/main/java/seedu/duke/commands/clients/FindClientCommand.java new file mode 100644 index 0000000000..2d43936c10 --- /dev/null +++ b/src/main/java/seedu/duke/commands/clients/FindClientCommand.java @@ -0,0 +1,27 @@ +package seedu.duke.commands.clients; + +import seedu.duke.commands.Command; + +/** + * Finds client(s) based on a certain substring. + */ +public class FindClientCommand extends Command { + private final String substring; + + /** + * Class constructor for FindClientCommand. + * + * @param substring substring used to determine which client(s) to find. + */ + public FindClientCommand(String substring) { + this.substring = substring; + } + + /** + * Executes the finding of client, as well as finding said client's packages. + * If there are no found clients, it will be shown to the user. + */ + public void execute() { + ui.showFindClient(clients, clientPackages, substring); + } +} diff --git a/src/main/java/seedu/duke/commands/clients/ListClientCommand.java b/src/main/java/seedu/duke/commands/clients/ListClientCommand.java new file mode 100644 index 0000000000..29dc3ca241 --- /dev/null +++ b/src/main/java/seedu/duke/commands/clients/ListClientCommand.java @@ -0,0 +1,15 @@ +package seedu.duke.commands.clients; + +import seedu.duke.commands.Command; + +/** + * Lists all clients in the database. + */ +public class ListClientCommand extends Command { + /** + * Executes the listing of clients to the terminal. + */ + public void execute() { + ui.showListClient(clients); + } +} diff --git a/src/main/java/seedu/duke/commands/clients/SortClientCommand.java b/src/main/java/seedu/duke/commands/clients/SortClientCommand.java new file mode 100644 index 0000000000..c55d468e31 --- /dev/null +++ b/src/main/java/seedu/duke/commands/clients/SortClientCommand.java @@ -0,0 +1,55 @@ +package seedu.duke.commands.clients; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; + +import java.util.ArrayList; + +/** + * Print a sorted list of clients, with the method of sorting specified by a filter. + * Sorts name of client, or ID of client alphabetically. + */ +public class SortClientCommand extends Command { + public static final String ERROR_MISSING_FILTER = "Missing filter! Sort clients with the format: \n" + + "sort -c /n (sort by name)\n" + + "sort -c /id (sort by id)"; + public static final String SORT_NAME_FILTER = "/n"; + public static final String SORT_ID_FILTER = "/id"; + + private final String filter; + + /** + * Class constructor for SortClientCommand. + * Defines filter input by user. + * + * @param filter the user input filter that specifies how the clients should be sorted + */ + public SortClientCommand(String filter) { + this.filter = filter; + } + + /** + * Executes the printing of the sorted client list, specified by the filter. + * If filter given is /n, sorts the clients by name (in alphabetical order). + * If filter given is /id, sorts the clients by ID (in alphabetical order). + */ + @Override + public void execute() { + try { + switch (filter) { + case SORT_NAME_FILTER: + ArrayList sortedNames = clients.getSortedClientNames(); + ui.showSortedClientByName(clients, sortedNames); + break; + case SORT_ID_FILTER: + ArrayList sortedIds = clients.getSortedClientIds(); + ui.showSortedClientById(clients, sortedIds); + break; + default: + throw new TourPlannerException(ERROR_MISSING_FILTER); + } + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/commands/flights/AddFlightCommand.java b/src/main/java/seedu/duke/commands/flights/AddFlightCommand.java new file mode 100644 index 0000000000..fe41becbf7 --- /dev/null +++ b/src/main/java/seedu/duke/commands/flights/AddFlightCommand.java @@ -0,0 +1,61 @@ +package seedu.duke.commands.flights; + +import seedu.duke.commands.Command; +import seedu.duke.data.Flight; + +/** + * Adds a flight into the database. + */ +public class AddFlightCommand extends Command { + private final Flight flight; + + /** + * Class constructor for AddFlightCommand. + * + * @param flight client to be added + */ + public AddFlightCommand(Flight flight) { + this.flight = flight; + } + + /** + * Returns the flight object that was added. + * + * @return the added flight object + */ + public Flight getFlight() { + return flight; + } + + /** + * Executes the addition of flight to flight list. + * If given flight ID already exists, the flight will not be added. + */ + @Override + public void execute() { + int count = flights.getFlightCount(); + for (int i = 0; i < count; i++) { + Flight currFlight = flights.getFlightByIndex(i); + boolean sameId = currFlight.getId().equals(flight.getId()); + if (sameId) { + System.out.println("ERROR: Flight ID already exists. Please try another flight ID."); + return; + } + boolean sameDepartDestination = + currFlight.getDepartDestination().equals(flight.getDepartDestination()); + boolean sameReturnDestination = + currFlight.getReturnDestination().equals(flight.getReturnDestination()); + boolean sameDepartDate = + currFlight.getDepartDate().equals(flight.getDepartDate()); + boolean sameReturnDate = + currFlight.getReturnDate().equals(flight.getReturnDate()); + if (sameDepartDestination && sameReturnDestination + && sameDepartDate && sameReturnDate) { + System.out.println("ERROR: Flight with same fields already exists with different ID."); + return; + } + } + flights.add(flight); + ui.showAdd(flight); + } +} diff --git a/src/main/java/seedu/duke/commands/flights/CutFlightCommand.java b/src/main/java/seedu/duke/commands/flights/CutFlightCommand.java new file mode 100644 index 0000000000..f2e4ecc19d --- /dev/null +++ b/src/main/java/seedu/duke/commands/flights/CutFlightCommand.java @@ -0,0 +1,64 @@ +package seedu.duke.commands.flights; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.Flight; + +import java.util.ArrayList; + +public class CutFlightCommand extends Command { + private final String flightId; + private Flight flight; + + /** + * Class constructor for CutFlightCommand. + * + * @param flightId ID of to-be-deleted flight in the flight list + */ + public CutFlightCommand(String flightId) { + this.flightId = flightId; + } + + /** + * Executes deletion of the specific flight and related client packages, according to the flightId. + */ + @Override + public void execute() { + try { + cutFlight(); + cutFlightPackage(); + } catch (IndexOutOfBoundsException e1) { + System.out.println("INVALID: Index out of bounds"); + } catch (TourPlannerException e2) { + System.out.println(e2.getMessage()); + } + } + + /** + * Executes deletion of the specific flight from the flight list. + * + * @throws TourPlannerException if flight cannot be found based on the flight id + */ + private void cutFlight() throws TourPlannerException { + this.flight = flights.getFlightById(flightId); + int newFlightCount = flights.getFlightCount() - 1; + ui.showCut(flight); + flights.cut(flight); + assert newFlightCount == flights.getFlightCount(); + assert newFlightCount >= 0; + } + + /** + * Executes deletion of the client packages containing the specific flight from the client package list. + */ + private void cutFlightPackage() { + ArrayList clientPackagesWithFlight = clientPackages.getClientPackageByFlight(flight); + for (ClientPackage clientPackage: clientPackagesWithFlight) { + System.out.println(); + ui.showCut(clientPackage); + clientPackages.cut(clientPackage); + } + } +} + diff --git a/src/main/java/seedu/duke/commands/flights/FindFlightCommand.java b/src/main/java/seedu/duke/commands/flights/FindFlightCommand.java new file mode 100644 index 0000000000..8a80cce3a4 --- /dev/null +++ b/src/main/java/seedu/duke/commands/flights/FindFlightCommand.java @@ -0,0 +1,32 @@ +package seedu.duke.commands.flights; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; + +/** + * Finds a flight based on a certain id. + */ +public class FindFlightCommand extends Command { + private final String id; + + /** + * Class constructor for FindFlightCommand. + * + * @param id id used to determine which flight to find. + */ + public FindFlightCommand(String id) { + this.id = id; + } + + /** + * Executes the finding of flight, as well as finding said flight's passengers. + * If there are no found flights, it will be shown to the user. + */ + public void execute() { + try { + ui.showFindFlight(flights, clientPackages, id); + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/commands/flights/ListFlightCommand.java b/src/main/java/seedu/duke/commands/flights/ListFlightCommand.java new file mode 100644 index 0000000000..504ba78c86 --- /dev/null +++ b/src/main/java/seedu/duke/commands/flights/ListFlightCommand.java @@ -0,0 +1,15 @@ +package seedu.duke.commands.flights; + +import seedu.duke.commands.Command; + +/** + * Lists all flights in the database. + */ +public class ListFlightCommand extends Command { + /** + * Executes the listing of flights to the terminal. + */ + public void execute() { + ui.showListFlight(flights); + } +} diff --git a/src/main/java/seedu/duke/commands/flights/SortFlightCommand.java b/src/main/java/seedu/duke/commands/flights/SortFlightCommand.java new file mode 100644 index 0000000000..8740286f78 --- /dev/null +++ b/src/main/java/seedu/duke/commands/flights/SortFlightCommand.java @@ -0,0 +1,59 @@ +package seedu.duke.commands.flights; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; + +import java.util.ArrayList; + +/** + * Print a sorted list of flights, with the method of sorting specified by a filter. + * Sorts departure time and return time of flight by time proximity. + * Sorts flight ID by alphabetical order. + */ +public class SortFlightCommand extends Command { + private final String filter; + public static final String ERROR_MISSING_FILTER = "Missing filter! Sort flights with the format: \n" + + "sort -f /d (sort by departure date and time) \n" + + "sort -f /r (sort by return date and time) \n" + + "sort -f /id (sort by id alphabetically)"; + + /** + * Class constructor for SortFlightCommand. + * Defines filter input by user. + * + * @param filter the user input filter that specifies how the flights should be sorted + */ + public SortFlightCommand(String filter) { + this.filter = filter; + } + + /** + * Executes the printing of the sorted flight list, specified by the filter. + * If filter given is /d, sorts the departure times (by natural order of time). + * If filter given is /r, sorts the return times (by natural order of time). + * If filter given is /id, sorts the flights by id (in alphabetical order). + */ + @Override + public void execute() { + try { + switch (filter) { + case "/d": + ArrayList sortedFlightByDepartureDates = flights.getSortedDepartDates(); + ui.showSortedFlightByDeparture(flights, sortedFlightByDepartureDates); + break; + case "/r": + ArrayList sortedFlightByReturnDates = flights.getSortedReturnDates(); + ui.showSortedFlightByReturn(flights, sortedFlightByReturnDates); + break; + case "/id": + ArrayList sortedIds = flights.getSortedFlightIds(); + ui.showSortedFlightById(flights, sortedIds); + break; + default: + throw new TourPlannerException(ERROR_MISSING_FILTER); + } + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/commands/tours/AddTourCommand.java b/src/main/java/seedu/duke/commands/tours/AddTourCommand.java new file mode 100644 index 0000000000..1c8ceb7dea --- /dev/null +++ b/src/main/java/seedu/duke/commands/tours/AddTourCommand.java @@ -0,0 +1,58 @@ +package seedu.duke.commands.tours; + +import seedu.duke.commands.Command; +import seedu.duke.data.Tour; + +/** + * Adds a tour into the database. + */ +public class AddTourCommand extends Command { + + private final Tour tour; + + /** + * Class constructor for AddTourCommand. + * + * @param tour tour to be added + */ + public AddTourCommand(Tour tour) { + this.tour = tour; + } + + /** + * Returns the tour object that was added. + * + * @return the added tour object + */ + public Tour getTour() { + return tour; + } + + /** + * Executes the addition of tour to tour list. + * If given tour code already exists, the tour will not be added. + */ + @Override + public void execute() { + final int newTourCount = tours.getTourCount() + 1; + int count = tours.getTourCount(); + for (int i = 0; i < count; i++) { + Tour currTour = tours.getTourByIndex(i); + boolean sameId = currTour.getId().equals(tour.getId()); + if (sameId) { + System.out.println("ERROR: Tour code already exists. Please try another tour code."); + return; + } + boolean sameName = currTour.getName().equals(tour.getName()); + boolean samePrice = currTour.getPrice().equals(tour.getPrice()); + if (sameName && samePrice) { + System.out.println("ERROR: Tour with same fields already exists with different ID."); + return; + } + } + tours.add(tour); + ui.showAdd(tour); + assert newTourCount == tours.getTourCount(); + } + +} diff --git a/src/main/java/seedu/duke/commands/tours/CutTourCommand.java b/src/main/java/seedu/duke/commands/tours/CutTourCommand.java new file mode 100644 index 0000000000..929d985bff --- /dev/null +++ b/src/main/java/seedu/duke/commands/tours/CutTourCommand.java @@ -0,0 +1,64 @@ +package seedu.duke.commands.tours; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.Tour; + +import java.util.ArrayList; + +public class CutTourCommand extends Command { + private final String tourId; + private Tour tour; + + /** + * Class constructor for CutTourCommand. + * + * @param tourId ID of to-be-deleted tour in the tour list + */ + public CutTourCommand(String tourId) { + this.tourId = tourId; + } + + /** + * Executes deletion of the specific tour and related client packages, according to the tourId. + */ + @Override + public void execute() { + try { + cutTour(); + cutTourPackage(); + } catch (IndexOutOfBoundsException e1) { + System.out.println("INVALID: Index out of bounds"); + } catch (TourPlannerException e2) { + System.out.println(e2.getMessage()); + } + } + + /** + * Executes deletion of the specific tour from the tour list. + * + * @throws TourPlannerException if tour cannot be found based on the tour id + */ + private void cutTour() throws TourPlannerException { + this.tour = tours.getTourById(tourId); + int newTourCount = tours.getTourCount() - 1; + ui.showCut(tour); + tours.cut(tour); + assert newTourCount == tours.getTourCount(); + assert newTourCount >= 0; + } + + /** + * Executes deletion of the client packages containing the specific tour from the client package list. + */ + private void cutTourPackage() { + ArrayList clientPackagesWithTour = clientPackages.getClientPackageByTour(tour); + for (ClientPackage clientPackage: clientPackagesWithTour) { + System.out.println(); + ui.showCut(clientPackage); + clientPackages.cut(clientPackage); + } + } + +} diff --git a/src/main/java/seedu/duke/commands/tours/FindTourCommand.java b/src/main/java/seedu/duke/commands/tours/FindTourCommand.java new file mode 100644 index 0000000000..ee801cce24 --- /dev/null +++ b/src/main/java/seedu/duke/commands/tours/FindTourCommand.java @@ -0,0 +1,32 @@ +package seedu.duke.commands.tours; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; + +/** + * Finds a tour based on a certain id. + */ +public class FindTourCommand extends Command { + private final String id; + + /** + * Class constructor for FindTourCommand. + * + * @param id id used to determine which tour to find. + */ + public FindTourCommand(String id) { + this.id = id; + } + + /** + * Executes the finding of tour, as well as finding said tour's subscribers. + * If there are no found tours, it will be shown to the user. + */ + public void execute() { + try { + ui.showFindTour(tours, clientPackages, id); + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/commands/tours/ListTourCommand.java b/src/main/java/seedu/duke/commands/tours/ListTourCommand.java new file mode 100644 index 0000000000..fc89ca2096 --- /dev/null +++ b/src/main/java/seedu/duke/commands/tours/ListTourCommand.java @@ -0,0 +1,15 @@ +package seedu.duke.commands.tours; + +import seedu.duke.commands.Command; + +/** + * Lists all tours in the database. + */ +public class ListTourCommand extends Command { + /** + * Executes the listing of tours to the terminal. + */ + public void execute() { + ui.showListTour(tours); + } +} diff --git a/src/main/java/seedu/duke/commands/tours/SortTourCommand.java b/src/main/java/seedu/duke/commands/tours/SortTourCommand.java new file mode 100644 index 0000000000..16a7621fd3 --- /dev/null +++ b/src/main/java/seedu/duke/commands/tours/SortTourCommand.java @@ -0,0 +1,57 @@ +package seedu.duke.commands.tours; + +import seedu.duke.TourPlannerException; +import seedu.duke.commands.Command; + +import java.util.ArrayList; + +/** + * Print a sorted list of tours, with the method of sorting specified by a filter. + * Sorts tour prices by ascending order. + * Sorts tour ID by alphabetical order. + */ +public class SortTourCommand extends Command { + private final String filter; + public static final String ERROR_MISSING_FILTER = "Missing filter! Sort tours with the format: \n" + + "sort -t /id (sort by ID alphabetically) \n" + + "sort -t /p (sort by increasing price)"; + + /** + * Class constructor for SortTourCommand. + * Defines filter input by user. + * + * @param filter the user input filter that specifies how the tours should be sorted + */ + public SortTourCommand(String filter) { + this.filter = filter; + } + + /** + * Executes the printing of the sorted flight list, specified by the filter. + * If filter given is /id, sorts the tours by id (in alphabetical order). + * If filter given is /r, sorts the tours by price (in ascending order). + */ + @Override + public void execute() { + try { + switch (filter) { + case "/id": + ArrayList sortedIds = tours.getSortedTourIds(); + ui.showSortedTourById(tours, sortedIds); + break; + case "/p": + ArrayList sortedPrices = tours.getSortedTourPrices(); + ui.showSortedTourByPrice(tours, sortedPrices); + break; + case "/n": + ArrayList sortedNames = tours.getSortedTourNames(); + ui.showSortedTourByName(tours, sortedNames); + break; + default: + throw new TourPlannerException(ERROR_MISSING_FILTER); + } + } catch (TourPlannerException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/data/Client.java b/src/main/java/seedu/duke/data/Client.java new file mode 100644 index 0000000000..5093be2a57 --- /dev/null +++ b/src/main/java/seedu/duke/data/Client.java @@ -0,0 +1,74 @@ +package seedu.duke.data; + +/** + * Represents a client under the Tour agency. + */ +public class Client { + private final String id; + private final String name; + private final String contactNum; + private final String email; + + /** + * Client object constructor, that inputs the client's information obtained from Add command. + * + * @param values the array of values of the client, ordered in this manner: + * name, contact number, email + */ + public Client(String[] values) { + id = values[0]; + name = values[1]; + contactNum = values[2]; + email = values[3]; + } + + /** + * Getter for id value in Client object. + * + * @return client's id + */ + public String getId() { + return id; + } + + /** + * Getter for name value in Client object. + * + * @return client's name + */ + public String getName() { + return name; + } + + /** + * Getter for contact number value in Client object. + * + * @return client's contact number + */ + public String getContactNum() { + return contactNum; + } + + /** + * Getter for email value in Client object. + * + * @return client's email + */ + public String getEmail() { + return email; + } + + /** + * Formats client's information as a string that is viewable as an indexed list item. + * + * @return the formatted string containing client's information + */ + @Override + public String toString() { + + return "Client ID: " + id + "\n" + + "Name: " + name + "\n" + + "Contact Number: " + contactNum + "\n" + + "Email: " + email; + } +} diff --git a/src/main/java/seedu/duke/data/ClientList.java b/src/main/java/seedu/duke/data/ClientList.java new file mode 100644 index 0000000000..ea430635a5 --- /dev/null +++ b/src/main/java/seedu/duke/data/ClientList.java @@ -0,0 +1,151 @@ +package seedu.duke.data; + +import seedu.duke.TourPlannerException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * List of clients. + */ +public class ClientList { + public static final String CLIENT_NOT_FOUND_MESSAGE = "ERROR: Client cannot be found. " + + "Please try another client ID"; + + private final ArrayList clients; + private final ArrayList clientIds; + private final ArrayList clientNames; + private ArrayList iteratedClientIds; + private int clientCount; + + /** + * Class constructor for ClientList. + */ + public ClientList() { + clients = new ArrayList<>(); + clientIds = new ArrayList<>(); + clientNames = new ArrayList<>(); + clientCount = 0; + } + + /** + * Getter for clients. + * + * @return ArrayList containing all clients in the database. + */ + public ArrayList getClients() { + return clients; + } + + /** + * Getter for number of clients in the client list. + * + * @return the number of clients in client list. + */ + public int getClientCount() { + return clientCount; + } + + /** + * Getter for client object in the client list, corresponding to the index given. + * + * @param index the index of the specific client in the client list + * @return the client object corresponding to the index + */ + public Client getClientByIndex(int index) { + return clients.get(index); + } + + /** + * Getter for client object in the client list, corresponding to the client id given. + * + * @param clientId client id of the specific client in the client list + * @return the client object corresponding to the client id + * @throws TourPlannerException if client object with clientId cannot be found in clients + */ + public Client getClientById(String clientId) throws TourPlannerException { + for (Client client : clients) { + if (client.getId().equals(clientId)) { + return client; + } + } + throw new TourPlannerException(CLIENT_NOT_FOUND_MESSAGE); + } + + /** + * Getter for client object in the client list, corresponding to the client name given. + * + * @param clientName the name of a client in the clientList + * @return the client object corresponding to the name + */ + public Client getClientByName(String clientName) throws TourPlannerException { + for (Client currClient : clients) { + String clientId = currClient.getId(); + String currClientName = currClient.getName(); + if (currClientName.equals(clientName) && !iteratedClientIds.contains(clientId)) { + iteratedClientIds.add(clientId); + return currClient; + } + } + throw new TourPlannerException(CLIENT_NOT_FOUND_MESSAGE); + } + + /** + * Getter for the sorted list of client IDs. + * Sorts the client IDs by the natural ordering of String, ignoring case sensitivities. + * + * @return the list of sorted client IDs, by natural(alphabetical, numerical) order + * @see Collections#sort(List, Comparator) + */ + public ArrayList getSortedClientIds() { + Collections.sort(clientIds, String.CASE_INSENSITIVE_ORDER); + return clientIds; + } + + /** + * Getter for the sorted list of client names. + * Sorts the client names by the natural ordering of String, ignoring case sensitivities. + * + * @return the list of sorted client names, by natural(alphabetical, numerical) order + * @see Collections#sort(List, Comparator) + */ + public ArrayList getSortedClientNames() { + Collections.sort(clientNames, String.CASE_INSENSITIVE_ORDER); + return clientNames; + } + + /** + * Creates a new temporary array each time the function is called. + * The flight IDs that have been iterated by Ui in the sort command will be added into FlightList's + * temporary array to prevent duplicates. + */ + public void initTempArray() { + iteratedClientIds = new ArrayList<>(); + } + + /** + * Main method for adding a client. + * + * @param client the client to-be-added + */ + public void add(Client client) { + clientCount++; + clients.add(client); + clientIds.add(client.getId()); + clientNames.add(client.getName()); + } + + /** + * Main method for deleting a client. + * + * @param client the client-to-be-deleted + */ + public void cut(Client client) { + clientIds.remove(client.getId()); + clientNames.remove(client.getName()); + clients.remove(client); + clientCount--; + } +} diff --git a/src/main/java/seedu/duke/data/ClientPackage.java b/src/main/java/seedu/duke/data/ClientPackage.java new file mode 100644 index 0000000000..375f4168b5 --- /dev/null +++ b/src/main/java/seedu/duke/data/ClientPackage.java @@ -0,0 +1,78 @@ +package seedu.duke.data; + +/** + * Represents a packaged client under the Tour agency. + */ +public class ClientPackage { + private final String clientPackageId; + private final Client client; + private final Tour tour; + private final Flight flight; + + /** + * Class constructor for ClientPackage. + * + * @param clientPackageId unique ID of client package to identify it by + * @param client client to be added to the client package + * @param tour tour to be added to the client package + * @param flight flight to be added to the client package + */ + public ClientPackage(String clientPackageId, Client client, Tour tour, Flight flight) { + this.clientPackageId = clientPackageId; + this.client = client; + this.tour = tour; + this.flight = flight; + } + + /** + * Getter for Client object of client in the client package. + * + * @return client in the client package + */ + public Client getClient() { + return client; + } + + /** + * Getter for Tour object of tour in the client package. + * + * @return tour in the client package + */ + public Tour getTour() { + return tour; + } + + /** + * Getter for Flight object of flight in the client package. + * + * @return flight in the client package + */ + public Flight getFlight() { + return flight; + } + + /** + * Getter for id value of ClientPackage object. + * + * @return id of the client package + */ + public String getId() { + return clientPackageId; + } + + /** + * Formats client package's information as a string that is viewable as an indexed list item. + * + * @return the formatted string containing client package's information + */ + @Override + public String toString() { + return "Package ID: " + clientPackageId + "\n\n" + + "Client: " + "\n" + + client + "\n\n" + + "Tour: " + "\n" + + tour + "\n\n" + + "Flight: " + "\n" + + flight; + } +} diff --git a/src/main/java/seedu/duke/data/ClientPackageList.java b/src/main/java/seedu/duke/data/ClientPackageList.java new file mode 100644 index 0000000000..70cdab4d19 --- /dev/null +++ b/src/main/java/seedu/duke/data/ClientPackageList.java @@ -0,0 +1,140 @@ +package seedu.duke.data; + +import seedu.duke.TourPlannerException; + +import java.util.ArrayList; + +/** + * List of client packages. + */ +public class ClientPackageList { + public static final String CLIENTPACKAGE_NOT_FOUND_MESSAGE + = "Client Package cannot be found. Please try another client package ID"; + + private final ArrayList clientPackages; + private int clientPackageCount; + + /** + * Class constructor for ClientPackageList. + */ + public ClientPackageList() { + clientPackages = new ArrayList<>(); + clientPackageCount = 0; + } + + /** + * Getter for clientPackages. + * + * @return ArrayList containing all client packages in the database + */ + public ArrayList getClientPackages() { + return clientPackages; + } + + /** + * Getter for ClientPackage object according to its index in clientPackages. + * + * @param index index of the client package in clientPackages + * @return ClientPackage object of the corresponding index + */ + public ClientPackage getClientPackageByIndex(int index) { + return clientPackages.get(index); + } + + /** + * Getter for ClientPackage object according to its clientPackageId. + * + * @param clientPackageId id of the client package to get from clientPackages + * @return ClientPackage object of the corresponding client package id + * @throws TourPlannerException if client package containing clientPackageId cannot be found in clientPackages + */ + public ClientPackage getClientPackageById(String clientPackageId) throws TourPlannerException { + for (int i = 0; i < clientPackageCount; i++) { + if (clientPackages.get(i).getId().equals(clientPackageId)) { + return clientPackages.get(i); + } + } + throw new TourPlannerException(CLIENTPACKAGE_NOT_FOUND_MESSAGE); + } + + /** + * Getter for ClientPackage objects that contain client. + * + * @param client client that is contained inside the client package(s) + * @return ArrayList of client packages that contain that client + */ + public ArrayList getClientPackageByClient(Client client) { + ArrayList clientPackagesWithClient = new ArrayList<>(); + for (int i = 0; i < clientPackageCount; i++) { + ClientPackage clientPackage = clientPackages.get(i); + if (clientPackage.getClient().equals(client)) { + clientPackagesWithClient.add(clientPackage); + } + } + return clientPackagesWithClient; + } + + /** + * Getter for ClientPackage objects that contain tour. + * + * @param tour tour that is contained inside the client package(s) + * @return ArrayList of client packages that contain tour + */ + public ArrayList getClientPackageByTour(Tour tour) { + ArrayList clientPackagesWithTour = new ArrayList<>(); + for (int i = 0; i < clientPackageCount; i++) { + ClientPackage clientPackage = clientPackages.get(i); + if (clientPackage.getTour().equals(tour)) { + clientPackagesWithTour.add(clientPackage); + } + } + return clientPackagesWithTour; + } + + /** + * Getter for ClientPackage objects that contain flight. + * + * @param flight flight that is contained inside the client package(s) + * @return ArrayList of client packages that contain flight + */ + public ArrayList getClientPackageByFlight(Flight flight) { + ArrayList clientPackagesWithFlight = new ArrayList<>(); + for (int i = 0; i < clientPackageCount; i++) { + ClientPackage clientPackage = clientPackages.get(i); + if (clientPackage.getFlight().equals(flight)) { + clientPackagesWithFlight.add(clientPackage); + } + } + return clientPackagesWithFlight; + } + + /** + * Getter for number of client packages in ClientPackageList clientPackages. + * + * @return clientPackageCount of clientPackages + */ + public int getClientPackageCount() { + return clientPackageCount; + } + + /** + * Adds clientPackage to clientPackages. + * + * @param clientPackage ClientPackage object to be added to clientPackages + */ + public void add(ClientPackage clientPackage) { + clientPackageCount++; + clientPackages.add(clientPackage); + } + + /** + * Cuts clientPackage from clientPackages. + * + * @param clientPackage ClientPackage object to be deleted from clientPackages + */ + public void cut(ClientPackage clientPackage) { + clientPackages.remove(clientPackage); + clientPackageCount--; + } + +} diff --git a/src/main/java/seedu/duke/data/Flight.java b/src/main/java/seedu/duke/data/Flight.java new file mode 100644 index 0000000000..eb3ebbb2ff --- /dev/null +++ b/src/main/java/seedu/duke/data/Flight.java @@ -0,0 +1,83 @@ +package seedu.duke.data; + +/** + * Represents a flight under the Tour agency. + */ +public class Flight { + private final String id; + private final String departDestination; + private final String returnDestination; + private final String departDate; + private final String returnDate; + + /** + * Flight object constructor, that inputs the flight's information obtained from Add command. + * + * @param values the array of values of the flight, ordered in this manner: + * flight ID, departure destination, return destination, departure date-time, return date-time + */ + public Flight(String[] values) { + id = values[0]; + departDestination = values[1]; + returnDestination = values[2]; + departDate = values[3]; + returnDate = values[4]; + } + + /** + * Getter for id value in Flight object. + * + * @return flight's id + */ + public String getId() { + return id; + } + + /** + * Getter for return destination in Flight object. + * + * @return flight's return destination + */ + public String getReturnDestination() { + return returnDestination; + } + + /** + * Getter for departure destination in Flight object. + * + * @return flight's departure destination + */ + public String getDepartDestination() { + return departDestination; + } + + /** + * Getter for return date-time in Flight's object. + * + * @return flight's return date-time + */ + public String getReturnDate() { + return returnDate; + } + + /** + * Getter for departure date-time in Flight's object. + * + * @return flight's departure date-time + */ + public String getDepartDate() { + return departDate; + } + + /** + * Formats flight's information as a string that is viewable as an indexed list item. + * + * @return the formatted string containing flight's information + */ + @Override + public String toString() { + return "Flight ID: " + id + "\n" + + "Departure Flight: " + departDestination + ", " + departDate + "\n" + + "Return Flight: " + returnDestination + ", " + returnDate; + } +} diff --git a/src/main/java/seedu/duke/data/FlightList.java b/src/main/java/seedu/duke/data/FlightList.java new file mode 100644 index 0000000000..c2a5be1cf8 --- /dev/null +++ b/src/main/java/seedu/duke/data/FlightList.java @@ -0,0 +1,218 @@ +package seedu.duke.data; + +import seedu.duke.TourPlannerException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * List of flights. + */ +public class FlightList { + public static final String FLIGHT_NOT_FOUND_MESSAGE = "ERROR: Flight cannot be found. " + + "Please try another flight ID."; + + private final ArrayList flights; + private final ArrayList flightIds; + private final ArrayList flightReturnDates; + private final ArrayList flightDepartureDates; + private ArrayList iteratedFlightIds; + private int flightCount; + + /** + * Class constructor for FlightList. + */ + public FlightList() { + flights = new ArrayList<>(); + flightIds = new ArrayList<>(); + flightReturnDates = new ArrayList<>(); + flightDepartureDates = new ArrayList<>(); + flightCount = 0; + } + + /** + * Getter for flight list. + * + * @return the list of flights + */ + public ArrayList getFlights() { + return flights; + } + + + /** + * Getter for number of flights in the flight list. + * + * @return the number of flights in flight list. + */ + public int getFlightCount() { + return flightCount; + } + + /** + * Getter for flight object in the flight list, corresponding to the index given. + * + * @param index the index of the specific flight in the flight list + * @return the flight object corresponding to the index + */ + public Flight getFlightByIndex(int index) { + return flights.get(index); + } + + /** + * Getter for flight object in the flight list, corresponding to the flight ID given. + * + * @param flightId the given flight ID, unique to each set of flights + * @return the flight object, specified by the flight ID + * @throws TourPlannerException if the flight with the given flight ID is not found in the database + */ + public Flight getFlightById(String flightId) throws TourPlannerException { + for (int i = 0; i < flightCount; i++) { + if (flights.get(i).getId().equals(flightId)) { + return flights.get(i); + } + } + throw new TourPlannerException(FLIGHT_NOT_FOUND_MESSAGE); + } + + /** + * Comparator for extension of Collections.sort() which does not include date-time sort functionality. + * Overrides compare method within the Comparator to compare between date-times. + * + * @see Comparator + */ + private static final Comparator dateTimeStringComparator = new Comparator<>() { + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yy HH:mm"); + + @Override + public int compare(String dateTimeStringOne, String dateTimeStringTwo) { + LocalDateTime dateTimeOne = LocalDateTime.parse(dateTimeStringOne, formatter); + LocalDateTime dateTimeTwo = LocalDateTime.parse(dateTimeStringTwo, formatter); + if (dateTimeOne.isBefore(dateTimeTwo)) { + return -1; + } else if (dateTimeOne.isEqual(dateTimeTwo)) { + return 0; + } else { + return 1; + } + } + }; + + /** + * Getter for flight object in the flight list, corresponding to the flight return date given. + * + * @param flightReturnDate the date-time of the return flight, in String + * @return the flight object, specified by the return flight's date-time + * @throws TourPlannerException if the flight with the given return flight's date-time is not found in the database + */ + public Flight getFlightByReturnDate(String flightReturnDate) throws TourPlannerException { + for (Flight currFlight : flights) { + String flightId = currFlight.getId(); + String currFlightReturnDate = currFlight.getReturnDate(); + if (currFlightReturnDate.equals(flightReturnDate) && !iteratedFlightIds.contains(flightId)) { + iteratedFlightIds.add(flightId); + return currFlight; + } + } + throw new TourPlannerException(FLIGHT_NOT_FOUND_MESSAGE); + } + + /** + * Getter for flight object in the flight list, corresponding to the flight departure date given. + * + * @param flightDepartDate the date-time of the departure flight, in String + * @return the flight object, specified by the departure flight's date-time + * @throws TourPlannerException if the flight with the given departure flight's date-time + * is not found in the database + */ + public Flight getFlightByDepartDate(String flightDepartDate) throws TourPlannerException { + for (Flight currFlight : flights) { + String flightId = currFlight.getId(); + String currFlightDepartDate = currFlight.getDepartDate(); + if (currFlightDepartDate.equals(flightDepartDate) && !iteratedFlightIds.contains(flightId)) { + iteratedFlightIds.add(flightId); + return currFlight; + } + } + throw new TourPlannerException(FLIGHT_NOT_FOUND_MESSAGE); + } + + /** + * Getter for the sorted list of flight IDs. + * Sorts the flight IDs by the natural ordering of String, ignoring case sensitivities. + * + * @return the list of sorted flight IDs, by natural(alphabetical, numerical) order + * @see Collections#sort(List, Comparator) + */ + public ArrayList getSortedFlightIds() { + Collections.sort(flightIds, String.CASE_INSENSITIVE_ORDER); + return flightIds; + } + + /** + * Getter for the sorted list of flight return date-times. + * Sorts using the dateTimeStringComparator in FlightList. + * + * @return the list of sorted return flight date-times, as specified in dateTimeStringComparator + * @see FlightList#dateTimeStringComparator + * @see Collections#sort(List) + */ + public ArrayList getSortedReturnDates() { + flightReturnDates.sort(dateTimeStringComparator); + return flightReturnDates; + } + + /** + * Getter for the sorted list of flight departure date-times. + * Sorts using the dateTimeStringComparator in FlightList. + * + * @return the list of sorted departure flight date-times, as specified in dateTimeStringComparator + * @see FlightList#dateTimeStringComparator + * @see Collections#sort(List) + */ + public ArrayList getSortedDepartDates() { + flightDepartureDates.sort(dateTimeStringComparator); + return flightDepartureDates; + } + + /** + * Creates a new temporary array each time the function is called. + * The flight IDs that have been iterated by Ui in the sort command will be added into FlightList's + * temporary array to prevent duplicates. + */ + public void initTempArray() { + iteratedFlightIds = new ArrayList<>(); + } + + /** + * Main method for adding a flight. + * Add all information of different fields into their respective ArrayLists. + * + * @param flight the flight to be added + */ + public void add(Flight flight) { + flights.add(flight); + flightIds.add(flight.getId()); + flightReturnDates.add(flight.getReturnDate()); + flightDepartureDates.add(flight.getDepartDate()); + flightCount++; + } + + /** + * Main method for cutting a flight. + * Removes all information of different fields from their respective ArrayLists. + * + * @param flight the flight to be added + */ + public void cut(Flight flight) { + flights.remove(flight); + flightIds.remove(flight.getId()); + flightReturnDates.remove(flight.getReturnDate()); + flightDepartureDates.remove(flight.getDepartDate()); + flightCount--; + } +} diff --git a/src/main/java/seedu/duke/data/Tour.java b/src/main/java/seedu/duke/data/Tour.java new file mode 100644 index 0000000000..3b03abc6fc --- /dev/null +++ b/src/main/java/seedu/duke/data/Tour.java @@ -0,0 +1,61 @@ +package seedu.duke.data; + +/** + * Represents a tour under the Tour agency. + */ +public class Tour { + private final String id; + private final String name; + private final Float price; + + /** + * Tour object constructor, that inputs the tour's information obtained from Add command. + * + * @param values the array of values of the tour, ordered in this manner: + * id, name, price + */ + public Tour(String[] values) { + id = values[0]; + name = values[1]; + price = Float.parseFloat(values[2]); + } + + /** + * Getter for id value in Tour object. + * + * @return tour's ID. + */ + public String getId() { + return id; + } + + /** + * Getter for name value in Tour object. + * + * @return tour's name. + */ + public String getName() { + return name; + } + + /** + * Getter for price value in Tour object. + * + * @return tour's price. + */ + public Float getPrice() { + return price; + } + + /** + * Formats tour's information as a string that is viewable as an indexed list item. + * + * @return the formatted string containing tour's information + */ + @Override + public String toString() { + return "Tour ID: " + id + "\n" + + "Name: " + name + "\n" + + "Price per pax: $" + String.format("%.02f", price); + } +} diff --git a/src/main/java/seedu/duke/data/TourList.java b/src/main/java/seedu/duke/data/TourList.java new file mode 100644 index 0000000000..3658ed8361 --- /dev/null +++ b/src/main/java/seedu/duke/data/TourList.java @@ -0,0 +1,174 @@ +package seedu.duke.data; + +import seedu.duke.TourPlannerException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +/** + * List of tours. + */ +public class TourList { + public static final String TOUR_NOT_FOUND_MESSAGE = "ERROR: Tour cannot be found. Please try another tour ID."; + + private final ArrayList tours; + private final ArrayList tourIds; + private final ArrayList tourNames; + private final ArrayList tourPrices; + private ArrayList iteratedTourIds; + private int tourCount; + + /** + * Class constructor for TourList. + */ + public TourList() { + tours = new ArrayList<>(); + tourIds = new ArrayList<>(); + tourPrices = new ArrayList<>(); + tourNames = new ArrayList<>(); + tourCount = 0; + } + + /** + * Getter for clients. + * + * @return ArrayList containing all clients in the database. + */ + public ArrayList getTours() { + return tours; + } + + public int getTourCount() { + return tourCount; + } + + public Tour getTourByIndex(int index) { + return tours.get(index); + } + + /** + * Getter for tour object in the tour list, corresponding to the tour price given. + * + * @param price the given tour price + * @return the tour object, specified by the tour price + * @throws TourPlannerException if the tour with the given tour price is not found in the database + */ + public Tour getTourByPrice(Float price) throws TourPlannerException { + for (Tour currTour : tours) { + String currTourId = currTour.getId(); + if (Objects.equals(currTour.getPrice(), price) && !iteratedTourIds.contains(currTourId)) { + iteratedTourIds.add(currTourId); + return currTour; + } + } + throw new TourPlannerException(TOUR_NOT_FOUND_MESSAGE); + } + + /** + * Getter for tour object in the tour list, corresponding to the tour name given. + * + * @param name the given tour name + * @return the tour object, specified by the tour ID + * @throws TourPlannerException if the tour with the given tour ID is not found in the database + */ + public Tour getTourByName(String name) throws TourPlannerException { + for (int i = 0; i < tourCount; i++) { + Tour currentTour = tours.get(i); + if (currentTour.getName().equals(name)) { + return currentTour; + } + } + throw new TourPlannerException(TOUR_NOT_FOUND_MESSAGE); + } + + /** + * Getter for tour object in the tour list, corresponding to the tour ID given. + * + * @param id the given tour ID, unique to each tour + * @return the tour object, specified by the tour ID + * @throws TourPlannerException if the tour with the given tour ID is not found in the database + */ + public Tour getTourById(String id) throws TourPlannerException { + for (int i = 0; i < tourCount; i++) { + Tour currentTour = tours.get(i); + if (currentTour.getId().equals(id)) { + return currentTour; + } + } + throw new TourPlannerException(TOUR_NOT_FOUND_MESSAGE); + } + + /** + * Getter for the sorted list of tour prices. + * Sorts the tour prices by the natural ordering of Float. + * + * @return the list of sorted tour prices, by ascending order + * @see Collections#sort + */ + public ArrayList getSortedTourPrices() { + Collections.sort(tourPrices); + return tourPrices; + } + + /** + * Getter for the sorted list of tour IDs. + * Sorts the tour IDs by the natural ordering of String, ignoring case sensitivities. + * + * @return the list of sorted tour IDs, by natural(alphabetical, numerical) order + * @see Collections#sort(List, Comparator) + */ + public ArrayList getSortedTourIds() { + Collections.sort(tourIds, String.CASE_INSENSITIVE_ORDER); + return tourIds; + } + + /** + * Getter for the sorted list of tour names. + * Sorts the tour names by the natural ordering of String, ignoring case sensitivities. + * + * @return the list of sorted tour names, by natural(alphabetical, numerical) order + * @see Collections#sort(List, Comparator) + */ + public ArrayList getSortedTourNames() { + Collections.sort(tourNames, String.CASE_INSENSITIVE_ORDER); + return tourNames; + } + + /** + * Creates a new temporary array each time the function is called. + * The tour IDs that have been iterated by Ui in the sort command will be added into TourList's + * temporary array to prevent duplicates. + */ + public void initTempArray() { + iteratedTourIds = new ArrayList<>(); + } + + /** + * Main method for adding a tour. + * + * @param tour the tour to-be-added + */ + public void add(Tour tour) { + tours.add(tour); + tourIds.add(tour.getId()); + tourPrices.add(tour.getPrice()); + tourNames.add(tour.getName()); + tourCount++; + } + + /** + * Main method for deleting a tour. + * + * @param tour the tour to-be-added + */ + public void cut(Tour tour) { + tourIds.remove(tour.getId()); + tourPrices.remove(tour.getPrice()); + tourNames.remove(tour.getName()); + tours.remove(tour); + tourCount--; + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/storage/ClientPackageStorage.java b/src/main/java/seedu/duke/storage/ClientPackageStorage.java new file mode 100644 index 0000000000..3fe1eb1c2c --- /dev/null +++ b/src/main/java/seedu/duke/storage/ClientPackageStorage.java @@ -0,0 +1,160 @@ + + +package seedu.duke.storage; + +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.clientpackages.AddClientPackageCommand; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Scanner; + +//@@author Demonshaha + +/** + * Storage class for all ClientPackages. Creates and loads up the ClientPackageList. + */ +public class ClientPackageStorage { + public static final String EMPTY_STRING = ""; + private final ClientPackageList clientPackages = new ClientPackageList(); + private static final String root = System.getProperty("user.dir"); + private static final Path filePath = Paths.get(root, "data", "TourPlannerClientPackages.txt"); + private static final Path dirPath = Paths.get(root, "data"); + private static boolean hasClient = false; + private static boolean hasTour = false; + private static boolean hasFlight = false; + private static boolean hasClientPackage = false; + private static boolean isPackageAdded = false; + private ArrayList rawClientPackage = new ArrayList<>(); + + /** + * Class constructor for ClientPackageStorage. + * Creates directory and files for storage if they do not already exist. + * + * @throws TourPlannerException if IOException is thrown when creating a new directory/file + * @see File + * @see IOException + */ + public ClientPackageStorage() throws TourPlannerException { + try { + File fileDirectory = new File(dirPath.toString()); + if (!fileDirectory.exists()) { + fileDirectory.mkdir(); + } + + File dataFile = new File(filePath.toString()); + dataFile.createNewFile(); + } catch (IOException e) { + throw new TourPlannerException(EMPTY_STRING); + } + } + + /** + * Getter for ClientPackageList created and populated with client packages retrieved from data file + * containing previously stored client packages. + * + * @return clientPackages retrieved + */ + public ClientPackageList getClientPackages() { + return clientPackages; + } + + /** + * Reads each line of the file and creates the ClientPackage and adds it to the ClientPackageList + * using AddClientPackageCommand. + * + * @param clients list of clients retrieved from storage file + * @param tours list of tours retrieved from storage file + * @param flights list of flights retrieved from storage file + * @param ui user interface used to display messages to the user + * @throws TourPlannerException if FileNotFoundException or NumberFormatException thrown + * due to corrupted or missing file + * @see AddClientPackageCommand + * @see FileNotFoundException + * @see NumberFormatException + */ + public void loadFile(ClientList clients, TourList tours, FlightList flights, Ui ui) throws TourPlannerException { + try { + File dataFile = new File(filePath.toString()); + Scanner scanner = new Scanner(dataFile); + + while (scanner.hasNext()) { + String line = scanner.nextLine(); + String clientPackageId; + String clientId; + String tourId; + String flightId; + + if (line.equals("Client Package Details: ")) { + resetCheckerStates(); + } else if (line.contains("Package ID")) { + clientPackageId = line.substring(12); + rawClientPackage.add(clientPackageId); + hasClientPackage = true; + } else if (line.equals("Client: ")) { + line = scanner.nextLine(); + clientId = line.substring(11); + rawClientPackage.add(clientId); + hasClient = true; + } else if (line.equals("Tour: ")) { + line = scanner.nextLine(); + tourId = line.substring(9); + rawClientPackage.add(tourId); + hasTour = true; + } else if (line.equals("Flight: ")) { + line = scanner.nextLine(); + flightId = line.substring(11); + rawClientPackage.add(flightId); + hasFlight = true; + } + + if (hasClient && hasFlight && hasTour && hasClientPackage && !isPackageAdded) { + AddClientPackageCommand command = + new AddClientPackageCommand(rawClientPackage.toArray(new String[]{})); + command.setData(clients, flights, tours, clientPackages, ui); + command.executeStorage(); + isPackageAdded = true; + } + } + } catch (FileNotFoundException | NumberFormatException e) { + throw new TourPlannerException(EMPTY_STRING); + } + } + + private void resetCheckerStates() { + hasClient = false; + hasFlight = false; + hasTour = false; + hasClientPackage = false; + isPackageAdded = false; + rawClientPackage = new ArrayList<>(); + } + + /** + * Loops through clientPackages and writes into storage file for ClientPackage. + */ + public void saveFile() { + try { + ArrayList clientPackageList = clientPackages.getClientPackages(); + FileWriter writer = new FileWriter(filePath.toString()); + for (ClientPackage clientPackage : clientPackageList) { + writer.write("Client Package Details: \n"); + writer.write(clientPackage.toString() + "\n\n"); + } + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seedu/duke/storage/ClientStorage.java b/src/main/java/seedu/duke/storage/ClientStorage.java new file mode 100644 index 0000000000..cb4bc7293d --- /dev/null +++ b/src/main/java/seedu/duke/storage/ClientStorage.java @@ -0,0 +1,113 @@ +package seedu.duke.storage; + +import seedu.duke.TourPlannerException; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Scanner; + +//@@author Demonshaha +/** + * Storage class for all Clients. Creates and loads up the ClientList. + */ +public class ClientStorage { + public static final String EMPTY_STRING = ""; + private final ClientList clients = new ClientList(); + private static final String root = System.getProperty("user.dir"); + private static final Path filePath = Paths.get(root, "data", "TourPlannerClients.txt"); + private static final Path dirPath = Paths.get(root, "data"); + + /** + * Class constructor for ClientStorage. + * Creates directory and files for storage if they do not already exist. + * + * @throws TourPlannerException if IOException is thrown when creating a new directory/file + * @see File + * @see IOException + */ + public ClientStorage() throws TourPlannerException { + try { + File fileDirectory = new File(dirPath.toString()); + if (!fileDirectory.exists()) { + fileDirectory.mkdir(); + } + + File dataFile = new File(filePath.toString()); + dataFile.createNewFile(); + } catch (IOException e) { + throw new TourPlannerException(EMPTY_STRING); + } + } + + /** + * Getter for ClientList created and populated with clients retrieved from data file + * containing previously stored clients. + * + * @return clients retrieved + */ + public ClientList getClients() { + return clients; + } + + /** + * Reads each line of the file and identifies client fields -- clientId, name, contactNum and mail. + * Creates new Client object based on the client fields and adds to ClientList. + * + * @throws TourPlannerException if FileNotFoundException due to corrupted or missing file + * @see FileNotFoundException + */ + public void loadFile() throws TourPlannerException { + try { + File dataFile = new File(filePath.toString()); + Scanner scanner = new Scanner(dataFile); + String clientId; + String clientName; + String clientContactNum; + String clientEmail; + + while (scanner.hasNext()) { + String line = scanner.nextLine(); + + if (line.contains("Client Details:")) { + line = scanner.nextLine(); + clientId = line.substring(11); + line = scanner.nextLine(); + clientName = line.substring(6); + line = scanner.nextLine(); + clientContactNum = line.substring(16); + line = scanner.nextLine(); + clientEmail = line.substring(7); + String[] clientArray = {clientId, clientName, clientContactNum, clientEmail}; + Client client = new Client(clientArray); + clients.add(client); + } + } + } catch (FileNotFoundException e) { + throw new TourPlannerException(EMPTY_STRING); + } + } + + /** + * Loops through clients and writes into storage file for Client. + */ + public void saveFile() { + try { + ArrayList clientList = clients.getClients(); + FileWriter writer = new FileWriter(filePath.toString()); + for (Client client : clientList) { + writer.write("Client Details: \n"); + writer.write(client.toString() + System.lineSeparator()); + } + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seedu/duke/storage/FlightStorage.java b/src/main/java/seedu/duke/storage/FlightStorage.java new file mode 100644 index 0000000000..0a918bf9ba --- /dev/null +++ b/src/main/java/seedu/duke/storage/FlightStorage.java @@ -0,0 +1,115 @@ +package seedu.duke.storage; + +import seedu.duke.TourPlannerException; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Scanner; + +//@@author Demonshaha +/** + * Storage class for flights. Creates and loads up the FlightList. + */ +public class FlightStorage { + private final FlightList flights = new FlightList(); + private static final String root = System.getProperty("user.dir"); + private static final Path filePath = Paths.get(root, "data", "TourPlannerFlights.txt"); + private static final Path dirPath = Paths.get(root, "data"); + + /** + * Class constructor for FlightStorage. + * Creates directory and files for storage if they do not already exist. + * + * @throws TourPlannerException if there are IOException thrown when creating a new directory/file + * @see File + * @see IOException + */ + public FlightStorage() throws TourPlannerException { + try { + File fileDirectory = new File(dirPath.toString()); + if (!fileDirectory.exists()) { + fileDirectory.mkdir(); + } + + File dataFile = new File(filePath.toString()); + dataFile.createNewFile(); + } catch (IOException e) { + throw new TourPlannerException("File ERROR"); + } + } + + /** + * Getter for flight list. + * + * @return the list of flights stored in database + */ + public FlightList getFlights() { + return flights; + } + + /** + * Reads each line of the file and identifies flight fields -- flightId, + * departure destination, return destination, departure date-time, return date-time. + * Creates new Flight object based on the flight fields and adds to FlightList. + * + * @throws TourPlannerException if FileNotFoundException due to corrupted or missing file + * @see FileNotFoundException + */ + public void loadFile() throws TourPlannerException { + try { + File dataFile = new File(filePath.toString()); + Scanner scanner = new Scanner(dataFile); + String flightId; + String from; + String to; + String fromDate; + String toDate; + + while (scanner.hasNext()) { + String line = scanner.nextLine(); + + if (line.contains("Flight Details:")) { + line = scanner.nextLine(); + flightId = line.substring(11); + line = scanner.nextLine(); + int index = line.indexOf(", "); + from = line.substring(18, index); + fromDate = line.substring(index + 2); + line = scanner.nextLine(); + index = line.indexOf(", "); + to = line.substring(15, index); + toDate = line.substring(index + 2); + String[] flightArray = {flightId, to, from, toDate, fromDate}; + Flight flight = new Flight(flightArray); + flights.add(flight); + } + } + } catch (FileNotFoundException e) { + throw new TourPlannerException("File not found!"); + } + } + + /** + * Loops through flights and writes into storage file for Flights. + */ + public void saveFile() { + ArrayList flightList = flights.getFlights(); + try { + FileWriter writer = new FileWriter(filePath.toString()); + for (Flight flight : flightList) { + writer.write("Flight Details: \n"); + writer.write(flight.toString() + System.lineSeparator()); + } + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seedu/duke/storage/TourStorage.java b/src/main/java/seedu/duke/storage/TourStorage.java new file mode 100644 index 0000000000..5cb633984a --- /dev/null +++ b/src/main/java/seedu/duke/storage/TourStorage.java @@ -0,0 +1,111 @@ +package seedu.duke.storage; + +import seedu.duke.TourPlannerException; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Scanner; + +//@@author Demonshaha +/** + * Storage class for all Tours. Creates and loads up the TourList. + */ +public class TourStorage { + public static final String EMPTY_STRING = ""; + private final TourList tours = new TourList(); + private static final String root = System.getProperty("user.dir"); + private static final Path filePath = Paths.get(root, "data", "TourPlannerTours.txt"); + private static final Path dirPath = Paths.get(root, "data"); + + /** + * Class constructor for TourStorage. + * Creates directory and files for storage if they do not already exist. + * + * @throws TourPlannerException if IOException is thrown when creating a new directory/file + * @see File + * @see IOException + */ + public TourStorage() throws TourPlannerException { + try { + File fileDirectory = new File(dirPath.toString()); + if (!fileDirectory.exists()) { + fileDirectory.mkdir(); + } + + File dataFile = new File(filePath.toString()); + dataFile.createNewFile(); + } catch (IOException e) { + throw new TourPlannerException(EMPTY_STRING); + } + } + + /** + * Getter for ClientList created and populated with clients retrieved from data file + * containing previously stored clients. + * + * @return clients retrieved + */ + public TourList getTours() { + return tours; + } + + /** + * Reads each line of the file and identifies tour fields -- tourId, name and price. + * Creates new Tour object based on the tour fields and adds to TourList. + * + * @throws TourPlannerException if FileNotFoundException thrown due to corrupted or missing file + * @see FileNotFoundException + */ + public void loadFile() throws TourPlannerException { + try { + File dataFile = new File(filePath.toString()); + Scanner scanner = new Scanner(dataFile); + String tourName; + String tourId; + String tourPrice; + + while (scanner.hasNext()) { + String line = scanner.nextLine(); + + if (line.contains("Tour Details:")) { + line = scanner.nextLine(); + tourId = line.substring(9); + line = scanner.nextLine(); + tourName = line.substring(6); + line = scanner.nextLine(); + int index = line.indexOf("$"); + tourPrice = line.substring(index + 1); + String[] tourArray = {tourId, tourName, tourPrice}; + Tour tour = new Tour(tourArray); + tours.add(tour); + } + } + } catch (FileNotFoundException e) { + throw new TourPlannerException(EMPTY_STRING); + } + } + + /** + * Loops through tours and writes into storage file for Tour. + */ + public void saveFile() { + ArrayList tourList = tours.getTours(); + try { + FileWriter writer = new FileWriter(filePath.toString()); + for (Tour tour : tourList) { + writer.write("Tour Details: \n"); + writer.write(tour.toString() + System.lineSeparator()); + } + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seedu/uml/AddClientPackageCommand.puml b/src/main/java/seedu/uml/AddClientPackageCommand.puml new file mode 100644 index 0000000000..bcce4de7bb --- /dev/null +++ b/src/main/java/seedu/uml/AddClientPackageCommand.puml @@ -0,0 +1,49 @@ +@startuml + +participant ":Parser" as Parser +participant ":AddClientPackageCommand" as APC +participant ":ClientPackageList" as ClientPackageList +participant ":Ui" as Ui + +-> Parser : parse("add -p p001 ARGS") +activate Parser + Parser -> Parser: parseAdd("-p p001 ARGS") + activate Parser + create APC + Parser -> APC : new AddClientPackageCommand + activate APC + APC --> Parser : AddClientPackageCommand + deactivate APC + Parser --> Parser: AddClientPackageCommand + deactivate Parser + <-- Parser: AddClientPackageCommand +deactivate Parser + +-> APC : execute() + activate APC + + + alt#Gold #LightBlue if added package is unique and has unique fields + + APC -> APC: createClientPackage() + activate APC + APC --> APC: clientPackage: package of extracted Objects from ID + deactivate APC + + APC -> ClientPackageList: add(clientPackage) + activate ClientPackageList + APC <-- ClientPackageList: + deactivate ClientPackageList + APC -> Ui : showAdd(ClientPackage clientPackage) + activate Ui + APC <-- Ui : add clientPackage Ui display + deactivate Ui + <-- APC: add clientPackage Ui display + + else #Pink if added package ID already exists or has duplicated fields + <-- APC : ERROR message + + deactivate APC + destroy APC + end +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/AddTourCommand.puml b/src/main/java/seedu/uml/AddTourCommand.puml new file mode 100644 index 0000000000..1057621013 --- /dev/null +++ b/src/main/java/seedu/uml/AddTourCommand.puml @@ -0,0 +1,44 @@ +@startuml + +participant ":Parser" as Parser +participant ":AddTourCommand" as ATC +participant ":TourList" as TourList +participant ":Ui" as Ui + +-> Parser : parse("add -t t001 ARGS") +activate Parser + Parser -> Parser: parseAdd("-t t001 ARGS") + activate Parser + create ATC + Parser -> ATC : new AddTourCommand + activate ATC + ATC --> Parser : AddTourCommand + deactivate ATC + Parser --> Parser: AddTourCommand + deactivate Parser + <-- Parser: AddTourCommand +deactivate Parser + +-> ATC : execute() + activate ATC + + + alt#Gold #LightBlue if added tour is unique and has unique fields + + ATC -> TourList: add(Tour tour) + activate TourList + ATC <-- TourList: + deactivate TourList + ATC -> Ui : showAdd(Tour tour) + activate Ui + ATC <-- Ui : add tour Ui display + deactivate Ui + <-- ATC: add tour Ui display + + else #Pink if added tour ID already exists or has duplicated fields + <-- ATC : ERROR message + + deactivate ATC + destroy ATC + end +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/Architecture.puml b/src/main/java/seedu/uml/Architecture.puml new file mode 100644 index 0000000000..9f35ed3b90 --- /dev/null +++ b/src/main/java/seedu/uml/Architecture.puml @@ -0,0 +1,24 @@ +@startuml + +skinparam componentStyle rectangle +component { + [TourPlanner] as TP + [Ui] as Ui + [Parser] as Parser + [Command] as Command + [ObjectList] as ObjectList + [Storage] as Storage +} +database Computer +actor User + +User .> TP +TP --> Ui +TP --> Storage +Storage ..> Computer +Ui --> Parser +Ui ..> ObjectList +Parser -> Command +Command --> ObjectList +Command -> Ui +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/CutClientCommand.puml b/src/main/java/seedu/uml/CutClientCommand.puml new file mode 100644 index 0000000000..baa5d01043 --- /dev/null +++ b/src/main/java/seedu/uml/CutClientCommand.puml @@ -0,0 +1,51 @@ +@startuml + +participant ":Parser" as Parser +participant ":CutClientCommand" as CCC +participant ":ClientList" as ClientList +participant ":ClientPackageList" as ClientPackageList + +-> Parser: parse("cut -c c001") +activate Parser +Parser -> Parser : parseCut("-c c001") +activate Parser + +create CCC +Parser -> CCC +activate CCC +CCC --> Parser +deactivate CCC +Parser --> Parser +deactivate Parser + +<-- Parser: CutClientCommand +deactivate Parser + +-> CCC: execute() +activate CCC + +CCC -> ClientList : getClientById("c001") +activate ClientList +ClientList --> CCC : Client +deactivate ClientList + +CCC -> ClientList : cut(Client) +activate ClientList +ClientList --> CCC +deactivate ClientList + +CCC -> ClientPackageList : getClientPackageByClient("c001") +activate ClientPackageList +ClientPackageList --> CCC : ClientPackage +deactivate ClientPackageList + +CCC -> ClientPackageList : cut(ClientPackage) +activate ClientPackageList +ClientPackageList --> CCC +deactivate ClientPackageList + +<-- CCC +deactivate CCC +deactivate Parser +destroy CCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/CutClientPackageCommand.puml b/src/main/java/seedu/uml/CutClientPackageCommand.puml new file mode 100644 index 0000000000..2dbae24de8 --- /dev/null +++ b/src/main/java/seedu/uml/CutClientPackageCommand.puml @@ -0,0 +1,42 @@ +@startuml + +participant ":Parser" as Parser +participant ":CutClientPackageCommand" as CCC +participant ":ClientPackageList" as ClientPackageList + + +-> Parser: parse("cut -p p001") +activate Parser +Parser -> Parser : parseCut("-p p001") +activate Parser + +create CCC +Parser -> CCC +activate CCC +CCC --> Parser +deactivate CCC +Parser --> Parser +deactivate Parser + +<-- Parser: CutClientPackageCommand +deactivate Parser + +-> CCC: execute() +activate CCC + +CCC -> ClientPackageList : getClientPackageById("p001") +activate ClientPackageList +ClientPackageList --> CCC : ClientPackage +deactivate ClientPackageList + +CCC -> ClientPackageList: cut(ClientPackage) +activate ClientPackageList +ClientPackageList --> CCC +deactivate ClientPackageList + + +<-- CCC +deactivate CCC +deactivate Parser +destroy CCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/FindClientTest.puml b/src/main/java/seedu/uml/FindClientTest.puml new file mode 100644 index 0000000000..203059da00 --- /dev/null +++ b/src/main/java/seedu/uml/FindClientTest.puml @@ -0,0 +1,51 @@ +@startuml + +participant ":Parser" as Parser +participant ":FindClientCommand" as FCC +participant ":Ui" as Ui +participant ":ClientList" as TourList + +-> Parser: execute "find -c Bo Tuan" +activate Parser +Parser -> Parser : parseFind("-c Bo Tuan") +activate Parser + + + +create FCC +Parser -> FCC +activate FCC +FCC --> Parser +deactivate FCC +Parser --> Parser +deactivate Parser +<-- Parser: FindClientCommand +deactivate Parser + +-> FCC: execute() +activate FCC + + + +FCC -> Ui : showFindClient() +activate Ui +Ui -> TourList : getClient("Bo Tuan") +activate TourList +TourList --> Ui : id, name, contact no, email +deactivate TourList +loop until all clientPackage iterated + Ui -> ClientPackageList : getClientPackageByIndex() + activate ClientPackageList + ClientPackageList --> Ui: client packages of found client(s) + deactivate ClientPackageList + Ui -> Ui: show ClientPackage + activate Ui + Ui --> Ui + deactivate Ui +end +Ui --> FCC +deactivate Ui + +<-- FCC +deactivate FCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/FindFlightTest.puml b/src/main/java/seedu/uml/FindFlightTest.puml new file mode 100644 index 0000000000..0ebc5a82d1 --- /dev/null +++ b/src/main/java/seedu/uml/FindFlightTest.puml @@ -0,0 +1,53 @@ +@startuml + +participant ":Parser" as Parser +participant ":FindFlightCommand" as FCC +participant ":Ui" as Ui +participant ":FlightList" as TourList + +-> Parser: execute "find -f SQ-JPN" +activate Parser +Parser -> Parser : parseFind("-f SQ-JPN") +activate Parser + + + +create FCC +Parser -> FCC +activate FCC +FCC --> Parser +deactivate FCC +Parser --> Parser +deactivate Parser +<-- Parser: FindFlightCommand +deactivate Parser + +-> FCC: execute() +activate FCC + + + +FCC -> Ui : showFindFlight() +activate Ui +Ui -> TourList : getFlight("SQ-JPN") +activate TourList +TourList --> Ui : id, departure / return destination, place +deactivate TourList +loop until all clientPackage iterated + Ui -> ClientPackageList : getClientPackageByIndex() + activate ClientPackageList + ClientPackageList --> Ui: passengers + deactivate ClientPackageList + Ui -> Ui: show Client + activate Ui + Ui --> Ui + deactivate Ui +end +Ui --> FCC +deactivate Ui + +<-- FCC +deactivate FCC + +destroy FCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/FindTourTest.puml b/src/main/java/seedu/uml/FindTourTest.puml new file mode 100644 index 0000000000..b5126cc476 --- /dev/null +++ b/src/main/java/seedu/uml/FindTourTest.puml @@ -0,0 +1,53 @@ +@startuml + +participant ":Parser" as Parser +participant ":FindTourCommand" as FCC +participant ":Ui" as Ui +participant ":TourList" as TourList + +-> Parser: execute "find -t JPN" +activate Parser +Parser -> Parser : parseFind("-t JPN") +activate Parser + + + +create FCC +Parser -> FCC +activate FCC +FCC --> Parser +deactivate FCC +Parser --> Parser +deactivate Parser +<-- Parser: FindTourCommand +deactivate Parser + +-> FCC: execute() +activate FCC + + + +FCC -> Ui : showFindTour() +activate Ui +Ui -> TourList : getTour("JPN") +activate TourList +TourList --> Ui : id, name, price +deactivate TourList +loop until all clientPackage iterated + Ui -> ClientPackageList : getClientPackageByIndex() + activate ClientPackageList + ClientPackageList --> Ui: subscribed clients + deactivate ClientPackageList + Ui -> Ui: show Client + activate Ui + Ui --> Ui + deactivate Ui +end +Ui --> FCC +deactivate Ui + +<-- FCC +deactivate FCC + +destroy FCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/ListClientCommand.puml b/src/main/java/seedu/uml/ListClientCommand.puml new file mode 100644 index 0000000000..d26f40e0b3 --- /dev/null +++ b/src/main/java/seedu/uml/ListClientCommand.puml @@ -0,0 +1,46 @@ +@startuml + +participant ":Parser" as Parser +participant ":ListClientCommand" as LCC +participant ":Ui" as Ui +participant ":ClientList" as ClientList + +-> Parser: parse("list -c") +activate Parser +Parser -> Parser : parseList("-c") +activate Parser + +create LCC +Parser -> LCC +activate LCC +LCC --> Parser +deactivate LCC +Parser --> Parser +deactivate Parser + +<-- Parser: ListClientCommand +deactivate Parser + +-> LCC: execute() +activate LCC + +LCC -> Ui: showListClient(ClientList) +activate Ui +loop until all clients iterated + Ui -> ClientList : getClientByIndex(index) + activate ClientList + ClientList --> Ui: Client + deactivate ClientList + Ui -> Ui: show Client + activate Ui + Ui --> Ui + deactivate Ui +end +Ui --> LCC +deactivate Ui + +<-- LCC +deactivate LCC + +destroy LCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/Parser.puml b/src/main/java/seedu/uml/Parser.puml new file mode 100644 index 0000000000..ee5efbe47d --- /dev/null +++ b/src/main/java/seedu/uml/Parser.puml @@ -0,0 +1,42 @@ +@startuml + +participant ":Parser" as Parser +participant ":XYZCommand" as XYZ + +-> Parser: parse(input: String) +activate Parser + +Parser -> Parser : splitCommandString(input: String) +activate Parser +Parser --> Parser : Command and Params +deactivate Parser + +alt Command is "xyz", Params are valid + +Parser -> Parser : parseXYZ(params: String) +note left +"XYZ" refers to general +operations such as +add, list and sort. +end note +activate Parser + +create XYZ +Parser -> XYZ : new XYZCommand() +activate XYZ +XYZ --> Parser : XYZCommand +deactivate XYZ + +Parser --> Parser : XYZCommand +deactivate Parser + +<-- Parser : XYZCommand + + +else Command and/or Params are invalid + <-- Parser : TourPlannerException + +deactivate Parser + +end +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/SortClientCommand.puml b/src/main/java/seedu/uml/SortClientCommand.puml new file mode 100644 index 0000000000..2af18f25bf --- /dev/null +++ b/src/main/java/seedu/uml/SortClientCommand.puml @@ -0,0 +1,52 @@ +@startuml + +participant ":Parser" as Parser +participant ":SortClientCommand" as SCC +participant ":ClientList" as ClientList +participant ":Ui" as Ui + +-> Parser: parse("sort -c /id") +activate Parser +Parser -> Parser : parseSort("-c /id") +activate Parser + +create SCC +Parser -> SCC +activate SCC +SCC --> Parser +deactivate SCC +Parser --> Parser +deactivate Parser + +<-- Parser: SortClientCommand +deactivate Parser + +-> SCC: execute() +activate SCC + +SCC -> ClientList : getSortedClientIds() +activate ClientList +ClientList --> SCC : sorted client IDs +deactivate ClientList + + +SCC -> Ui : showSortedClientById() +activate Ui +loop until all clientIds iterated + Ui -> ClientList : getClientById(clientId) + activate ClientList + ClientList --> Ui: Client + deactivate ClientList + Ui -> Ui: show Client + activate Ui + Ui --> Ui + deactivate Ui +end +Ui --> SCC +deactivate Ui + +<-- SCC +deactivate SCC + +destroy SCC +@enduml \ No newline at end of file diff --git a/src/main/java/seedu/uml/Ui.puml b/src/main/java/seedu/uml/Ui.puml new file mode 100644 index 0000000000..bb18054818 --- /dev/null +++ b/src/main/java/seedu/uml/Ui.puml @@ -0,0 +1,18 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam componentStyle rectangle +component { + [Ui] as Ui + [Parser] as Parser + [Command] as Command + +} + +actor User + +User - Ui +Ui -- Parser : read by > +Parser .> "0..1" Command : returns > +Command .> Ui : calls appropriate function > +@enduml \ No newline at end of file diff --git a/src/test/java/seedu/duke/ParserTest.java b/src/test/java/seedu/duke/ParserTest.java new file mode 100644 index 0000000000..834a52a3fe --- /dev/null +++ b/src/test/java/seedu/duke/ParserTest.java @@ -0,0 +1,171 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import seedu.duke.commands.clientpackages.ListClientPackageCommand; +import seedu.duke.commands.clients.AddClientCommand; +import seedu.duke.commands.Command; +import seedu.duke.commands.clients.ListClientCommand; +import seedu.duke.commands.clients.SortClientCommand; +import seedu.duke.commands.flights.AddFlightCommand; +import seedu.duke.commands.flights.ListFlightCommand; +import seedu.duke.commands.flights.SortFlightCommand; +import seedu.duke.commands.tours.AddTourCommand; +import seedu.duke.commands.tours.ListTourCommand; +import seedu.duke.commands.tours.SortTourCommand; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ParserTest { + + private static final String CORRECT_ADDCLIENT_ONE = "add -c j12 /n john /cn 91234567 /m johndoe@gmail.com"; + private static final String CORRECT_ADDCLIENT_TWO = " add -c j12 /n john /cn 91234567 /m johndoe@gmail.com"; + private static final String CORRECT_ADDCLIENT_THREE = "add -c j12 /n john /m johndoe@gmail.com /cn 91234567"; + private static final String CORRECT_ADDFLIGHT_ONE = + "add -f SQ-JPN1 /d JPN /r SG /dd 23/10/21 13:00 /rd 27/10/21 02:00"; + private static final String CORRECT_ADDFLIGHT_TWO = + "add -f SQ-JPN1 /d JPN /r SG /dd 23/10/21 13:00 /rd 27/10/21 02:00"; + private static final String CORRECT_ADDFLIGHT_THREE = + "add -f SQ-JPN1 /d JPN /dd 23/10/21 13:00 /r SG /rd 27/10/21 02:00"; + private static final String CORRECT_ADDTOUR_ONE = "add -t aus1369 /n australiaromance /p 1300"; + private static final String CORRECT_ADDTOUR_TWO = "add -t aus1369 /n australiaromance /p 1300"; + private static final String CORRECT_ADDTOUR_THREE = "add -t aus1369 /p 1300 /n australiaromance"; + private static final String WRONG_ADDCLIENT_MISSING_PREFIX = "add -c j12 /n john /m johndoe@gmail.com"; + private static final String WRONG_ADDTOUR_MISSING_PREFIX = "add -t aus1369 /p 1300"; + private static final String WRONG_ADDFLIGHT_MISSING_PREFIX = "add -f SQ-JPN1 /d JPN /r SG /dd 23/10/21 13:00"; + private static final String WRONG_ADDCLIENT_MISSING_ID = "add -c /n botuan /cn 91234567 /m johndoe@gmail.com"; + private static final String WRONG_ADDFLIGHT_MISSING_ID = + "add -f /d JPN /r SG /dd 23/10/21 13:00 /rd 27/10/21 02:00"; + private static final String WRONG_ADDTOUR_MISSING_ID = "add -t /n australiaromance /p 1300"; + private static final String WRONG_ADDCLIENT_DUPLICATE_PREFIX = + "add -c c001 /n Bo Tuan /cn 91234567 /m botuan@gmail.com /cn 13465789"; + private static final String WRONG_ADDTOUR_DUPLICATE_PREFIX = "add -t aus1369 /n australiaromance /p 1300 /p 2400"; + private static final String WRONG_ADDFLIGHT_DUPLICATE_PREFIX = + "add -f SQ-JPN1 /d JPN /d JAPAN /r SG /dd 23/10/21 13:00 /rd 27/10/21 02:00"; + private static final String WRONG_ADD_MISSING_IDENTIFIER = "add -o c001 /n botuan /cn 91234567 /m john@mail.com"; + private static final String WRONG_SORT_MISSING_IDENTIFIER = "sort /d"; + private static final String CORRECT_SORT_TOUR = "sort -t /p"; + private static final String CORRECT_SORT_CLIENT = "sort -c /n"; + private static final String CORRECT_SORT_FLIGHT = "sort -f /d"; + public static final String CORRECT_LIST_FLIGHT = "list -f"; + public static final String CORRECT_LIST_CLIENT = "list -c"; + public static final String CORRECT_LIST_TOUR = "list -t"; + public static final String CORRECT_LIST_PACKAGE = "list -p"; + private static final String WRONG_ADD_FLIGHT_DATETIME_FORMAT = + "add -f SQ-JPN1 /d JPN /r SG /dd 23/10/21 25:00 /rd 27/10/21 02:00"; + private static final String WRONG_ADD_FLIGHT_LOGIC_ERROR = + "add -f SQ-JPN1 /d JPN /r SG /dd 23/11/21 21:00 /rd 19/10/21 02:00"; + public static final String WRONG_MISSING_IDENTIFIER_ONE = "list /f"; + public static final String WRONG_IDENTIFIER_GIVEN = "sort -o /n"; + public static final String WRONG_MISSING_IDENTIFIER_TWO = "cut c001"; + + + @Test + void parse_addClientCommand_correctCommandCreated() throws TourPlannerException { + parseAndAssertCommandType(CORRECT_ADDCLIENT_ONE, AddClientCommand.class); + + //whitespaces are allowed + parseAndAssertCommandType(CORRECT_ADDCLIENT_TWO, AddClientCommand.class); + + //order of prefixes does not matter + parseAndAssertCommandType(CORRECT_ADDCLIENT_THREE, AddClientCommand.class); + } + + @Test + void parse_addFlightCommand_correctFlightCreated() throws TourPlannerException { + parseAndAssertCommandType(CORRECT_ADDFLIGHT_ONE, AddFlightCommand.class); + + //whitespaces are allowed + parseAndAssertCommandType(CORRECT_ADDFLIGHT_TWO, AddFlightCommand.class); + + //order of prefixes does not matter + parseAndAssertCommandType(CORRECT_ADDFLIGHT_THREE, AddFlightCommand.class); + } + + @Test + void parse_addTourCommand_correctTourCreated() throws TourPlannerException { + parseAndAssertCommandType(CORRECT_ADDTOUR_ONE, AddTourCommand.class); + + //whitespaces are allowed + parseAndAssertCommandType(CORRECT_ADDTOUR_TWO, AddTourCommand.class); + + //order of prefixes does not matter + parseAndAssertCommandType(CORRECT_ADDTOUR_THREE, AddTourCommand.class); + } + + @Test + void parse_sortCommand_correctCommand() throws TourPlannerException { + parseAndAssertCommandType(CORRECT_SORT_TOUR, SortTourCommand.class); + parseAndAssertCommandType(CORRECT_SORT_CLIENT, SortClientCommand.class); + parseAndAssertCommandType(CORRECT_SORT_FLIGHT, SortFlightCommand.class); + } + + @Test + void parse_listCommand_correctCommand() throws TourPlannerException { + parseAndAssertCommandType(CORRECT_LIST_FLIGHT, ListFlightCommand.class); + parseAndAssertCommandType(CORRECT_LIST_CLIENT, ListClientCommand.class); + parseAndAssertCommandType(CORRECT_LIST_TOUR, ListTourCommand.class); + parseAndAssertCommandType(CORRECT_LIST_PACKAGE, ListClientPackageCommand.class); + } + + @Test + void parse_missingIdentifier_failure() { + assertParseFailure(WRONG_MISSING_IDENTIFIER_ONE, Parser.ERROR_MISSING_IDENTIFIER); + assertParseFailure(WRONG_IDENTIFIER_GIVEN, Parser.ERROR_MISSING_IDENTIFIER); + assertParseFailure(WRONG_MISSING_IDENTIFIER_TWO, Parser.ERROR_MISSING_IDENTIFIER); + } + + @Test + void parse_missingPrefixes_failure() { + //missing contact number + assertParseFailure(WRONG_ADDCLIENT_MISSING_PREFIX, Parser.ERROR_MISSING_PREFIXES); + + //missing tour name + assertParseFailure(WRONG_ADDTOUR_MISSING_PREFIX, Parser.ERROR_MISSING_PREFIXES); + + //missing return flight date + assertParseFailure(WRONG_ADDFLIGHT_MISSING_PREFIX, Parser.ERROR_MISSING_PREFIXES); + } + + @Test + void parse_missingIdOrName_failure() { + assertParseFailure(WRONG_ADDCLIENT_MISSING_ID, Parser.ERROR_MISSING_FIELDS); + assertParseFailure(WRONG_ADDFLIGHT_MISSING_ID, Parser.ERROR_MISSING_FIELDS); + assertParseFailure(WRONG_ADDTOUR_MISSING_ID, Parser.ERROR_MISSING_FIELDS); + } + + @Test + void parse_duplicatePrefixes_failure() { + assertParseFailure(WRONG_ADDCLIENT_DUPLICATE_PREFIX, Parser.ERROR_DUPLICATE_PREFIXES); + assertParseFailure(WRONG_ADDTOUR_DUPLICATE_PREFIX, Parser.ERROR_DUPLICATE_PREFIXES); + assertParseFailure(WRONG_ADDFLIGHT_DUPLICATE_PREFIX, Parser.ERROR_DUPLICATE_PREFIXES); + } + + @Test + void parse_wrongOrMissingIdentifier_failure() { + assertParseFailure(WRONG_ADD_MISSING_IDENTIFIER, Parser.ERROR_MISSING_IDENTIFIER); + assertParseFailure(WRONG_SORT_MISSING_IDENTIFIER, Parser.ERROR_MISSING_IDENTIFIER); + } + + @Test + void parse_wrongDateTimeEntry_failure() { + assertParseFailure(WRONG_ADD_FLIGHT_DATETIME_FORMAT, Parser.ERROR_FLIGHT_TIME_FORMAT); + assertParseFailure(WRONG_ADD_FLIGHT_LOGIC_ERROR, Parser.ERROR_FLIGHT_TIME_INVERT); + } + + + private void parseAndAssertCommandType(String input, Class expectedCommandClass) + throws TourPlannerException { + final Command result = Parser.parse(input); + assertTrue(result.getClass().isAssignableFrom(expectedCommandClass)); + } + + private void assertParseFailure(String input, String expectedMessage) { + try { + Parser.parse(input); + throw new AssertionError("The expected exception was not thrown"); + } catch (TourPlannerException e) { + assertEquals(expectedMessage, e.getMessage()); + } + } +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/TourPlannerTest.java similarity index 88% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/seedu/duke/TourPlannerTest.java index 2dda5fd651..89663895e5 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/seedu/duke/TourPlannerTest.java @@ -4,9 +4,10 @@ import org.junit.jupiter.api.Test; -class DukeTest { +class TourPlannerTest { @Test public void sampleTest() { assertTrue(true); } + } diff --git a/src/test/java/seedu/duke/commands/ByeCommandTest.java b/src/test/java/seedu/duke/commands/ByeCommandTest.java new file mode 100644 index 0000000000..78dd2d14b6 --- /dev/null +++ b/src/test/java/seedu/duke/commands/ByeCommandTest.java @@ -0,0 +1,43 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ByeCommandTest { + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + @Test + public void byeCommand_validUserInput_showMessage() throws TourPlannerException { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + System.setOut(new PrintStream(newConsole)); + Command byeCommand = new ByeCommand(); + byeCommand.setData(clients, flights, tours, clientPackages, ui); + byeCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String expectedString = "Thanks for using TourPlanner. Goodbye!\n" + + "____________________________________________________________"; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/HelpCommandTest.java b/src/test/java/seedu/duke/commands/HelpCommandTest.java new file mode 100644 index 0000000000..0679f14bce --- /dev/null +++ b/src/test/java/seedu/duke/commands/HelpCommandTest.java @@ -0,0 +1,76 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HelpCommandTest { + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + @Test + public void helpCommand_validUserInput_showMessage() throws TourPlannerException { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + System.setOut(new PrintStream(newConsole)); + Command helpCommand = new HelpCommand(); + helpCommand.setData(clients, flights, tours, clientPackages, ui); + helpCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String expectedString = "add: Add information of all data types into the database.\n" + + "Prefixes can be input in any order.\n" + + " Add client: add -c CLIENT_ID /n NAME /cn CONTACT_NUM /m EMAIL\n" + + " Add flight: add -f FLIGHT_ID /d DEPART_DESTINATION /r RETURN_DESTINATION\n" + + " /dd DEPARTURE_DATETIME /rd RETURN_DATETIME\n" + + " Add tour: add -t TOUR_ID /n DEPART_DESTINATION /p TOUR_PRICE\n" + + " Add client package: add -p PACKAGE_ID /c CLIENT_ID /t TOUR_ID /f FLIGHT_ID\n\n" + + "list: Shows a list of all available entries of a specific data type, along with their " + + "respective fields.\n" + + " List client: list -c\n" + + " List flight: list -f\n" + + " List tour: list -t\n" + + " List client package: list -p\n\n" + + "cut: Deletes entry of a certain data type and all client packages corresponding to the entry.\n" + + " Cut client: cut -c CLIENT_ID\n" + + " Cut flight: cut -f FLIGHT_ID\n" + + " Cut tour: cut -t TOUR_ID\n" + + " Cut client package: cut -p PACKAGE_ID\n\n" + + "find: Finds specific entry of data type, returns the entry and other relevant information.\n" + + " Find client: find -c CLIENT_NAME\n" + + " Find flight: find -f FLIGHT_ID\n" + + " Find tour: find -t TOUR_ID\n\n" + + "sort: Sorts entries in the data type based on the criteria.\n " + + " Sort client:\n" + + " Sort by id: sort -c /id\n" + + " Sort by name: sort -c /n\n" + + " Sort flight:\n" + + " Sort by id: sort -f /id\n" + + " Sort by departure date: sort -f /d\n" + + " Sort by return date: sort -f /r\n" + + " Sort tour:\n" + + " Sort by id: sort -t /id\n" + + " Sort by name: sort -t /n\n" + + " Sort by price: sort -t /p\n\n" + + "bye: Exits the program."; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/clientpackages/AddClientPackageCommandTest.java b/src/test/java/seedu/duke/commands/clientpackages/AddClientPackageCommandTest.java new file mode 100644 index 0000000000..dea3232b48 --- /dev/null +++ b/src/test/java/seedu/duke/commands/clientpackages/AddClientPackageCommandTest.java @@ -0,0 +1,123 @@ +package seedu.duke.commands.clientpackages; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddClientPackageCommandTest { + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + private Client botuan; + private Tour kor; + private Flight sqkor; + private ClientPackageList populatedClientPackages; + + @BeforeEach + public void setUp() { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + botuan = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + Client wayne = new Client(new String[]{"c002", "Wayne", "56667888", "wen@mail.com"}); + clients = createClientList(botuan, wayne); + + Tour jpn = new Tour(new String[]{"JPN", "Japan Basic Tour", "1500.00"}); + kor = new Tour(new String[]{"KOR", "Korea Cultural Tour", "3000.00"}); + tours = createTourList(jpn, kor); + + Flight sqjpn = new Flight(new String[]{"SQ-JPN", "JPN", "SG", "20/10/21 18:00", "21/10/21 03:00"}); + sqkor = new Flight(new String[]{"SQ-KOR", "KOR", "SG", "23/10/2021 18:00", "30/10/2021 03:00"}); + flights = createFlightList(sqjpn, sqkor); + + ClientPackage botuanPack = new ClientPackage("p001", botuan, kor, sqkor); + populatedClientPackages = createClientPackageList(botuanPack); + + clientPackages = createClientPackageList(); + } + + @Test + void addClientPackageCommand_validData_correctlyConstructed() throws TourPlannerException { + String[] rawClientPackage = new String[] {"p001", "c001", "KOR", "SQ-KOR"}; + Command addClientPackage = new AddClientPackageCommand(rawClientPackage); + addClientPackage.setData(clients, flights, tours, clientPackages, ui); + addClientPackage.execute(); + + ClientPackage retrieveClientPackage = ((AddClientPackageCommand) addClientPackage).getClientPackage(); + assertEquals("p001", retrieveClientPackage.getId()); + assertEquals(botuan, retrieveClientPackage.getClient()); + assertEquals(kor, retrieveClientPackage.getTour()); + assertEquals(sqkor, retrieveClientPackage.getFlight()); + } + + @Test + void addClientPackageCommand_emptyClientPackageList_populatedClientPackageList() throws TourPlannerException { + String[] rawClientPackage = new String[] {"p001", "c001", "KOR", "SQ-KOR"}; + Command addClientPackage = new AddClientPackageCommand(rawClientPackage); + addClientPackage.setData(clients, flights, tours, clientPackages, ui); + addClientPackage.execute(); + + assertEquals(populatedClientPackages.getClientPackageCount(), clientPackages.getClientPackageCount()); + for (int i = 0; i < populatedClientPackages.getClientPackageCount(); i++) { + ClientPackage expected = populatedClientPackages.getClientPackageByIndex(i); + ClientPackage actual = clientPackages.getClientPackageByIndex(i); + assertEquals(expected.getId(), actual.getId()); + assertEquals(expected.getClient(), actual.getClient()); + assertEquals(expected.getTour(), actual.getTour()); + assertEquals(expected.getFlight(), actual.getFlight()); + } + } + + private ClientList createClientList(Client...clientList) { + ClientList newClientList = new ClientList(); + for (Client client : clientList) { + newClientList.add(client); + } + return newClientList; + } + + private FlightList createFlightList(Flight...flightList) { + FlightList newFlightList = new FlightList(); + for (Flight flight : flightList) { + newFlightList.add(flight); + } + return newFlightList; + } + + private TourList createTourList(Tour...tourList) { + TourList newTourList = new TourList(); + for (Tour tour : tourList) { + newTourList.add(tour); + } + return newTourList; + } + + private ClientPackageList createClientPackageList(ClientPackage...clientPackageList) { + ClientPackageList newClientPackageList = new ClientPackageList(); + for (ClientPackage clientPackage: clientPackageList) { + newClientPackageList.add(clientPackage); + } + return newClientPackageList; + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/clientpackages/CutClientPackageCommandTest.java b/src/test/java/seedu/duke/commands/clientpackages/CutClientPackageCommandTest.java new file mode 100644 index 0000000000..9086e0c0e4 --- /dev/null +++ b/src/test/java/seedu/duke/commands/clientpackages/CutClientPackageCommandTest.java @@ -0,0 +1,113 @@ +package seedu.duke.commands.clientpackages; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CutClientPackageCommandTest { + public static final String[] CLIENT_BOTUAN = {"c001", "Bo Tuan", "93338333", "bt@mail.com"}; + public static final String[] CLIENT_WAYNE = {"c002", "Wayne", "56667888", "wen@mail.com"}; + + public static final String[] TOUR_JPN = {"JPN", "Japan Basic Tour", "1500.00"}; + public static final String[] TOUR_KOR = {"KOR", "Korea Cultural Tour", "3000.00"}; + + public static final String[] FLIGHT_SQJPN = {"SQ-JPN", "JPN", "SG", "20/10/21 18:00", "21/10/21 03:00"}; + public static final String[] FLIGHT_SQKOR = {"SQ-KOR", "KOR", "SG", "23/10/2021 18:00", "30/10/2021 03:00"}; + + public static final String PACKAGE_ID_1 = "p001"; + public static final String PACKAGE_ID_2 = "p002"; + public static final String PACKAGE_ID_3 = "p003"; + public static final String PACKAGE_ID_WRONG = "1234po0o"; + + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + private ClientPackageList allClientPackages; + private ClientPackageList clientPackagesWithoutP002; + + @BeforeEach + public void setUp() { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + Client botuan = new Client(CLIENT_BOTUAN); + Client wayne = new Client(CLIENT_WAYNE); + + Tour jpn = new Tour(TOUR_JPN); + Tour kor = new Tour(TOUR_KOR); + + Flight sqjpn = new Flight(FLIGHT_SQJPN); + Flight sqkor = new Flight(FLIGHT_SQKOR); + + ClientPackage botuanPack1 = new ClientPackage(PACKAGE_ID_1, botuan, jpn, sqjpn); + ClientPackage botuanPack2 = new ClientPackage(PACKAGE_ID_2, botuan, kor, sqkor); + ClientPackage waynePack = new ClientPackage(PACKAGE_ID_3, wayne, jpn, sqkor); + clientPackages = createClientPackageList(botuanPack1, botuanPack2, waynePack); + + allClientPackages = createClientPackageList(botuanPack1, botuanPack2, waynePack); + clientPackagesWithoutP002 = createClientPackageList(botuanPack1, waynePack); + } + + @Test + public void cutClientPackageCommand_validClientPackageId_clientPackageDeleted() throws TourPlannerException { + assertEquals(clientPackages.getClientPackageCount(), 3); + Command cutClientPackageCommand = new CutClientPackageCommand(PACKAGE_ID_2); + cutClientPackageCommand.setData(clients, flights, tours, clientPackages, ui); + cutClientPackageCommand.execute(); + assertEquals(clientPackages.getClientPackageCount(), clientPackagesWithoutP002.getClientPackageCount()); + for (int i = 0; i < clientPackagesWithoutP002.getClientPackageCount(); i++) { + assertEquals(clientPackages.getClientPackageByIndex(i), + clientPackagesWithoutP002.getClientPackageByIndex(i)); + } + } + + @Test + public void cutClientPackageCommand_invalidClientPackageId_clientPackageNotFoundMessage() + throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command cutClientPackageCommand = new CutClientPackageCommand(PACKAGE_ID_WRONG); + cutClientPackageCommand.setData(clients, flights, tours, clientPackages, ui); + cutClientPackageCommand.execute(); + + for (int i = 0; i < allClientPackages.getClientPackageCount(); i++) { + assertEquals(clientPackages.getClientPackageByIndex(i), + allClientPackages.getClientPackageByIndex(i)); + } + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String expectedString = ClientPackageList.CLIENTPACKAGE_NOT_FOUND_MESSAGE; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + private ClientPackageList createClientPackageList(ClientPackage...clientPackageList) { + ClientPackageList newClientPackageList = new ClientPackageList(); + for (ClientPackage clientPackage: clientPackageList) { + newClientPackageList.add(clientPackage); + } + return newClientPackageList; + } +} diff --git a/src/test/java/seedu/duke/commands/clientpackages/ListClientPackageCommandTest.java b/src/test/java/seedu/duke/commands/clientpackages/ListClientPackageCommandTest.java new file mode 100644 index 0000000000..e9ebc1430a --- /dev/null +++ b/src/test/java/seedu/duke/commands/clientpackages/ListClientPackageCommandTest.java @@ -0,0 +1,60 @@ +package seedu.duke.commands.clientpackages; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.Tour; +import seedu.duke.data.Flight; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientList; +import seedu.duke.data.TourList; +import seedu.duke.data.FlightList; +import seedu.duke.data.ClientPackageList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListClientPackageCommandTest { + + private static Client TEST_CLIENT = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + private static Tour TEST_TOUR = new Tour(new String[]{"JPN", "Japan Basic Tour", "1500.00"}); + private static Flight TEST_FLIGHT = new Flight(new String[]{"SQ-JPN", "JPN", "SG", "20/10/2021 18:00", + "21/10/2021 03:00"}); + private static ClientPackage TEST_CLIENTPACKAGE = new ClientPackage("p001", + TEST_CLIENT, TEST_TOUR, TEST_FLIGHT); + private static final String VALID_DATA_OUTPUT = "Here is a list of all packages:\n" + + "1. " + TEST_CLIENTPACKAGE + "\n\n\n" + + "Total Packages: 1"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList dummyClientList = new ClientList(); + TourList dummyTourList = new TourList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList testPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void listClientCommand_validData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + dummyClientList.add(TEST_CLIENT); + dummyTourList.add(TEST_TOUR); + dummyFlightList.add(TEST_FLIGHT); + testPackageList.add(TEST_CLIENTPACKAGE); + + Command listPackage = new ListClientPackageCommand(); + listPackage.setData(dummyClientList, dummyFlightList, dummyTourList, testPackageList, testUi); + listPackage.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/clients/AddClientCommandTest.java b/src/test/java/seedu/duke/commands/clients/AddClientCommandTest.java new file mode 100644 index 0000000000..54299c92a8 --- /dev/null +++ b/src/test/java/seedu/duke/commands/clients/AddClientCommandTest.java @@ -0,0 +1,62 @@ +package seedu.duke.commands.clients; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddClientCommandTest { + + public static final String TEST_CLIENT_ID = "c001"; + public static final String TEST_CLIENT_NAME = "Bo Tuan"; + public static final String TEST_CLIENT_CONTACT = "91234567"; + public static final String TEST_CLIENT_EMAIL = "bobotea@gmail.com"; + + ClientList testClientList = new ClientList(); + FlightList dummyFlightList = new FlightList(); + TourList dummyTourList = new TourList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void addClientCommand_validData_correctlyConstructed() { + Client testClient = + new Client(new String[]{ + TEST_CLIENT_ID, + TEST_CLIENT_NAME, + TEST_CLIENT_CONTACT, + TEST_CLIENT_EMAIL}); + AddClientCommand addClient = new AddClientCommand(testClient); + + Client retrieveClient = addClient.getClient(); + assertEquals(TEST_CLIENT_ID, retrieveClient.getId()); + assertEquals(TEST_CLIENT_NAME, retrieveClient.getName()); + assertEquals(TEST_CLIENT_CONTACT, retrieveClient.getContactNum()); + assertEquals(TEST_CLIENT_EMAIL, retrieveClient.getEmail()); + } + + @Test + void addClientCommand_emptyClientList_populatedClientList() throws TourPlannerException { + Client testClient = + new Client(new String[]{ + TEST_CLIENT_ID, + TEST_CLIENT_NAME, + TEST_CLIENT_CONTACT, + TEST_CLIENT_EMAIL}); + AddClientCommand addClient = new AddClientCommand(testClient); + addClient.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + addClient.execute(); + + Client retrieveClient = testClientList.getClientById(TEST_CLIENT_ID); + assertEquals(TEST_CLIENT_ID, retrieveClient.getId()); + assertEquals(TEST_CLIENT_NAME, retrieveClient.getName()); + assertEquals(TEST_CLIENT_CONTACT, retrieveClient.getContactNum()); + assertEquals(TEST_CLIENT_EMAIL, retrieveClient.getEmail()); + } +} diff --git a/src/test/java/seedu/duke/commands/clients/CutClientCommandTest.java b/src/test/java/seedu/duke/commands/clients/CutClientCommandTest.java new file mode 100644 index 0000000000..030b29f0ac --- /dev/null +++ b/src/test/java/seedu/duke/commands/clients/CutClientCommandTest.java @@ -0,0 +1,125 @@ +package seedu.duke.commands.clients; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CutClientCommandTest { + public static final String CLIENT_ID_1 = "c001"; + + public static final String[] CLIENT_BOTUAN = {CLIENT_ID_1, "Bo Tuan", "93338333", "bt@mail.com"}; + public static final String[] CLIENT_WAYNE = {"c002", "Wayne", "56667888", "wen@mail.com"}; + public static final String[] CLIENT_CHENGXU = {"c003", "ChengXu", "10101010", "demonshaha@mail.com"}; + + public static final String[] TOUR_JPN = {"JPN", "Japan Basic Tour", "1500.00"}; + public static final String[] TOUR_KOR = {"KOR", "Korea Cultural Tour", "3000.00"}; + + public static final String[] FLIGHT_SQJPN = {"SQ-JPN", "JPN", "SG", "20/10/21 18:00", "21/10/21 03:00"}; + public static final String[] FLIGHT_SQKOR = {"SQ-KOR", "KOR", "SG", "23/10/2021 18:00", "30/10/2021 03:00"}; + + public static final String PACKAGE_ID_1 = "p001"; + public static final String PACKAGE_ID_2 = "p002"; + public static final String CLIENT_ID_WRONG = "1234po0o"; + + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + private ClientList allClients; + private ClientList clientsWithoutBotuan; + + @BeforeEach + public void setUp() { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + Client botuan = new Client(CLIENT_BOTUAN); + Client wayne = new Client(CLIENT_WAYNE); + Client chengxu = new Client(CLIENT_CHENGXU); + clients = createClientList(botuan, wayne, chengxu); + allClients = createClientList(botuan, wayne, chengxu); + clientsWithoutBotuan = createClientList(wayne, chengxu); + + Tour jpn = new Tour(TOUR_JPN); + Tour kor = new Tour(TOUR_KOR); + + Flight sqjpn = new Flight(FLIGHT_SQJPN); + Flight sqkor = new Flight(FLIGHT_SQKOR); + + ClientPackage botuanPack1 = new ClientPackage(PACKAGE_ID_1, botuan, jpn, sqjpn); + ClientPackage botuanPack2 = new ClientPackage(PACKAGE_ID_2, botuan, kor, sqkor); + clientPackages = createClientPackageList(botuanPack1, botuanPack2); + } + + @Test + public void cutClientCommand_validClientId_clientDeleted() throws TourPlannerException { + assertEquals(clients.getClientCount(), 3); + Command cutClientCommand = new CutClientCommand(CLIENT_ID_1); + cutClientCommand.setData(clients, flights, tours, clientPackages, ui); + cutClientCommand.execute(); + assertEquals(clients.getClientCount(), clientsWithoutBotuan.getClientCount()); + for (int i = 0; i < clientsWithoutBotuan.getClientCount(); i++) { + assertEquals(clients.getClientByIndex(i), clientsWithoutBotuan.getClientByIndex(i)); + } + } + + @Test + public void cutClientCommand_validClientId_clientPackagesDeleted() throws TourPlannerException { + Command cutClientCommand = new CutClientCommand(CLIENT_ID_1); + cutClientCommand.setData(clients, flights, tours, clientPackages, ui); + cutClientCommand.execute(); + assertEquals(clientPackages.getClientPackageCount(), 0); + } + + @Test + public void cutClientCommand_invalidClientId_clientNotFoundMessage() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command cutClientCommand = new CutClientCommand(CLIENT_ID_WRONG); + cutClientCommand.setData(clients, flights, tours, clientPackages, ui); + cutClientCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String expectedString = ClientList.CLIENT_NOT_FOUND_MESSAGE; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + private ClientList createClientList(Client...clientList) { + ClientList newClientList = new ClientList(); + for (Client client : clientList) { + newClientList.add(client); + } + return newClientList; + } + + private ClientPackageList createClientPackageList(ClientPackage...clientPackageList) { + ClientPackageList newClientPackageList = new ClientPackageList(); + for (ClientPackage clientPackage: clientPackageList) { + newClientPackageList.add(clientPackage); + } + return newClientPackageList; + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/clients/FindClientCommandTest.java b/src/test/java/seedu/duke/commands/clients/FindClientCommandTest.java new file mode 100644 index 0000000000..45b214de8e --- /dev/null +++ b/src/test/java/seedu/duke/commands/clients/FindClientCommandTest.java @@ -0,0 +1,88 @@ +package seedu.duke.commands.clients; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class FindClientCommandTest { + + private static Client TEST_CLIENT_ONE = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + private static Client TEST_CLIENT_TWO = new Client(new String[]{"c002", "Wayne", "56667888", "wen@mail.com"}); + private static Client TEST_CLIENT_THREE = new Client(new String[]{"c002", "Bo Tuan", "56667888", "bbt@mail.com"}); + + private static final String VALID_DATA_OUTPUT = "This is the client(s) that matches your search\n" + + "1. " + TEST_CLIENT_ONE; + private static final String TWO_OR_MORE_OUTPUT = "This is the client(s) that matches your search\n" + + "1. " + TEST_CLIENT_ONE + "\n\n" + + "2. " + TEST_CLIENT_THREE; + private static final String INVALID_DATA_OUTPUT = "I'm sorry, " + + "there seems to be no client(s) that matches your search"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList testClientList = new ClientList(); + TourList dummyTourList = new TourList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void findClientCommand_validData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testClientList.add(TEST_CLIENT_ONE); + testClientList.add(TEST_CLIENT_TWO); + Command findClient = new FindClientCommand("Bo Tuan"); + findClient.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + findClient.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } + + @Test + void findClientCommand_TwoOrMoreSameName_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testClientList.add(TEST_CLIENT_ONE); + testClientList.add(TEST_CLIENT_THREE); + Command findClient = new FindClientCommand("Bo Tuan"); + findClient.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + findClient.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(TWO_OR_MORE_OUTPUT, actualString); + } + + @Test + void findClientCommand_invalidData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testClientList.add(TEST_CLIENT_ONE); + testClientList.add(TEST_CLIENT_TWO); + Command findClient = new FindClientCommand("Sem"); + findClient.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + findClient.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(INVALID_DATA_OUTPUT, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/clients/ListClientCommandTest.java b/src/test/java/seedu/duke/commands/clients/ListClientCommandTest.java new file mode 100644 index 0000000000..496630774b --- /dev/null +++ b/src/test/java/seedu/duke/commands/clients/ListClientCommandTest.java @@ -0,0 +1,66 @@ +package seedu.duke.commands.clients; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.TourList; +import seedu.duke.data.FlightList; +import seedu.duke.data.ClientPackageList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListClientCommandTest { + + private static Client TEST_CLIENT_ONE = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + private static Client TEST_CLIENT_TWO = new Client(new String[]{"c002", "Wayne", "56667888", "wen@mail.com"}); + private static final String VALID_DATA_OUTPUT = "Here is a list of all clients:\n" + + "1. " + TEST_CLIENT_ONE + "\n\n" + + "2. " + TEST_CLIENT_TWO + "\n\n" + + "Total Clients: 2"; + private static final String NO_DATA_OUTPUT = "I'm sorry, there seems to be no clients"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList testClientList = new ClientList(); + TourList dummyTourList = new TourList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void listClientCommand_validData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testClientList.add(TEST_CLIENT_ONE); + testClientList.add(TEST_CLIENT_TWO); + Command listClient = new ListClientCommand(); + listClient.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + listClient.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } + + @Test + void listClientCommand_noData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command listClient = new ListClientCommand(); + listClient.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + listClient.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(NO_DATA_OUTPUT, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/clients/SortClientCommandTest.java b/src/test/java/seedu/duke/commands/clients/SortClientCommandTest.java new file mode 100644 index 0000000000..68749b6c8a --- /dev/null +++ b/src/test/java/seedu/duke/commands/clients/SortClientCommandTest.java @@ -0,0 +1,99 @@ +package seedu.duke.commands.clients; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SortClientCommandTest { + + public static final String VALID_SORT_BY_NAME_FILTER = "/n"; + private static final String VALID_SORT_BY_ID_FILTER = "/id"; + public static final String INVALID_FILTER = "/t"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList testClientList = new ClientList(); + TourList dummyTourList = new TourList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + void initialiseClientListForTesting() { + Client botuan = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + Client wayne = new Client(new String[]{"c002", "Wayne", "56667888", "wen@mail.com"}); + testClientList.add(botuan); + testClientList.add(wayne); + } + + @Test + void sortClientCommand_validFilterByName_correctlySortedNameAlphabetically() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseClientListForTesting(); + + Command testSortClientCommand = new SortClientCommand(VALID_SORT_BY_NAME_FILTER); + testSortClientCommand.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + testSortClientCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testClientList.initTempArray(); + String expectedString = Ui.SORT_CLIENT_NAME_MESSAGE + "\n" + + "1. " + testClientList.getClientByName("Bo Tuan") + "\n\n" + + "2. " + testClientList.getClientByName("Wayne"); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortClientCommand_validFilterById_correctlySortedIdAlphabetically() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseClientListForTesting(); + + Command testSortClientCommand = new SortClientCommand(VALID_SORT_BY_ID_FILTER); + testSortClientCommand.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + testSortClientCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testClientList.initTempArray(); + String expectedString = Ui.SORT_CLIENT_ID_MESSAGE + "\n" + + "1. " + testClientList.getClientById("c001") + "\n\n" + + "2. " + testClientList.getClientById("c002"); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortClientCommand_invalidFilter_throwException() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseClientListForTesting(); + + Command testSortClientCommand = new SortClientCommand(INVALID_FILTER); + testSortClientCommand.setData(testClientList, dummyFlightList, dummyTourList, dummyPackageList, testUi); + testSortClientCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testClientList.initTempArray(); + String expectedString = SortClientCommand.ERROR_MISSING_FILTER; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/flights/AddFlightCommandTest.java b/src/test/java/seedu/duke/commands/flights/AddFlightCommandTest.java new file mode 100644 index 0000000000..8c3f114c4e --- /dev/null +++ b/src/test/java/seedu/duke/commands/flights/AddFlightCommandTest.java @@ -0,0 +1,67 @@ +package seedu.duke.commands.flights; + +import org.junit.jupiter.api.Test; +import seedu.duke.Ui; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddFlightCommandTest { + + public static final String TEST_FLIGHT_ID = "SQ-JPN1"; + public static final String TEST_FLIGHT_DEPART = "JPN"; + public static final String TEST_FLIGHT_RETURN = "SG"; + public static final String TEST_DEPART_TIME = "23/10/21 13:00"; + public static final String TEST_RETURN_TIME = "27/10/21 02:00"; + + FlightList testFlightList = new FlightList(); + ClientList dummyClientList = new ClientList(); + TourList dummyTourList = new TourList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void addFlightCommand_validData_correctlyConstructed() { + Flight testFlight = + new Flight(new String[]{ + TEST_FLIGHT_ID, + TEST_FLIGHT_DEPART, + TEST_FLIGHT_RETURN, + TEST_DEPART_TIME, + TEST_RETURN_TIME}); + AddFlightCommand addFlight = new AddFlightCommand(testFlight); + + Flight retrieveFlight = addFlight.getFlight(); + assertEquals(TEST_FLIGHT_ID, retrieveFlight.getId()); + assertEquals(TEST_FLIGHT_DEPART, retrieveFlight.getDepartDestination()); + assertEquals(TEST_FLIGHT_RETURN, retrieveFlight.getReturnDestination()); + assertEquals(TEST_DEPART_TIME, retrieveFlight.getDepartDate()); + assertEquals(TEST_RETURN_TIME, retrieveFlight.getReturnDate()); + } + + @Test + void addFlightCommand_emptyFlightList_populatedFlightList() { + Flight testFlight = + new Flight(new String[]{ + TEST_FLIGHT_ID, + TEST_FLIGHT_DEPART, + TEST_FLIGHT_RETURN, + TEST_DEPART_TIME, + TEST_RETURN_TIME}); + AddFlightCommand addFlight = new AddFlightCommand(testFlight); + addFlight.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + addFlight.execute(); + + Flight retrieveFlight = testFlightList.getFlightByIndex(0); + assertEquals(TEST_FLIGHT_ID, retrieveFlight.getId()); + assertEquals(TEST_FLIGHT_DEPART, retrieveFlight.getDepartDestination()); + assertEquals(TEST_FLIGHT_RETURN, retrieveFlight.getReturnDestination()); + assertEquals(TEST_DEPART_TIME, retrieveFlight.getDepartDate()); + assertEquals(TEST_RETURN_TIME, retrieveFlight.getReturnDate()); + } + +} diff --git a/src/test/java/seedu/duke/commands/flights/CutFlightCommandTest.java b/src/test/java/seedu/duke/commands/flights/CutFlightCommandTest.java new file mode 100644 index 0000000000..e28970899b --- /dev/null +++ b/src/test/java/seedu/duke/commands/flights/CutFlightCommandTest.java @@ -0,0 +1,124 @@ +package seedu.duke.commands.flights; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CutFlightCommandTest { + public static final String FLIGHT_ID_2 = "SQ-KOR"; + + public static final String[] FLIGHT_SQJPN = {"SQ-JPN", "JPN", "SG", "20/10/21 18:00", "21/10/21 03:00"}; + public static final String[] FLIGHT_SQKOR = {FLIGHT_ID_2, "KOR", "SG", "23/10/2021 18:00", "30/10/2021 03:00"}; + public static final String[] FLIGHT_SQZWM = {"SQ-ZWM", "ZWM", "SG", "5/11/21 09:00", "7/11/21 15:00"}; + + public static final String[] CLIENT_BOTUAN = {"c001", "Bo Tuan", "93338333", "bt@mail.com"}; + public static final String[] CLIENT_WAYNE = {"c002", "Wayne", "56667888", "wen@mail.com"}; + + public static final String[] TOUR_JPN = {"JPN", "Japan Basic Tour", "1500.00"}; + public static final String[] TOUR_KOR = {"KOR", "Korea Cultural Tour", "3000.00"}; + + public static final String CLIENT_PACKAGE_ID_1 = "p001"; + public static final String CLIENT_PACKAGE_ID_2 = "p002"; + public static final String FLIGHT_ID_WRONG = "1234po0o"; + + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + private FlightList flightsWithoutKor; + + @BeforeEach + public void setUp() { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + Flight sqjpn = new Flight(FLIGHT_SQJPN); + Flight sqkor = new Flight(FLIGHT_SQKOR); + Flight sqzwm = new Flight(FLIGHT_SQZWM); + + Client botuan = new Client(CLIENT_BOTUAN); + Client wayne = new Client(CLIENT_WAYNE); + + Tour jpn = new Tour(TOUR_JPN); + Tour kor = new Tour(TOUR_KOR); + + ClientPackage korPack1 = new ClientPackage(CLIENT_PACKAGE_ID_1, botuan, jpn, sqkor); + ClientPackage korPack2 = new ClientPackage(CLIENT_PACKAGE_ID_2, wayne, kor, sqkor); + + flights = createFlightList(sqjpn, sqkor, sqzwm); + flightsWithoutKor = createFlightList(sqjpn, sqzwm); + + clientPackages = createClientPackageList(korPack1, korPack2); + } + + @Test + public void cutFlightCommand_validFlightId_flightDeleted() throws TourPlannerException { + assertEquals(flights.getFlightCount(), 3); + Command cutFlightCommand = new CutFlightCommand(FLIGHT_ID_2); + cutFlightCommand.setData(clients, flights, tours, clientPackages, ui); + cutFlightCommand.execute(); + assertEquals(flights.getFlightCount(), flightsWithoutKor.getFlightCount()); + for (int i = 0; i < flightsWithoutKor.getFlightCount(); i++) { + assertEquals(flights.getFlightByIndex(i), flightsWithoutKor.getFlightByIndex(i)); + } + } + + @Test + public void cutFlightCommand_validFlightId_clientPackagesDeleted() throws TourPlannerException { + Command cutFlightCommand = new CutFlightCommand(FLIGHT_ID_2); + cutFlightCommand.setData(clients, flights, tours, clientPackages, ui); + cutFlightCommand.execute(); + assertEquals(clientPackages.getClientPackageCount(), 0); + } + + @Test + public void cutFlightCommand_invalidFlightId_flightNotFoundMessage() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command cutFlightCommand = new CutFlightCommand(FLIGHT_ID_WRONG); + cutFlightCommand.setData(clients, flights, tours, clientPackages, ui); + cutFlightCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String expectedString = FlightList.FLIGHT_NOT_FOUND_MESSAGE; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + private FlightList createFlightList(Flight...flightList) { + FlightList newFlightList = new FlightList(); + for (Flight flight : flightList) { + newFlightList.add(flight); + } + return newFlightList; + } + + private ClientPackageList createClientPackageList(ClientPackage...clientPackageList) { + ClientPackageList newClientPackageList = new ClientPackageList(); + for (ClientPackage clientPackage: clientPackageList) { + newClientPackageList.add(clientPackage); + } + return newClientPackageList; + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/flights/FindFlightCommandTest.java b/src/test/java/seedu/duke/commands/flights/FindFlightCommandTest.java new file mode 100644 index 0000000000..4cd188e7e2 --- /dev/null +++ b/src/test/java/seedu/duke/commands/flights/FindFlightCommandTest.java @@ -0,0 +1,103 @@ +package seedu.duke.commands.flights; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FindFlightCommandTest { + + private static Flight TEST_FLIGHT_ONE = new Flight(new String[]{"SQ-JPN", "JPN", "SG", "20/10/2021 18:00", + "21/10/2021 03:00"}); + private static Flight TEST_FLIGHT_TWO = new Flight(new String[]{"SQ-KOR", "KOR", "SG", "23/10/2021 18:00", + "30/10/2021 03:00"}); + private static Client TEST_CLIENT = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + private static Tour TEST_TOUR = new Tour(new String[]{"JPN", "Japan Basic Tour", "1500.00"}); + private static ClientPackage TEST_CLIENTPACKAGE = new ClientPackage("p001", + TEST_CLIENT, TEST_TOUR, TEST_FLIGHT_ONE); + + private static final String VALID_DATA_OUTPUT = "This is the flight that matches your search\n" + + TEST_FLIGHT_ONE + "\n\n\n" + + "Passengers:\n\n" + + "Total Passengers: 0"; + private static final String VALID_PASSENGER_OUTPUT = "This is the flight that matches your search\n" + + TEST_FLIGHT_ONE + "\n\n\n" + + "Passengers:\n" + + "1. Bo Tuan (ID: c001)\n" + "\n" + + "Total Passengers: 1"; + private static final String INVALID_DATA_OUTPUT = "ERROR: Flight cannot be found. Please try another flight ID."; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList dummyClientList = new ClientList(); + TourList dummyTourList = new TourList(); + FlightList testFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void findFlightCommand_validData_correctlyConstructed() throws TourPlannerException { + + System.setOut(new PrintStream(newConsole)); + + testFlightList.add(TEST_FLIGHT_ONE); + testFlightList.add(TEST_FLIGHT_TWO); + Command findFlight = new FindFlightCommand("SQ-JPN"); + findFlight.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + findFlight.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } + + @Test + void findFlightCommand_validPassenger_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + dummyClientList.add(TEST_CLIENT); + dummyTourList.add(TEST_TOUR); + testFlightList.add(TEST_FLIGHT_ONE); + dummyPackageList.add(TEST_CLIENTPACKAGE); + + Command findFlight = new FindFlightCommand("SQ-JPN"); + findFlight.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + findFlight.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_PASSENGER_OUTPUT, actualString); + } + + @Test + void findFlightCommand_invalidData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testFlightList.add(TEST_FLIGHT_ONE); + testFlightList.add(TEST_FLIGHT_TWO); + Command findFlight = new FindFlightCommand("SQ-ZBW"); + findFlight.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + findFlight.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(INVALID_DATA_OUTPUT, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/flights/ListFlightCommandTest.java b/src/test/java/seedu/duke/commands/flights/ListFlightCommandTest.java new file mode 100644 index 0000000000..a35ff7f5d2 --- /dev/null +++ b/src/test/java/seedu/duke/commands/flights/ListFlightCommandTest.java @@ -0,0 +1,68 @@ +package seedu.duke.commands.flights; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListFlightCommandTest { + + private static Flight TEST_FLIGHT_ONE = new Flight(new String[]{"SQ-JPN", "JPN", "SG", "20/10/2021 18:00", + "21/10/2021 03:00"}); + private static Flight TEST_FLIGHT_TWO = new Flight(new String[]{"SQ-KOR", "KOR", "SG", "23/10/2021 18:00", + "30/10/2021 03:00"}); + private static final String VALID_DATA_OUTPUT = "Here is a list of all flights:\n" + + "1. " + TEST_FLIGHT_ONE + "\n\n" + + "2. " + TEST_FLIGHT_TWO + "\n\n" + + "Total Flights: 2"; + private static final String NO_DATA_OUTPUT = "I'm sorry, there seems to be no flights"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList dummyClientList = new ClientList(); + TourList dummyTourList = new TourList(); + FlightList testFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void listClientCommand_validData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testFlightList.add(TEST_FLIGHT_ONE); + testFlightList.add(TEST_FLIGHT_TWO); + Command listTour = new ListFlightCommand(); + listTour.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + listTour.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } + + @Test + void listTourCommand_noData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command listTour = new ListFlightCommand(); + listTour.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + listTour.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(NO_DATA_OUTPUT, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/flights/SortFlightCommandTest.java b/src/test/java/seedu/duke/commands/flights/SortFlightCommandTest.java new file mode 100644 index 0000000000..f793e1e3cc --- /dev/null +++ b/src/test/java/seedu/duke/commands/flights/SortFlightCommandTest.java @@ -0,0 +1,126 @@ +package seedu.duke.commands.flights; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SortFlightCommandTest { + + private static final String VALID_SORT_BY_DEPART_FILTER = "/d"; + private static final String VALID_SORT_BY_ARRIVE_FILTER = "/r"; + public static final String VALID_SORT_BY_ID_FILTER = "/id"; + private static final String INVALID_FILTER = "/rubbish"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + TourList dummyTourList = new TourList(); + ClientList dummyClientList = new ClientList(); + FlightList testFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + void initialiseFlightListForTesting() { + Flight sqjpn = new Flight(new String[]{"SQ-JPN", "JPN", "SG", "20/10/21 18:00", "24/10/21 03:00"}); + Flight msiaaus = new Flight(new String[]{"MSIA-AUS", "AUS", "MSIA", "2/11/21 23:00", "9/11/21 11:00"}); + Flight sqkor = new Flight(new String[]{"SQ-KOR", "KOR", "SG", "26/10/21 14:00", "28/10/21 05:00"}); + testFlightList.add(sqjpn); + testFlightList.add(msiaaus); + testFlightList.add(sqkor); + } + + @Test + void sortFlightCommand_validFilterByDepartureDate_correctlySortedTime() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseFlightListForTesting(); + + Command testSortFlightCommand = new SortFlightCommand(VALID_SORT_BY_DEPART_FILTER); + testSortFlightCommand.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + testSortFlightCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testFlightList.initTempArray(); + String expectedString = Ui.SORT_FLIGHT_BY_DEPARTURE_MESSAGE + "\n" + + "1. " + testFlightList.getFlightByDepartDate("20/10/21 18:00") + "\n\n" + + "2. " + testFlightList.getFlightByDepartDate("26/10/21 14:00") + "\n\n" + + "3. " + testFlightList.getFlightByDepartDate("2/11/21 23:00"); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortFlightCommand_validFilterByArriveDate_correctlySortedTime() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseFlightListForTesting(); + + Command testSortFlightCommand = new SortFlightCommand(VALID_SORT_BY_ARRIVE_FILTER); + testSortFlightCommand.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + testSortFlightCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testFlightList.initTempArray(); + String expectedString = Ui.SORT_FLIGHT_BY_ARRIVAL_MESSAGE + "\n" + + "1. " + testFlightList.getFlightByReturnDate("24/10/21 03:00") + "\n\n" + + "2. " + testFlightList.getFlightByReturnDate("28/10/21 05:00") + "\n\n" + + "3. " + testFlightList.getFlightByReturnDate("9/11/21 11:00"); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortFlightCommand_validFilterById_correctlySortedIdAlphabetically() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseFlightListForTesting(); + + Command testSortFlightCommand = new SortFlightCommand(VALID_SORT_BY_ID_FILTER); + testSortFlightCommand.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + testSortFlightCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testFlightList.initTempArray(); + String expectedString = Ui.SORT_FLIGHT_ID_MESSAGE + "\n" + + "1. " + testFlightList.getFlightById("MSIA-AUS") + "\n\n" + + "2. " + testFlightList.getFlightById("SQ-JPN") + "\n\n" + + "3. " + testFlightList.getFlightById("SQ-KOR"); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortTourCommand_invalidFilter_throwException() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseFlightListForTesting(); + + Command testSortFlightCommand = new SortFlightCommand(INVALID_FILTER); + testSortFlightCommand.setData(dummyClientList, testFlightList, dummyTourList, dummyPackageList, testUi); + testSortFlightCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testFlightList.initTempArray(); + String expectedString = SortFlightCommand.ERROR_MISSING_FILTER; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/tours/AddTourCommandTest.java b/src/test/java/seedu/duke/commands/tours/AddTourCommandTest.java new file mode 100644 index 0000000000..7f7006c2b0 --- /dev/null +++ b/src/test/java/seedu/duke/commands/tours/AddTourCommandTest.java @@ -0,0 +1,57 @@ +package seedu.duke.commands.tours; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddTourCommandTest { + + public static final String TEST_TOUR_ID = "t001"; + public static final String TEST_TOUR_NAME = "AustralianRomance"; + public static final String TEST_TOUR_PRICE_STRING = "1500"; + + TourList testTourList = new TourList(); + ClientList dummyClientList = new ClientList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + + @Test + void addTourCommand_validData_correctlyConstructed() { + Tour testTour = + new Tour(new String[]{TEST_TOUR_ID, + TEST_TOUR_NAME, + TEST_TOUR_PRICE_STRING}); + AddTourCommand addTour = new AddTourCommand(testTour); + + Tour retrieveTour = addTour.getTour(); + assertEquals(TEST_TOUR_ID, retrieveTour.getId()); + assertEquals(TEST_TOUR_NAME, retrieveTour.getName()); + assertEquals(Float.parseFloat(TEST_TOUR_PRICE_STRING), retrieveTour.getPrice()); + } + + @Test + void addTourCommand_emptyTourList_populatedTourList() { + Tour testTour = + new Tour(new String[]{ + TEST_TOUR_ID, + TEST_TOUR_NAME, + TEST_TOUR_PRICE_STRING}); + AddTourCommand addTour = new AddTourCommand(testTour); + addTour.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + addTour.execute(); + + Tour retrieveTour = addTour.getTour(); + assertEquals(TEST_TOUR_ID, retrieveTour.getId()); + assertEquals(TEST_TOUR_NAME, retrieveTour.getName()); + assertEquals(Float.parseFloat(TEST_TOUR_PRICE_STRING), retrieveTour.getPrice()); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/tours/CutTourCommandTest.java b/src/test/java/seedu/duke/commands/tours/CutTourCommandTest.java new file mode 100644 index 0000000000..1ab074bc78 --- /dev/null +++ b/src/test/java/seedu/duke/commands/tours/CutTourCommandTest.java @@ -0,0 +1,124 @@ +package seedu.duke.commands.tours; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CutTourCommandTest { + public static final String TOUR_ID_2 = "KOR"; + + public static final String[] TOUR_JPN = {"JPN", "Japan Basic Tour", "1500.00"}; + public static final String[] TOUR_KOR = {TOUR_ID_2, "Korea Cultural Tour", "3000.00"}; + public static final String[] TOUR_ZWM = {"ZWM", "Zimbabwe Tour", "1700.00"}; + + public static final String[] FLIGHT_SQJPN = {"SQ-JPN", "JPN", "SG", "20/10/21 18:00", "21/10/21 03:00"}; + public static final String[] FLIGHT_SQKOR = {"SQ-KOR", "KOR", "SG", "23/10/2021 18:00", "30/10/2021 03:00"}; + + public static final String[] CLIENT_BOTUAN = {"c001", "Bo Tuan", "93338333", "bt@mail.com"}; + public static final String[] CLIENT_WAYNE = {"c002", "Wayne", "56667888", "wen@mail.com"}; + + public static final String CLIENT_PACKAGE_ID_1 = "p001"; + public static final String CLIENT_PACKAGE_ID_2 = "p002"; + public static final String TOUR_ID_WRONG = "1234po0o"; + + PrintStream previousConsole; + ByteArrayOutputStream newConsole; + + private ClientList clients; + private FlightList flights; + private TourList tours; + private ClientPackageList clientPackages; + private Ui ui = new Ui(); + + private TourList toursWithoutKor; + + @BeforeEach + public void setUp() { + previousConsole = System.out; + newConsole = new ByteArrayOutputStream(); + + Tour jpn = new Tour(TOUR_JPN); + Tour kor = new Tour(TOUR_KOR); + Tour zwm = new Tour(TOUR_ZWM); + + Flight sqjpn = new Flight(FLIGHT_SQJPN); + Flight sqkor = new Flight(FLIGHT_SQKOR); + + Client botuan = new Client(CLIENT_BOTUAN); + Client wayne = new Client(CLIENT_WAYNE); + + ClientPackage korPack1 = new ClientPackage(CLIENT_PACKAGE_ID_1, botuan, kor, sqjpn); + ClientPackage korPack2 = new ClientPackage(CLIENT_PACKAGE_ID_2, wayne, kor, sqkor); + + tours = createTourList(jpn, kor, zwm); + toursWithoutKor = createTourList(jpn, zwm); + + clientPackages = createClientPackageList(korPack1, korPack2); + } + + @Test + public void cutTourCommand_validTourId_tourDeleted() throws TourPlannerException { + assertEquals(tours.getTourCount(), 3); + Command cutTourCommand = new CutTourCommand(TOUR_ID_2); + cutTourCommand.setData(clients, flights, tours, clientPackages, ui); + cutTourCommand.execute(); + assertEquals(tours.getTourCount(), toursWithoutKor.getTourCount()); + for (int i = 0; i < toursWithoutKor.getTourCount(); i++) { + assertEquals(tours.getTourByIndex(i), toursWithoutKor.getTourByIndex(i)); + } + } + + @Test + public void cutTourCommand_validTourId_clientPackagesDeleted() throws TourPlannerException { + Command cutTourCommand = new CutTourCommand(TOUR_ID_2); + cutTourCommand.setData(clients, flights, tours, clientPackages, ui); + cutTourCommand.execute(); + assertEquals(clientPackages.getClientPackageCount(), 0); + } + + @Test + public void cutTourCommand_invalidTourId_tourNotFoundMessage() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command cutTourCommand = new CutTourCommand(TOUR_ID_WRONG); + cutTourCommand.setData(clients, flights, tours, clientPackages, ui); + cutTourCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String expectedString = TourList.TOUR_NOT_FOUND_MESSAGE; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + private TourList createTourList(Tour... tourList) { + TourList newTourList = new TourList(); + for (Tour tour : tourList) { + newTourList.add(tour); + } + return newTourList; + } + + private ClientPackageList createClientPackageList(ClientPackage...clientPackageList) { + ClientPackageList newClientPackageList = new ClientPackageList(); + for (ClientPackage clientPackage: clientPackageList) { + newClientPackageList.add(clientPackage); + } + return newClientPackageList; + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/tours/FindTourCommandTest.java b/src/test/java/seedu/duke/commands/tours/FindTourCommandTest.java new file mode 100644 index 0000000000..17d465bd5a --- /dev/null +++ b/src/test/java/seedu/duke/commands/tours/FindTourCommandTest.java @@ -0,0 +1,101 @@ +package seedu.duke.commands.tours; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.Client; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackage; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.Flight; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FindTourCommandTest { + + private static Tour TEST_TOUR_ONE = new Tour(new String[]{"JPN", "Japan Basic Tour", "1500.00"}); + private static Tour TEST_TOUR_TWO = new Tour(new String[]{"KOR", "Korea Cultural Tour", "3000.00"}); + private static Client TEST_CLIENT = new Client(new String[]{"c001", "Bo Tuan", "93338333", "bt@mail.com"}); + private static Flight TEST_FLIGHT = new Flight(new String[]{"SQ-JPN", "JPN", "SG", "20/10/2021 18:00", + "21/10/2021 03:00"}); + private static ClientPackage TEST_CLIENTPACKAGE = new ClientPackage("p001", + TEST_CLIENT, TEST_TOUR_ONE, TEST_FLIGHT); + + private static final String VALID_DATA_OUTPUT = "This is the tour that matches your search\n" + + TEST_TOUR_ONE + "\n\n\n" + + "Subscribed Clients:\n\n" + + "Total Subscribed Clients: 0"; + private static final String VALID_SUBSCRIPTION_OUTPUT = "This is the tour that matches your search\n" + + TEST_TOUR_ONE + "\n\n\n" + + "Subscribed Clients:\n" + + "1. Bo Tuan (ID: c001)\n" + + "\n" + + "Total Subscribed Clients: 1"; + private static final String INVALID_DATA_OUTPUT = "ERROR: Tour cannot be found. " + + "Please try another tour ID."; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList dummyClientList = new ClientList(); + TourList testTourList = new TourList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void findTourCommand_validData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + testTourList.add(TEST_TOUR_ONE); + testTourList.add(TEST_TOUR_TWO); + Command findTour = new FindTourCommand("JPN"); + findTour.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + findTour.execute(); + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } + + @Test + void findTourCommand_validSubscription_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + dummyClientList.add(TEST_CLIENT); + testTourList.add(TEST_TOUR_ONE); + dummyFlightList.add(TEST_FLIGHT); + dummyPackageList.add(TEST_CLIENTPACKAGE); + + Command findTour = new FindTourCommand("JPN"); + findTour.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + findTour.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_SUBSCRIPTION_OUTPUT, actualString); + } + + @Test + void findTourCommand_invalidData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + testTourList.add(TEST_TOUR_ONE); + testTourList.add(TEST_TOUR_TWO); + Command findTour = new FindTourCommand("SGP"); + findTour.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + findTour.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(INVALID_DATA_OUTPUT, actualString); + } + +} diff --git a/src/test/java/seedu/duke/commands/tours/ListTourCommandTest.java b/src/test/java/seedu/duke/commands/tours/ListTourCommandTest.java new file mode 100644 index 0000000000..31e4f699b4 --- /dev/null +++ b/src/test/java/seedu/duke/commands/tours/ListTourCommandTest.java @@ -0,0 +1,66 @@ +package seedu.duke.commands.tours; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListTourCommandTest { + + private static Tour TEST_TOUR_ONE = new Tour(new String[]{"JPN", "Japan Basic Tour", "1500.00"}); + private static Tour TEST_TOUR_TWO = new Tour(new String[]{"KOR", "Korea Cultural Tour", "3000.00"}); + private static final String VALID_DATA_OUTPUT = "Here is a list of all tours:\n" + + "1. " + TEST_TOUR_ONE + "\n\n" + + "2. " + TEST_TOUR_TWO + "\n\n" + + "Total Tours: 2"; + private static final String NO_DATA_OUTPUT = "I'm sorry, there seems to be no tours"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + ClientList dummyClientList = new ClientList(); + TourList testTourList = new TourList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + @Test + void listClientCommand_validData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + testTourList.add(TEST_TOUR_ONE); + testTourList.add(TEST_TOUR_TWO); + Command listTour = new ListTourCommand(); + listTour.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + listTour.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(VALID_DATA_OUTPUT, actualString); + } + + @Test + void listTourCommand_noData_correctlyConstructed() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + + Command listTour = new ListTourCommand(); + listTour.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + listTour.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(NO_DATA_OUTPUT, actualString); + } +} diff --git a/src/test/java/seedu/duke/commands/tours/SortTourCommandTest.java b/src/test/java/seedu/duke/commands/tours/SortTourCommandTest.java new file mode 100644 index 0000000000..80378dd715 --- /dev/null +++ b/src/test/java/seedu/duke/commands/tours/SortTourCommandTest.java @@ -0,0 +1,162 @@ +package seedu.duke.commands.tours; + +import org.junit.jupiter.api.Test; +import seedu.duke.TourPlannerException; +import seedu.duke.Ui; +import seedu.duke.commands.Command; +import seedu.duke.data.ClientList; +import seedu.duke.data.ClientPackageList; +import seedu.duke.data.FlightList; +import seedu.duke.data.Tour; +import seedu.duke.data.TourList; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SortTourCommandTest { + + public static final String VALID_SORT_BY_PRICE_FILTER = "/p"; + private static final String VALID_SORT_BY_NAME_FILTER = "/n"; + private static final String VALID_SORT_BY_ID_FILTER = "/id"; + private static final String INVALID_FILTER = "/i"; + private static final String EMPTY_FILTER = ""; + + public static final String ID_ORDER_TWO = "JPN"; + public static final String ID_ORDER_THREE = "KOR"; + public static final String ID_ORDER_ONE = "AUS"; + + public static final String NAME_ORDER_ONE = "Australia Romantic Tour"; + public static final String NAME_ORDER_TWO = "Japan Basic Tour"; + public static final String NAME_ORDER_THREE = "Korea Cultural Tour"; + + public static final String PRICE_ORDER_ONE = "1500.00"; + public static final String PRICE_ORDER_THREE = "3000.00"; + public static final String PRICE_ORDER_TWO = "1800.00"; + + PrintStream previousConsole = System.out; + ByteArrayOutputStream newConsole = new ByteArrayOutputStream(); + + TourList testTourList = new TourList(); + ClientList dummyClientList = new ClientList(); + FlightList dummyFlightList = new FlightList(); + ClientPackageList dummyPackageList = new ClientPackageList(); + Ui testUi = new Ui(); + + void initialiseTourListForTesting() { + Tour jpn = new Tour(new String[]{ID_ORDER_TWO, NAME_ORDER_TWO, PRICE_ORDER_ONE}); + Tour kor = new Tour(new String[]{ID_ORDER_THREE, NAME_ORDER_THREE, PRICE_ORDER_THREE}); + Tour aus = new Tour(new String[]{ID_ORDER_ONE, NAME_ORDER_ONE, PRICE_ORDER_TWO}); + testTourList.add(jpn); + testTourList.add(kor); + testTourList.add(aus); + } + + Float parseFloat(String value) { + return Float.parseFloat(value); + } + + @Test + void sortTourCommand_validFilterByPrice_correctlySortedPriceAscending() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseTourListForTesting(); + + Command testSortTourCommand = new SortTourCommand(VALID_SORT_BY_PRICE_FILTER); + testSortTourCommand.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + testSortTourCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testTourList.initTempArray(); + String expectedString = Ui.SORT_TOUR_PRICE_MESSAGE + "\n" + + "1. " + testTourList.getTourByPrice(parseFloat(PRICE_ORDER_ONE)) + "\n\n" + + "2. " + testTourList.getTourByPrice(parseFloat(PRICE_ORDER_TWO)) + "\n\n" + + "3. " + testTourList.getTourByPrice(parseFloat(PRICE_ORDER_THREE)); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortTourCommand_validFilterById_correctlySortedIdAscending() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseTourListForTesting(); + + Command testSortTourCommand = new SortTourCommand(VALID_SORT_BY_ID_FILTER); + testSortTourCommand.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + testSortTourCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testTourList.initTempArray(); + String expectedString = Ui.SORT_TOUR_ID_MESSAGE + "\n" + + "1. " + testTourList.getTourById(ID_ORDER_ONE) + "\n\n" + + "2. " + testTourList.getTourById(ID_ORDER_TWO) + "\n\n" + + "3. " + testTourList.getTourById(ID_ORDER_THREE); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortTourCommand_validFilterByName_correctlySortedNameAscending() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseTourListForTesting(); + + Command testSortTourCommand = new SortTourCommand(VALID_SORT_BY_NAME_FILTER); + testSortTourCommand.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + testSortTourCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testTourList.initTempArray(); + String expectedString = Ui.SORT_TOUR_NAME_MESSAGE + "\n" + + "1. " + testTourList.getTourByName(NAME_ORDER_ONE) + "\n\n" + + "2. " + testTourList.getTourByName(NAME_ORDER_TWO) + "\n\n" + + "3. " + testTourList.getTourByName(NAME_ORDER_THREE); + + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortTourCommand_invalidFilter_throwException() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseTourListForTesting(); + + Command testSortTourCommand = new SortTourCommand(INVALID_FILTER); + testSortTourCommand.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + testSortTourCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testTourList.initTempArray(); + String expectedString = SortTourCommand.ERROR_MISSING_FILTER; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + + @Test + void sortTourCommand_emptyFilter_throwException() throws TourPlannerException { + System.setOut(new PrintStream(newConsole)); + initialiseTourListForTesting(); + + Command testSortTourCommand = new SortTourCommand(EMPTY_FILTER); + testSortTourCommand.setData(dummyClientList, dummyFlightList, testTourList, dummyPackageList, testUi); + testSortTourCommand.execute(); + + previousConsole.println(newConsole.toString()); + System.setOut(previousConsole); + + testTourList.initTempArray(); + String expectedString = SortTourCommand.ERROR_MISSING_FILTER; + String actualString = newConsole.toString().trim().replace("\r\n", "\n"); + assertEquals(expectedString, actualString); + } + +} \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..e69de29bb2 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +0,0 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..e69de29bb2 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +0,0 @@ -James Gosling \ No newline at end of file