diff --git a/.gitignore b/.gitignore index 99712178bf..dbf9b3565a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,10 @@ src/main/resources/docs/ .DS_Store *.iml bin/ + +# ignore save file +/src/main/data/archive.txt +/src/main/data/optix.txt + +# ignore log files +/OptixLogger.* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..dff5f3a5d0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: java diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000000..62a559384c --- /dev/null +++ b/README.adoc @@ -0,0 +1,20 @@ += OPTIX - An easy way to manage theatre bookings +ifdef::env-github,env-browser[:relfileprefix: docs/] + + +ifdef::env-github[] +image::docs/images/Ui.png[width="600"] +endif::[] + +ifndef::env-github[] +image::images/Ui.png[width="600"] +endif::[] + +* This is a desktop application made for users who want to manage the seating of a theatre or other venues while keeping track of the finances earned. A GUI is made but the main user input is done using the Command Line Interface (CLI). + +== Site Map + +* <> +* <> +* <> + diff --git a/README.md b/README.md deleted file mode 100644 index 84755485a7..0000000000 --- a/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Setting up - -**Prerequisites** - -* JDK 11 -* Recommended: IntelliJ IDE -* Fork this repo to your GitHub account and clone the fork to your computer - -**Importing the project into IntelliJ** - -1. Open IntelliJ (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first). -1. Set up the correct JDK version. - * Click `Configure` > `Structure for new Projects` (in older versions of Intellij:`Configure` > `Project Defaults` > `Project Structure`). - * If JDK 11 is listed in the drop down, select it. If it is not, click `New...` and select the directory where you installed JDK 11. - * Click `OK`. -1. Click `Import Project`. -1. Locate the project directory and click `OK`. -1. Select `Create project from existing sources` and click `Next`. -1. Rename the project if you want. Click `Next`. -1. Ensure that your src folder is checked. Keep clicking `Next`. -1. Click `Finish`. - -# Tutorials - -Duke Increment | Tutorial ----------------|--------------- -`A-Gradle` | [Gradle Tutorial](tutorials/gradleTutorial.md) -`A-TextUiTesting` | [Text UI Testing Tutorial](tutorials/textUiTestingTutorial.md) -`Level-10` | JavaFX tutorials:
→ [Part 1: Introduction to JavaFX][fx1]
→ [Part 2: Creating a GUI for Duke][fx2]
→ [Part 3: Interacting with the user][fx3]
→ [Part 4: Introduction to FXML][fx4] - -[fx1]: -[fx2]: -[fx3]: -[fx4]: - -# Feedback, Bug Reports - -* If you have feedback or bug reports, please post in [se-edu/duke issue tracker](https://github.com/se-edu/duke/issues). -* We welcome pull requests too. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..604d3fc9ea --- /dev/null +++ b/build.gradle @@ -0,0 +1,66 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.openjfx.javafxplugin' version '0.0.7' +} + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.jfoenix:jfoenix:9.0.8' + testImplementation 'org.junit.jupiter:junit-jupiter:5.5.0' +} + +test { + useJUnitPlatform() +} + +javafx { + version = "11.0.2" + modules = [ 'javafx.controls', 'javafx.fxml' ] +} + +shadowJar { + archiveBaseName = "Optix" + archiveVersion = "1.3" + archiveClassifier = null + archiveAppendix = null +} + +checkstyle { + toolVersion = '8.23' +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +group 'duke' +version '0.1.0' + + +application { + // Change this to your main class. + mainClassName = "Main" +} + +run { + standardInput = System.in +} + +sourceSets { + main { + java { + srcDirs = ["src/main/java"] + } + resources { + srcDirs = ["src/main/resource"] + } + } +} + + diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml new file mode 100644 index 0000000000..df2aa6c8b1 --- /dev/null +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..668d7d1f2d --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc new file mode 100644 index 0000000000..b26c739123 --- /dev/null +++ b/docs/AboutUs.adoc @@ -0,0 +1,45 @@ +# About Us + +Optix was developed by the AY1920S1-CS2113T-T12-1 team. + +We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore], taking CS2113T in AY19/20 S1. + +This is a group project as part of this module. We are an egoless team. + +## Project Team +### Nicholas Liu + +image::images/nicholasliu97.png[width="150", align="left"] +{empty}[https://https://github.com/NicholasLiu97[github]] + +Role: Team Lead + +Responsibilities: Documentation, Deliverables & Deadlines + +### Kennedy Oung + +image::images/oungkennedy.png[width="150", align="left"] +{empty}[https://https://github.com/OungKennedy[github]] + +Role: Developer + +Responsibilities: Integration & Testing + + +### Tian Chang + +image::images/tianchangliao.png[width ="150", align="left"] +{empty}[https://https://github.com/TianchangLiao[github]] + +Role: Developer + +Responsibilities: Scheduling & Tracking + +### Brian + +image::images/cheesengg.png[width ="150", align="left"] +{empty}[https://https://github.com/CheeSengg[github]] + +Role: Developer + +Responsibilities: Code Quality diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc new file mode 100644 index 0000000000..17e02425e4 --- /dev/null +++ b/docs/DeveloperGuide.adoc @@ -0,0 +1,1311 @@ += OPTIX - Developer Guide +:site-section: DeveloperGuide +:toc: +:toc-title: +:toc-placement: preamble +:sectnums: +:stylesDir: stylesheets +:xrefstyle: full +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:warning-caption: :warning: +endif::[] +:repoURL: https://github.com/AY1920S1-CS2113T-T12-1/main + +By: `Team AY1920S1-CS2113T-T12-1`      Since: `Aug 2019` + + +== Introduction +Optix is a desktop application for users to manage ticket booking and the finances of a show at its respective venue. +User input is done mainly using the Command Line Interface (CLI) and GUI is used to display the output. The CLI and GUI +are integrated to make the execution of commands more efficient and user-friendly. + +This document describes the architecture and system design of Optix and serves to let other developers understand the +implementation of the program should they wish to work on it. This document is divided into four +parts: System architecture, implementations, functionalities of the program and requirements of the program. + +*Problem we are solving*: + +Provide an open-source software for planning committees who wish to schedule shows at a venue but lack proper +management system. Optix is also an all-in-one platform that allows for seat bookings and finance tracking. + +=== Target User Profile: +* Any user with a need to track the seat booking of a show or event as well as the finances from the bookings. +* Prefers CLI to mouse input. + +=== Product features: +* New shows scheduled can be added to the list. +* Sales of seats for a show. +* Program is designed to price the seats according to the different tiers. +* Revenue earned from a show can be calculated based on the seats booked. + +=== Value proposition: +Many planning committees often plan performances and events at various venues for guests. However, as many of them do +not have access to ticket management programs, they use conventional methods like using Microsoft Excel to record seat +purchases and track finances. Such tracking methods are rather inefficient and error prone as it is difficult to check +if a particular seat has been purchased or not. Optix is an all-in-one application that facilitates the management of +seats and finances of a show more efficiently than an average GUI-based app. Users can easily track the seating +arrangement with the GUI and sell seats to customers efficiently through the CLI. An in-built finance tracker also +helps to track the profit earned from the shows. + +== Setting up + +=== Prerequisites +. *JDK* `11` or above + +. *IntelliJ* IDE + +[NOTE] +IntelliJ by default has Gradle and JavaFx plugins installed. Do not disable them. +If you have disabled them, go to `File` > `Settings` > `Plugins` to re-enable them. + +=== Setting up the project +. Fork link:{repoURL}[this] repo, and clone the fork to your computer. + +. Open IntelliJ (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project +dialog first) +. Set up the correct JDK version for Gradle +.. Click `Configure` > `Project Defaults` > `Project Structure` +.. Click `New…` find the directory of the JDK +. Click `Import Project` +. Locate the `build.gradle` file and select it. Click `OK` +. Click `Open as Project` +. Click `OK` to accept the default settings. + +=== Verifying the setup +. Run Main and try a few commands. +. Run the tests(To hyperlink to test portion later on) to ensure they all pass. + +=== Configurations to do before writing code + +== Design + +This section documents the system architecture for Optix and noteworthy components that supports Optix. + +=== Architecture + +.Architecture Diagram +image::images/devguide/DG_SystemArch.png[width ="600", align="center"] + +The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick +overview of each component. + +`Main` has two classes named `Main` and `MainApp`. It is responsible for, + +* At app launch: Initialize the components in the correct sequence, and connects them up with each other. +* At shut down: Shuts down the components and invokes cleanup method where necessary. + +The rest of the App consists of 3 components. + +* `GUI`: The Graphical User Interface of the App. +* `Optix`: The intermediary between `GUI` and `Command`. +* `XYZCommand`: Various Commands that changes data in `Model`. + +==== How the architecture components interact with each other + +The Sequence Diagram below shows how the components interact with each other for the scenario where user issues the command +`list Nov 2020` + +.Component interactions for `list Nov 2020` command +image::images/devguide/DG_Design_SequenceDiagram.png[width ="600", align="center"] + +The sections below give more details of each component. + +=== GUI Component + +.Class Diagram of the GUI Component +image::images/devguide/DG_GUI_ClassDiagram.png[width ="600", align="center"] + +*API*: link:{repoURL}/tree/master/src/main/java/optix/ui/windows[`GUI`] + +The `GUI` consists of a `MainWindow` that is made up of parts e.g `DialogBox`, `HelpWindow`, `SeatDisplayController`, etc. +`MainWindow` is split into two segment, on the left the display window and on the right the chat box, as show +in the GUI diagram. + +.Optix GUI +image::images/Ui.png[width ="600", align="center"] + +The display window consists of 5 different viewing mode. + +* *Shows*: Scheduled shows for the event venue. Controlled by `ShowController`. +* *Seats*: The seat availability for a specific show. Controlled by `SeatDisplayController`. +* *Finance*: The projected revenue for scheduled shows. Controlled by `FinanceController`. +* *Archive*: The total revenue for a show that has been performed. Controlled by `FinanceController`. +* *Help*: All possible command to operate Optix. Controlled by `HelpWindow`. + +The `GUI` component uses JavaFx UI framework and JFoenix API. The layout of these UI parts are defined in +matching .fxml files that are in the `src/main/resources/view` folder. For example, the layout of the +`MainWindow` is specified in `MainWindow.fxml`. + +The `GUI` component, + +* Passes user command to `Optix` component to be executed. + +* Listens for changes in `Model` in `Optix` so that the GUI can be updated with the modified data. + +=== Optix Component + +*API* : link:{repoURL}/tree/master/src/main/java/optix/Optix.java[`Optix.java`] + +`Optix` is the backend software that acts as the intermediary between `GUI` and `Command` logic. +`Optix`, + +* Instantiates `Command` through `Parser` based on user command in `GUI`. +* Passes updated model to be displayed by `GUI`. +* Holds all the file path for `Storage`. +* Stores `Optix` response to user. + +The sections below give more details of each component in `Optix`. + +==== Model Component + +*API* : link:{repoURL}/tree/master/src/main/java/optix/commons[`Model`] + +.Class Diagram of the Model Component +image::images/devguide/DG_Model_Structure_Diagram.png[width ="600", align="center"] + +`Model`, + +* Stores the data used for the App. It contains 3 `ShowMap`. + +** *shows*: All the shows that are scheduled for the future. +** *showsHistory*: All the shows that are in the past. +** *showsGui*: All the shows that are to be displayed on the `GUI` component. + +* Depends on the storage component to store `shows` and `showsHistory`. + +[NOTE] +For *showsHistory*, the multiplicity of `Seat` within `Theatre` is 0 as it is irrelevant. + +==== Storage Component + +*API* : link:{repoURL}/tree/master/src/main/java/optix/commons/Storage.java[`Storage.java`] + +`Storage` reads and writes to its respective .txt files so that information can be loaded on the +following app launch. The .txt files are saved in `{currDir}/src/main/data`. There is a total of 2 +.txt files that are written and read in `Storage` component. + +1. optix.txt + +2. archive.txt + +==== UI Component + + +*API* : link:{repoURL}/tree/master/src/main/java/optix/ui/Ui.java[`Ui.java`] + +The `UI` component handles all of `Optix` response. `Optix` response is determined by `Command` component +and subsequently displayed using `GUI` component. + +==== Parser Component + +*API* : link:{repoURL}/tree/master/src/main/java/optix/util/Parser.java[`Parser.java`] + +The `Parser`, + +* Determines the command word and constructs `Command` component. +* Stores command aliases in a HashMap. +* Updates command aliases in the HashMap. +* Reads and writes to parserPreferences.txt. + +=== Command Component + +*API* : link:{repoURL}/tree/master/src/main/java/optix/commands[`Command`] + +All commands found in commands package extends `Command`. The `Command` for the different data structures +are stored under different subpackages. Currently the commands package consists of 4 different subpackages. + +* *shows*: Commands that deal with the management fo shows for the theatre. +* *seats*: Commands that deal with the management of seats within the theatre. +* *finance*: Commands that manage finances within the theatre. +* *parser*: Commands that deal with command aliases. + +== Implementation +This section describes some noteworthy details on how certain features are implemented. + +=== View Monthly Revenue Feature +Allows user to view their profit for a specific month. + +==== Proposed Implementation +Viewing the monthly revenue is executed by the `ViewMonthlyCommand`, which extends from the abstract class `Command` and +is stored under the commands package. + +Additionally, it implements the following operations based on the query date: + +* Model#findMonthly() -- Retrieves the list of shows in the month specified by the input. +* Model#getShows() -- Retrieves the current list of shows should the user query be in the future. +* Model#getShowshistory() -- Retrieves the archive list should the user query be in the past. +* OptixDateFormatter#getMonth() -- get the integer value of the month. +* OptixDateFormatter#getYear() -- get the integer value of the year. +* Theatre#getProfit() -- get the profit earned for the show. + +Given below is an example usage scenario of the `ViewMonthlyCommand` at each step. + +*Step 1* + +The user starts the application. `Storage` will be initialised with the saved contents from previous runs. `Model` +will then be initialised and the current list and archived list of shows are loaded into `Model`. + +*Step 2* + +The user executes `view-monthly June 2017` to check the revenue earned by all shows in June 2017. Once `Parser` verifies +that the command is of correct format, `ViewMonthlyCommand` calls `OptixDateFormatter#getMonth()` and +`OptixDateFormatter#getYear()` to get the integer values of month and year respectively. + +*Step 3* + +`ViewMonthlyCommand` calls `Model#getShowsHistory()` since the date is in the past. This hashmap of `ShowsHistory` is +then passed into the `Model#findMonthly()` of the `Model` where a list of the shows in the specified month is created. + +*Step 4* + +The profit for each of the shows in the remaining list is then added up in `Model` by calling `Theatre#getProfit()`. + +The following sequence diagram shows how the view-monthly operation works: + +.Sequence Diagram of `ViewMonthlyCommand` +image::images/devguide/DG_ViewMonthly_SequenceDiagram.png[width ="600", align="center"] + +The following activity diagram summarizes what happens when a user executes the ViewMonthly Command: + +.Activity Diagram of `ViewMonthlyCommand` +image::images/devguide/DG_ViewMonthly_ActivityDiagram.png[width ="300", align="center"] + +==== Design Considerations + +*Aspect: How view-monthly executes* + +* Alternative 1 (current choice): Obtain the required shows from the current and archive list separately. +** Pros: +Reduces search time if Optix knows which list to search from. + +Also increases user friendliness since message can be customised depending on whether the show is from the current or archive or both lists. + +** Cons: More bug prone since there are more conditional statements. + +* Alternative 2: Combine the current and archive list before searching for the shows. +** Pros: More efficient since the entire list would be sorted. +** Cons: Message showing the monthly revenue will always be the same, making it less user-friendly. + +=== Sell Seats Feature +Allows user to sell seats for a specific show. + +==== Proposed Implementation +Selling of seats is executed by the `SellSeatCommand`, which extends from an abstract class `Command` and +is stored under the commands package. Additionally, it implements the following operations based on the user input. + +* OptixDateFormatter#isValidDate -- Ensures that the date keyed is valid. +* Model#containsKey -- Check if the date has any show scheduled. +* Model#hasSameName -- Check if the show name matches the show in the TreeMap for the specified date. +* Model#sellSeat -- Sell seats corresponding to the seat number that is keyed by user. + +Given below is an example usage scenario and how the sell seat mechanism behaves at each step. + +*Step 1* + +The user executes `sell Phantom of the Opera|5/5/2020|C1 D6 E10` command to sell the following seats C1 D6 E10 +for the show Phantom of the Opera on 5th May 2020. The `SellSeatCommand` command calls `OptixDateFormatter#isValidDate(String date)` +to first check if the given date is a valid date. + +*Step 2* + +Once verified, the `SellSeatCommand` command calls `Model#containsKey(LocalDate date)` and `Model#hasSameName(LocalDate date, String showName)` +to check if the show in query exist within `Model`. + +*Step 3* + +Once it has been confirmed that the show exist, the `SellSeatCommand` command calls `Model#sellSeats(LocalDate date, String[] seats)` +to query if the seats have been booked. Whenever a seat has been purchased successfully, the revenue obtained from +the show will then be updated accordingly. + +The following activity diagram summarizes what happens when a user executes sell seat command. + +.Activity Diagram of `SellSeatCommand` +image::images/devguide/DG_SellSeat_ActivityDiagram.png[width ="300", align="center"] + +==== Design Considerations + +*Aspect: How sell seat executes* + + +* Alternative 1 (current choice) +** Format: `sell Harry Potter | 5/5/2020 | A1 A2 A3 A4` + +** Pros: +*** Easy to implement and less buggy. +*** Simplicity of code allows it to handle exception and edge cases more efficiently. +*** Ability to bulk purchase seats. + +** Cons: +*** Less intuitive. User has to carry out one additional command view before carrying out the booking. + +* Alternative 2 (previous choice): +** Format: `sell Harry Potter | 5/5/2020` +** Alternative: `sell Harry Potter | 5/5/2020 | A1 A2 A3 A4` + +** Pros: +*** More flexible. Seating arrangement will be shown without explicit command for it. +*** Ability to bulk purchase seats. + +** Cons: +*** Incompatible with GUI as code requires CLI query for seats, causing GUI to hang once command is used. +*** Code is deeply-nested which violates coding standards. +*** User has to key in the seats 1 by 1. + +Below is the code snippet for our previous implementation: + +.Previous Implementation of `SellSeatCommand` +image::images/devguide/DG_SellSeat_CodeBlock.png[align="center"] + +*Choice for current implementation:* + + +While *Alternative 2* is more intuitive and allows for better control over the sales of seats, +ultimately we have chosen *Alternative 1* as it is more compatible with our GUI codebase. +Furthermore with the implementation of GUI, it would also be more intuitive for the users +to get the seating arrangements for a particular show before they attempt to make any sales for the seat +as it would be impossible for them to memorise all the seats that they have sold. + +=== Delete Show Feature +Allows users to delete shows from the shows ShowMap. + +==== Implementation +It is executed by the `DeleteCommand`, which is extended from the abstract class `Command`, and is stored under the +Command package. Additionally, it implements the following operations based on the user input: + +* DeleteCommand#hasValidDate -- checks if the input date is of a valid format +* Model#containsKey -- Checks for key in ShowMap. +* Model#hasSameName -- Checks for the existence of the show for the specified date in ShowMap. +* Model#deleteShow -- Removes the show from ShowMap. + +Given below is an example usage scenario and how the sell seat mechanism behaves at each step. + +*Step 1* + +The user inputs `delete Phantom of the Opera|5/5/2020|6/5/2020`, with the intention to delete shows dated on 5th May 2020 +and 6th May 2020. The `DeleteCommand` is initialised with `Phantom of the Opera|5/5/2020|6/5/2020` as the `details` +attribute. The `details` string is parsed into the show name, and the individual dates. + +.Class Diagram of `DeleteCommand` +image::images/devguide/DG_Delete_ObjectDiagram.png[width ="300", align="center"] + +*Step 2* + +`DeleteCommand` iterates through the dates. It formats and verifies them through +`OptixDateFormatter#hasValidDate(String date)` to ensure the given dates are valid(that the date follows the format +DD/MM/YYYY). `LocalDate` instances are generated from these date strings. + + +*Step 3* + +`DeleteCommand` checks if the specified show exists on the verified dates using +`Model#containsKey(LocalDate showLocalDate)` and `Model#hasSameName(LocalDate showLocalDate, LocalDate showName)`. If it +exists, `Model#deleteShow(LocalDate showLocalDate)` is called to remove the show from the shows ShowMap. The details are +also appended to message to record the successful deletion. If it does not exist, then the date is added to +`missingShows`. + +*Step 4* + +`DeleteCommand` checks if the specified show exists on the verified dates using +`Model#containsKey(LocalDate showLocalDate)` and `Model#hasSameName(LocalDate showLocalDate, LocalDate showName)`. + +The activity diagram below illustrates the logic process of the `DeleteCommand`: + +.Activity Diagram of `DeleteCommand` +image::images/devguide/DG_Delete_ActivityDiagram.png[width ="300", align="center"] + +The following code snippet highlights the key logic of the `DeleteCommand`. + +.Code snippet for `DeleteCommand` +image::images/devguide/DG_Delete_CodeBlock.png[align="center"] + +==== Design Considerations + +* Aspect: How delete works + +* *Alternative 1*: Splitting the delete feature into `delete-one` and `delete-all`. I.e. Splitting the delete feature +into the 2 abilities: to delete 1 specific show, and to delete all shows of the specified show name. + +This design would use 2 separate commands, `DeleteOneCommand` and `DeleteAllCommand`. The user would use +`delete-one SHOW_NAME|SHOW_DATE`, and `delete SHOW_NAME` as the input format for the function to delete one +show, and to delete all the specified shows respectively. + +** Pros: `delete-all` would enable the user to delete all the shows of specified show name with less hassle, without +having to input all the dates. +** Cons: Having 2 delete functions is less intuitive and may confuse new users, making them more prone to deleting +multiple shows unintentionally. + +* *Alternative 2 (Current Implementation)*: One common delete feature that can delete multiple shows at once. + +** Pros: This method is intuitive and flexible, as it enables users to delete multiple shows with a single command. +This makes deletions faster, and users will save time by typing less. This is important since this is a command-line +based application. +** Cons: If the user wishes to delete all shows of a specific name, the user has to input all of the dates which the +show is scheduled for. This can in turn slow down the user instead. However, the instances of this happening is less +likely. + +=== Command Aliasing +Enabling users to set up aliases for the commands. + +==== Implementation +Command aliasing is executed primarily by the `Parser`, which is stored under the `util` package. Some features related +to command aliasing such as adding, removing, or listing of aliases is executed via commands. The aliases for commands +are stored in the `commandAliasMap` within the Parser object. These pairs are also saved within a text file, +`ParserPreferences.txt`, so that the user can continue to use the aliases after the application is restarted. The +location of `ParserPreferences.txt` is decided in `Optix.java`. By default, it is located in `src/main/data`. + +The following operations are operations related to command aliasing: + +* Parser#addAlias(NEW_ALIAS, COMMAND) -- adds a alias-command pair to the commandAliasMap. +* Parser#removeAlias(NEW_ALIAS, COMMAND) -- removes a alias-command pair from the commandAliasMap. +* Parser#loadPreferences() -- loads all alias-command pairs from `ParserPreferences.txt`. `ParserPreferences.txt` is the +file where all the alias-command pairs are saved. +* Parser#resetPreferences() -- clears all alias-command pairs from `commandAliasMap` and adds in the default +alias-command pairs. This method does not access `ParserPreferences.txt`. + +Given below is an example usage scenario of where command aliasing is used. + +*Step 1* + +The user starts the application. A parser object will be initialised, and `loadPreferences()` will be called as part of +the initialisation process for it. If `ParserPreferences.txt` does not exist, it is created, and `resetPreferences()` is +called. `CommandAliasMap` is populated, and these pairs are also written to `ParserPreferences.txt`. + +*Step 2* + +The user adds a new alias. An example input would be `add-alias q|add`. This command would associate the character +q with the command `add`. This calls the `AddAliasCommand`, which verifies that ‘q’ and “add” are valid aliases and +commands respectively (more on valid aliases and commands below). After the alias-command pair is verified, the pair +is put in the `commandAliasList`, and a success message is presented. + +*Step 3* + +The user uses the alias by replacing “add” with “q” for any input. An example would be to use +`q The Lion King|20|31/12/2019` instead of `add The Lion King|20|31/12/2019`. + +*Step 4* + +The user decides that setting this alias was a mistake, and decides to remove it with the input `remove-alias q|add`. +This calls the `RemoveAliasCommand()`. The command verifies that this pair exists, and can be removed. It then +removes this pair from the `commandAliasList`. + +*Step 5* + +The user decides to list all aliases with the input `list-alias`. This calls the `ListAliasCommand`, which prints +all alias-command pairs in the `commandAliasList`. + +*Step 6* + +The user decides that he has too many self-added aliases which he no longer wants. He uses the reset alias function +to reset his aliases to the default settings, with the input `reset-alias`. This calls the `ResetAliasCommand`, which +clears all existing aliases, and adds in the default aliases, which are defined in the `resetPreferences()` method in +the `Parser` class. + +.Code snippet for Alias +image::images/devguide/DG_Alias_CodeBlock.png[align="center"] + +== Documentation +We use asciidoc for writing documentation. + +[NOTE] +We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. + +=== Editing Documentation + +Download the AsciiDoc plugin for IntelliJ, this allows you to preview the changes you have made to your `.adoc` files in real-time. + +== Logging + +Logs are written and saved into `OptixLogger.txt` files. Loggers are created and called in classes that interact with +user input, such as `Parser`, and the various `Commands`. It only logs the latest user session, +and any previous log data is overwritten once a new session is begun. This is to avoid clutter and a needlessl +number of logs. When there is no log file, a new log file is created automatically. + + +[NOTE] +Different logging levels are used in logging. This is to differentiate the severity level of the log. +Logger level of `Level.INFO` is primarily used for marking instantiations of classes , +and logger level of `Level.Warning` is used for marking Exceptions. + + +=== Editing Logging Configurations +Edit the specific log configurations in the `initLogger()` function in the desired class. +Learn more about logging link:{loggingBasicsURL}[here]. + +:loggingBasicsURL: https://www.loggly.com/ultimate-guide/java-logging-basics/ + + +== Dev Ops + +=== Build Automation + +See <> to learn how to use Gradle for build automation. + +=== Making a Release + +Here are the steps to create a new release. + +. Generate a JAR file <>. +. Tag the repo with the version number. e.g. `v0.1` +. https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. + +=== Managing Dependencies + +A project often depends on third-party libraries. For example, Optix depends on the https://github.com/jfoenixadmin/JFoenix[JFoenix library] for other JavaFX elements. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +a. Include those libraries in the repo (this bloats the repo size) + +b. Require developers to download those libraries manually (this creates extra work for developers) + +[appendix] +== Product Scope + +*Target user profile*: + +* Stakeholders like SISTIC theatre managers who need to track a large number of theatre bookings as well as seat bookings for each show. +* Prefers CLI to mouse input. + +*Product*: + +* New shows by performers can be added to the list. +* Seats requested by customers for a show can be booked by the theatre manager. +* Program is desgined to price the seats according to the different tiers. +* Revenue earned from a show can be calculated based on the seats booked. + +*Value proposition*: manage seats and finances faster than an average GUI-based app. + +[appendix] +== User stories + +// tag::base-alt[] +[width="90"] +|=== +|Priority |As a ... |I want to ... |So that I can ... +|Must-have |new user |see the command summary |refer to them when I forgot how to use the system. + +|Must-have |manager |track seats sold to customers |track my sales and avoid double-selling the same seat. + +|Must-have |manager |add new shows to my current list |record the booking of the venue on a particular date. + +|Must-have |user |view all the seats of a particular show |inform my customers on the availability of seats. + +|Must-have |accountant |view the finances from each show |calculate my profits from my business. + +|Must-have |manager |set the prices of the seats |vary seat prices depending on the popularity of the show. + +|Must-have |manager |remove a particular show from my listing |free up the cancelled slot to other performers. + +|Must-have |manager |remove shows that are in the past |reduce the quantity of shows in the listing to make query more +efficient + +|Must-have |manager |set the tiers of the seats |set different prices depending on the popularity of the seats. + +|Must-have |manager |reschedule shows |keep track of my shows even in the event of unforeseen circumstances. + +|Must-have |manager |edit the name of existing shows |correct spelling mistakes. + +|Must-have |user |customise the hotkeys for the different commands |increase the efficiency of keying in commands. + +|Must-have |manager |remove the booking on a seat |keep track of finances properly in case the booking was made wrongly. + +|Nice-to-have |user |add seats from different shows to a customer's purchase |manage bookings across multiple shows in one transaction. + +|Nice-to-have |user |refund seats before a certain date |Accomodate the needs of customers while being able to resell +the seats to new customers + +|Nice-to-have |user |keep track of payments |track the expenditure by each customer and find out the specific amount for a refund(if applicable). + +|Nice-to-have |user |reassign seats booked by customers |fulfill customers' requests to change seats. + +|Nice-to-have |user |blacklist customers who break the rules |serve as a deterrent for potential troublemakers and avoid further losses. + +|Nice-to-have |user |know the number of available seats in each tier for a particular show |get a rough idea of the number of seats left to sell from each tier. + +|Nice-to-have |user |modify the prices of the seats relative to the date of the show |sell off the seats and maximise my profits. + +|Nice-to-have |accountant |view monthly revenue |compare earnings and make new policies to improve business. + +|Nice-to-have |user |query shows by month |would not be flooded with information that is not relevant. + +|Unlikely-to-have |user |keep track of the number of tickets each customer can buy |limit the number of tickets a customer can buy to avoid ticket scalping. + +|Unlikely-to-have |manager |customer rewards system |encourage customers to visit more often and thank loyal customers for their patronage. + +|Unlikely-to-have |manager |put seats on hold |give customers more time to decide if they want to purchase the seats. + +|Unlikely-to-have |manager |reserve seats for VIPs |allow VIPs to enjoy benefits as a reward for their support. + +|=== +// end::base-alt[] + + +[appendix] +== Use cases + +=== Use case: UC01 - Add new shows to list of shows + +*Actor*: Theatre manager + +*MSS* + +. User enters the add command, followed by the name of the show, the date, the cost of the show and the base selling price of the seats. +. Optix responds by displaying the show that the user has added to the list. + +Use case ends. + + +==== *Extensions* + + +* Extension 1 +.. Optix detects an error in the details entered. +.. Optix displays an error message that shows the part of the command that was entered incorrectly. +.. Optix requests command from user again. +.. User re-enters details. + +Steps i to iv are repeated until details entered are correct. +Use case resumes from step 2 of MSS. + + +Use case ends. + + + +=== Use case: UC02 - Sell seats to buyer + +*Actor*: Theatre manager + +*MSS* + + +. User enters the sell command, followed by the name of the show, the date, the name of the buyer and lastly the seats requested by the buyer. +. Optix responds by displaying the seats bought and the cost of the transaction. + +Use case ends. + + +==== *Extensions* +* Extension 1 +.. Optix detects an error in the details of the show entered. +.. Optix displays an error message that shows the part of the command that was entered + incorrectly. +.. Optix requests command from user again. +.. User re-enters details. + +Steps i to iv are repeated until details entered are correct. +Use case resumes from step 1 of MSS. + +* Extension 2 +.. Optix detects that the seats entered are unavailable. +.. Optix requests command from user again. +.. User re-enters details. + +Steps i to iii are repeated until details entered are correct. +Use case resumes from step 2 of MSS. + +=== Use case: UC03 - Delete a single show scheduled on a particular date + +*Actor*: Theatre manager + +*MSS* + + +. User enters the delete command followed by the date of the show, and the show name. +. Optix responds by displaying the show/shows that have been removed from the list. + +Use case ends. + + +*Extensions* + +* Extension 1 +.. Optix cannot find the show using the details entered. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iii are repeated until shows entered are correct. +Use case resumes from step 2 of MSS. + +=== Use case: UC04 - Delete multiple shows by show name + +*Actor*: Theatre manager + +*MSS* + +. User enters the delete command followed by the names of the shows to delete. +. Optix searches through the list of shows for all shows with the specified name, and deletes them. +. Optix replies by displaying the show/shows that have been removed from the list. + Use case ends. + +*Extensions* + +* Extension 1 +.. Optix cannot find intended show(s) using the show name(s) specified. +.. Optix replies by listing all the shows the could not be found. +.. User re-enters command. + +Steps i to iii are repeated until shows entered are correct. +Use case resumes from step 2 of MSS. + +=== Use case: UC05 - View the entire list of shows + +*Actor*: Theatre manager + +*MSS* + +. User enters the list command. +. Optix responds by displaying the list of shows available for booking. + +Use case ends. + +=== Use case: UC06- View available seats of a show + +*Actor*: Theatre Manager + +*MSS* + +. User enters the view command, followed by the show name and date of the show. +. Optix responds by displaying the available seats of the theatre for the show, by marking reserved seats with a cross, and available seats with a tick. + +Use case ends. + + +*Extensions* + +* Extension 1 +.. Optix detects that there is no such show on that date. +.. Optix replies that there is no such show on the date. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2 of MSS. + +=== Use Case: UC07 - Refund ticket for seat + +*Actor*: Theatre manager + +*MSS* + +. User enters the refund command, followed by show name, show date, and seat number. +. Optix responds by querying if the ticket for the seat was purchased. If it is, then the seat is marked as available again. +. Optix updates the finances from the refund. +. Optix replies with a success confirmation. + +Use case ends. + +*Extensions* + +* Extension 1 +.. Optix detects that the show does not exist +.. Optix replies that there is no such show. +.. Optix requests command from user again. +.. User re-enters command, with the correct show name. + +Steps i to iv are repeated until the command entered is valid. +Use case resumes from step 3 of MSS. + +* Extension 2 + +.. Optix detects that the seat was not purchased. +.. Optix replies that the request to refund this seat is invalid. +.. Optix requests command from user again. +.. User re-enters command, with a correct seat number. + +Steps i to iv is repeated until the command entered is valid. +Use case resumes from step 3 of MSS. + +=== Use case: UC08- Tutorial/ Help Command + +*Actor*: Theatre Manager + +*MSS* + +. User enters the help command. +. Optix responds by displaying all available commands and their usage. + +=== Use case: UC09 - Reschedule shows to another day + +*Actor*: Theatre manager + +*MSS*: + +. User enters the reschedule command, followed by the name of the show, the current-date of the show to reschedule and the new-date. +. Optix responds by displaying the new-date for the show. + Use case ends. + +*Extensions*: + +* Extension 1 +.. Optix detects an error in the details entered. +.. Optix informs the user that the show has passed. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2 of MSS. + +* Extension 2 +.. Optix detects that there is no show of the given input in the list. +.. Optix informs the user that the show does not exist. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2 of MSS. + +=== Use Case: UC10 - Edit show's name without removal and addition +*Actor*: Theatre manager + +*MSS*: + +. User enters the edit command, followed by the show name, the show date of the show to edit and the new show name. +. Optix responds by displaying the new show name for the show. + +Use case ends. + +*Extensions*: + +* Extension 1 +.. Optix detects an error in the details entered. +.. Optix informs the user that the show has passed. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. Use case resumes from step 2. + +* Extension 2 +.. Optix detects that there is no show of the given input in the list. +.. Optix informs the user that the show does not exist. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. Use case resumes from step 2. + +=== Use Case: UC11 - View profit of a show +*Actor*: Theatre manager + +*MSS*: +. User enters the view-profit command, followed by the show name, the show date of the show +to reschedule and the new show name. +. Optix responds by displaying the profit for the show. + +Use case ends. + +*Extensions*: + +* Extension 1 +.. Optix detects that there is no such show on that date. +.. Optix replies that there is no such show on the date. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + +=== Use Case: UC12 - View profit of a certain month +*Actor*: Accountant + +*MSS*: +. User enters the view-monthly command, followed by the month and year. +. Optix responds by displaying the profit for that month. + +Use case ends. + +*Extensions*: + +* Extension 1 +.. Optix detects that there are no shows for that particular month. +.. Optix replies that there is no show. +.. Optix requests command from user again. +.. User re-enters command. +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + +* Extension 2 +.. Optix detects that the month and year entered are in the future. +.. Optix replies with the projected profit for that month. + +Use case resumes from step 2. + +=== Use Case: UC13 - Reassign seat +*Actor*: User + +*MSS*: +. User enters the reassign command, followed by the show name, show date, old seat and new seat. +. Optix searches for that specific seat in that particular show and removes the seat. +. Optix marks the new seat as booked and updates the profit of that show as well as the number of seats left. +. Optix responds by displaying the success message and the cost difference between the seats. + +Use case ends. + +*Extensions*: + +* Extension 1 +.. Optix detects that there is no such show on that date. +.. Optix replies that there is no such show on the date. +.. Optix requests command from user again. +.. User re-enters command. +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + +* Extension 2 +.. Optix detects that the old seat and new seat are the same. +.. Optix replies that the command is invalid. +.. Optix requests command from user again. +.. User re-enters command. +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + +* Extension 3 +.. Optix detects that the seats entered are invalid. +.. Optix replies that the command is invalid. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + +* Extension 4 +.. Optix detects that the old seat has not been booked. +.. Optix replies that there is no need to reassign the seat. +.. Optix requests command from user again. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + +* Extension 5 +.. Optix detects that the new seat has already been booked. +.. Optix replies that the seat cannot be reassigned. +.. Optix requests the user to view the seating arrangement and re-enter the command with a valid seat. +.. User re-enters command. + +Steps i to iv are repeated until the command entered is correct. +Use case resumes from step 2. + + +[appendix] +== Functional Requirements + +* Command to add shows +* Command to Sell Tickets to audience. +* Command to delete a particular show. +* Command to delete multiple shows (by showName). +* Command to list all shows before date of showing. (Query for booking and check seat availability) +* Command to list specific show to get date of showing (To check when the show is happening and check seat availability) +* Command to refund ticket for the show. +* Tutorial/ Help function +* Command to reschedule particular show to an empty slot + +[appendix] +== Non-functional Requirements + +* Should work on any OS as long as it has Java 11 or above installed. +* The list should be able to hold at least 100 shows without reduction in performance. +* The system should be usable by a novice who has never booked theatres/ theatre seats before. I.e. the commands should be intuitive for any user. +* Parser to parse user input +* Command class to execute add/ delete/ list tasks +* UI class to format output + +[appendix] +== Glossary + +*Must-have*: + +A feature that is declared as must have is viewed as a priority for development + +*Nice-to-have*: + +A feature that is declared as nice-to-have is viewed as a non- priority. The feature with this label will not be completed with as much urgency, and may not even be developed at all if it is deemed unimportant. + +*Unlikely-to-have*: + +A feature that is declared as unlikely-to-have is viewed as the least priority. The features with this label are likely to be ignored and only be done should they be viewed as extensions that could add on to the functionality of the program. + +[appendix] + +== Instructions for manual testing + +The instructions to test the program manually are given below. + +[NOTE] +These instructions are meant to provide a starting point for testers to work on. +Testers are expected to conduct more _exploratory_ testing. + +[NOTE] +All commands, show names and parameters are case insensitive. + +=== Launch and Shutdown + +. Initial launch + +.. Download the latest `optix.jar` link:{repoURL}/releases[here] and copy it into an empty folder. +.. Double-click the jar file + + Expected: Shows the GUI with the display window on the left(empty) and Chat Box on the right. + +. Loading saved data + +.. Add some shows using the following format `add SHOW_NAME|SEAT_BASEPRICE|SHOWDATE1|...`. Input bye or close the + window. +.. Re-launch the app by double-clicking the jar file. + + Expected: The display window on the left retained the shows added before shutting down the app. + +. Transferring saved data + +.. Move your jar file into another empty folder. +.. Create new folder and name it src. Double-click the folder. +.. Create new folder and name it main. Double-click the folder. +.. Create new folder and name it data. Copy the previous archive.txt and optix.txt files into this folder. +.. Double-click the jar file. + + Expected: The display window on the left retained the shows added before it was transferred to the new directory. + +=== Adding a show + +. Add new shows to the current list of shows with `SHOW_NAME`, `SEAT BASE PRICE` and `SHOW_DATES` + +.. Test case: `add aladdin|36|5/5/2020|30/5/2020|4/6/2020` + + Expected: A show where `SHOW_NAME` is aladdin with `SHOW_DATES` on 28/5/2020, 30/5/2020 and 4/6/2020 and + `SEAT BASE PRICE` of $36 is added to the GUI. Chat box displays a message indicating the shows that have been + added to the list. + +.. Test case: `add just-dance|40|5/5/2020` + + Prerequisites: Test case above(G.2.1.a) has been executed. + + Expected: No show added since there is already a show on 5/5/2020. Chat box displays a message showing failure to + add show. + +. Invalid price + +.. Test case: `add just-dance|-20 | 10/10/2022` + + Expected: No show added. Chat box displays a message showing that a negative pricing is used. + +. Date related errors + +.. Test case: `add just-dance|40| 5th may 2022` + + Expected: No show added since date format is incorrect. Chat box displays a message showing failure to + add show. + +.. Test case: `add just-dance|40|5/5/2017` + + Expected: No show added since the date of the show is already in the past. Chat box displays a message showing + failure to add show. + +.. Test case: `add just-dance|40|5/5/2122` + + Expected: No show added since the date of the show is more than 100 years into the future. Chat box displays a + message showing failure to add show. + + +=== Deleting shows +. Delete shows from the current list of shows with `SHOW_NAME` and `SHOW_DATE`. + +.. Prerequisites: List all shows using the `list` command. There should be multiple shows in the list. + +.. Test case: `delete aladdin|30/5/2020` +... Prerequisites: There must be a show called aladdin on 30/5/2020 in the list. + + Expected: aladdin on 30/5/2020 is removed from the GUI. Chat box also shows that aladdin on 30/5/2020 is deleted + from the list. + +.. Test case: `delete lion king|1/1/2022|3/3/2022` +... Prerequisites: There must be a show called lion king on 1/1/2022 and 3/3/2022 in the list. + + Expected: lion king on 1/1/2022 and 3/3/2022 are removed from the GUI. Chat box also shows that lion king on + 1/1/2022 and 3/3/2022 are deleted from the list.ox. + +. Non-existent show + +.. Test case: `delete non-existent show|4/4/2024` where `non-existent show` is any show that does not exist in the + current list. + Expected: No show deleted since there is no show called "non-existent show". Chat box displays a message showing + failure to delete show. + +. Date related errors + +.. Test case: `delete any show|5 may 2020` where `any show` is any show that is present in the list. + + Expected: No show deleted since the date format is incorrect. Chat box displays a message showing + failure to delete show. + +.. Test case: `delete any show|incorrect date` where `any show is any show that is present in the list and + `incorrect date` a date is in the format dd/mm/yyyy and does not match the date of that show in the list. + + Expected: No show deleted since the date format is incorrect. Chat box displays a message showing + failure to delete show. + +=== Listing dates for a specific show. +. List the available dates for the specified show with `SHOW_NAME` + +.. Prerequisites: List all shows using the `list` command. There should be multiple shows in the list. + +.. Test case: `list aladdin` +... There should be a show called aladdin in the list. + + Expected: The available dates for aladdin is displayed in the GUI. Chat box displays a message showing the + available dates for aladdin. + +. Non-existent show + +.. Test case: `list non-existent show` where `non-existent show` is a show that does not exist in the list. + + Expected: Chat box displays a message showing failure to find show of that `SHOW_NAME`. + +=== Listing shows for a specific month. +. List the available shows for the specified month with `MONTH YEAR` + +.. Prerequisites: List all shows using the `list` command. There should be multiple shows in the list. + +.. Test case: `list 12 2020` or `list dec 2020` or `list december 2020` +... Prerequisites: There should be at least one show in december 2020. + + Expected: The shows in December 2020 is displayed in the GUI. Chat box displays a message showing the list of shows + in December 2020. + +=== Selling a Seat +. Sell `SEATS` from a show with `SHOW_DATE` + +.. Prerequisites: + + List all shows using the `list` command. There should be multiple shows in the list. + + There should be a show of the specified `SHOW_NAME` and `SHOW_DATE` in the list. + +.. Test case: `sell aladdin|5/5/2020|A1 A2 D5` +... Prerequisites: + + Seats A1, A2 and D5 are still available for aladdin on 5/5/2020. + + Expected: Seats A1, A2 and D5 are changed from blue to red in the GUI to indicate they are booked. Chat box + displays a message showing that A1, A2 and D5 are purchased and the cost of the purchase. + +. Seat related errors + +.. Test case: `sell Lion King|11/10/2020|A11 G1` +... Prerequisites: There should be a show called lion king on 11/10/2020 in the list. + + Expected: The seat layout of lion king on 11/10/2020 is displayed in the GUI. Chat Box displays a message showing + that the seats do not exist. + +.. Test case: `sell Lion King|11/10/2020|A1 G2` +... Prerequisites: There should be a show called lion king on 11/10/2020 in the list. + + Expected: Seat A1 is changed from blue to red in the GUI to indicate it is booked. Chat box displays a message + showing that A1 is purchased and the cost of the purchase. Chat box also displays a message showing that seat + G1 does not exist. + +. Non-existent show + +.. Test case: `sell non-existent show|any date|A1 A2` where `non-existent show` is a show not found in the list and + `any date` is any date in the format dd/mm/yyyy. + + Expected: Display window does not change. Chat Box displays a message showing that the show cannot be found. + +. Date related errors + +.. Test case: `sell Lion King|31/2/2021| A1 A2` + + Expected: Display window does not change. Chat Box displays a message showing that it is an invalid date. + +=== Rescheduling shows + +. Changes the date of a show with `SHOW_NAME` from `OLD_DATE` to `NEW_DATE` + +.. Prerequisites: List all shows using the `list` command. There should be multiple shows in the list. + +.. Test case: `reschedule lion king|11/10/2020|20/10/2020` + + Expected: Display window shows that the date of lion king has been changed from 11/10/2020 to 20/10/2020. Chat box + displays a message showing that lion king on 11/10/2020 has been rescheduled to 20/10/2020. + +. Date related errors + +.. Test case: `reschedule lion king|20/10/2020|6/2/2018` + + Expected: Display window does not change since the date to change to is in the past. Chat box displays a message + showing failure to reschedule show. + +.. Test case: `reschedule lion king|20/10/2020|6/20/2018` + + Expected: Display window does not change since the date is invalid. Chat box displays a message showing that the + date is invalid. + +=== Editing a show name. + +. Changes the name of a show with `SHOW_NAME` from `OLD_SHOW_NAME` to `NEW_SHOW_NAME` + +.. Prerequisites: + + List all shows using the `list` command. There should be multiple shows in the list. + + There should be a show with the specified `SHOW_NAME` on `SHOW_DATE` in the list. + +.. Test case: `edit lioness king|5/11/2020|lion king` + + Expected: Display window changes the name of lioness king on 5/11/2020 to lion king. Chat box displays a message + showing that the name of the show has been successfully changed to lion king. + +=== Viewing a show + +. Display the layout of a show with `SHOW_NAME` on `SHOW_DATE` + +.. Prerequisites: + + List all shows using the `list` command. There should be multiple shows in the list. + + There should be a show with the specified `SHOW_NAME` on `SHOW_DATE` in the list. + +.. Test case: `view aladdin|5/5/2020` + + Expected: Display window shows the seat layout of aladdin on 5/5/2020. The red seats indicate they are booked while + the blue seats indicate they are available. Chat box displays a message showing the layout of the show with + a ✓ indicating the seats are booked and a ✘ indicating the seats are available. + +. Non-existent show + +.. Test case: `view non-existent show|11/5/2020` + + Expected: Display window does not change. Chat box displays a message showing that the show cannot be found. + +=== Re-assigning seats of a show + +. Changes the `SEAT` of a customer for a show with `SHOW_NAME` and `SHOW_DATE` + +.. Prerequisites: + + List all shows using the `list` command. There should be multiple shows in the list. + + There should be a show with the specified `SHOW_NAME` on `SHOW_DATE` in the list. + +.. Test case: `reassign-seat aladdin|5/5/2020|A1|F8` +... Prerequisites: + + Seat A1 of aladdin on 5/5/2020 should be booked. + + Seat F8 of aladdin on 5/5/2020 should be available. + + Expected: Display window shows the layout of the show and changes seat A1 to blue (available) and seat F8 to + red(booked). Chat box displays a message showing the seat has been re-assigned and the cost difference between + the changed seats. + +. Non-existent seat + +.. Test case: `reassign-seat aladdin|5/5/2020|A1|A12` + + Expected: Display window shows the layout of aladdin on 5/5/2020. No change in seating is made since seat A12 + does not exist. Chat box displays a message showing that the seat numbers are invalid. + +. Reassigning to an unavailable seat + +.. Test case: `reassign-seat aladdin|5/5/2020|A2|F8` +... Prerequisites: Seats A2 and F8 should be booked. + + Expected: Display window shows the layout of aladdin on 5/5/2020. No change in seating is made since seat F8 has + already been booked. + +=== Viewing profit of a show + +. Displays the revenue earned from a show with `SHOW_NAME` on `SHOW_DATE` + +.. Prerequisites: + + List all shows using the `list` command. There should be multiple shows in the list. + + List all archived shows using the `archive` command. There should be multiple shows in the archive list. + + There should be a show with the specified `SHOW_NAME` on `SHOW_DATE` in either of the lists. + +.. Test case: `view-profit aladdin|5/5/2020` + + Expected: Display window shows the list of shows and their respective profits. Chat box displays a message showing + the projected earnings for aladdin on 5/5/2020. + +.. Test case: `view-profit just-dance|17/7/2017` +... Prerequisites: There should be a show called just-dance on 17/7/2017 in the archive list. + + Expected: Display window does not change. Chat box displays a message showing the revenue earned + from just-dance on 17/7/2017. + +=== Viewing monthly revenue + +. Displays the revenue earned in a particular `MONTH YEAR` + +.. Prerequisites: + + List all shows using the `list` command. There should be multiple shows in the list. + + List all archived shows using the `archive` command. There should be multiple shows in the archive list. + + There should be a show with the specified `MONTH YEAR` in either of the lists. + +.. Test case: `view-monthly May 2020` + + Expected: Display window shows the list of shows and their profits. Chat box displays a message showing the + projected earnings for May 2020. + +. No show in the month + +.. Test case: 'view-monthly July 2024' +... Prerequisites: There should be no show in July 2024 + + Expected: Display window shows the list of shows and their profits. Chat box displays a message showing that there + are no shows in July 2024. + +=== Adding alias + +. Adds a new alias to an existing `command` + +.. Test case: `add-alias q|add` + + Expected: Chat box displays a message showing that the `add` command has a new alias `q`. + +.. Test case: `add-alias l|add` +... Prerequisites: Another command has `l` as its alias (list command has alias `l` by default) + + Expected: Chat box displays a message showing failure in adding alias since the alias is already in use. + +=== Removing alias + +. Removes alias from an existing `command` + +.. Test case: `remove-alias t|add` +... Prerequisites: `add` command has an alias `t` + + Expected: Chat box displays a message showing that the alias `t` has been removed from the `add` command. + +. Non-matching alias + +.. Test case: `remove-alias f|delete` +... Prerequisites: `delete` command has an alias other than `f` + + Expected: Chat box displays a message showing failure in removing alias since the alias for `delete` does not match + `f` + + + + + diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index fd44069597..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# User Guide - -## Features - -### Feature 1 -Description of feature. - -## Usage - -### `Keyword` - Describe action - -Describe action and its outcome. - -Example of usage: - -`keyword (optional arguments)` - -Expected outcome: - -`outcome` diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc new file mode 100644 index 0000000000..ee40096062 --- /dev/null +++ b/docs/UserGuide.adoc @@ -0,0 +1,465 @@ += Optix User Guide +:site-section: UserGuide +:toc: +:toclevels: 4 +:toc-title: +:toc-placement: preamble +:sectnums: +:stylesDir: stylesheets +:xrefstyle: full +:experimental: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +endif::[] +:repoURL: https://github.com/AY1920S1-CS2113T-T12-1/main + +By: `Team AY1920S1-CS2113T-T12-1` Since: `Oct 2019` + +== Introduction + +Optix is a desktop application made for users who want to manage the seating of a theatre or other venues while keeping track of the finances earned. Optix is optimised for users who can type fast and prefers using a Command Line Interface (CLI) to other methods. A Graphical User Interface (GUI) is also developed for additional user interaction. Want to skip the process of going through multiple steps to manage bookings and viewing your profits? If you can type fast and want to avoid this hassle, Optix is the perfect application for you! Jump to <> to get started. + +== Quick Start + +. Ensure that Java 11 or above is installed on your computer. +. Download the latest `optix.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your theatre bookings. +. Double-click the file to start the application. The GUI should appear in a few seconds. + +image::images/Ui.png[width ="600", align="center"] + +[start=5] +. Type the command in the command box and press kbd:[Enter] to execute it +(e.g. type help and press kbd:[Enter] to open the help window.) +. Some example commands you can try: +** `add` Phantom of the Opera|20|5/5/2020 : adds a show called “Phantom of the Opera” to be scheduled on the 5/5/2020 and the base price of $20 per seat to Optix. +** `list`: list all shows currently added to Optix. +** `delete` Phantom of the Opera|5/5/2020 : finds a show called Phantom of the Opera scheduled on the 5/5/2020 in Optix, and deletes it. +** `help`: displays the list of commands available. +** `bye`: exits the application. + +== Features + +=== Command Format + +* Words in `UPPER_CASE` are the parameters to be supplied by the user. +e.g. in add `SHOW_NAME|SCHEDULED_DATE|PRICE`, the `SHOW_NAME`, `SCHEDULED_DATE`, and `PRICE` are parameters that can be used as `add Phantom of the Opera|5/5/2020|20`. + +* Items with `...` after them can be used multiple times (minimally once). +e.g. `SEAT` can be used as `A1 A2 B6` etc. + +* Parameters have to be in order. +e.g. if the command specifies `SHOW_NAME|SHOW_DATE`, `SHOW_DATE|SHOW_NAME` is invalid. + +* All `SHOW_DATE` entered must be in the format dd/mm/yyyy + + +* All *COMMAND* and `SHOW_NAME` are case insensitive. + +=== Generic Commands +The following set of commands + +==== Viewing Help: `help` +As a new or returning user you may be unsure about the commands. +The `help` command displays descriptions of all the available commands. + +Format: `help` + +==== Tab navigation: `show`, `finance`, `archive` +Hate clicking? Simply type in the tab headers to achieve fast navigation +between the various tabs! + +Format: `finance`, `archive`, `list` + +[NOTE] +Refer to <> for more details on `list` command + +==== Exiting the program: `bye` +Saves all the shows and statuses of the seats within the show list, then exits the program. + +Format: `bye` + +=== Show Commands +The following set of commands helps with managing shows within the theatre. + +==== Adding shows: `add` +Let's say that a new event is coming up and you'd like to add it to the application showlist. +Use the `add` command to add shows to the current show list. + +Format: `add SHOW_NAME|SEATS_BASE_PRICE|DATE1|DATE2|...` + +[TIP] +use `DATE1|DATE2|...` to easily add multiple dates for the show +* `SEATS_BASE_PRICE` represents the lowest cost of a seat in the venue. +* There must be no other show on that `DATE`. +* The `DATE` must be in the future. +* The `DATE` must be no further than 100 years from the current date. +* The format of `DATE` must be dd/mm/yyyy +* Shows of the same name but on different dates can be added. + +Examples: +*Add a single show*: + +`add Phantom of the Opera|20|5/5/2020` + +*Add multiple shows*: + +`add Lion King|30|6/5/2020|7/5/2020|8/5/2020` + +==== Deleting shows: `delete` +In the event a show is cancelled, you can remove it from the list using the `delete` command. It +deletes shows for specific dates. + +Format: `delete SHOW_NAME|DATE1|DATE2|...` + +[TIP] +use `DATE1|DATE2|DATE3|...` to easily remove multiple dates for the show + +* Removes `SHOW_NAME` on the specified `DATE` +* The exact `SHOW_NAME` and `DATE` must be entered for show to be removed successfully. + + +Examples: +*Delete a single show*: + +---- +Delete Lion King|10/10/2020 +---- + +*Delete multiple shows*: + +---- +delete Phantom of the Opera|5/5/2020|6/5/2020 +---- + +==== Listing shows: `list` +The `list` command allows you to view all current shows. + +Format: `list` + +[TIP] +Use `list` before you type other commands so you can see all the show names and show dates + +==== Listing dates for specific show: `list` + +You can also search for all listings of a particular show with the `list` command! +Scheduled shows with the specified name will be listed. + +Format: `list SHOW_NAME` + +Examples: +---- +list Phantom of the Opera +list Lion King +---- + +==== Listing shows for a specific month: `list` +You can also search for all listings of a particular month! +Lists all the shows for a specific month. + + +[TIP] +use numbers or abbreviations to represent the month instead of spelling it out! + +Format: `list MONTH YEAR` + + +Examples: +---- +list 12 2020 +list Dec 2020 +list December 2020 +---- + +==== Reschedule a show to a another date: `reschedule` +Use the `reschedule` command to reschedule the requested +current show in the showlist to a specified date. + +Format: `reschedule SHOW_NAME|OLD_DATE|NEW_DATE` + +* Changes the date of the specified `SHOW_NAME` from `OLD_DATE` to `NEW_DATE` +* Invalid if the date of `SHOW_NAME` does not match `OLD_DATE` +* Invalid if `NEW_DATE` has already passed. + +Example: +---- +reschedule Phantom of the Opera|5/5/2020|10/5/2020 +---- + +==== Editing a show’s name: `edit` +If you made a spelling error while adding an event, there is no need to delete and add it again. +Use `edit` to change its name. + +Format: `edit OLD_SHOW_NAME|SHOW_DATE|NEW_SHOW_NAME` + +* Changes the name of the specified `SHOW_DATE` with `OLD_SHOW_NAME` to `NEW_SHOW_NAME` + +* Invalid if the date of `OLD_SHOW_NAME` does not match `SHOW_DATE` + +Example: +---- +edit Phanom of the Opera|5/5/2020|Phantom of the Opera +---- + +[NOTE] +==== +Phanom of the Opera is intentionally mispelled +==== + +=== Seats Commands +The following set of commands helps with managing ticketing for any show that is scheduled +to perform in the theatre. + +==== View the seats for a show: `view` +You can use the `view` command to check seat availabilities for shows. +The layout of the seats within the theatre will be displayed. + +Format: `view SHOW_NAME|SHOW_DATE` + +* Displays the layout of the specified `SHOW_NAME` on `SHOW_DATE` in a 2D array format. +* The layout will not be displayed if the specified `SHOW_DATE` does not +correspond to the actual show date of the indicated `SHOW_NAME`. +* The layout will not be displayed if the specified `SHOW_DATE` does not have any show scheduled for the day. + +Examples: +---- +view Lion King|11/10/2020 +view Phantom of the Opera|20/11/2021 +---- + +image::images/userguide/UG_view_success.png[width ="600", align="center"] + +[NOTE] +red seats indicates that seat has been sold, while blue seats +indicates that seat is still available for booking. + +==== Sell seats: `sell` +The `sell` command can be used to record bookings. It is useful for managing seats sold to customers and for tracking the availability of the seats + +Format: `sell SHOW_NAME|SHOW_DATE|SEAT …` + +[TIP] +Use the `view` command for a visual representation of the statuses of all the seats to ensure successful purchase of seats + +* Sells `SEAT` specified by customers for the indicated `SHOW_NAME` on `SHOW_DATE`. +* Each `SEAT` is represented by an alphabet followed by an integer e.g. A1 +* Multiple `SEAT` can be entered in the parameter `SEAT ...` to book all those seats + + +Examples: + +Sell a single seat: + +---- +sell Phantom of the Opera|5/5/2020|C1 +---- + +Sell multiple seats in a single command: + +---- +sell Lion King|6/5/2020| A1 A2 A3 A4 +---- + +==== Reassign seat for a show: `reassign-seat` +Changes the seat of a customer. + +Format: `reassign-seat SHOW_NAME|SHOW_DATE|OLD_SEAT|NEW_SEAT` + +* Reassign a booked seat to another available seat for the show. +* Seat cannot be reassigned if: +** `OLD_SEAT` is not booked previously. +** `NEW_SEAT` has been booked. +** `OLD_SEAT`/`NEW_SEAT` does not exist +** Both `OLD_SEAT` and `NEW_SEAT` are the same. + +Examples: +---- +reassign-seat Phantom of the Opera|5/5/2020|A1|A2 +reassign-seat Lion King|10/5/2020|D6|A1 +---- + +==== Refund seats: `refund-seat` +Refund half the cost of the seat back to the cutsomer. + +Format: `refund-seat SHOW_NAME|SHOW_DATE|SEAT …` + +* Refund `SEAT` specified by customers for the indicated `SHOW_NAME` on `SHOW_DATE`. +* Each `SEAT` is represented by an alphabet followed by an integer e.g. A1 +* Multiple `SEAT` can be entered in the parameter `SEAT ...` to refund cost of seats back to customer + +Examples: + +Refund a single seat: + +---- +refund-seat Phantom of the Opera|5/5/2020|C1 +---- + +Refund multiple seats in a single command: + +---- +refund-seat Lion King|6/5/2020| A1 A2 A3 A4 +---- + +==== Remove seats: `remove-seat` +Remove a seat that has been sold wrongly. + +Format: `remove-seat SHOW_NAME|SHOW_DATE|SEAT …` + +* Remove `SEAT` specified by customers for the indicated `SHOW_NAME` on `SHOW_DATE`. +* Each `SEAT` is represented by an alphabet followed by an integer e.g. A1 +* Multiple `SEAT` can be entered in the parameter `SEAT ...` to remove all wrongly booked seats + +Examples: + +Remove a single seat: + +---- +remove-seat Phantom of the Opera|5/5/2020|C1 +---- + +Remove multiple seats in a single command: + +---- +remove-seat Lion King|6/5/2020| A1 A2 A3 A4 +---- + +=== Finance Commands +The following set of commands helps with tracking the finance of the theatre. + +==== View the profit of a show: `view-profit` + +Displays the profit earned from that particular show. +Format: `view-profit SHOW_NAME|SHOW_DATE` + +* Displays the profit for the specified `SHOW_NAME` on `SHOW_DATE` +* Displays projected earnings for a show if `SHOW_DATE` is in the future. + +Example: +---- +view-profit Lion King|5/5/2020 +---- + +==== View the amount earned for a particular month: `view-monthly` + +Displays the profit earned for a particular month. + + +[TIP] +use numbers or abbreviations to represent the month instead of spelling it out! + +Format: `view-monthly MONTH YEAR` + +* Displays the total profit collected for all the shows in MONTH YEAR +* Displays projected earnings if MONTH YEAR is in the future. + +Examples: +---- +view-monthly 1 2020 +view-monthly Jan 2020 +view-monthly January 2018 +---- + +image::images/userguide/UG_ViewMonthly_CLI.png[width ="600", align="center"] + +As shown in the figure above, entering the `view-monthly` command would cause Optix to +respond with the profit of that month. + +image::images/userguide/UG_ViewMonthly_GUI.png[width ="600", align="center"] + +As shown in the figure above, the GUI would also display the shows performed in that month, together with +the revenue of each show + +=== Alias Commands +Aliases help you to create shortcuts for commands, hence enabling +you to to input commands with less effort! + +==== Add new alias: `add-alias` +As a user, you can give a Command an alternate name for easy access. +You can add them with `add-alias` to an existing command. +After adding the alias, it is immediately available for use! + + +Format: `add-alias ALIAS|COMMAND` + +* Adds a new alias for `COMMAND` +* The alias must not already be in use. +* The alias must not be the name of a command. + +Examples: + +Give the command `add` an alias 't': + +---- +add-alias t|add +---- + +image::images/userguide/UG_add-alias_success.png[width ="600", align="center"] + +Assigning an alias that is already in use is not allowed: + +`add-alias t|delete` would not work if 't' is paired to another command already. + +image::images/userguide/UG_add-alias_failure.png[width ="600", align="center"] + +Assigning a command keyword as an alias is also not allowed: + +`add-alias add|delete` is not allowed. + +image::images/userguide/UG_add-alias_illegal.png[width ="600", align="center"] + +Assigning a command keyword with space is also not allowed: + +`add-alias g g|add` is not allowed. + +image::images/userguide/UG_add-alias_illegal_space.png[width ="600", align="center"] + +==== Remove alias: `remove-alias` + +Let's say you changed your mind after adding the alias. +Use the `remove-alias` command to delete an existing alias. + +Format: `remove-alias ALIAS|COMMAND` + +* Remove the `ALIAS` for `COMMAND` + +Example: +---- +remove-alias t|add +---- + +image::images/userguide/UG_remove-alias_success.png[width ="600", align="center"] + +==== List alias: `list-alias` +If you forgot the aliases you set, or would like to view the default aliases, +use the `list-alias` command! + +Format: `list-alias` + +image::images/userguide/UG_list-alias_success.png[width ="600", align="center"] + +==== Reset alias: `reset-alias` +To undo all your alias modifications, use the `reset-alias` command +to set them back to the defaults. After the reset, use `list-alias` to +view the default aliases. + +Format: `reset-alias` + +image::images/userguide/UG_reset-alias_success.png[width ="600", align="center"] + + +== FAQ + +*Q*: How do I transfer my data to another computer? + +*A*: Download the link:{repoURL}/releases[optix.jar] in the other computer and paste the file that contains the data from the previous Optix folder in the same folder as the jar file. + +== Command summary + +* *Add*: `add SHOW_NAME|SEATS_BASE_PRICE|DATE1|DATE2|...` +Eg. `add Phantom of the Opera|20|5/5/2020|6/5/2020` + +* *Add-alias*: `add-alias ALIAS|COMMAND` +Eg. `add-alias q|add` + +* *Bye* + +* *Delete*: `delete SHOW_NAME|DATE1|DATE2|...` +Eg. `delete Phantom of the Opera|5/5/2020` + +* *Edit*: `edit OLD_SHOW_NAME|SHOW_DATE|NEW_SHOW_NAME` +Eg. `edit Phanom of the Opera|5/5/2020|Phantom of the Opera` + +* *List*: `list` + +* *List*: `list SHOW_NAME` +Eg. `list Phantom of the Opera` + +* *List*: `list MONTH YEAR` +Eg. `list May 2020` + +* *List Alias*: `List-alias` + +* *reschedule*: `reschedule SHOW_NAME|OLD_DATE|NEW_DATE` +Eg. `reschedule Phantom of the Opera|5/5/2020|10/5/2020` + +* *Reassign-seat*: `reassign-seat SHOW_NAME|SHOW_DATE|OLD_SEAT|NEW_SEAT` +Eg. `reassign-seat Phantom of the Opera|5/5/2020|A1|A2` + +* *Remove-alias*: `remove-alias ALIAS|COMMAND` +Eg. `remove-alias q|add` + +* *Reset-alias*: `reset-alias` + +* *Sell*: `sell SHOW_NAME|SHOW_DATE|SEAT1 SEAT2 SEAT3 …` +Eg. `sell Phantom of the Opera|5/5/2020| C1 D6 E10` + +* *View*: `view SHOW_NAME|SHOW_DATE` +Eg. `view Phantom of the Opera|5/5/2020` + +* *View-profit*: `view-profit SHOW_NAME|SHOW_DATE` +Eg. `view-profit Lion King|5/5/2020` + +* *View-monthly*: `view-monthly MONTH YEAR` +Eg. `view-monthly May 2020` + +* *Help*: `help` diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc new file mode 100644 index 0000000000..7adef82104 --- /dev/null +++ b/docs/UsingGradle.adoc @@ -0,0 +1,89 @@ += Using Gradle +:site-section: DeveloperGuide +:imagesDir: images +:stylesDir: stylesheets +:experimental: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:warning-caption: :warning: +endif::[] + +https://gradle.org/[Gradle] is a build automation tool. It can automate build-related tasks such as + +* Running tests +* Managing library dependencies +* Analyzing code for style compliance + +The gradle configuration for this project is defined in the _build script_ link:../build.gradle[`build.gradle`]. + +[NOTE] +To learn more about gradle build scripts, refer https://docs.gradle.org/current/userguide/tutorial_using_tasks.html[Build Scripts Basics]. + +== Running Gradle Commands + +To run a Gradle command, open a command window on the project folder and enter the Gradle command. Gradle commands look like this: + +* On Windows: `gradlew ...` e.g. `gradlew clean test` +* On Mac/Linux: `./gradlew ...` e.g. +`./gradlew clean test` + +[NOTE] +If you do not specify any tasks, Gradlew will run the default tasks `clean` `test`. + +== Cleaning the Project + +* *`clean`* + +Deletes the files created during the previous build tasks (e.g. files in the `build` folder). e.g. `./gradlew clean` + +[TIP] +*`clean` to force Gradle to execute a task*: + +When running a Gradle task, Gradle will try to figure out if the task needs running at all. If Gradle determines that the output of the task will be same as the previous time, it will not run the task. For example, it will not build the JAR file again if the relevant source files have not changed since the last time the JAR file was built. If we want to force Gradle to run a task, we can combine that task with `clean`. Once the build files have been `clean` ed, Gradle has no way to determine if the output will be same as before, so it will be forced to execute the task. + +== Creating the JAR file + +* *`shadowJar`* + +Creates the `Optix.jar` file in the `build/jar` folder, _if the current file is outdated_. + +e.g. `./gradlew shadowJar` + +**** +To force Gradle to create the JAR file even if the current one is up-to-date, you can '`clean`' first. + +e.g. `./gradlew clean shadowJar` +**** + +[NOTE] +*Why do we create a fat JAR?* If we package only our own class files into the JAR file, it will not work properly unless the user has all the other JAR files (i.e. third party libraries) our classes depend on, which is rather inconvenient. Therefore, we package all dependencies into a single JAR files, creating what is also known as a _fat_ JAR file. To create a fat JAR file, we use the Gradle plugin https://github.com/johnrengelman/shadow[shadow jar]. + +== Running the application + +* *`run`* + +Builds and runs the application. +* *`runShadow`* + +Builds the application as a fat JAR, and then runs it. + +== Running code style checks + +* **`checkstyleMain`** + +Runs the code style check for the main code base +* **`checkstyleTest`** + +Runs the code style check for the test code base + +The set of code style rules implemented can be found in `config/checkstyle/checkstyle.xml`. To enable _exceptions_ to code styles, add in the comment `//CODESTYLE.OFF: RuleName` at the start of the section and `//CODESTYLE.ON: RuleName` at the end of the section. + +[[Running-Tests]] +== Running Tests + +* **`test`** + +Runs all tests. + +== Updating Dependencies + +There is no need to run these Gradle tasks manually as they are called automatically by other relevant Gradle tasks. + +* **`compileJava`** + +Checks whether the project has the required dependencies to compile and run the main program, and download any missing dependencies before compiling the classes. + +See `build.gradle` -> +`allprojects` -> `dependencies` -> `compile` for the list of dependencies required. +* **`compileTestJava`** + +Checks whether the project has the required dependencies to perform testing, and download any missing dependencies before compiling the test classes. + +See `build.gradle` -> `allprojects` -> `dependencies` -> `testCompile` for the list of dependencies required. diff --git a/docs/images/Ui.png b/docs/images/Ui.png new file mode 100644 index 0000000000..b159df80b7 Binary files /dev/null and b/docs/images/Ui.png differ diff --git a/docs/images/aboutus/cheesengg.png b/docs/images/aboutus/cheesengg.png new file mode 100644 index 0000000000..8d49aba9fe Binary files /dev/null and b/docs/images/aboutus/cheesengg.png differ diff --git a/docs/images/aboutus/nicholasliu97.png b/docs/images/aboutus/nicholasliu97.png new file mode 100644 index 0000000000..4d8bcc8a44 Binary files /dev/null and b/docs/images/aboutus/nicholasliu97.png differ diff --git a/docs/images/aboutus/oungkennedy.png b/docs/images/aboutus/oungkennedy.png new file mode 100644 index 0000000000..1528bd26e0 Binary files /dev/null and b/docs/images/aboutus/oungkennedy.png differ diff --git a/docs/images/aboutus/tianchangliao.png b/docs/images/aboutus/tianchangliao.png new file mode 100644 index 0000000000..200862fce5 Binary files /dev/null and b/docs/images/aboutus/tianchangliao.png differ diff --git a/docs/images/cheesengg.png b/docs/images/cheesengg.png new file mode 100644 index 0000000000..107e01d249 Binary files /dev/null and b/docs/images/cheesengg.png differ diff --git a/docs/images/devguide/DG_Alias_CodeBlock.png b/docs/images/devguide/DG_Alias_CodeBlock.png new file mode 100644 index 0000000000..92f6f87da4 Binary files /dev/null and b/docs/images/devguide/DG_Alias_CodeBlock.png differ diff --git a/docs/images/devguide/DG_Delete_ActivityDiagram.png b/docs/images/devguide/DG_Delete_ActivityDiagram.png new file mode 100644 index 0000000000..53b00cb785 Binary files /dev/null and b/docs/images/devguide/DG_Delete_ActivityDiagram.png differ diff --git a/docs/images/devguide/DG_Delete_CodeBlock.png b/docs/images/devguide/DG_Delete_CodeBlock.png new file mode 100644 index 0000000000..8cc08db78f Binary files /dev/null and b/docs/images/devguide/DG_Delete_CodeBlock.png differ diff --git a/docs/images/devguide/DG_Delete_ObjectDiagram.png b/docs/images/devguide/DG_Delete_ObjectDiagram.png new file mode 100644 index 0000000000..bfcde19885 Binary files /dev/null and b/docs/images/devguide/DG_Delete_ObjectDiagram.png differ diff --git a/docs/images/devguide/DG_Design_SequenceDiagram.png b/docs/images/devguide/DG_Design_SequenceDiagram.png new file mode 100644 index 0000000000..786995cf2f Binary files /dev/null and b/docs/images/devguide/DG_Design_SequenceDiagram.png differ diff --git a/docs/images/devguide/DG_GUI_ClassDiagram.png b/docs/images/devguide/DG_GUI_ClassDiagram.png new file mode 100644 index 0000000000..6202591ba0 Binary files /dev/null and b/docs/images/devguide/DG_GUI_ClassDiagram.png differ diff --git a/docs/images/devguide/DG_Model_ClassDiagram.png b/docs/images/devguide/DG_Model_ClassDiagram.png new file mode 100644 index 0000000000..a29fa493ff Binary files /dev/null and b/docs/images/devguide/DG_Model_ClassDiagram.png differ diff --git a/docs/images/devguide/DG_Model_Structure_Diagram.png b/docs/images/devguide/DG_Model_Structure_Diagram.png new file mode 100644 index 0000000000..ca7a2b09ab Binary files /dev/null and b/docs/images/devguide/DG_Model_Structure_Diagram.png differ diff --git a/docs/images/devguide/DG_SellSeat_ActivityDiagram.png b/docs/images/devguide/DG_SellSeat_ActivityDiagram.png new file mode 100644 index 0000000000..88368d8526 Binary files /dev/null and b/docs/images/devguide/DG_SellSeat_ActivityDiagram.png differ diff --git a/docs/images/devguide/DG_SellSeat_CodeBlock.png b/docs/images/devguide/DG_SellSeat_CodeBlock.png new file mode 100644 index 0000000000..41cbbbb48c Binary files /dev/null and b/docs/images/devguide/DG_SellSeat_CodeBlock.png differ diff --git a/docs/images/devguide/DG_SystemArch.png b/docs/images/devguide/DG_SystemArch.png new file mode 100644 index 0000000000..dd7675c23c Binary files /dev/null and b/docs/images/devguide/DG_SystemArch.png differ diff --git a/docs/images/devguide/DG_ViewMonthly_ActivityDiagram.png b/docs/images/devguide/DG_ViewMonthly_ActivityDiagram.png new file mode 100644 index 0000000000..ca8508f3ed Binary files /dev/null and b/docs/images/devguide/DG_ViewMonthly_ActivityDiagram.png differ diff --git a/docs/images/devguide/DG_ViewMonthly_SequenceDiagram.png b/docs/images/devguide/DG_ViewMonthly_SequenceDiagram.png new file mode 100644 index 0000000000..24349d38c2 Binary files /dev/null and b/docs/images/devguide/DG_ViewMonthly_SequenceDiagram.png differ diff --git a/docs/images/nicholasliu97.png b/docs/images/nicholasliu97.png new file mode 100644 index 0000000000..4d8bcc8a44 Binary files /dev/null and b/docs/images/nicholasliu97.png differ diff --git a/docs/images/oungkennedy.png b/docs/images/oungkennedy.png new file mode 100644 index 0000000000..3cd9382e3a Binary files /dev/null and b/docs/images/oungkennedy.png differ diff --git a/docs/images/team/cheesengg_Activity_Diagram.png b/docs/images/team/cheesengg_Activity_Diagram.png new file mode 100644 index 0000000000..434fd12de1 Binary files /dev/null and b/docs/images/team/cheesengg_Activity_Diagram.png differ diff --git a/docs/images/team/cheesengg_Design_Consideration.png b/docs/images/team/cheesengg_Design_Consideration.png new file mode 100644 index 0000000000..a385458b08 Binary files /dev/null and b/docs/images/team/cheesengg_Design_Consideration.png differ diff --git a/docs/images/team/cheesengg_Overview_of_Feature.png b/docs/images/team/cheesengg_Overview_of_Feature.png new file mode 100644 index 0000000000..454d254edf Binary files /dev/null and b/docs/images/team/cheesengg_Overview_of_Feature.png differ diff --git a/docs/images/team/cheesengg_UG_New.png b/docs/images/team/cheesengg_UG_New.png new file mode 100644 index 0000000000..dbfb054683 Binary files /dev/null and b/docs/images/team/cheesengg_UG_New.png differ diff --git a/docs/images/team/cheesengg_UG_Old.png b/docs/images/team/cheesengg_UG_Old.png new file mode 100644 index 0000000000..09824d7464 Binary files /dev/null and b/docs/images/team/cheesengg_UG_Old.png differ diff --git a/docs/images/tianchangliao.png b/docs/images/tianchangliao.png new file mode 100644 index 0000000000..a09b6569dd Binary files /dev/null and b/docs/images/tianchangliao.png differ diff --git a/docs/images/userguide/UG_ViewMonthly_CLI.png b/docs/images/userguide/UG_ViewMonthly_CLI.png new file mode 100644 index 0000000000..cb2cae30cb Binary files /dev/null and b/docs/images/userguide/UG_ViewMonthly_CLI.png differ diff --git a/docs/images/userguide/UG_ViewMonthly_GUI.png b/docs/images/userguide/UG_ViewMonthly_GUI.png new file mode 100644 index 0000000000..6f07e10a5d Binary files /dev/null and b/docs/images/userguide/UG_ViewMonthly_GUI.png differ diff --git a/docs/images/userguide/UG_add-alias_failure.png b/docs/images/userguide/UG_add-alias_failure.png new file mode 100644 index 0000000000..ae85ff6bd2 Binary files /dev/null and b/docs/images/userguide/UG_add-alias_failure.png differ diff --git a/docs/images/userguide/UG_add-alias_illegal.png b/docs/images/userguide/UG_add-alias_illegal.png new file mode 100644 index 0000000000..4bc9cbf836 Binary files /dev/null and b/docs/images/userguide/UG_add-alias_illegal.png differ diff --git a/docs/images/userguide/UG_add-alias_illegal_space.png b/docs/images/userguide/UG_add-alias_illegal_space.png new file mode 100644 index 0000000000..016522ae6e Binary files /dev/null and b/docs/images/userguide/UG_add-alias_illegal_space.png differ diff --git a/docs/images/userguide/UG_add-alias_success.png b/docs/images/userguide/UG_add-alias_success.png new file mode 100644 index 0000000000..fed0159ebd Binary files /dev/null and b/docs/images/userguide/UG_add-alias_success.png differ diff --git a/docs/images/userguide/UG_list-alias_success.png b/docs/images/userguide/UG_list-alias_success.png new file mode 100644 index 0000000000..c819096ff6 Binary files /dev/null and b/docs/images/userguide/UG_list-alias_success.png differ diff --git a/docs/images/userguide/UG_remove-alias_success.png b/docs/images/userguide/UG_remove-alias_success.png new file mode 100644 index 0000000000..5f8fd3caf3 Binary files /dev/null and b/docs/images/userguide/UG_remove-alias_success.png differ diff --git a/docs/images/userguide/UG_reset-alias_success.png b/docs/images/userguide/UG_reset-alias_success.png new file mode 100644 index 0000000000..9092ce2b4e Binary files /dev/null and b/docs/images/userguide/UG_reset-alias_success.png differ diff --git a/docs/images/userguide/UG_view_success.png b/docs/images/userguide/UG_view_success.png new file mode 100644 index 0000000000..ce801f81d7 Binary files /dev/null and b/docs/images/userguide/UG_view_success.png differ diff --git a/docs/team/[T12-1]-[Oung Yong Sheng Kennedy]PPP.pdf b/docs/team/[T12-1]-[Oung Yong Sheng Kennedy]PPP.pdf new file mode 100644 index 0000000000..792e75169a Binary files /dev/null and b/docs/team/[T12-1]-[Oung Yong Sheng Kennedy]PPP.pdf differ diff --git a/docs/team/cheesengg.adoc b/docs/team/cheesengg.adoc new file mode 100644 index 0000000000..80ea387d8d --- /dev/null +++ b/docs/team/cheesengg.adoc @@ -0,0 +1,102 @@ += Brian Lim -- Project Portfolio for Optix +:imagesDir: ../images +:repoURL: https://github.com/AY1920S1-CS2113T-T12-1/main + +== About the project + +My team of 3 software engineering students and I were tasked with enhancing a basic command line interface desktop task manager application for our Software Engineering project. We chose to morph it into a show and ticketing management system called Optix. This enhanced application enables theatre and concert hall managers to schedule shows, manage ticket purchase and account for their finance all within a single application. + + +This is what our project looks like: + +.The Graphical User Interface of Optix +image::{imagesDir}/Ui.png[GUI Optix] + +My role was to design Commands and Models that manages ticket purchase and to ensure that my team adheres to the coding standards. Two prominent features that I have designed are the sell seat and view seat features. Once the main features were up, I took on the additional role of designing and implementing the Graphical User Interface for Optix. The following sections illustrate these enhancements in more detail, as well as the relevant documentation I have added to the user and developer guides in relation to these enhancements. + +== Summary of contributions + +This section shows a summary of my coding, documentation, and other helpful contributions to the team project. + + +*Enhancement added*: I added the ability for user to sell seats and view seating arrangements. + +* *What it does*: The sell command allows the user to manage ticket purchase and the view command allows user to see the seating arrangement for a show. +* *Justification*: For the application to fulfil its purpose, it is necessary for it to be able to keep track of the number of tickets that can be sold for each showing instead of just scheduling shows. +* *Highlights*: This enhancement works with existing as well as future commands. The sell feature implementation was particularly challenging because there were several implementations that we could explore but each implementation has their own challenges which will be discussed in the Contributions to the Developer Guide section. +* *Code contributed*: Please click the link to see a sample of my code: +link:https://nuscs2113-ay1920s1.github.io/dashboard/#=undefined&search=cheesengg[[Functional Code\]] link:{repoURL}/tree/master/src/test/java/Optix/commands/seats/SellSeatCommandTest.java[[Test Code\]] + +*Other Code contributions*: I helped to set up the base code for Optix (Pull request: link:{repoURL}/pull/10[#10]) and subsequently added most of the basic command so that my group could work on the features for Optix. + +*Other contributions*: + +* Project management: +** Ensured that coding standards were adhered to. (Pull request: link:{repoURL}/pull/87[#87], link:{repoURL}/pull/96[#96]) +** Updated and managed issue tracker by assigning issues to ensure milestones are met. +** Structured Command Class and Test code guidelines for my team so that the code is standardised. (Pull request: link:{repoURL}/pull/87[#87], link:{repoURL}/pull/97[#97]) +* Enhancements to existing features: +** AddCommand and DeleteCommand to enable bulk process. (Pull request: link:{repoURL}/pull/87[#87]) +*** Justification: Shows are normally scheduled for consecutive days, hence the enhancement to allow bulk process reduces the amount of work for managers to schedule the shows. Similarly, if the trope is unable to perform due to unforeseen circumstances, the manager can bulk delete the shows that cannot be performed. +** ListShowCommand and ListDateCommand. (Pull request: link:{repoURL}/pull/14[#14] and link:{repoURL}/pull/75[#75] respectively) +*** Justification: To act as filters when shows scheduled increases. +** Added Graphical User Interface for Optix. (Pull request: link:{repoURL}/pull/89[#89]) +*** Justification: To make the application more user-friendly as Graphical User Interface is more interactive as compared to command line. +* Documentation: +** Ensured that the stylings of the user guide are aligned and separated commands into various headers to make it reader-friendly. +* Tools: +** Integrated a third-party library (JFoenix) to the project. (Pull request: link:{repoURL}/pull/87[#87]) + +== Contributions to User Guide + +The following section shows my contribution to the user guide. + + +While updating the user guide, I noticed that as more commands were added it made locating a specific command within the user guide to be extremely tedious. This made it non-user-friendly. + + +Below is a screenshot of the previous version of our user guide: + +.Old User Guide Format +image::{imagesDir}/team/cheesengg_UG_Old.png[] + +Since there was no structure to how the commands were listed in our user guide, I noticed that it is hard for the user to find specific commands to use. I also noticed that as the user guide was not updated constantly after every feature, it posed as a challenge for us when we were trying to update the user guide since the commands were not categorised properly. + + +Since each Optix’s command deals with a specific component in the program, I categorised the commands to be representative of the different components that they deal with in the program. + + +Below is the screenshot of the changes that I made for the user guide: + +.New User Guide Format +image::{imagesDir}/team/cheesengg_UG_New.png[] + +== Contributions to Developer Guide + +The following section shows my contribution to the developer guide. + + +Below details my contributions for Section 2 of developer guide. + + +.Architecture Diagram +image::{imagesDir}/devguide/DG_SystemArch.png[] + +I drew the System Architecture for the developer guide to show the general overview for the relationship between components in Optix. + + +Additionally, I color-coded the main components that will be discussed and will be drawn in subsequent subsections. This allows readers to recognise which component is being discussed in the subsection and its role in the system easily. + +Next the following details my documentation of the enhancement feature for Optix, SellSeatCommand. + +.SellSeatCommand Overview +image::{imagesDir}/team/cheesengg_Overview_of_Feature.png[] + +The first part of the section states clearly the location of the feature and the relevant components to take note of so that developers reading the guide can zoom in on these components and take note of them when looking at the code. + +.Flow of Algorithmn +image::{imagesDir}/team/cheesengg_Activity_Diagram.png[] + +Next, the flow of the feature is described in a logical manner so that when they read the code they will be able to visualise what is happening in the code. An activity diagram is then used to give a summary for the flow of the Command which enhances the developers understanding for the feature. + +.Design Considerations +image::{imagesDir}/team/cheesengg_Design_Consideration.png[] + +Lastly, design considerations were included in the documentations, so that future developers would be able to understand the thought process for the implementations of the program as well as the various possibilities that the program could be structured. They would also be able to understand the strengths and weaknesses of the different design implementations and work on improving them. The code block at the very bottom of Figure 3 was included in the documentation so that if they would like to improve on our current design they would have a basic codebase to work with. + +== Conclusion + +This portfolio provides a brief overview of my contributions to the code, User Guide and Developer Guide of the Optix software. Steps were taken to justify my design choices for the guide and code. Most of my contributions were towards designing a suitable model for our application such that there is high cohesion between our model and features implemented. It is designed with considerations of expanding it into a much larger projects in the future in mind. + + +My main takeaway for this project is that it is important to have a good domain knowledge of the problem that we want to solve. As none of us have any experience or knowledge on how ticketing is like in the real world, it was hard for us to come up with suitable data structure and model to manage the entire system and this led to constant refactoring of our model to suit the features. By having a strong understanding, we would then be able to design a model that is compatible with all the features that we have discussed in our user stories and be more efficient when coding the program. diff --git a/docs/team/e.g.[CS2113T-T12-1][Liu Wei Jie Nicholas]PPP.pdf b/docs/team/e.g.[CS2113T-T12-1][Liu Wei Jie Nicholas]PPP.pdf new file mode 100644 index 0000000000..d91ee44894 Binary files /dev/null and b/docs/team/e.g.[CS2113T-T12-1][Liu Wei Jie Nicholas]PPP.pdf differ diff --git a/git b/git new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..5c2d1cf016 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..4b7e1f3d38 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..8e25e6c19d --- /dev/null +++ b/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..24467a141f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..d1e92fe5db --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'duke' diff --git a/src/main/data/ParserPreferences.txt b/src/main/data/ParserPreferences.txt new file mode 100644 index 0000000000..a3a5b84855 --- /dev/null +++ b/src/main/data/ParserPreferences.txt @@ -0,0 +1,20 @@ +b|bye +shw|show +d|delete +pmb|bye +e|edit +h|help +fin|finance +l|list +rd|reschedule +pb|bye +re|reassign-seat +arc|archive +s|sell +a-a|add-alias +nb|bye +v|view +vm|view-monthly +vp|view-profit +rm-a|remove-alias +rst-a|reset-alias diff --git a/src/main/data/archive.txt b/src/main/data/archive.txt new file mode 100644 index 0000000000..7b97ce10ec --- /dev/null +++ b/src/main/data/archive.txt @@ -0,0 +1,8 @@ +2015-10-13 | Harry Potter | 2000.0 +2018-11-13 | Lion King | 2100.0 +2018-11-17 | Harry Potter | 2000.0 +2018-12-13 | Harry Potter | 2000.0 +2019-10-13 | Lion King | 200.0 +2019-10-19 | tt | 123.0 +2019-10-31 | tt | 200.0 +2019-11-11 | Lion King | 0.0 diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000000..d54b98e2a0 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,7 @@ +import javafx.application.Application; + +public class Main { + public static void main(String[] args) { + Application.launch(MainApp.class, args); + } +} diff --git a/src/main/java/MainApp.java b/src/main/java/MainApp.java new file mode 100644 index 0000000000..32002374da --- /dev/null +++ b/src/main/java/MainApp.java @@ -0,0 +1,33 @@ +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import optix.Optix; +import optix.ui.windows.MainWindow; + +import java.io.File; + +public class MainApp extends Application { + + private Optix optix; + + @Override + public void init() throws Exception { + super.init(); + + File currentDir = new File(System.getProperty("user.dir")); + File filePath = new File(currentDir.toString() + "\\src\\main\\data"); + + optix = new Optix(filePath); + } + + @Override + public void start(Stage primaryStage) { + Parent root = new MainWindow(optix); + Scene scene = new Scene(root); + + primaryStage.setTitle("Optix"); + primaryStage.setScene(scene); + primaryStage.show(); + } +} diff --git a/src/main/java/optix/Optix.java b/src/main/java/optix/Optix.java new file mode 100644 index 0000000000..4815d4dbd5 --- /dev/null +++ b/src/main/java/optix/Optix.java @@ -0,0 +1,77 @@ +package optix; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.commons.model.ShowMap; +import optix.exceptions.OptixException; +import optix.ui.Ui; +import optix.util.Parser; + +import java.io.File; + + +/** + * Software that stores all the finance for the Opera Hall. + */ +public class Optix { + private Model model; + + private Ui ui; + + private Storage storage; + + private Parser parser; + + /** + * Set up the storage, ui, and list of shows. + * Save data is loaded from storage.load() + * + * @param filePath is the path to the file which contains save data. + */ + + public Optix(File filePath) { + ui = new Ui(); + storage = new Storage(filePath); + model = new Model(storage); + parser = new Parser(filePath); + } + + /** + * Processes user input command. + * + * @param fullCommand User input command. + * @return String for the type of Model that command deals with. E.g Show, Seat and Alias. + */ + public String runGui(String fullCommand) { + String taskType = ""; + try { + Command c = parser.parse(fullCommand); + taskType = c.execute(model, ui, storage); + } catch (OptixException e) { + ui.setMessage(e.getMessage()); + } + return taskType; + } + + public ShowMap getShows() { + return model.getShows(); + } + + public ShowMap getShowsGui() { + return model.getShowsGui(); + } + + public void resetShows() { + model.setShowsGui(model.getShows()); + } + + public void resetArchive() { + model.setShowsGui(model.getShowsHistory()); + } + + public String getResponse() { + return ui.getMessage(); + } +} + diff --git a/src/main/java/optix/commands/ByeCommand.java b/src/main/java/optix/commands/ByeCommand.java new file mode 100644 index 0000000000..90d340941e --- /dev/null +++ b/src/main/java/optix/commands/ByeCommand.java @@ -0,0 +1,33 @@ +package optix.commands; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; + +import java.util.logging.Level; + +public class ByeCommand extends Command { + private static final String MESSAGE_BYE = "Bye. Hope to see you again soon!\n"; + + /** + * initialise logger. + */ + public ByeCommand() { + super.initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + storage.write(model.getShows()); + ui.setMessage(MESSAGE_BYE); + return "bye"; + } + + @Override + public String[] parseDetails(String details) { + return new String[0]; + + } + +} diff --git a/src/main/java/optix/commands/Command.java b/src/main/java/optix/commands/Command.java new file mode 100644 index 0000000000..e206752d3c --- /dev/null +++ b/src/main/java/optix/commands/Command.java @@ -0,0 +1,53 @@ +package optix.commands; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixInvalidCommandException; +import optix.ui.Ui; + +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class Command { + public final Logger OPTIXLOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);//CHECKSTYLE IGNORE THIS LINE + + /** + * Processes user input to be stored, queried, modified in ShowMap, + * to show response by program in ui and store existing data in Storage. + * + * @param model The data structure holding all the information. + * @param ui The User Interface that reads user input and response to user. + * @param storage The filepath of txt file which data are being stored. + */ + public abstract String execute(Model model, Ui ui, Storage storage); + + /** + * Parses user input into its respective parameters. + * + * @param details User input command. + * @return Array of string with respective parameters + * @throws OptixInvalidCommandException The size of String array is not equals to expected + * number of parameters for the Command. + */ + public abstract String[] parseDetails(String details) throws OptixInvalidCommandException; + + /** + * Initialise logger. + */ + public void initLogger() { + // add a handler if there is no handler in the logger + if (OPTIXLOGGER.getHandlers().length == 0) { + OPTIXLOGGER.setLevel(Level.ALL); + try { + FileHandler fh = new FileHandler("OptixLogger.log"); + fh.setLevel(Level.FINE); + OPTIXLOGGER.addHandler(fh); + } catch (IOException e) { + OPTIXLOGGER.log(Level.SEVERE, "File logger not working", e); + } + } + OPTIXLOGGER.log(Level.FINEST, "Logging in " + this.getClass().getName()); + } +} diff --git a/src/main/java/optix/commands/TabCommand.java b/src/main/java/optix/commands/TabCommand.java new file mode 100644 index 0000000000..513fca04c7 --- /dev/null +++ b/src/main/java/optix/commands/TabCommand.java @@ -0,0 +1,61 @@ +package optix.commands; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixInvalidCommandException; +import optix.ui.Ui; + +import java.util.logging.Level; + +public class TabCommand extends Command { + String commandWord; + + private static final String MESSAGE_ARCHIVE = "Here is your list of archived shows.\n"; + private static final String MESSAGE_SHOW = "Here is your list of scheduled shows.\n"; + private static final String MESSAGE_FINANCE = "Here is your list of projected earnings.\n"; + private static final String MESSAGE_HELP = "Here are the list of commands you can use.\n"; + + public TabCommand(String commandWord) { + this.commandWord = commandWord.trim().toLowerCase(); + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + StringBuilder message = new StringBuilder(); + OPTIXLOGGER.log(Level.INFO, "executing command"); + try { + switch (commandWord) { + case "archive": + OPTIXLOGGER.log(Level.INFO, "archive case"); + message.append(MESSAGE_ARCHIVE); + message.append(model.listShowHistory()); + ui.setMessage(message.toString()); + break; + case "finance": + OPTIXLOGGER.log(Level.INFO, "finance case"); + message.append(MESSAGE_FINANCE); + message.append(model.listFinance()); + ui.setMessage(message.toString()); + break; + case "help": + OPTIXLOGGER.log(Level.INFO, "help case"); + ui.setMessage(MESSAGE_HELP); + break; + default: + OPTIXLOGGER.log(Level.WARNING, "TabCommand Error. commandWord:" + this.commandWord); + throw new OptixInvalidCommandException(); + } + } catch (OptixInvalidCommandException e) { + OPTIXLOGGER.log(Level.WARNING, "Invalid input:" + this.commandWord); + ui.setMessage(e.getMessage()); + } + return commandWord; + } + + @Override + public String[] parseDetails(String details) { + return new String[0]; + } + +} diff --git a/src/main/java/optix/commands/finance/ViewMonthlyCommand.java b/src/main/java/optix/commands/finance/ViewMonthlyCommand.java new file mode 100644 index 0000000000..ec8e1a5160 --- /dev/null +++ b/src/main/java/optix/commands/finance/ViewMonthlyCommand.java @@ -0,0 +1,70 @@ +package optix.commands.finance; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.util.logging.Level; + +//@@author NicholasLiu97 +public class ViewMonthlyCommand extends Command { + private String details; + private OptixDateFormatter formatter = new OptixDateFormatter(); + + public ViewMonthlyCommand(String details) { + this.details = details; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + int mth; + int yr; + try { + String[] detailsArray = parseDetails(this.details); + mth = formatter.getMonth(detailsArray[0].trim().toLowerCase()); + yr = formatter.getYear(detailsArray[1].trim()); + + if (mth == 0 || yr == 0) { + OPTIXLOGGER.log(Level.WARNING, "month is 0 or year is 0"); + throw new OptixInvalidDateException(); + } + if (yr < storage.getToday().getYear()) { + message.append(model.findMonthly(mth, yr, model.getShowsHistory())); + } else if (yr > storage.getToday().getYear()) { + message.append(model.findMonthly(mth, yr, model.getShows())); + } else { // year is the current year or later + if (mth < storage.getToday().getMonthValue()) { + message.append(model.findMonthly(mth, yr, model.getShowsHistory())); + } else if (mth == storage.getToday().getMonthValue()) { + message.append(model.findMonthly(mth, yr, model.getShowsHistory(), model.getShows())); + } else { + message.append(model.findMonthly(mth, yr, model.getShows())); + } + } + } catch (OptixException e) { + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "finance"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split(" "); + if (detailsArray.length != 2) { + OPTIXLOGGER.log(Level.WARNING, "full command has too many spaces. Expected detailsArray length: 2"); + throw new OptixInvalidCommandException(); + } + return detailsArray; + } +} \ No newline at end of file diff --git a/src/main/java/optix/commands/finance/ViewProfitCommand.java b/src/main/java/optix/commands/finance/ViewProfitCommand.java new file mode 100644 index 0000000000..7323c84f77 --- /dev/null +++ b/src/main/java/optix/commands/finance/ViewProfitCommand.java @@ -0,0 +1,93 @@ +package optix.commands.finance; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.commons.model.ShowMap; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author NicholasLiu97 +public class ViewProfitCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + private static final String MESSAGE_SUCCESSFUL = "The profit for %1$s on %2$s is $%3$.2f\n"; + + /** + * Views the profit made from a show on a certain date. + * + * @param splitStr String of format "SHOW_NAME|SHOW_DATE" + */ + public ViewProfitCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + String showName; + String showDate; + try { + String[] detailsArray = parseDetails(this.details); + showName = detailsArray[0].trim(); + showDate = detailsArray[1].trim(); + + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "invalid date provided"); + throw new OptixInvalidDateException(); + } + + LocalDate localDate = formatter.toLocalDate(showDate); + + if (localDate.compareTo(storage.getToday()) <= 0) { //in archive list + ShowMap showsHistory = model.getShowsHistory(); + if (showsHistory.containsKey(localDate) && showsHistory.get(localDate).hasSameName(showName)) { //date not found + message.append(String.format(MESSAGE_SUCCESSFUL, showName, showDate, + showsHistory.getProfit(localDate))); + } else { + OPTIXLOGGER.log(Level.WARNING, "Show not found"); + message.append(MESSAGE_SHOW_NOT_FOUND); + } + } else { + ShowMap shows = model.getShows(); + if (shows.containsKey(localDate) && model.hasSameName(localDate, showName)) { + message.append(String.format(MESSAGE_SUCCESSFUL, showName, showDate, + shows.getProfit(localDate))); + } else { + OPTIXLOGGER.log(Level.WARNING, "Show not found"); + message.append(MESSAGE_SHOW_NOT_FOUND); + } + } + } catch (OptixException e) { + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "finance"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|"); + if (detailsArray.length != 2) { + OPTIXLOGGER.log(Level.WARNING, "Error parsing command details"); + OPTIXLOGGER.log(Level.WARNING, "Expected details array length: 2"); + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} diff --git a/src/main/java/optix/commands/parser/AddAliasCommand.java b/src/main/java/optix/commands/parser/AddAliasCommand.java new file mode 100644 index 0000000000..4e0b4744ec --- /dev/null +++ b/src/main/java/optix/commands/parser/AddAliasCommand.java @@ -0,0 +1,86 @@ +package optix.commands.parser; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.ui.Ui; +import optix.util.Parser; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +//@@author OungKennedy +public class AddAliasCommand extends Command { + private String details; + private File preferenceFilePath; + + private static final String MESSAGE_NOT_ACCEPTED = "☹ OOPS!!! Spaces are not allowed for alias command.\n" + + "Please try again"; + + /** + * Command to add a new alias to the command alias map. + * + * @param details String containing "NEW_ALIAS|COMMAND" + */ + public AddAliasCommand(String details, File filePath) { + this.details = details.trim(); + this.preferenceFilePath = filePath; + super.initLogger(); + } + + /** + * Processes user input to be stored, queried, modified in ShowMap, + * to show response by program in ui and store existing data in Storage. + * + * @param model The data structure holding all the information. + * @param ui The User Interface that reads user input and response to user. + * @param storage The filepath of txt file which data are being stored. + */ + @Override + public String execute(Model model, Ui ui, Storage storage) { + // parse the new alias and command from the details + String newAlias; + String command; + try { + String[] detailsArray = parseDetails(this.details); + newAlias = detailsArray[0].trim(); + command = detailsArray[1].trim(); + } catch (OptixInvalidCommandException e) { + OPTIXLOGGER.log(Level.WARNING, "error parsing details: " + this.details); + ui.setMessage(e.getMessage()); + return ""; + } + + String message; + Parser dummyParser = new Parser(preferenceFilePath); // create parser object + try { + String[] aliasArray = newAlias.split(" "); + if (aliasArray.length > 1 || aliasArray[0].equals("")) { + throw new OptixException(MESSAGE_NOT_ACCEPTED); + } + // adds the alias-command pair to commandAliasMap, and saves it to file + dummyParser.addAlias(newAlias, command); + dummyParser.savePreferences(); + message = String.format("The new alias %s was successfully paired to the command %s\n", newAlias, command); + } catch (OptixException | IOException e) { + OPTIXLOGGER.log(Level.WARNING, String.format("error adding or saving alias. Alias:%s Command:%s", newAlias, command)); + message = e.getMessage(); + } + ui.setMessage(message); + return ""; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.split("\\|", 2); + if (detailsArray.length != 2) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} + diff --git a/src/main/java/optix/commands/parser/ListAliasCommand.java b/src/main/java/optix/commands/parser/ListAliasCommand.java new file mode 100644 index 0000000000..d30a0e8d66 --- /dev/null +++ b/src/main/java/optix/commands/parser/ListAliasCommand.java @@ -0,0 +1,48 @@ +package optix.commands.parser; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import optix.util.Parser; + +import java.util.Map; +import java.util.logging.Level; + +//@@author OungKennedy +public class ListAliasCommand extends Command { + + /** + * initialise logger. + */ + public ListAliasCommand() { + initLogger(); + } + + /** + * Iterates through all entries in the commandAliasMap of a parser object. + * Prints every key (alias) and value (command) pair. + * to show response by program in ui and store existing data in Storage. + * + * @param model The data structure holding all the information. + * @param ui The User Interface that reads user input and response to user. + * @param storage The filepath of txt file which data are being stored. + */ + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder systemMessage = new StringBuilder("Alias list: \n"); + for (Map.Entry entry : Parser.commandAliasMap.entrySet()) { + systemMessage.append(entry.getKey()).append(" : ").append(entry.getValue()).append('\n'); + } + ui.setMessage(systemMessage.toString()); + return ""; + } + + @Override + public String[] parseDetails(String details) { + return new String[0]; + } + +} + diff --git a/src/main/java/optix/commands/parser/RemoveAliasCommand.java b/src/main/java/optix/commands/parser/RemoveAliasCommand.java new file mode 100644 index 0000000000..552111077e --- /dev/null +++ b/src/main/java/optix/commands/parser/RemoveAliasCommand.java @@ -0,0 +1,91 @@ +package optix.commands.parser; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.ui.Ui; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +//@@author OungKennedy +public class RemoveAliasCommand extends Command { + private String details; + private HashMap commandAliasMap; + + /** + * Command to remove an existing alias from aliasCommandMap. + * + * @param details the details of alias to remove and its command, in an array + * @param commandAliasMap the command alias map + */ + public RemoveAliasCommand(String details, HashMap commandAliasMap) { + this.details = details; + this.commandAliasMap = commandAliasMap; + initLogger(); + } + + /** + * Processes user input to be stored, queried, modified in ShowMap, + * to show response by program in ui and store existing data in Storage. + * + * @param model The data structure holding all the information. + * @param ui The User Interface that reads user input and response to user. + * @param storage The filepath of txt file which data are being stored. + */ + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + // parse the string to assign the details and command strings to local variables + String alias; + String command; + try { + String[] detailsArray = parseDetails(this.details); + alias = detailsArray[0].trim(); + command = detailsArray[1].trim(); + } catch (OptixInvalidCommandException e) { + OPTIXLOGGER.log(Level.WARNING, "Error parsing details:" + this.details); + ui.setMessage(e.getMessage()); + return ""; + } + + try { + // check if the alias exists + if (!commandAliasMap.containsValue(command) || !commandAliasMap.containsKey(alias)) { + throw new OptixException("Error removing alias.\n"); + } + // edit command alias map + commandAliasMap.remove(alias, command); + // open target file + File currentDir = new File(System.getProperty("user.dir")); + File filePath = new File(currentDir.toString() + "\\src\\main\\data\\ParserPreferences.txt"); + + PrintWriter writer = new PrintWriter(filePath); + for (Map.Entry entry : commandAliasMap.entrySet()) { + writer.write(entry.getKey() + "|" + entry.getValue() + '\n'); + } + writer.close(); + String successMessage = String.format("Noted. The alias %s has been removed\n", alias); + ui.setMessage(successMessage); + } catch (IOException | OptixException e) { + ui.setMessage(e.getMessage()); + } + return ""; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.split("\\|", 2); + if (detailsArray.length != 2) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} diff --git a/src/main/java/optix/commands/parser/ResetAliasCommand.java b/src/main/java/optix/commands/parser/ResetAliasCommand.java new file mode 100644 index 0000000000..5b56c18727 --- /dev/null +++ b/src/main/java/optix/commands/parser/ResetAliasCommand.java @@ -0,0 +1,63 @@ +package optix.commands.parser; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import optix.util.Parser; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +//@@author OungKennedy +public class ResetAliasCommand extends Command { + private File preferenceFilePath; + + public ResetAliasCommand(File filePath) { + this.preferenceFilePath = filePath; + initLogger(); + } + + /** + * Processes user input to be stored, queried, modified in ShowMap, + * to show response by program in ui and store existing data in Storage. + * + * @param model The data structure holding all the information. + * @param ui The User Interface that reads user input and response to user. + * @param storage The filepath of txt file which data are being stored. + */ + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + // open target file + Parser dummyParser = new Parser(this.preferenceFilePath); + // reset commandAliasMap in Parser class + Parser.resetPreferences(); + // write the current contents of the commandAliasMap to the saveFile. + String systemMessage; + try { + dummyParser.savePreferences(); + systemMessage = "Alias settings have been reset to default.\n"; + + } catch (IOException e) { + OPTIXLOGGER.log(Level.WARNING, "Error saving preferences."); + systemMessage = e.getMessage(); + } + ui.setMessage(systemMessage); + return ""; + } + + /** + * Dummy command. + * + * @param details n.a + * @return n.a + */ + @Override + public String[] parseDetails(String details) { + return new String[0]; + } + +} + diff --git a/src/main/java/optix/commands/seats/ReassignSeatCommand.java b/src/main/java/optix/commands/seats/ReassignSeatCommand.java new file mode 100644 index 0000000000..4f24de3ac1 --- /dev/null +++ b/src/main/java/optix/commands/seats/ReassignSeatCommand.java @@ -0,0 +1,78 @@ +package optix.commands.seats; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author NicholasLiu97 +public class ReassignSeatCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + /** + * Changes the seat of an existing customer. + * + * @param details String of format "SHOW_NAME|SHOW_DATE|OLD_SEAT|NEW_SEAT" + */ + public ReassignSeatCommand(String details) { + this.details = details; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + try { + String[] detailsArray = parseDetails(this.details); + String showName = detailsArray[0].trim(); + String showDate = detailsArray[1].trim(); + String oldSeat = detailsArray[2].trim(); + String newSeat = detailsArray[3].trim(); + + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "Error with showDate:" + showDate); + throw new OptixInvalidDateException(); + } + + LocalDate showLocalDate = formatter.toLocalDate(showDate); + + if (model.containsKey(showLocalDate) && model.hasSameName(showLocalDate, showName)) { //found the show + message.append(model.reassignSeat(showLocalDate, oldSeat, newSeat)); + storage.write(model.getShows()); + } else { //no show on the showDate + OPTIXLOGGER.log(Level.WARNING, "Show not found: " + showName); + ui.setMessage(MESSAGE_SHOW_NOT_FOUND); + return ""; + } + } catch (OptixException e) { + OPTIXLOGGER.log(Level.WARNING, "Error reassigning seat. Details:" + this.details); + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "seat"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|"); + if (detailsArray.length != 4) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} diff --git a/src/main/java/optix/commands/seats/RefundSeatCommand.java b/src/main/java/optix/commands/seats/RefundSeatCommand.java new file mode 100644 index 0000000000..3da1a65172 --- /dev/null +++ b/src/main/java/optix/commands/seats/RefundSeatCommand.java @@ -0,0 +1,73 @@ +package optix.commands.seats; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//author TianchangLiao +public class RefundSeatCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + public RefundSeatCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + + try { + String[] detailsArray = parseDetails(this.details); + String showName = detailsArray[0].trim(); + String showDate = detailsArray[1].trim(); + String[] seats = detailsArray[2].trim().split(" "); + for (int i = 0; i < seats.length; i += 1) { + seats[i] = seats[i].trim(); + } + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date given:" + showDate); + throw new OptixInvalidDateException(); + } + + LocalDate showLocalDate = formatter.toLocalDate(showDate); + + if (model.containsKey(showLocalDate) && model.hasSameName(showLocalDate, showName)) { + message.append(model.refundSeats(showLocalDate, seats)); + storage.write(model.getShows()); + } else { + OPTIXLOGGER.log(Level.WARNING, "Show not found: " + showName); + ui.setMessage(MESSAGE_SHOW_NOT_FOUND); + return ""; + } + } catch (OptixException e) { + OPTIXLOGGER.log(Level.WARNING, "Error refunding seat. Details:" + this.details); + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "seat"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|"); + if (detailsArray.length != 3) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } +} diff --git a/src/main/java/optix/commands/seats/RemoveSeatCommand.java b/src/main/java/optix/commands/seats/RemoveSeatCommand.java new file mode 100644 index 0000000000..3d524ebfd2 --- /dev/null +++ b/src/main/java/optix/commands/seats/RemoveSeatCommand.java @@ -0,0 +1,73 @@ +package optix.commands.seats; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//author TianchangLiao +public class RemoveSeatCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + public RemoveSeatCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + + try { + String[] detailsArray = parseDetails(this.details); + String showName = detailsArray[0].trim(); + String showDate = detailsArray[1].trim(); + String[] seats = detailsArray[2].trim().split(" "); + for (int i = 0; i < seats.length; i += 1) { + seats[i] = seats[i].trim(); + } + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date given:" + showDate); + throw new OptixInvalidDateException(); + } + + LocalDate showLocalDate = formatter.toLocalDate(showDate); + + if (model.containsKey(showLocalDate) && model.hasSameName(showLocalDate, showName)) { + message.append(model.removeSeats(showLocalDate, seats)); + storage.write(model.getShows()); + } else { + OPTIXLOGGER.log(Level.WARNING, "Show not found: " + showName); + ui.setMessage(MESSAGE_SHOW_NOT_FOUND); + return ""; + } + } catch (OptixException e) { + OPTIXLOGGER.log(Level.WARNING, "Error removing seat. Details:" + this.details); + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "seat"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|"); + if (detailsArray.length != 3) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } +} diff --git a/src/main/java/optix/commands/seats/SellSeatCommand.java b/src/main/java/optix/commands/seats/SellSeatCommand.java new file mode 100644 index 0000000000..1cd7e629a5 --- /dev/null +++ b/src/main/java/optix/commands/seats/SellSeatCommand.java @@ -0,0 +1,81 @@ +package optix.commands.seats; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author CheeSengg +public class SellSeatCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + /** + * Instantiates the command. + * This function is called when the customer has already + * decided on his seat. + * + * @param splitStr String in the format "SHOW_NAME|SHOW_DATE|DATE_1 DATE_2 etc." + */ + public SellSeatCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + + try { + String[] detailsArray = parseDetails(this.details); + String showName = detailsArray[0].trim(); + String showDate = detailsArray[1].trim(); + String[] seats = detailsArray[2].trim().split(" "); + for (int i = 0; i < seats.length; i += 1) { + seats[i] = seats[i].trim(); + } + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date given:" + showDate); + throw new OptixInvalidDateException(); + } + + LocalDate showLocalDate = formatter.toLocalDate(showDate); + + if (model.containsKey(showLocalDate) && model.hasSameName(showLocalDate, showName)) { + message.append(model.sellSeats(showLocalDate, seats)); + storage.write(model.getShows()); + } else { + OPTIXLOGGER.log(Level.WARNING, "Show not found: " + showName); + ui.setMessage(MESSAGE_SHOW_NOT_FOUND); + return ""; + } + } catch (OptixException e) { + OPTIXLOGGER.log(Level.WARNING, "Error selling seat. Details:" + this.details); + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "seat"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|"); + if (detailsArray.length != 3) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} \ No newline at end of file diff --git a/src/main/java/optix/commands/seats/ViewSeatsCommand.java b/src/main/java/optix/commands/seats/ViewSeatsCommand.java new file mode 100644 index 0000000000..4d663d50ee --- /dev/null +++ b/src/main/java/optix/commands/seats/ViewSeatsCommand.java @@ -0,0 +1,78 @@ +package optix.commands.seats; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author CheeSengg +public class ViewSeatsCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_SHOW_FOUND = "Here is the layout of the theatre for %1$s on %2$s:\n"; + + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! Sorry the show %1$s cannot be found.\n"; + + /** + * Command to view seats of a show. + * + * @param splitStr String of format "SHOW_NAME|SHOW_DATE" + */ + public ViewSeatsCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + OPTIXLOGGER.log(Level.INFO, "executing command"); + StringBuilder message = new StringBuilder(); + try { + String[] arrayDetails = parseDetails(this.details); + String showName = arrayDetails[0].trim(); + String showDate = arrayDetails[1].trim(); + + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date given:" + showDate); + throw new OptixInvalidDateException(); + } + + LocalDate showLocalDate = formatter.toLocalDate(showDate); + + if (model.containsKey(showLocalDate) && model.hasSameName(showLocalDate, showName)) { + message = new StringBuilder(String.format(MESSAGE_SHOW_FOUND, showName, showDate)); + message.append(model.viewSeats(showLocalDate)); + } else { + OPTIXLOGGER.log(Level.WARNING, "Show not found: " + showName); + ui.setMessage(String.format(MESSAGE_SHOW_NOT_FOUND, showName)); + return ""; + } + } catch (OptixException e) { + OPTIXLOGGER.log(Level.WARNING, "Error viewing seat. Details:" + this.details); + message.append(e.getMessage()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "seat"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|"); + if (detailsArray.length != 2) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} diff --git a/src/main/java/optix/commands/shows/AddCommand.java b/src/main/java/optix/commands/shows/AddCommand.java new file mode 100644 index 0000000000..cfd666e7f5 --- /dev/null +++ b/src/main/java/optix/commands/shows/AddCommand.java @@ -0,0 +1,105 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.ArrayList; + +//@@author CheeSengg +public class AddCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_SUCCESSFUL = "Noted. The following shows has been added:\n"; + + private static final String MESSAGE_ENTRY = "%1$d. %2$s (on: %3$s)\n"; + + private static final String MESSAGE_UNSUCCESSFUL = "☹ OOPS!!! Unable to add the following shows:\n"; + + /** + * Add a show to the show list. + * + * @param splitStr String of format "SHOW_NAME|SEAT_BASE_PRICE|DATE_1|DATE_2|etc" + */ + public AddCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + String showName; + String[] showDates; + double seatBasePrice; + try { + String[] detailsArray = parseDetails(details); + showName = detailsArray[0].trim(); + showDates = detailsArray[2].trim().split("\\|"); + seatBasePrice = Double.parseDouble(detailsArray[1]); + if (seatBasePrice < 0) { + throw new OptixException("Seat base price cannot be negative.\n"); + } + } catch (NumberFormatException e) { + ui.setMessage("Please set a number for the seat base price.\n"); + return ""; + } catch (OptixException e) { + ui.setMessage(e.getMessage()); + return ""; + } + + LocalDate today = storage.getToday(); + ArrayList errorShows = new ArrayList<>(); + StringBuilder message = new StringBuilder(MESSAGE_SUCCESSFUL); + int counter = 1; + + for (String showDate : showDates) { + String date = showDate.trim(); + if (!hasValidDate(date)) { + errorShows.add(date); + continue; + } + + LocalDate showLocalDate = formatter.toLocalDate(date); + + if (showLocalDate.compareTo(today) <= 0 || model.containsKey(showLocalDate)) { + errorShows.add(date); + } else { + model.addShow(showName, showLocalDate, seatBasePrice); + message.append(String.format(MESSAGE_ENTRY, counter, showName, date)); + counter++; + } + } + if (errorShows.size() == showDates.length) { + message = new StringBuilder(MESSAGE_UNSUCCESSFUL); + } else if (errorShows.size() != 0) { + message.append("\n" + MESSAGE_UNSUCCESSFUL); + } + for (int i = 0; i < errorShows.size(); i++) { + message.append(String.format(MESSAGE_ENTRY, i + 1, showName, errorShows.get(i))); + } + ui.setMessage(message.toString()); + storage.write(model.getShows()); + return "show"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|", 3); + if (detailsArray.length != 3) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + + private boolean hasValidDate(String date) { + return formatter.isValidDate(date); + } + +} diff --git a/src/main/java/optix/commands/shows/DeleteCommand.java b/src/main/java/optix/commands/shows/DeleteCommand.java new file mode 100644 index 0000000000..69db4b9e2e --- /dev/null +++ b/src/main/java/optix/commands/shows/DeleteCommand.java @@ -0,0 +1,102 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixInvalidCommandException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.logging.Level; + +//@@author OungKennedy +public class DeleteCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_SUCCESSFUL = "Noted. The following shows has been deleted:\n"; + + private static final String MESSAGE_ENTRY = "%1$d. %2$s (on: %3$s)\n"; + + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! Unable to find the following shows:\n"; + + /** + * Instantiate vars. + * + * @param splitStr String of format "SHOW_NAME|DATE_1|DATE_2|etc." + */ + public DeleteCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + String[] detailsArray; + try { + detailsArray = parseDetails(this.details); + } catch (OptixInvalidCommandException e) { + OPTIXLOGGER.log(Level.WARNING, "Error parsing details:" + this.details); + ui.setMessage(e.getMessage()); + return ""; + } + + String[] showDates = detailsArray[1].split("\\|"); + String showName = detailsArray[0].trim(); + + StringBuilder message = new StringBuilder(MESSAGE_SUCCESSFUL); + ArrayList missingShows = new ArrayList<>(); + int counter = 1; + + for (String showDate : showDates) { + String date = showDate.trim(); + + if (!hasValidDate(date)) { + missingShows.add(date); + continue; + } + + LocalDate showLocalDate = formatter.toLocalDate(date); + + if (model.containsKey(showLocalDate) && model.hasSameName(showLocalDate, showName)) { + model.deleteShow(showLocalDate); + message.append(String.format(MESSAGE_ENTRY, counter, showName, date)); + counter++; + } else { + missingShows.add(date); + } + } + if (missingShows.size() == showDates.length) { + OPTIXLOGGER.log(Level.WARNING, "could not find show"); + message = new StringBuilder(MESSAGE_SHOW_NOT_FOUND); + } else if (missingShows.size() != 0) { + OPTIXLOGGER.log(Level.WARNING, "could not find show"); + message.append("\n" + MESSAGE_SHOW_NOT_FOUND); + } + for (int i = 0; i < missingShows.size(); i++) { + message.append(String.format(MESSAGE_ENTRY, i + 1, showName, missingShows.get(i))); + } + ui.setMessage(message.toString()); + storage.write(model.getShows()); + return "show"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray; + detailsArray = details.split("\\|", 2); + + if (detailsArray.length != 2) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + + private boolean hasValidDate(String date) { + return formatter.isValidDate(date); + } + +} diff --git a/src/main/java/optix/commands/shows/EditCommand.java b/src/main/java/optix/commands/shows/EditCommand.java new file mode 100644 index 0000000000..4a111a8efe --- /dev/null +++ b/src/main/java/optix/commands/shows/EditCommand.java @@ -0,0 +1,76 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author CheeSengg +public class EditCommand extends Command { + private String details; + + private static final String MESSAGE_UPDATE_SUCCESSFUL = "Show has been successfully updated to %1$s.\n"; + + private static final String MESSAGE_UPDATE_UNSUCCESSFUL = "☹ OOPS!!! The show you are finding does not exist!\n"; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + /** + * Edit the name of an existing show. + * + * @param splitStr String of format "OLD_SHOW_NAME|SHOW_DATE|NEW_SHOW_NAME" + */ + public EditCommand(String splitStr) { + this.details = splitStr; + super.initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + try { + String[] details = parseDetails(this.details); + String oldShowName = details[0].trim(); + String showDate = details[1].trim(); + String newShowName = details[2].trim(); + + if (!formatter.isValidDate(showDate)) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date"); + throw new OptixInvalidDateException(); + } + + LocalDate localShowDate = formatter.toLocalDate(showDate); + StringBuilder message = new StringBuilder(); + + if (model.containsKey(localShowDate) && model.hasSameName(localShowDate, oldShowName)) { + model.editShowName(localShowDate, newShowName); + storage.write(model.getShows()); + message.append(String.format(MESSAGE_UPDATE_SUCCESSFUL, newShowName)); + } else { + OPTIXLOGGER.log(Level.WARNING, MESSAGE_UPDATE_UNSUCCESSFUL); + message.append(MESSAGE_UPDATE_UNSUCCESSFUL); + } + ui.setMessage(message.toString()); + } catch (OptixException e) { + ui.setMessage(e.getMessage()); + return ""; + } + return "show"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.split("\\|"); + if (detailsArray.length != 3) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} diff --git a/src/main/java/optix/commands/shows/ListCommand.java b/src/main/java/optix/commands/shows/ListCommand.java new file mode 100644 index 0000000000..276eb2796c --- /dev/null +++ b/src/main/java/optix/commands/shows/ListCommand.java @@ -0,0 +1,42 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.commons.model.ShowMap; +import optix.ui.Ui; + +import java.util.logging.Level; + +//@@author CheeSengg +public class ListCommand extends Command { + private static final String MESSAGE_LIST_FOUND = "Here are the list of shows:\n"; + + private static final String MESSAGE_LIST_NOT_FOUND = "☹ OOPS!!! There are no shows in the near future.\n"; + + public ListCommand() { + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + ShowMap shows = model.getShows(); + StringBuilder message = new StringBuilder(); + + if (!shows.isEmpty()) { + message.append(MESSAGE_LIST_FOUND); + message.append(model.listShow()); + } else { + message = new StringBuilder(MESSAGE_LIST_NOT_FOUND); + OPTIXLOGGER.log(Level.WARNING, MESSAGE_LIST_NOT_FOUND); + } + ui.setMessage(message.toString()); + return "show"; + } + + @Override + public String[] parseDetails(String details) { + return new String[0]; + } + +} diff --git a/src/main/java/optix/commands/shows/ListDateCommand.java b/src/main/java/optix/commands/shows/ListDateCommand.java new file mode 100644 index 0000000000..9ddae8aeef --- /dev/null +++ b/src/main/java/optix/commands/shows/ListDateCommand.java @@ -0,0 +1,78 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author CheeSengg +public class ListDateCommand extends Command { + private final String monthOfYear; + private String formattedMonthOfYear; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_FOUND_SHOW = "These shows are showing on %1$s: \n"; + + private static final String MESSAGE_NO_SHOWS_FOUND = "☹ OOPS!!! There are no shows on %1$s.\n"; + + public ListDateCommand(String monthOfYear) { + this.monthOfYear = monthOfYear; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + StringBuilder message = new StringBuilder(); + try { + String[] splitStr = monthOfYear.trim().split(" "); + if (splitStr.length != 2) { + throw new OptixInvalidCommandException(); + } + int year = formatter.getYear(splitStr[1].trim()); + int month = formatter.getMonth(splitStr[0].trim().toLowerCase()); + if (year < storage.getToday().getYear() || month == 0) { + throw new OptixInvalidDateException(); + } + formattedMonthOfYear = formatter.intToMonth(month) + ' ' + year; + LocalDate startOfMonth = formatter.getStartOfMonth(year, month); + LocalDate endOfMonth = formatter.getEndOfMonth(year, month); + message.append(String.format(MESSAGE_FOUND_SHOW, formattedMonthOfYear)); + message.append(model.listShow(startOfMonth, endOfMonth)); + if (!hasShow(message.toString())) { + message = new StringBuilder(String.format(MESSAGE_NO_SHOWS_FOUND, formattedMonthOfYear)); + OPTIXLOGGER.log(Level.WARNING, message.toString()); + } + } catch (OptixException e) { + message.append(e.getMessage()); + OPTIXLOGGER.log(Level.WARNING, message.toString()); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "show"; + } + + /** + * Dummy Command. Not used + * + * @param details n.a + * @return n.a. + */ + @Override + public String[] parseDetails(String details) { + return new String[0]; + } + + private boolean hasShow(String message) { + return !message.equals(String.format(MESSAGE_FOUND_SHOW, formattedMonthOfYear)); + } + +} diff --git a/src/main/java/optix/commands/shows/ListShowCommand.java b/src/main/java/optix/commands/shows/ListShowCommand.java new file mode 100644 index 0000000000..b2a86772b8 --- /dev/null +++ b/src/main/java/optix/commands/shows/ListShowCommand.java @@ -0,0 +1,45 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; + +import java.util.logging.Level; + +//@@author CheeSengg +public class ListShowCommand extends Command { + private String showName; + + private static final String MESSAGE_FOUND_SHOW = "The show %1$s is showing on the following following dates: \n"; + + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + public ListShowCommand(String showName) { + this.showName = showName.trim(); + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + StringBuilder message = new StringBuilder(String.format(MESSAGE_FOUND_SHOW, showName)); + + message.append(model.listShow(showName)); + if (!hasShow(message.toString())) { + message = new StringBuilder(MESSAGE_SHOW_NOT_FOUND); + OPTIXLOGGER.log(Level.WARNING, MESSAGE_SHOW_NOT_FOUND); + } + ui.setMessage(message.toString()); + return "show"; + } + + @Override + public String[] parseDetails(String details) { + return new String[0]; + } + + private boolean hasShow(String message) { + return !message.equals(String.format(MESSAGE_FOUND_SHOW, showName)); + } + +} diff --git a/src/main/java/optix/commands/shows/RescheduleCommand.java b/src/main/java/optix/commands/shows/RescheduleCommand.java new file mode 100644 index 0000000000..c9ba20f05a --- /dev/null +++ b/src/main/java/optix/commands/shows/RescheduleCommand.java @@ -0,0 +1,97 @@ +package optix.commands.shows; + +import optix.commands.Command; +import optix.commons.Model; +import optix.commons.Storage; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import optix.exceptions.OptixInvalidDateException; +import optix.ui.Ui; +import optix.util.OptixDateFormatter; + +import java.time.LocalDate; +import java.util.logging.Level; + +//@@author CheeSengg +public class RescheduleCommand extends Command { + private String details; + + private OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_DOES_NOT_MATCH = "☹ OOPS!!! Did you get the wrong date or wrong show. \n" + + "Try again!\n"; + + private static final String MESSAGE_SHOW_NOT_FOUND = "☹ OOPS!!! The show cannot be found.\n"; + + private static final String MESSAGE_SHOW_CLASH = "☹ OOPS!!! There already exists a show for %1$s.\n"; + + private static final String MESSAGE_INVALID_NEW_DATE = "☹ OOPS!!! It is not possible to reschedule to the past.\n"; + + private static final String MESSAGE_SUCCESSFUL = "%1$s has been rescheduled from %2$s to %3$s.\n"; + + /** + * Command to reschedule show. + * + * @param splitStr String containing "SHOW_NAME|OLD_DATE|NEW_DATE" + */ + public RescheduleCommand(String splitStr) { + this.details = splitStr; + initLogger(); + } + + @Override + public String execute(Model model, Ui ui, Storage storage) { + String message = ""; + LocalDate today = storage.getToday(); + try { + String[] details = parseDetails(this.details); + String showName = details[0].trim(); + String oldDate = details[1].trim(); + String newDate = details[2].trim(); + + if (!formatter.isValidDate(oldDate) || !formatter.isValidDate(newDate)) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date"); + throw new OptixInvalidDateException(); + } + + LocalDate localOldDate = formatter.toLocalDate(oldDate); + LocalDate localNewDate = formatter.toLocalDate(newDate); + + if (localNewDate.compareTo(today) <= 0) { + OPTIXLOGGER.log(Level.WARNING, "Invalid date"); + message = MESSAGE_INVALID_NEW_DATE; + } else { + if (!model.containsKey(localOldDate)) { + OPTIXLOGGER.log(Level.WARNING, MESSAGE_SHOW_NOT_FOUND); + message = MESSAGE_SHOW_NOT_FOUND; + } else if (model.containsKey(localNewDate)) { + OPTIXLOGGER.log(Level.WARNING, MESSAGE_SHOW_CLASH); + message = String.format(MESSAGE_SHOW_CLASH, newDate); + } else if (!model.hasSameName(localOldDate, showName)) { + OPTIXLOGGER.log(Level.WARNING, MESSAGE_DOES_NOT_MATCH); + message = MESSAGE_DOES_NOT_MATCH; + } else { + model.rescheduleShow(localOldDate, localNewDate); + storage.write(model.getShows()); + message = String.format(MESSAGE_SUCCESSFUL, showName, oldDate, newDate); + } + } + } catch (OptixException e) { + message = e.getMessage(); + ui.setMessage(message.toString()); + return ""; + } + ui.setMessage(message.toString()); + return "show"; + } + + @Override + public String[] parseDetails(String details) throws OptixInvalidCommandException { + String[] detailsArray = details.trim().split("\\|", 3); + if ((detailsArray.length) != 3) { + throw new OptixInvalidCommandException(); + } + return detailsArray; + } + +} diff --git a/src/main/java/optix/commons/Model.java b/src/main/java/optix/commons/Model.java new file mode 100644 index 0000000000..a727557a84 --- /dev/null +++ b/src/main/java/optix/commons/Model.java @@ -0,0 +1,261 @@ +package optix.commons; + +import optix.commons.model.ShowMap; +import optix.commons.model.Theatre; +import optix.util.OptixDateFormatter; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.Map; + +import java.util.logging.LogManager; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author CheeSengg +public class Model { + private ShowMap showsHistory = new ShowMap(); + private ShowMap shows = new ShowMap(); + private ShowMap showsGui; + private OptixDateFormatter formatter = new OptixDateFormatter(); + private static final Logger OPTIXLOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + + /** + * The Optix model. + * + * @param storage the object which handles data from the save file. + */ + public Model(Storage storage) { + storage.loadShows(shows, showsHistory); + storage.loadArchive(showsHistory); + storage.writeArchive(showsHistory); + showsGui = this.getShows(); + initLogger(); + } + + public ShowMap getShows() { + return shows; + } + + public ShowMap getShowsHistory() { + return showsHistory; + } + + public ShowMap getShowsGui() { + return showsGui; + } + + public void setShowsGui(ShowMap showsGui) { + this.showsGui = showsGui; + } + + public boolean hasSameName(LocalDate key, String showName) { + return shows.get(key).hasSameName(showName); + } + + public boolean containsKey(LocalDate key) { + return shows.containsKey(key); + } + + //// Commands that deals with Shows + + /** + * Method to add show to "shows" ShowMap. + * @param showName name of show to add + * @param showDate date of show to add + * @param seatBasePrice base price for seats of show to add + */ + public void addShow(String showName, LocalDate showDate, double seatBasePrice) { + OPTIXLOGGER.log(Level.INFO, "adding show: " + showName + " on " + showDate.toString() + + "at the base price of " + seatBasePrice); + shows.addShow(showName, showDate, seatBasePrice); + this.setShowsGui(shows); + } + + /** + * Method to edit the name of an existing show. + * @param showDate date of show to change + * @param showName New name of show + */ + public void editShowName(LocalDate showDate, String showName) { + OPTIXLOGGER.log(Level.INFO, "editing show: " + showName + " scheduled on " + showDate.toString()); + shows.editShowName(showDate, showName); + this.setShowsGui(shows); + } + + /** + * Change the date of an existing show. + * @param oldDate Current date of show + * @param newDate New date of show + */ + public void rescheduleShow(LocalDate oldDate, LocalDate newDate) { + OPTIXLOGGER.log(Level.INFO, "rescheduling show from " + oldDate.toString() + " to " + newDate.toString()); + shows.rescheduleShow(oldDate, newDate); + this.setShowsGui(shows); + } + + /** Method to List all shows. + * + * @return String containing details of all current shows in "shows" ShowMap + */ + public String listShow() { + OPTIXLOGGER.log(Level.INFO, "listing shows"); + this.setShowsGui(shows); + return shows.listShow(); + } + + /** + * Get the list of show dates for the show in query. + * + * @param showName The name of the show. + * @return String message for the list of dates for the show in query. + */ + public String listShow(String showName) { + OPTIXLOGGER.log(Level.INFO, "Listing show by name:" + showName); + this.setShowsGui(shows.listShow(showName)); + StringBuilder message = new StringBuilder(); + int counter = 1; + for (Map.Entry entry : showsGui.entrySet()) { + String date = new OptixDateFormatter().toStringDate(entry.getKey()); + message.append(String.format("%d. %s\n", counter, date)); + counter++; + } + return message.toString(); + } + + /** + * Get the list of show for the month in query. + * + * @param startOfMonth The first day of month in query. + * @param endOfMonth The first day of the following month for the month in query. + * @return String message for the list of shows that are scheduled for the month in query. + */ + public String listShow(LocalDate startOfMonth, LocalDate endOfMonth) { + OPTIXLOGGER.log(Level.INFO, "listing show by month:" + startOfMonth.toString()); + this.setShowsGui(shows.listShow(startOfMonth, endOfMonth)); + StringBuilder message = new StringBuilder(); + int counter = 1; + for (Map.Entry entry : showsGui.entrySet()) { + String date = new OptixDateFormatter().toStringDate(entry.getKey()); + String showName = entry.getValue().getShowName(); + message.append(String.format("%d. %s (on: %s)\n", counter, showName, date)); + counter++; + } + return message.toString(); + } + + public String listShowHistory() { + this.setShowsGui(showsHistory); + return showsHistory.listShow(); + } + + public String listFinance() { + this.setShowsGui(shows); + return shows.listFinance(); + } + + /** + * Calculates the earnings for a certain month from the Optix file. + * + * @param mth The month in numerical form. + * @param yr The year. + * @param showsQuery shows, showsHistory or both. Contains all the shows from these ShowMaps + * @return A message String that contains the profit to show to the user. + */ + public String findMonthly(int mth, int yr, ShowMap... showsQuery) { + OPTIXLOGGER.log(Level.INFO, String.format("calculating earnings for month %s of year %s", mth, yr)); + StringBuilder message = new StringBuilder(); + double profit = 0; + double projectedProfit = 0.0; + + int searchMonth; + int searchYear; + ShowMap showsWanted = new ShowMap(); + String monthYear = formatter.intToMonth(mth) + " " + yr; + for (int i = 0; i < showsQuery.length; i++) { //maximum 2 + for (Map.Entry entry : showsQuery[i].entrySet()) { + searchMonth = entry.getKey().getMonthValue(); + searchYear = entry.getKey().getYear(); + if (searchMonth == mth && searchYear == yr) { //show matches query date + showsWanted.put(entry.getKey(), entry.getValue()); + if (i == 1) { // if the query is the current month + projectedProfit += entry.getValue().getProfit(); + } else { + profit += entry.getValue().getProfit(); + } + } + } + } + + if (profit == 0) { + if (showsWanted.isEmpty()) { + message.append(String.format("☹ OOPS!!! There are no shows in %1$s.\n", monthYear)); + } else { + message.append(String.format("None of the seats for the shows in %1$s has been sold yet!\n", + monthYear)); + } + } else { + if (showsQuery.length == 2) { // query is for current month + message.append(String.format("The current earnings for %1$s is $%2$.2f.\n", monthYear, profit)); + if (projectedProfit > 0) { + message.append(String.format("The projected earnings for the rest of the month is $%1$.2f.\n", + projectedProfit)); + } + } else { //either from ShowMap or ShowHistoryMap + if (showsQuery[0].equals(shows)) { + message.append(String.format("The projected earnings for %1$s is $%2$.2f.\n", monthYear, + profit)); + } else { + message.append(String.format("The earnings for %1$s is $%2$.2f.\n", monthYear, profit)); + } + } + } + return message.toString(); + } + + /** + * Method to delete a show from "shows" ShowMap. + * @param showDate date of show to delete + */ + public void deleteShow(LocalDate showDate) { + OPTIXLOGGER.log(Level.INFO, "Deleting show"); + shows.deleteShow(showDate); + this.setShowsGui(shows); + } + + //// Commands that deals with Seats. + + public String viewSeats(LocalDate localDate) { + return shows.viewSeats(localDate); + } + + public String sellSeats(LocalDate localDate, String... seats) { + return shows.sellSeats(localDate, seats); + } + + public String reassignSeat(LocalDate showlocalDate, String oldSeat, String newSeat) { + return shows.reassignSeat(showlocalDate, oldSeat, newSeat); + } + + public String removeSeats(LocalDate localDate, String... seats) { + return shows.removeSeats(localDate, seats); + } + + public String refundSeats(LocalDate localDate, String... seats) { + return shows.refundSeats(localDate, seats); + } + + private void initLogger() { + LogManager.getLogManager().reset(); + OPTIXLOGGER.setLevel(Level.ALL); + try { + FileHandler fh = new FileHandler("OptixLogger.log",true); + fh.setLevel(Level.FINE); + OPTIXLOGGER.addHandler(fh); + } catch (IOException e) { + OPTIXLOGGER.log(Level.SEVERE, "File logger not working", e); + } + OPTIXLOGGER.log(Level.FINEST, "Logging in " + this.getClass().getName()); + } +} diff --git a/src/main/java/optix/commons/Storage.java b/src/main/java/optix/commons/Storage.java new file mode 100644 index 0000000000..0d43afd924 --- /dev/null +++ b/src/main/java/optix/commons/Storage.java @@ -0,0 +1,208 @@ +package optix.commons; + +import optix.commons.model.Seat; +import optix.commons.model.ShowMap; +import optix.commons.model.Theatre; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +public class Storage { + private File archiveFilePath; + private File showMapFilePath; + private LocalDate today; + + /** + * Initialise a new storage object. + * + * @param filePath path to the save file. + */ + public Storage(File filePath) { + today = LocalDate.now(); + + this.showMapFilePath = new File(filePath + "\\optix.txt"); + this.archiveFilePath = new File(filePath + "\\archive.txt"); + try { + if (!filePath.exists()) { + filePath.mkdirs(); + } + if (!showMapFilePath.exists()) { + showMapFilePath.createNewFile(); + } + if (!archiveFilePath.exists()) { + archiveFilePath.createNewFile(); + } + } catch (IOException e) { + System.out.println("Unable to create file.\n"); + } + } + + /** + * Load the data from the save file into model. + */ + public void loadShows(ShowMap shows, ShowMap showsHistory) { + try { + FileReader rd = new FileReader(showMapFilePath); + BufferedReader br = new BufferedReader(rd); + + String message; + + while ((message = br.readLine()) != null) { + String[] arrStr = message.split(" \\| "); + + if (arrStr[0].toLowerCase().equals("s")) { + LocalDate date = localDate(arrStr[1]); + String showName = arrStr[2].trim(); + double revenue = Double.parseDouble(arrStr[3]); + double seatBasePrice = Double.parseDouble(arrStr[4]); + + if (date.compareTo(today) <= 0) { + showsHistory.addShowHistory(date, showName, revenue); + continue; + } + + Theatre theatre = new Theatre(showName, revenue, seatBasePrice); + loadSeat(br, theatre); + shows.put(date, theatre); + } + } + + br.close(); + rd.close(); + + } catch (IOException e) { + System.out.println("Unable to load file.\n"); + } + } + + /** + * Load the seats from the save file. + * + * @param br buffered reader + * @param theatre the theatre to populate + * @return the populated theatre + * @throws IOException when buffered reader has problems with readLine(). + */ + private Theatre loadSeat(BufferedReader br, Theatre theatre) throws IOException { + String message; + while ((message = br.readLine()) != null && !message.equals("next")) { + String[] arrStr = message.split("\\|"); + int row = Integer.parseInt(arrStr[0].trim()); + int col = Integer.parseInt(arrStr[1].trim()); + + theatre.setSeat(row, col); + } + + return theatre; + } + + /** + * Load information from archive. + * + * @param showsHistory Map of shows. + */ + public void loadArchive(ShowMap showsHistory) { + try { + FileReader rd = new FileReader(archiveFilePath); + BufferedReader br = new BufferedReader(rd); + + String message; + + while ((message = br.readLine()) != null) { + String[] arrStr = message.split(" \\| "); + + LocalDate date = localDate(arrStr[0]); + String showName = arrStr[1].trim(); + double revenue = Double.parseDouble(arrStr[2]); + + showsHistory.addShowHistory(date, showName, revenue); + } + + br.close(); + rd.close(); + + } catch (IOException e) { + System.out.println("Unable to load file.\n"); + } + } + + /** + * write to the save file. + * Deletes the old file and writes a new file. + * + * @param shows ShowMap of shows. + */ + public void write(ShowMap shows) { + try { + showMapFilePath.delete(); + showMapFilePath.createNewFile(); + FileWriter wr = new FileWriter(showMapFilePath, true); + + for (Map.Entry entry : shows.entrySet()) { + Theatre theatre = entry.getValue(); + LocalDate date = entry.getKey(); + + wr.write(String.format("S | %s | %s", date, theatre.writeToFile())); + + writeSeats(wr, theatre); + } + wr.close(); + } catch (IOException e) { + System.out.println("Unable to write to file."); + } + } + + private void writeSeats(FileWriter wr, Theatre theatre) throws IOException { + Seat[][] seats = theatre.getSeats(); + + for (int i = 0; i < seats.length; i++) { + for (int j = 0; j < seats[i].length; j++) { + if (seats[i][j].isSold()) { + wr.write(String.format("%d | %d\n", i, j)); + } + } + } + wr.write("next\n"); + } + + /** + * Place shows that have passed into an archive. + * + * @param showsHistory Map of shows. + */ + public void writeArchive(ShowMap showsHistory) { + try { + archiveFilePath.delete(); + archiveFilePath.createNewFile(); + FileWriter wr = new FileWriter(archiveFilePath, true); + + for (Map.Entry entry : showsHistory.entrySet()) { + Theatre theatre = entry.getValue(); + LocalDate date = entry.getKey(); + + wr.write(String.format("%s | %s | %s\n", date, theatre.getShowName(), theatre.getProfit())); + } + wr.close(); + } catch (IOException e) { + System.out.println("Unable to write to file."); + } + } + + private LocalDate localDate(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return LocalDate.parse(date, formatter); + } + + /** + * Get today's date as LocalDate object. + */ + public LocalDate getToday() { + return today; + } +} \ No newline at end of file diff --git a/src/main/java/optix/commons/model/Seat.java b/src/main/java/optix/commons/model/Seat.java new file mode 100644 index 0000000000..d07e6ac2e3 --- /dev/null +++ b/src/main/java/optix/commons/model/Seat.java @@ -0,0 +1,62 @@ +package optix.commons.model; + +public class Seat { + private double ticketPrice; + private String seatTier; + private boolean isSold; + + /** + * the seat object. + * + * @param seatTier tier of the seat. Higher tier seat is more precious. + */ + public Seat(String seatTier) { + this.isSold = false; + this.seatTier = seatTier; + } + + public void setSold(boolean sold) { + isSold = sold; + } + + public boolean isSold() { + return isSold; + } + + private String getStatusIcon() { + return isSold ? "✓" : "✘"; + } + + public String getSeat() { + return "[" + getStatusIcon() + "]"; + } + + public void setSeatTier(String seatTier) { + this.seatTier = seatTier; + } + + public String getSeatTier() { + return seatTier; + } + + /** + * Get the price of the seat according to its tier. + * The seat tier cannot be out of bounds. + * + * @param basePrice base seat price of a show. + * @return price seat according to its tier. + */ + public double getSeatPrice(double basePrice) { + assert (Integer.parseInt(seatTier) <= 3 && Integer.parseInt(seatTier) > 0); + if (seatTier.equals("1")) { + ticketPrice = basePrice; + } + if (seatTier.equals("2")) { + ticketPrice = basePrice * 1.2; + } + if (seatTier.equals("3")) { + ticketPrice = basePrice * 1.5; + } + return ticketPrice; + } +} diff --git a/src/main/java/optix/commons/model/Show.java b/src/main/java/optix/commons/model/Show.java new file mode 100644 index 0000000000..dd754bd5d3 --- /dev/null +++ b/src/main/java/optix/commons/model/Show.java @@ -0,0 +1,31 @@ +package optix.commons.model; + +public class Show { + private String showName; + private double profit; + + public Show(String showName, double profit) { + this.showName = showName; + this.profit = profit; + } + + public void setShowName(String showName) { + this.showName = showName; + } + + public void setProfit(double profit) { + this.profit = profit; + } + + public String getShowName() { + return showName; + } + + public double getProfit() { + return profit; + } + + public boolean hasSameName(String checkName) { + return showName.toLowerCase().equals(checkName.toLowerCase()); + } +} \ No newline at end of file diff --git a/src/main/java/optix/commons/model/ShowMap.java b/src/main/java/optix/commons/model/ShowMap.java new file mode 100644 index 0000000000..e751b2f8c3 --- /dev/null +++ b/src/main/java/optix/commons/model/ShowMap.java @@ -0,0 +1,187 @@ +package optix.commons.model; + +import optix.util.OptixDateFormatter; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * TreeMap to sort all usage of the Opera Theatre according to calendar. + */ +public class ShowMap extends TreeMap { + + private OptixDateFormatter formatter = new OptixDateFormatter(); + private static final Logger OPTIXLOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + + /** + * Get show name. + * + * @param key the show being queried. + * @return the name of the show. + */ + public String getShowName(Object key) { + return this.get(key).getShowName(); + } + + //// Commands that deals with shows + + /** + * Add shows into Archive. + * + * @param showDate The date of the show. + * @param showName The name of the show. + * @param revenue The money earned from the show. + */ + public void addShowHistory(LocalDate showDate, String showName, double revenue) { + Show show = new Show(showName, revenue); + Theatre theatre = new Theatre(show); + this.put(showDate, theatre); + } + + public void addShow(String showName, LocalDate showDate, double seatBasePrice) { + Theatre theatre = new Theatre(showName, seatBasePrice); + this.put(showDate, theatre); + } + + public void editShowName(LocalDate showDate, String showName) { + this.get(showDate).setShowName(showName); + } + + public void rescheduleShow(LocalDate oldDate, LocalDate newDate) { + Theatre theatre = this.deleteShow(oldDate); + this.put(newDate, theatre); + } + + /** + * Get all the shows that are scheduled and their earnings. + * @return String message of all the shows and their earnings. + */ + public String listFinance() { + StringBuilder message = new StringBuilder(); + + int counter = 1; + + for (Map.Entry entry : this.entrySet()) { + String date = formatter.toStringDate(entry.getKey()); + String showName = entry.getValue().getShowName(); + double earnings = entry.getValue().getProfit(); + message.append(String.format("%d. %s (on: %s): $%.2f\n", counter, showName, date, earnings)); + counter++; + } + return message.toString(); + } + + /** + * Get all the shows that are scheduled and their dates. + * + * @return String message of all the shows that are registered. + */ + public String listShow() { + StringBuilder message = new StringBuilder(); + + int counter = 1; + + for (Map.Entry entry : this.entrySet()) { + String date = formatter.toStringDate(entry.getKey()); + String showName = entry.getValue().getShowName(); + + message.append(String.format("%d. %s (on: %s)\n", counter, showName, date)); + counter++; + } + return message.toString(); + } + + /** + * Get the list of show dates for the show in query. + * + * @param showName The name of the show. + * @return new ShowMap with shows that have the show in query. + */ + public ShowMap listShow(String showName) { + ShowMap shows = new ShowMap(); + for (Map.Entry entry : this.entrySet()) { + if (entry.getValue().hasSameName(showName)) { + shows.put(entry.getKey(), entry.getValue()); + } + } + return shows; + } + + /** + * Get the list of show for the month in query. + * + * @param startOfMonth The first day of month in query. + * @param endOfMonth The first day of the following month for the month in query. + * @return new ShowMap with shows that are within the month of query. + */ + public ShowMap listShow(LocalDate startOfMonth, LocalDate endOfMonth) { + ShowMap shows = new ShowMap(); + + while (startOfMonth.compareTo(endOfMonth) != 0) { + if (this.containsKey(startOfMonth)) { + shows.put(startOfMonth, this.get(startOfMonth)); + } + startOfMonth = startOfMonth.plusDays(1); + } + return shows; + } + + /** + * Remove a show from the show map. + * + * @param key the show to be removed. + * @return the show that is removed. + */ + public Theatre deleteShow(Object key) { + Theatre show = this.get(key); + this.remove(key); + + return show; + } + + //// Command that deals with seats + + public String viewSeats(LocalDate localDate) { + return this.get(localDate).getSeatingArrangement(); + } + + public String sellSeats(LocalDate localDate, String... seats) { + return this.get(localDate).sellSeats(seats); + } + + + public String reassignSeat(LocalDate showLocalDate, String oldSeat, String newSeat) { + return this.get(showLocalDate).reassignSeat(oldSeat, newSeat); + } + + public String removeSeats(LocalDate localDate, String... seats) { + return this.get(localDate).removeSeat(seats); + } + + public String refundSeats(LocalDate localDate, String... seats) { + return this.get(localDate).refundSeat(seats); + } + + public double getProfit(LocalDate localDate) { + return this.get(localDate).getProfit(); + } + + private void initLogger() { + LogManager.getLogManager().reset(); + OPTIXLOGGER.setLevel(Level.ALL); + try { + FileHandler fh = new FileHandler("OptixLogger.log", true); + fh.setLevel(Level.FINE); + OPTIXLOGGER.addHandler(fh); + } catch (IOException e) { + OPTIXLOGGER.log(Level.SEVERE, "File logger not working", e); + } + OPTIXLOGGER.log(Level.FINEST, "Logging in " + this.getClass().getName()); + } +} \ No newline at end of file diff --git a/src/main/java/optix/commons/model/Theatre.java b/src/main/java/optix/commons/model/Theatre.java new file mode 100644 index 0000000000..b9a2e7176e --- /dev/null +++ b/src/main/java/optix/commons/model/Theatre.java @@ -0,0 +1,527 @@ +package optix.commons.model; + +import java.util.ArrayList; + +public class Theatre { + //@SuppressWarnings("checkstyle:membername") + private static final String SPACES = " "; // CHECKSTYLE IGNORE THIS LINE + private static final String STAGE = " |STAGE| \n"; // CHECKSTYLE IGNORE THIS LINE + private static final String MESSAGE_TICKET_COST = "The total cost of the tickets are $%1$.2f\n"; + + private Seat[][] seats = new Seat[6][10]; + private int tierOneSeats; + private int tierTwoSeats; + private int tierThreeSeats; + private double seatBasePrice; + + private Show show; + + /** + * instantiates Theatre Object. Used when loading save file data. + * + * @param showName name of show + * @param revenue expected revenue, calculated from seat purchases - cost + * @param seatBasePrice base price of seats + */ + public Theatre(String showName, double revenue, double seatBasePrice) { + show = new Show(showName, revenue); + this.seatBasePrice = seatBasePrice; + initializeLayout(); + } + + /** + * Instantiates Theatre Object. Used when there is no revenue yet (fresh instance). + * + * @param showName name of show + * @param seatBasePrice base price of seats. + */ + public Theatre(String showName, double seatBasePrice) { + show = new Show(showName, 0); + this.seatBasePrice = seatBasePrice; + initializeLayout(); + } + + public Theatre(Show show) { + this.show = show; + } + + // can have multiple layouts to be added for future extensions. + + private void initializeLayout() { + tierOneSeats = 0; + tierTwoSeats = 0; + tierThreeSeats = 0; + for (int i = 0; i < seats.length; i++) { + for (int j = 0; j < seats[i].length; j++) { + switch (i) { + case 0: + case 1: + seats[i][j] = new Seat("3"); + tierThreeSeats++; + break; + case 2: + case 3: + seats[i][j] = new Seat("2"); + tierTwoSeats++; + break; + case 4: + case 5: + seats[i][j] = new Seat("1"); + tierOneSeats++; + break; + default: + assert i > seats.length; + break; + } + } + } + } + + public void setShowName(String showName) { + show.setShowName(showName); + } + + public String getShowName() { + return show.getShowName(); + } + + public double getProfit() { + return show.getProfit(); + } + + public Seat[][] getSeats() { + return seats; + } + + public String getTierOneSeats() { + return Integer.toString(tierOneSeats); + } + + public String getTierTwoSeats() { + return Integer.toString(tierTwoSeats); + } + + public String getTierThreeSeats() { + return Integer.toString(tierThreeSeats); + } + + public double getSeatBasePrice() { + return seatBasePrice; + } + + /** + * function to set the status of a seat (change it to booked when a seat is bought). + * + * @param row desired seat row + * @param col desired seat column + */ + public void setSeat(int row, int col) { + seats[row][col].setSold(true); + switch (seats[row][col].getSeatTier()) { + case "1": + tierOneSeats--; + break; + case "2": + tierTwoSeats--; + break; + case "3": + tierThreeSeats--; + break; + default: + System.out.println("Should have a Seat Tier!"); + } + } + + /** + * function to reset the status of a seat (change it to available when a seat is removed). + * + * @param row desired seat row + * @param col desired seat column + */ + public void resetSeat(int row, int col) { + seats[row][col].setSold(false); + switch (seats[row][col].getSeatTier()) { + case "1": + tierOneSeats++; + break; + case "2": + tierTwoSeats++; + break; + case "3": + tierThreeSeats++; + break; + default: + System.out.println("Should have a Seat Tier!"); + } + } + + /** + * Get the seating arrangement of the Theatre. + * + * @return seating arrangement as a String. + */ + public String getSeatingArrangement() { + StringBuilder seatingArrangement = new StringBuilder(STAGE); + + for (int i = 0; i < seats.length; i++) { + seatingArrangement.append(SPACES); + for (int j = 0; j < seats[i].length; j++) { + seatingArrangement.append(seats[i][j].getSeat()); + } + seatingArrangement.append("\n"); + } + seatingArrangement.append(getSeatsLeft()); + + return seatingArrangement.toString(); + } + + private String getSeatsLeft() { + return "\nTier 1 Seats (rows E and F): " + tierOneSeats + "\n" + + "Tier 2 Seats (rows C and D): " + tierTwoSeats + "\n" + + "Tier 3 Seats (rows A and B): " + tierThreeSeats + "\n"; + } + + + /** + * Sell seats to customers. + * + * @param seat desired seat + * @return cost of seat. + */ + public double sellSeats(String seat) { + int row = getRow(seat.substring(0, 1)); + int col = getCol(seat.substring(1)); + + double costOfSeat = 0; + + //This needs to be changed in the event that the theatre dont have fixed seats for each row + if (row == -1 || col == -1) { + costOfSeat = -1; + return costOfSeat; + } + + double revenue = show.getProfit(); + + if (!seats[row][col].isSold()) { + Seat soldSeat = seats[row][col]; + soldSeat.setSold(true); + costOfSeat = soldSeat.getSeatPrice(seatBasePrice); + revenue += costOfSeat; + this.setSeat(row, col); + } + show.setProfit(revenue); + return costOfSeat; + } + + /** + * Sell seats to customers. Used when customer wants to buy multiple seats. + * + * @param seats String array of desired seats + * @return Message detailing status of desired seats (sold out or successfully purchased.) + */ + public String sellSeats(String... seats) { + double totalCost = 0; + ArrayList seatsSold = new ArrayList<>(); + ArrayList seatsNotSold = new ArrayList<>(); + ArrayList seatsNotExist = new ArrayList<>(); + StringBuilder message = new StringBuilder(); + for (String seatNumber : seats) { + double costOfSeat = sellSeats(seatNumber); + + if (costOfSeat > 0) { + totalCost += costOfSeat; + seatsSold.add(seatNumber); + } else if (costOfSeat == 0) { + seatsNotSold.add(seatNumber); + } else { + seatsNotExist.add(seatNumber); + } + } + + if (seatsSold.isEmpty()) { //all seats are unavailable or does not exist + if (!seatsNotSold.isEmpty()) { + message.append(String.format("☹ OOPS!!! All of the seats %s are unavailable.\n", seatsNotSold)); + } + if (!seatsNotExist.isEmpty()) { + message.append(String.format("☹ OOPS!!! All of the seats %s do not exist.\n", seatsNotExist)); + } + } else if (seatsNotSold.isEmpty() && seatsNotExist.isEmpty()) { //all seats are valid + message.append("You have successfully purchased the following seats: \n" + + seatsSold + "\n" + + String.format(MESSAGE_TICKET_COST, totalCost)); + } else { //combination of all + message.append("You have successfully purchased the following seats: \n" + + seatsSold + "\n" + + String.format(MESSAGE_TICKET_COST, totalCost)); + if (!seatsNotSold.isEmpty()) { + message.append("The following seats are unavailable: \n" + + seatsNotSold + "\n"); + } + if (!seatsNotExist.isEmpty()) { + message.append("The following seats do not exist: \n" + + seatsNotExist + "\n"); + } + } + return message.toString(); + } + + /** + * Reassigns the seat of a customer. + * + * @param oldSeat The seat to be changed. + * @param newSeat The seat to change to. + * @return Message detailing the success of reassignment and the cost difference between the seats. + */ + public String reassignSeat(String oldSeat, String newSeat) { + StringBuilder message = new StringBuilder(); + + int oldSeatRow = getRow(oldSeat.substring(0, 1)); + int oldSeatCol = getCol(oldSeat.substring(1)); + int newSeatRow = getRow(newSeat.substring(0, 1)); + int newSeatCol = getCol(newSeat.substring(1)); + + //if seat number is invalid. + if (oldSeatRow == -1 || oldSeatCol == -1 || newSeatRow == -1 || newSeatCol == -1) { + message.append("☹ OOPS!!! Please enter valid seat numbers.\n"); + return message.toString(); + } + + if (oldSeat.equals(newSeat)) { + message.append(String.format("Your current seat is already %1$s.\n", oldSeat)); + return message.toString(); + } + + if (!seats[oldSeatRow][oldSeatCol].isSold()) { //if the seat has not been booked yet. + message.append(String.format("The seat %1$s is still available for booking.\n", oldSeat)); + return message.toString(); + } + + if (seats[newSeatRow][newSeatCol].isSold()) { // if the new seat has already been booked. + message.append(String.format("☹ OOPS!!! Seat %1$s is unavailable. Use the View Command to" + + " view the available seats.\n", newSeat)); + return message.toString(); + } + + double costOfNewSeat = sellSeats(newSeat); + double costOfOldSeat = removeSeat(oldSeat); + + message.append(String.format("Your seat has been successfully changed from %1$s to %2$s.\n", oldSeat, + newSeat)); + + if (costOfNewSeat > costOfOldSeat) { + double extraCost = costOfNewSeat - costOfOldSeat; + message.append(String.format("An extra cost of $%1$.2f is required.\n", extraCost)); + } else if (costOfOldSeat > costOfNewSeat) { + double returnCost = costOfOldSeat - costOfNewSeat; + message.append(String.format("$%1$.2f will be returned.\n", returnCost)); + } + return message.toString(); + } + + /** + * Removes a seat booking from the theatre. + * + * @param seatToRemove The seat to be removed. + * @return The cost of the seat that has been removed. + */ + public double removeSeat(String seatToRemove) { + int row = getRow(seatToRemove.substring(0, 1)); + int col = getCol(seatToRemove.substring(1)); + double seatPrice = 0; + + if (row == -1 || col == -1) { //if seat number is invalid + seatPrice = -1; + } else if (!seats[row][col].isSold()) { //if the seat is not sold yet + seatPrice = 0; + } else { + double currRevenue = show.getProfit(); + seatPrice = seats[row][col].getSeatPrice(seatBasePrice); + show.setProfit(currRevenue - seatPrice); + this.resetSeat(row, col); + } + return seatPrice; + } + + /** + * Remove seats for customers. Used when customer wants to remove multiple seats. + * + * @param seatsToRemove String array of seats to be removed + * @return Message detailing status of the removal + */ + public String removeSeat(String... seatsToRemove) { + double totalRefund = 0; + ArrayList seatsRemoved = new ArrayList<>(); + ArrayList seatsEmpty = new ArrayList<>(); + ArrayList seatsNotExist = new ArrayList<>(); + StringBuilder message = new StringBuilder(); + for (String seatNumber : seatsToRemove) { + double costOfSeat = removeSeat(seatNumber); + + if (costOfSeat > 0) { + totalRefund += costOfSeat; + seatsRemoved.add(seatNumber); + } else if (costOfSeat == 0) { + seatsEmpty.add(seatNumber); + } else { + seatsNotExist.add(seatNumber); + } + } + + if (seatsRemoved.isEmpty()) { //all seats are not yet sold or does not exist + if (!seatsEmpty.isEmpty()) { + message.append(String.format("☹ OOPS!!! All of the seats %s are not yet sold.\n", seatsEmpty)); + } + if (!seatsNotExist.isEmpty()) { + message.append(String.format("☹ OOPS!!! All of the seats %s do not exist.\n", seatsNotExist)); + } + } else if (seatsEmpty.isEmpty() && seatsNotExist.isEmpty()) { //all seats are valid + message.append("You have successfully removed the following seats: \n" + + seatsRemoved + "\n" + + String.format(MESSAGE_TICKET_COST, totalRefund)); + } else { //combination of all + message.append("You have successfully removed the following seats: \n" + + seatsRemoved + "\n" + + String.format(MESSAGE_TICKET_COST, totalRefund)); + if (!seatsEmpty.isEmpty()) { + message.append("The following seats are not yet sold: \n" + + seatsEmpty + "\n"); + } + if (!seatsNotExist.isEmpty()) { + message.append("The following seats do not exist: \n" + + seatsNotExist + "\n"); + } + } + return message.toString(); + } + + /** + * Refunds a seat booking from the theatre. + * + * @param seatToRefund The seat to be refunded + * @return The amount of money refunded for the seat + */ + public double refundSeat(String seatToRefund) { + int row = getRow(seatToRefund.substring(0, 1)); + int col = getCol(seatToRefund.substring(1)); + double seatPrice = 0; + + if (row == -1 || col == -1) { //if seat number is invalid + seatPrice = -1; + } else if (!seats[row][col].isSold()) { //if the seat is not sold yet + seatPrice = 0; + } else { + double currRevenue = show.getProfit(); + seatPrice = seats[row][col].getSeatPrice(seatBasePrice) * 0.5; + show.setProfit(currRevenue - seatPrice); + this.resetSeat(row, col); + } + return seatPrice; + } + + /** + * Refund seats for customers. Used when customer wants to refund multiple seats. + * + * @param seatsToRefund String array of seats to be refunded + * @return Message detailing status of the refund + */ + public String refundSeat(String... seatsToRefund) { + double totalRefund = 0; + ArrayList seatsRefunded = new ArrayList<>(); + ArrayList seatsEmpty = new ArrayList<>(); + ArrayList seatsNotExist = new ArrayList<>(); + StringBuilder message = new StringBuilder(); + for (String seatNumber : seatsToRefund) { + double costOfSeat = refundSeat(seatNumber); + + if (costOfSeat > 0) { + totalRefund += costOfSeat; + seatsRefunded.add(seatNumber); + } else if (costOfSeat == 0) { + seatsEmpty.add(seatNumber); + } else { + seatsNotExist.add(seatNumber); + } + } + + if (seatsRefunded.isEmpty()) { //all seats are not yet sold or does not exist + if (!seatsEmpty.isEmpty()) { + message.append(String.format("☹ OOPS!!! All of the seats %s are not yet sold.\n", seatsEmpty)); + } + if (!seatsNotExist.isEmpty()) { + message.append(String.format("☹ OOPS!!! All of the seats %s do not exist.\n", seatsNotExist)); + } + } else if (seatsEmpty.isEmpty() && seatsNotExist.isEmpty()) { //all seats are valid + message.append("You have successfully refunded the following seats: \n" + + seatsRefunded + "\n" + + String.format(MESSAGE_TICKET_COST, totalRefund)); + } else { //combination of all + message.append("You have successfully refunded the following seats: \n" + + seatsRefunded + "\n" + + String.format(MESSAGE_TICKET_COST, totalRefund)); + if (!seatsEmpty.isEmpty()) { + message.append("The following seats are not yet sold: \n" + + seatsEmpty + "\n"); + } + if (!seatsNotExist.isEmpty()) { + message.append("The following seats do not exist: \n" + + seatsNotExist + "\n"); + } + } + return message.toString(); + } + + private int getRow(String row) { + switch (row.toUpperCase()) { + case "A": + return 0; + case "B": + return 1; + case "C": + return 2; + case "D": + return 3; + case "E": + return 4; + case "F": + return 5; + default: + return -1; + } + } + + private int getCol(String col) { + switch (col) { + case "1": + return 0; + case "2": + return 1; + case "3": + return 2; + case "4": + return 3; + case "5": + return 4; + case "6": + return 5; + case "7": + return 6; + case "8": + return 7; + case "9": + return 8; + case "10": + return 9; + default: + return -1; + } + } + + + public boolean hasSameName(String checkName) { + return show.hasSameName(checkName); + } + + public String writeToFile() { + return String.format("%s | %f | %f\n", show.getShowName(), show.getProfit(), seatBasePrice); + } +} \ No newline at end of file diff --git a/src/main/java/optix/exceptions/OptixException.java b/src/main/java/optix/exceptions/OptixException.java new file mode 100644 index 0000000000..b233387157 --- /dev/null +++ b/src/main/java/optix/exceptions/OptixException.java @@ -0,0 +1,7 @@ +package optix.exceptions; + +public class OptixException extends Exception { + public OptixException(String message) { + super(message); + } +} diff --git a/src/main/java/optix/exceptions/OptixInvalidCommandException.java b/src/main/java/optix/exceptions/OptixInvalidCommandException.java new file mode 100644 index 0000000000..186ae9e34d --- /dev/null +++ b/src/main/java/optix/exceptions/OptixInvalidCommandException.java @@ -0,0 +1,11 @@ +package optix.exceptions; + +/** + * Thrown when user does not provide the correct input. + */ +public class OptixInvalidCommandException extends OptixException { + public OptixInvalidCommandException() { + super("☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"); + } +} diff --git a/src/main/java/optix/exceptions/OptixInvalidDateException.java b/src/main/java/optix/exceptions/OptixInvalidDateException.java new file mode 100644 index 0000000000..b05c266270 --- /dev/null +++ b/src/main/java/optix/exceptions/OptixInvalidDateException.java @@ -0,0 +1,12 @@ +package optix.exceptions; + +/** + * Thrown when the format of the date given is wrong + * or when the date does not exist. + */ +public class OptixInvalidDateException extends OptixException { + public OptixInvalidDateException() { + super("☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"); + } +} \ No newline at end of file diff --git a/src/main/java/optix/ui/Ui.java b/src/main/java/optix/ui/Ui.java new file mode 100644 index 0000000000..62e04ebc66 --- /dev/null +++ b/src/main/java/optix/ui/Ui.java @@ -0,0 +1,37 @@ +package optix.ui; + +/** + * Reads in the input user and + * returns the response by Optix. + */ +public class Ui { + + /** + * Stores the appropriate response based on users input command. + * Once user command is processed, Optix's response is stored using + * setMessage method to store the appropriate message. Use + * by showCommandLine method to print out the response. + */ + private String message; + + private static final String MESSAGE_GREET = "Hello! I'm Optix\n" + + "What can I do for you?\n"; + + + public Ui() { + this.message = MESSAGE_GREET; + } + + /** + * Set appropriate response by Optix to be shown based on user's input. + * + * @param message String Optix will respond with based on user input. + */ + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/optix/ui/windows/DialogBox.java b/src/main/java/optix/ui/windows/DialogBox.java new file mode 100644 index 0000000000..8edee2068d --- /dev/null +++ b/src/main/java/optix/ui/windows/DialogBox.java @@ -0,0 +1,60 @@ +package optix.ui.windows; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +import java.io.IOException; +import java.util.Collections; + +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + dialog.setWrapText(true); + dialog.setText(text); + displayPicture.setImage(img); + } + + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_RIGHT); + } + + /** + * Creates a new DialogBox object that contains user input and user image. + * + * @param text User input command. + * @param img User image for the chat box. + * @return New DialogBox object that contains user input and user image for chat box. + */ + public static DialogBox getUserDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } + + public static DialogBox getOptixDialog(String text, Image img) { + return new DialogBox(text, img); + } +} diff --git a/src/main/java/optix/ui/windows/FinanceController.java b/src/main/java/optix/ui/windows/FinanceController.java new file mode 100644 index 0000000000..de45dabb32 --- /dev/null +++ b/src/main/java/optix/ui/windows/FinanceController.java @@ -0,0 +1,54 @@ +package optix.ui.windows; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import optix.commons.model.Theatre; +import optix.util.OptixDateFormatter; + +import java.io.IOException; +import java.time.LocalDate; + +public class FinanceController extends AnchorPane { + private Theatre theatre; + private LocalDate date; + + private final OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_PROFIT = "$%1$.2f"; + + @FXML + private Label displayDate; + @FXML + private Label displayShowName; + @FXML + private Label displayRevenue; + + + private FinanceController(Theatre theatre, LocalDate date) { + this.theatre = theatre; + this.date = date; + + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/FinanceEntry.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + displayShowName.setText(theatre.getShowName()); + displayDate.setText(formatter.toStringDate(date)); + displayRevenue.setText(showRevenue()); + } + + private String showRevenue() { + return String.format(MESSAGE_PROFIT, theatre.getProfit()); + } + + public static FinanceController displayFinance(Theatre theatre, LocalDate date) { + return new FinanceController(theatre, date); + } +} diff --git a/src/main/java/optix/ui/windows/HelpWindow.java b/src/main/java/optix/ui/windows/HelpWindow.java new file mode 100644 index 0000000000..d4b9ad7e3e --- /dev/null +++ b/src/main/java/optix/ui/windows/HelpWindow.java @@ -0,0 +1,85 @@ +package optix.ui.windows; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; + +import java.io.IOException; + +/** + * Prompts user on the correct Command Input format. + */ +public class HelpWindow extends VBox { + private static final String ADD_MENU = "To add a new show: " + + "\nadd SHOW_NAME | SEATS_BASE_PRICE | SHOW_DATE1 | SHOW_DATE2 | ...\n\n"; + + private static String DELETE_MENU = "To delete shows: " + + "\ndelete SHOW_NAME | SHOW_DATE1 | SHOW_DATE2 | ...\n\n"; + + private static String VIEW_MENU = "To view seats for show: " + + "\nview SHOW_NAME | SHOW_DATE\n\n"; + + private static String SELL_MENU = "To sell seats for show: " + + "\nsell SHOW_NAME | SHOW_DATE | SEAT1 SEAT2 SEAT3 ...\n\n"; + + private static String REMOVE_MENU = "To remove seats from show: " + + "\nremove-seat SHOW_NAME | SHOW_DATE | SEAT1 SEAT2 SEAT3 ...\n\n"; + + private static String REFUND_MENU = "To refund seats from show: " + + "\nrefund-seat SHOW_NAME | SHOW_DATE | SEAT1 SEAT2 SEAT3 ...\n\n"; + + private static String LIST_MENU = "To list shows: \nlist\n\n" + + "To list specific show: \nlist SHOW_NAME\n\n" + + "To list shows with date: \nlist MONTH YEAR\n\n"; + + private static String RESCHEDULE_MENU = "To reschedule show: " + + "\nreschedule SHOW_NAME | OLD_DATE | NEW_DATE\n\n"; + + private static String EDIT_MENU = "To edit show name: " + + "\nedit OLD_SHOW_NAME | SHOW_DATE | NEW_SHOW_NAME\n\n"; + + private static String ALIAS_MENU = "To add alias: " + + "\nadd-alias ALIAS | COMMAND\n\n" + + "To remove alias: " + + "\nremove-alias ALIAS | COMMAND\n\n" + + "To reset alias: " + + "\nreset-alias\n\n" + + "To list alias: " + + "\nlist-alias\n\n"; + + private static String PROFIT_MENU = "To view profits for a show: " + + "\nview-profit SHOW_NAME | SHOW_DATE\n\n" + + "To view monthly profits: " + + "\nview-monthly MONTH YEAR\n\n"; + + + @FXML + private Label showHelpLbl; + @FXML + private Label financeHelpLbl; + @FXML + private Label seatHelpLbl; + @FXML + private Label aliasHelpLbl; + + private HelpWindow() { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/HelpWindow.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + showHelpLbl.setText(ADD_MENU + DELETE_MENU + LIST_MENU + RESCHEDULE_MENU + EDIT_MENU); + seatHelpLbl.setText(SELL_MENU + VIEW_MENU + REMOVE_MENU + REFUND_MENU); + financeHelpLbl.setText(PROFIT_MENU); + aliasHelpLbl.setText(ALIAS_MENU); + } + + public static VBox getHelpWindow() { + return new HelpWindow(); + } +} diff --git a/src/main/java/optix/ui/windows/MainWindow.java b/src/main/java/optix/ui/windows/MainWindow.java new file mode 100644 index 0000000000..32339e72c9 --- /dev/null +++ b/src/main/java/optix/ui/windows/MainWindow.java @@ -0,0 +1,188 @@ +package optix.ui.windows; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXTextField; +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.util.Duration; +import optix.Optix; +import optix.commons.model.ShowMap; +import optix.commons.model.Theatre; +import optix.util.OptixDateFormatter; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.Map; + +public class MainWindow extends AnchorPane { + @FXML + private JFXButton showButton; + @FXML + private JFXButton financeButton; + @FXML + private JFXButton archiveButton; + @FXML + private JFXButton helpButton; + + //// All the elements on the left side of the window. + @FXML + private VBox display; + @FXML + private Label tabName; + + //// All the elements on the right side of the window. + @FXML + private ScrollPane scrollPane; + @FXML + private VBox chatBox; + @FXML + private JFXTextField userInput; + @FXML + private ImageView icon; + + private Image optixImage = new Image(this.getClass().getResourceAsStream("/img/optixImage.png")); + private Image userImage = new Image(this.getClass().getResourceAsStream("/img/userImage.png")); + private Image optixIcon = new Image(this.getClass().getResourceAsStream("/img/optixIcon.png")); + + private Optix optix; + + /** + * Initialize the main display for Optix. + * + * @param optix The software Optix. + */ + public MainWindow(Optix optix) { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/view/MainWindow.fxml")); + fxmlLoader.setRoot(this); + fxmlLoader.setController(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + this.optix = optix; + String greetings = this.optix.getResponse(); + chatBox.getChildren().add(DialogBox.getOptixDialog(greetings, optixImage)); + + icon.setImage(optixIcon); + displayShows(); + } + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(chatBox.heightProperty()); + } + + @FXML + private void handleResponse() { + String fullCommand = userInput.getText(); + String taskType = optix.runGui(fullCommand); + String response = optix.getResponse(); + + chatBox.getChildren().addAll( + DialogBox.getUserDialog(fullCommand, userImage), + DialogBox.getOptixDialog(response, optixImage) + ); + + switch (taskType) { + case "bye": + shutDown(); + break; + case "show": + tabName.setText("Show"); + displayShows(); + break; + case "seat": + tabName.setText("Seat"); + displaySeats(fullCommand); + break; + case "archive": + tabName.setText("Archive"); + displayFinance(); + break; + case "finance": + tabName.setText("Finance"); + displayFinance(); + break; + case "help": + displayHelp(); + break; + default: + break; + } + userInput.clear(); + } + + private void shutDown() { + PauseTransition delay = new PauseTransition(new Duration(500)); + delay.setOnFinished(event -> Platform.exit()); + delay.play(); + } + + private void displayShows() { + clearDisplay(); + for (Map.Entry entry : optix.getShowsGui().entrySet()) { + display.getChildren().add(ShowController.displayShow(entry.getValue(), entry.getKey())); + } + } + + private void displayFinance() { + clearDisplay(); + for (Map.Entry entry : optix.getShowsGui().entrySet()) { + display.getChildren().add(FinanceController.displayFinance(entry.getValue(), entry.getKey())); + } + } + + private void displaySeats(String fullCommand) { + String[] splitStr = fullCommand.split("\\|"); + if (!optix.getShowsGui().isEmpty()) { + clearDisplay(); + + LocalDate localDate = new OptixDateFormatter().toLocalDate(splitStr[1].trim()); + ShowMap shows = optix.getShows(); + Theatre theatre = shows.get(localDate); + + display.getChildren().add(SeatsDisplayController.displaySeats(theatre, localDate)); + } + } + + @FXML + private void displayHelp() { + clearDisplay(); + tabName.setText("Help"); + display.getChildren().add(HelpWindow.getHelpWindow()); + } + + @FXML + private void clickShow() { + optix.resetShows(); + tabName.setText("Show"); + displayShows(); + } + + @FXML + private void clickArchive() { + optix.resetArchive(); + tabName.setText("Archive"); + displayFinance(); + } + + @FXML + private void clickFinance() { + optix.resetShows(); + tabName.setText("Finance"); + displayFinance(); + } + + private void clearDisplay() { + display.getChildren().removeAll(display.getChildren()); + } +} diff --git a/src/main/java/optix/ui/windows/SeatController.java b/src/main/java/optix/ui/windows/SeatController.java new file mode 100644 index 0000000000..64a0ac397f --- /dev/null +++ b/src/main/java/optix/ui/windows/SeatController.java @@ -0,0 +1,80 @@ +package optix.ui.windows; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import optix.commons.model.Seat; + +import java.io.IOException; + +public class SeatController extends StackPane { + private Seat seat; + private int row; + private int col; + private String seatNumber; + + @FXML + private Label labelNumber; + @FXML + private Rectangle rectangle; + @FXML + private Circle circle; + + private SeatController(int row, int col, Seat seat) { + FXMLLoader fxmlLoader = new FXMLLoader(SeatsDisplayController.class.getResource("/view/Seat.fxml")); + fxmlLoader.setRoot(this); + fxmlLoader.setController(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + this.seat = seat; + seatNumber = getRow(row) + getCol(col); + labelNumber.setText(seatNumber); + + if (seat.isSold()) { + setBooked(); + } + } + + public static SeatController getSeat(int row, int col, Seat seat) { + return new SeatController(row, col, seat); + } + + private String getRow(int row) { + switch (row) { + case 0: + return "A"; + case 1: + return "B"; + case 2: + return "C"; + case 3: + return "D"; + case 4: + return "E"; + case 5: + return "F"; + default: + return "error"; + } + } + + private String getCol(int col) { + return Integer.toString(col + 1); + } + + private void setBooked() { + changeColor(Color.web("#CB4335")); + } + + private void changeColor(Color color) { + rectangle.setFill(color); + circle.setFill(color); + } +} diff --git a/src/main/java/optix/ui/windows/SeatsDisplayController.java b/src/main/java/optix/ui/windows/SeatsDisplayController.java new file mode 100644 index 0000000000..8dac6b4a76 --- /dev/null +++ b/src/main/java/optix/ui/windows/SeatsDisplayController.java @@ -0,0 +1,88 @@ +package optix.ui.windows; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import optix.commons.model.Seat; +import optix.commons.model.Theatre; +import optix.util.OptixDateFormatter; + +import java.io.IOException; +import java.time.LocalDate; + +public class SeatsDisplayController extends VBox { + private Theatre theatre; + private LocalDate localDate; + + private final OptixDateFormatter formatter = new OptixDateFormatter(); + + @FXML + private HBox rowA; + @FXML + private HBox rowB; + @FXML + private HBox rowC; + @FXML + private HBox rowD; + @FXML + private HBox rowE; + @FXML + private HBox rowF; + @FXML + private Label displayDate; + @FXML + private Label displayShowName; + + private SeatsDisplayController(Theatre theatre, LocalDate localDate) { + this.theatre = theatre; + this.localDate = localDate; + + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/SeatsDisplay.fxml")); + fxmlLoader.setRoot(this); + fxmlLoader.setController(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + displayShowName.setText(theatre.getShowName()); + displayDate.setText(formatter.toStringDate(localDate)); + fillSeats(); + } + + public static SeatsDisplayController displaySeats(Theatre theatre, LocalDate localDate) { + return new SeatsDisplayController(theatre, localDate); + } + + private void fillSeats() { + Seat[][] seats = theatre.getSeats(); + for (int i = 0; i < seats.length; i++) { + for (int j = 0; j < seats[i].length; j++) { + switch (i) { + case 0: + rowA.getChildren().add(SeatController.getSeat(i, j, seats[i][j])); + break; + case 1: + rowB.getChildren().add(SeatController.getSeat(i, j, seats[i][j])); + break; + case 2: + rowC.getChildren().add(SeatController.getSeat(i, j, seats[i][j])); + break; + case 3: + rowD.getChildren().add(SeatController.getSeat(i, j, seats[i][j])); + break; + case 4: + rowE.getChildren().add(SeatController.getSeat(i, j, seats[i][j])); + break; + case 5: + rowF.getChildren().add(SeatController.getSeat(i, j, seats[i][j])); + break; + default: + System.out.println("error"); + } + } + } + } +} diff --git a/src/main/java/optix/ui/windows/ShowController.java b/src/main/java/optix/ui/windows/ShowController.java new file mode 100644 index 0000000000..fcab71f859 --- /dev/null +++ b/src/main/java/optix/ui/windows/ShowController.java @@ -0,0 +1,59 @@ +package optix.ui.windows; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import optix.commons.model.Theatre; +import optix.util.OptixDateFormatter; + +import java.io.IOException; +import java.time.LocalDate; + +public class ShowController extends AnchorPane { + private Theatre theatre; + private LocalDate date; + + private final OptixDateFormatter formatter = new OptixDateFormatter(); + + private static final String MESSAGE_SEATS_AVAILABILITY = "%1$s (Price: $%2$.2f)"; + + @FXML + private Label displayDate; + @FXML + private Label displayShowName; + @FXML + private Label displayTier1Seats; + @FXML + private Label displayTier2Seats; + @FXML + private Label displayTier3Seats; + + private ShowController(Theatre theatre, LocalDate date) { + this.theatre = theatre; + this.date = date; + + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/ShowEntry.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + displayShowName.setText(theatre.getShowName()); + displayDate.setText(formatter.toStringDate(date)); + displayTier1Seats.setText(seatsAvailability(theatre.getTierOneSeats(), theatre.getSeatBasePrice())); + displayTier2Seats.setText(seatsAvailability(theatre.getTierTwoSeats(), theatre.getSeatBasePrice() * 1.2)); + displayTier3Seats.setText(seatsAvailability(theatre.getTierThreeSeats(), theatre.getSeatBasePrice() * 1.5)); + } + + private String seatsAvailability(String seatsLeft, double seatPrice) { + return String.format(MESSAGE_SEATS_AVAILABILITY, seatsLeft, seatPrice); + } + + public static ShowController displayShow(Theatre theatre, LocalDate date) { + return new ShowController(theatre, date); + } +} diff --git a/src/main/java/optix/util/OptixDateFormatter.java b/src/main/java/optix/util/OptixDateFormatter.java new file mode 100644 index 0000000000..a7f7a37b18 --- /dev/null +++ b/src/main/java/optix/util/OptixDateFormatter.java @@ -0,0 +1,226 @@ +package optix.util; + +import optix.exceptions.OptixInvalidDateException; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.text.DateFormatSymbols; + +/** + * Formats input date to YYYY-MM-DD to be sorted in ShowMap. + */ +public class OptixDateFormatter { + + /** + * Get correct String format for DateFormatter. + * + * @param date Input date. + * @return String prefix for DateFormatter. + */ + private String getFormat(String date) { + int padCount = 0; + + StringBuilder format = new StringBuilder(); + String[] timeType = {"d", "M", "y", "H", "H", "m", "m"}; + for (int i = 0; i < date.length(); i += 1) { + char c = date.charAt(i); + if (Character.isDigit(c)) { + format.append(timeType[padCount]); + } else { + format.append(c); + padCount += 1; + } + } + return format.toString(); + } + + /** + * Format date from String to LocalDate. + * + * @param dateString Input date. + * @return LocalDate for the input date. Format: YYYY-MM-DD + */ + public LocalDate toLocalDate(String dateString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(getFormat(dateString)); + //Convert string to localdate + return LocalDate.parse(dateString, formatter); + } + + /** + * Formate date from LocalDate to String. + * + * @param localDate YYYY-MM-DD. + * @return String for the date. Format: DD/MM/YYYY. + */ + public String toStringDate(LocalDate localDate) { + String[] splitStr = localDate.toString().split("-", 3); + + return String.format("%s/%s/%s", splitStr[2], splitStr[1], splitStr[0]); + } + + /** + * Checks if date given exists in calendar. + * + * @param date String input of the date. + * @return {@code true} date can be found in the calendar + * {@code false} otherwise + */ + public boolean isValidDate(String date) { + String[] splitStr = date.split("/"); + if (splitStr.length != 3) { + return false; + } + + int yr; + int mth = Integer.parseInt(splitStr[1]); + int dy = Integer.parseInt(splitStr[0]); + try { + yr = Integer.parseInt(splitStr[2]); + } catch (NumberFormatException e) { + return false; + } + + if (yr >= LocalDate.now().getYear() + 100) { + return false; + } + + if (mth == 2) { + return (isLeap(yr) && dy <= 29) || (!isLeap(yr) && dy <= 28); + } else if (mth == 4 || mth == 6 || mth == 9 || mth == 11) { + return dy <= 30; + } else if (mth == 1 || mth == 3 || mth == 5 || mth == 7 || mth == 8 || mth == 10 || mth == 12) { + return dy <= 31; + } + return false; + } + + /** + * Check if it is a leap year. + * + * @param year to check whether its a leap year + * @return {@code true} if it is a leap year + * {@code false} otherwise + */ + private boolean isLeap(int year) { + return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0); + } + + /** + * Get year in numeric form. + * + * @param year String format: YYYY. + * @return Integer number for year. + */ + public int getYear(String year) throws OptixInvalidDateException { + try { + int yr = Integer.parseInt(year); + if (yr < 100 && yr >= 10) { + return yr + 2000; + } else if (yr < 1000) { + throw new OptixInvalidDateException(); + } else { + return yr; + } + } catch (NumberFormatException e) { + throw new OptixInvalidDateException(); + } + } + + /** + * Get month in numeric form. + * + * @param month String format: MMMM. + * @return Integer number for month. + */ + public int getMonth(String month) { + int num; + try { + num = Integer.parseInt(month); + if (num < 0 || num > 12) { + num = 0; + } + } catch (NumberFormatException e) { + switch (month) { + case "january": + case "jan": + num = 1; + break; + case "february": + case "feb": + num = 2; + break; + case "march": + case "mar": + num = 3; + break; + case "april": + case "apr": + num = 4; + break; + case "may": + num = 5; + break; + case "june": + case "jun": + num = 6; + break; + case "july": + case "jul": + num = 7; + break; + case "august": + case "aug": + num = 8; + break; + case "september": + case "sep": + case "sept": + num = 9; + break; + case "october": + case "oct": + num = 10; + break; + case "november": + case "nov": + num = 11; + break; + case "december": + case "dec": + num = 12; + break; + default: + num = 0; + break; + } + } + return num; + } + + + public String intToMonth(int month) { + return new DateFormatSymbols().getMonths()[month - 1]; + } + + /** + * Get the first day of the month in query. + * + * @param year The year in query. + * @param month The month in query. + * @return The first day of the month in LocalDate. + */ + public LocalDate getStartOfMonth(int year, int month) { + return LocalDate.of(year, month, 1); + } + + /** + * Get the first day of the following month for the month in query. + * + * @param year The year in query. + * @param month The month in query. + * @return The first day of the following month for the month in query in LocalDate. + */ + public LocalDate getEndOfMonth(int year, int month) { + return LocalDate.of(year, month, 1).plusMonths(1); + } +} \ No newline at end of file diff --git a/src/main/java/optix/util/Parser.java b/src/main/java/optix/util/Parser.java new file mode 100644 index 0000000000..0621db9380 --- /dev/null +++ b/src/main/java/optix/util/Parser.java @@ -0,0 +1,293 @@ +package optix.util; + +import optix.commands.ByeCommand; +import optix.commands.Command; +import optix.commands.TabCommand; +import optix.commands.finance.ViewMonthlyCommand; +import optix.commands.finance.ViewProfitCommand; +import optix.commands.parser.AddAliasCommand; +import optix.commands.parser.ListAliasCommand; +import optix.commands.parser.RemoveAliasCommand; +import optix.commands.parser.ResetAliasCommand; +import optix.commands.seats.ReassignSeatCommand; +import optix.commands.seats.RefundSeatCommand; +import optix.commands.seats.RemoveSeatCommand; +import optix.commands.seats.SellSeatCommand; +import optix.commands.seats.ViewSeatsCommand; +import optix.commands.shows.AddCommand; +import optix.commands.shows.DeleteCommand; +import optix.commands.shows.EditCommand; +import optix.commands.shows.ListCommand; +import optix.commands.shows.ListDateCommand; +import optix.commands.shows.ListShowCommand; +import optix.commands.shows.RescheduleCommand; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * Parse input arguments and create a new Command Object. + */ +public class Parser { + public static HashMap commandAliasMap = new HashMap<>(); + private File preferenceFilePath; // the directory where the file is stored + private File preferenceFile; // the path to the file itself + // array of all possible command values + private static String[] commandList = {"bye", "list", "help", "edit", "sell", "view", + "reschedule", "add", "delete", "reassign-seat", "show", "archive", "finance", + "view-profit", "view-monthly", "add-alias", "remove-alias", "reset-alias", "list-alias", + "refund-seat", "remove-seat"}; + private static final Logger OPTIXLOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + + /** + * Set the path to directory containing the save file for preferences. + * Set the path to the save file for preferences. + * + * @param filePath path to directory containing the save file for preferences. + */ + public Parser(File filePath) { + initLogger(); + OPTIXLOGGER.log(Level.INFO, "Parser initialization begin"); + this.preferenceFile = new File(filePath + "\\ParserPreferences.txt"); + this.preferenceFilePath = filePath; + // load preferences from file + if (commandAliasMap.isEmpty()) { + try { + loadPreferences(); + } catch (IOException e) { + System.out.println(e.getMessage()); + OPTIXLOGGER.log(Level.WARNING, "Error loading preferences."); + } + } + OPTIXLOGGER.log(Level.INFO, "Parser initialization complete."); + } + + /** + * Parse input argument and create a new Command Object based on the first input word. + * + * @param fullCommand The entire input argument. + * @return Command Object based on the first input word. + * @throws OptixException if the Command word is not recognised by Optix. + */ + public Command parse(String fullCommand) throws OptixException { + // add exception for null pointer exception. e.g. reschedule + OPTIXLOGGER.log(Level.INFO, "Parsing string: " + fullCommand); + String[] splitStr = fullCommand.trim().split(" ", 2); + String aliasName = splitStr[0]; + String commandName = commandAliasMap.getOrDefault(aliasName, aliasName); + commandName = commandName.toLowerCase().trim(); + if (splitStr.length == 1) { + switch (commandName) { + case "bye": + return new ByeCommand(); + case "list": + return new ListCommand(); + case "reset-alias": + return new ResetAliasCommand(this.preferenceFilePath); + case "list-alias": + return new ListAliasCommand(); + case "help": + case "archive": + case "finance": + return new TabCommand(commandName); + default: + OPTIXLOGGER.log(Level.WARNING, "Error with command: " + commandName); + throw new OptixInvalidCommandException(); + } + } else if (splitStr.length == 2) { + + switch (commandName) { + case "edit": + return new EditCommand(splitStr[1]); + case "sell": + return new SellSeatCommand(splitStr[1]); + case "view": + return new ViewSeatsCommand(splitStr[1]); + case "reschedule": + return new RescheduleCommand(splitStr[1]); + case "list": + return parseList(splitStr[1]); + case "bye": + return new ByeCommand(); + case "add": // add poto|5/10/2020|20 + return new AddCommand(splitStr[1]); + case "delete": // e.g. delete SHOW_NAME DATE_1|DATE_2|etc + return new DeleteCommand(splitStr[1]); + case "view-profit": //e.g. view-profit lion king|5/5/2020 + return new ViewProfitCommand(splitStr[1]); + case "view-monthly": //e.g. view-monthly May 2020 + return new ViewMonthlyCommand(splitStr[1]); + case "add-alias": + return new AddAliasCommand(splitStr[1], this.preferenceFilePath); + case "remove-alias": + return new RemoveAliasCommand(splitStr[1], commandAliasMap); + case "reassign-seat": + return new ReassignSeatCommand(splitStr[1]); + case "remove-seat": + return new RemoveSeatCommand(splitStr[1]); + case "refund-seat": + return new RefundSeatCommand(splitStr[1]); + default: + OPTIXLOGGER.log(Level.WARNING, "Error with command: " + commandName); + throw new OptixInvalidCommandException(); + } + } else { + OPTIXLOGGER.log(Level.WARNING, "Error with command: " + fullCommand); + throw new OptixInvalidCommandException(); + } + } + + //@@ OungKennedy + /** + * Adds a new alias-command pair to commandAliasMap. + * + * @param newAlias new alias to add + * @param command existing command to be paired to + * @throws OptixException thrown when the alias-command pair is invalid (refer to below) + * the alias must not be the name of a command. + * the alias must not already be in use. use remove-alias to remove a pair to redirect existing aliases. + * the command to be paired to must exist (refer to commandList for list of existing commands). + * the pipe symbol is a special character- it cannot be used. + */ + public void addAlias(String newAlias, String command) throws OptixException { + OPTIXLOGGER.log(Level.INFO, "adding new alias"); + if (!newAlias.contains("|") // pipe symbol not in alias + && Arrays.asList(commandList).contains(command) // command exists + && !commandAliasMap.containsKey(newAlias) // new alias is not already in use + && !Arrays.asList(commandList).contains(newAlias)) { // new alias is not the name of a command + commandAliasMap.put(newAlias, command); + OPTIXLOGGER.log(Level.INFO, "add alias successful"); + } else { + OPTIXLOGGER.log(Level.INFO, "error adding alias."); + throw new OptixException("Invalid alias-command input.\n Alias cannot be a command keyword.\n" + + "Alias cannot already be in use."); + } + } + + //@@ OungKennedy + private void loadPreferences() throws IOException { + OPTIXLOGGER.log(Level.INFO, "loading preferences"); + File filePath = this.preferenceFile; + // if file does not exist, create a new file and write the default aliases + if (filePath.createNewFile()) { + OPTIXLOGGER.log(Level.INFO, "preference file not found. Creating new file."); + resetPreferences(); + savePreferences(); + } else { // if file exists then load the preferences within + OPTIXLOGGER.log(Level.INFO, "preference file found."); + FileReader fr = new FileReader(filePath); + BufferedReader br = new BufferedReader(fr); + String aliasPreference; + while ((aliasPreference = br.readLine()) != null) { + if (aliasPreference.length() == 0) { // handle empty line + continue; + } + String[] aliasDetails = aliasPreference.split("\\|"); + + String alias = aliasDetails[0]; + String command = aliasDetails[1]; + try { + this.addAlias(alias, command); + } catch (OptixException e) { + System.out.println(e.getMessage()); + } + } + br.close(); + fr.close(); + } + OPTIXLOGGER.log(Level.INFO, "load preferences completed"); + } + + //@@ OungKennedy + /** + * Writes the contents of commandAliasMap to the file in preferenceFilePath. + */ + public void savePreferences() throws IOException { + OPTIXLOGGER.log(Level.INFO, "saving preferences"); + FileWriter writer = new FileWriter(this.preferenceFile, false); + for (Map.Entry entry : commandAliasMap.entrySet()) { + String saveString = entry.getKey() + "|" + entry.getValue() + '\n'; // no need to escape. why? + writer.write(saveString); + } + writer.close(); + OPTIXLOGGER.log(Level.INFO, "preferences saved"); + } + + //@@ OungKennedy + /** + * Method to reset preferences to default values.op + */ + public static void resetPreferences() { + OPTIXLOGGER.log(Level.INFO, "Saving preferences"); + commandAliasMap.clear(); + commandAliasMap.put("re", "reassign-seat"); + commandAliasMap.put("arc", "archive"); + commandAliasMap.put("shw", "show"); + commandAliasMap.put("fin", "finance"); + commandAliasMap.put("b", "bye"); + commandAliasMap.put("l", "list"); + commandAliasMap.put("h", "help"); + commandAliasMap.put("e", "edit"); + commandAliasMap.put("s", "sell"); + commandAliasMap.put("v", "view"); + commandAliasMap.put("rd", "reschedule"); + commandAliasMap.put("a", "add"); + commandAliasMap.put("d", "delete"); + commandAliasMap.put("vp", "view-profit"); + commandAliasMap.put("vm", "view-monthly"); + commandAliasMap.put("a-a", "add-alias"); + commandAliasMap.put("rm-a", "remove-alias"); + commandAliasMap.put("rst-a", "reset-alias"); + commandAliasMap.put("rf-s", "refund-seat"); + commandAliasMap.put("rm-s", "remove-seat"); + OPTIXLOGGER.log(Level.INFO, "preferences saved"); + } + + /** + * Parse the remaining user input to its respective parameters for ListDateCommand or ListShowCommand. + * + * @param details The details to create a new ListDateCommand or ListShowCommand Object. + * @return new ListDateCommand or ListShowCommand Object. + */ + private static Command parseList(String details) { + String[] splitStr = details.split(" "); + + if (splitStr.length == 2) { + try { + Integer.parseInt(splitStr[1]); + return new ListDateCommand(details); + } catch (NumberFormatException e) { + return new ListShowCommand(details); + } + } + + return new ListShowCommand(details); + } + + private void initLogger() { + LogManager.getLogManager().reset(); + OPTIXLOGGER.setLevel(Level.ALL); + try { + // do not append here to avoid + FileHandler fh = new FileHandler("OptixLogger.log",1024 * 1024,1, false); + OPTIXLOGGER.addHandler(fh); + } catch (IOException e) { + OPTIXLOGGER.log(Level.SEVERE, "File logger not working", e); + } + OPTIXLOGGER.log(Level.FINEST, "Logging in " + this.getClass().getName()); + } + +} diff --git a/src/main/resource/img/animated-mask-image-0061.gif b/src/main/resource/img/animated-mask-image-0061.gif new file mode 100644 index 0000000000..33b45c4341 Binary files /dev/null and b/src/main/resource/img/animated-mask-image-0061.gif differ diff --git a/src/main/resource/img/optixIcon.png b/src/main/resource/img/optixIcon.png new file mode 100644 index 0000000000..08257a32e1 Binary files /dev/null and b/src/main/resource/img/optixIcon.png differ diff --git a/src/main/resource/img/optixImage.png b/src/main/resource/img/optixImage.png new file mode 100644 index 0000000000..57faec4200 Binary files /dev/null and b/src/main/resource/img/optixImage.png differ diff --git a/src/main/resource/img/userImage.png b/src/main/resource/img/userImage.png new file mode 100644 index 0000000000..c48a845625 Binary files /dev/null and b/src/main/resource/img/userImage.png differ diff --git a/src/main/resource/view/DialogBox.fxml b/src/main/resource/view/DialogBox.fxml new file mode 100644 index 0000000000..7f468c41fb --- /dev/null +++ b/src/main/resource/view/DialogBox.fxml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resource/view/FinanceEntry.fxml b/src/main/resource/view/FinanceEntry.fxml new file mode 100644 index 0000000000..4da9c0e5a8 --- /dev/null +++ b/src/main/resource/view/FinanceEntry.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/main/resource/view/HelpWindow.fxml b/src/main/resource/view/HelpWindow.fxml new file mode 100644 index 0000000000..4d83a47929 --- /dev/null +++ b/src/main/resource/view/HelpWindow.fxml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resource/view/MainWindow.fxml b/src/main/resource/view/MainWindow.fxml new file mode 100644 index 0000000000..64f446c660 --- /dev/null +++ b/src/main/resource/view/MainWindow.fxml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resource/view/Seat.fxml b/src/main/resource/view/Seat.fxml new file mode 100644 index 0000000000..550cfded08 --- /dev/null +++ b/src/main/resource/view/Seat.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/src/main/resource/view/SeatsDisplay.fxml b/src/main/resource/view/SeatsDisplay.fxml new file mode 100644 index 0000000000..954bdb1f45 --- /dev/null +++ b/src/main/resource/view/SeatsDisplay.fxml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resource/view/ShowEntry.fxml b/src/main/resource/view/ShowEntry.fxml new file mode 100644 index 0000000000..049e6702bc --- /dev/null +++ b/src/main/resource/view/ShowEntry.fxml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/src/test/data/testOptix/ParserPreferences.txt b/src/test/data/testOptix/ParserPreferences.txt new file mode 100644 index 0000000000..90fc22df37 --- /dev/null +++ b/src/test/data/testOptix/ParserPreferences.txt @@ -0,0 +1,13 @@ +a|add +b|bye +shw|show +d|delete +e|edit +h|help +fin|finance +l|list +rd|reschedule +re|reassign-seat +arc|archive +s|sell +v|view diff --git a/src/test/data/testOptix/archive.txt b/src/test/data/testOptix/archive.txt new file mode 100644 index 0000000000..a4e7f31649 --- /dev/null +++ b/src/test/data/testOptix/archive.txt @@ -0,0 +1,8 @@ +2015-10-13 | Harry Potter | 2000.0 +2018-10-13 | Harry Potter | 2000.0 +2018-11-13 | Lion King | 2000.0 +2018-11-14 | Harry Potter | 2000.0 +2018-11-15 | Harry Potter | 2000.0 +2018-12-13 | Harry Potter | 2000.0 +2019-09-13 | Lion King | 200.0 +2019-11-13 | Lion King | 200.0 diff --git a/src/test/java/optix/commands/ByeCommandTest.java b/src/test/java/optix/commands/ByeCommandTest.java new file mode 100644 index 0000000000..84b128ee05 --- /dev/null +++ b/src/test/java/optix/commands/ByeCommandTest.java @@ -0,0 +1,45 @@ +package optix.commands; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ByeCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + void testExecute() { + ByeCommand testCommand = new ByeCommand(); + testCommand.execute(model, ui, storage); + + String expected = "Bye. Hope to see you again soon!\n"; + assertEquals(expected, ui.getMessage()); + assertTrue(new File(filePath, "optix.txt").exists()); + assertTrue(new File(filePath, "archive.txt").exists()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/TabCommandTest.java b/src/test/java/optix/commands/TabCommandTest.java new file mode 100644 index 0000000000..1f8ed948cd --- /dev/null +++ b/src/test/java/optix/commands/TabCommandTest.java @@ -0,0 +1,67 @@ +package optix.commands; + +import optix.commands.shows.AddCommand; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TabCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + new AddCommand("Test Show|20|5/5/2020|6/5/2020|7/5/2020").execute(model, ui, storage); + } + + @Test + @DisplayName("No details parsed into command") + void testEmptyParameter() { + new TabCommand("").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Command parameter") + void testExecute() { + new TabCommand("archive").execute(model, ui, storage); + assertEquals("Here is your list of archived shows.\n" + + model.listShowHistory(), ui.getMessage()); + assertEquals(model.getShowsHistory(), model.getShowsGui()); + new TabCommand("finance").execute(model, ui, storage); + assertEquals("Here is your list of projected earnings.\n" + + "1. Test Show (on: 05/05/2020): $0.00\n" + + "2. Test Show (on: 06/05/2020): $0.00\n" + + "3. Test Show (on: 07/05/2020): $0.00\n", ui.getMessage()); + new TabCommand("help").execute(model, ui, storage); + assertEquals("Here are the list of commands you can use.\n", ui.getMessage()); + assertEquals(model.getShows(), model.getShowsGui()); + new TabCommand("random command").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + assertEquals(model.getShows(), model.getShowsGui()); + } + + @AfterEach + void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/finance/ViewMonthlyCommandTest.java b/src/test/java/optix/commands/finance/ViewMonthlyCommandTest.java new file mode 100644 index 0000000000..387487cf0c --- /dev/null +++ b/src/test/java/optix/commands/finance/ViewMonthlyCommandTest.java @@ -0,0 +1,104 @@ +package optix.commands.finance; + +import optix.commands.seats.SellSeatCommand; +import optix.commands.shows.AddCommand; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author NicholasLiu97 +class ViewMonthlyCommandTest { + private Ui ui = new Ui(); + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage = new Storage(filePath); + private Model model = new Model(storage); + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Incorrect number of parameters") + void testParseDetails() { + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + new ViewMonthlyCommand("").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + new ViewMonthlyCommand("October").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + new ViewMonthlyCommand("October 2020 2020").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Incorrect Date Format") + void testDateFormatter() { + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + new ViewMonthlyCommand("Octoberr 2020").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + new ViewMonthlyCommand("0 2020").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + new ViewMonthlyCommand("13 Oct").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("No show found") + void testMonth() { + String expected = "☹ OOPS!!! There are no shows in February 2019.\n"; + new ViewMonthlyCommand("2 2019").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Test for Current Month Profits") + void testCurrentMonthProfit() { + String expected = "The current earnings for November 2019 is $200.00.\n"; + new ViewMonthlyCommand("11 2019").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Test for Archive Profits") + void testArchiveProfit() { + String expected = "The earnings for November 2018 is $6000.00.\n"; + new ViewMonthlyCommand("Nov 2018").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + expected = "The earnings for September 2019 is $200.00.\n"; + new ViewMonthlyCommand("September 2019").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Test for Projected Profits") + void testShowProfit() { + new AddCommand("Lion King|20|9/12/2019|10/10/2020|11/10/2020").execute(model, ui, storage); + new SellSeatCommand("Lion King|10/10/2020|A1 A2 A3 A4 B5 C6 D7 E8 F9 A10").execute(model, ui, storage); + new SellSeatCommand("Lion King|9/12/2019|A1 A2 A3 A4 B5 C6 D7 E8 F9 A10").execute(model, ui, storage); + String expected = "The projected earnings for October 2020 is $268.00.\n"; + new ViewMonthlyCommand("Oct 2020").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + expected = "The projected earnings for December 2019 is $268.00.\n"; + new ViewMonthlyCommand("12 2019").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} diff --git a/src/test/java/optix/commands/finance/ViewProfitCommandTest.java b/src/test/java/optix/commands/finance/ViewProfitCommandTest.java new file mode 100644 index 0000000000..a880259355 --- /dev/null +++ b/src/test/java/optix/commands/finance/ViewProfitCommandTest.java @@ -0,0 +1,92 @@ +package optix.commands.finance; + +import optix.commands.seats.SellSeatCommand; +import optix.commands.shows.AddCommand; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author NicholasLiu97 +class ViewProfitCommandTest { + private Ui ui = new Ui(); + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage = new Storage(filePath); + private Model model = new Model(storage); + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Incorrect number of parameters") + void testParseDetails() { + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + new ViewProfitCommand("").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + new ViewProfitCommand("Lion King").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + new ViewProfitCommand("Lion King|5/5/2020|20202").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date") + void testInvalidDate() { + new ViewProfitCommand("Test Show|2020").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Show Name") + void testInvalidShow() { + new ViewProfitCommand("Test Show|5/5/2020").execute(model, ui, storage); //show don't exist + String expected = "☹ OOPS!!! The show cannot be found.\n"; + assertEquals(expected, ui.getMessage()); + new ViewProfitCommand("Test Show|14/10/2015").execute(model, ui, storage); //archive show don't exist + assertEquals(expected, ui.getMessage()); + new AddCommand("Test Show|20|5/5/2020").execute(model, ui, storage); + new ViewProfitCommand("Test |5/5/2020").execute(model, ui, storage); //show name does not match + assertEquals(expected, ui.getMessage()); + new ViewProfitCommand("Harry Potte|13/10/2015").execute(model, ui, storage); //show name does not match + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Archive Show exist") + void testArchiveShow() { + String expected = "The profit for Harry Potter on 13/10/2015 is $2000.00\n"; + new ViewProfitCommand("Harry Potter|13/10/2015").execute(model, ui, storage); + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Scheduled Show exist") + void testViewProfit() { + new AddCommand("Test Show|20|5/5/2020").execute(model, ui, storage); + new SellSeatCommand("Test Show|5/5/2020|A1 A2").execute(model, ui, storage); + new ViewProfitCommand("Test Show|5/5/2020").execute(model, ui, storage); + String expected = "The profit for Test Show on 5/5/2020 is $60.00\n"; + assertEquals(expected, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} diff --git a/src/test/java/optix/commands/seats/ReassignSeatCommandTest.java b/src/test/java/optix/commands/seats/ReassignSeatCommandTest.java new file mode 100644 index 0000000000..836417f1d4 --- /dev/null +++ b/src/test/java/optix/commands/seats/ReassignSeatCommandTest.java @@ -0,0 +1,83 @@ +package optix.commands.seats; + +import optix.commands.shows.AddCommand; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author NicholasLiu97 +class ReassignSeatCommandTest { + private Ui ui = new Ui(); + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage = new Storage(filePath); + private Model model = new Model(storage); + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new ReassignSeatCommand("").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date") + void testInvalidDate() { + new AddCommand("Test Show|20|5/5/2030").execute(model, ui, storage); + new ReassignSeatCommand("Test Show|2020|a1|a2").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Show Name Do Not Match") + void testInvalidShowName() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new ReassignSeatCommand("Test|5/5/2030|a1|a2").execute(model, ui, storage); + String expected = "☹ OOPS!!! The show cannot be found.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Execute") + void testReassignSeat() { + new AddCommand("Test Show|20|10/10/2030").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|C1 C2 C3").execute(model, ui, storage); + new ReassignSeatCommand("Test Show|10/10/2030|A1 | A5").execute(model, ui, storage); + String expected = "The seat A1 is still available for booking.\n"; + assertEquals(expected, ui.getMessage()); + new ReassignSeatCommand("Test Show|10/10/2030|C1 | A5").execute(model, ui, storage); + expected = "Your seat has been successfully changed from C1 to A5.\n" + + "An extra cost of $6.00 is required.\n"; + assertEquals(expected, ui.getMessage()); + new ReassignSeatCommand("Test Show|10/10/2030|C2 | E5").execute(model, ui, storage); + expected = "Your seat has been successfully changed from C2 to E5.\n" + + "$4.00 will be returned.\n"; + assertEquals(expected, ui.getMessage()); + } + + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} diff --git a/src/test/java/optix/commands/seats/SellSeatCommandTest.java b/src/test/java/optix/commands/seats/SellSeatCommandTest.java new file mode 100644 index 0000000000..166c143b21 --- /dev/null +++ b/src/test/java/optix/commands/seats/SellSeatCommandTest.java @@ -0,0 +1,110 @@ +package optix.commands.seats; + +import optix.commands.shows.AddCommand; +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SellSeatCommandTest { + private Ui ui = new Ui(); + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage = new Storage(filePath); + private Model model = new Model(storage); + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new SellSeatCommand("").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date") + void testInvalidDate() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new SellSeatCommand("Test Show|2020|a1 a2").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Show Name Do Not Match") + void testInvalidShowName() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new SellSeatCommand("Test|5/5/2030|a1 a2").execute(model, ui, storage); + String expected = "☹ OOPS!!! The show cannot be found.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Sell available seats") + void testSellAvailableSeat() { + new AddCommand("Test Show|20|10/10/2030").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|A1 B3").execute(model, ui, storage); + String expected = "You have successfully purchased the following seats: \n" + + "[A1, B3]\n" + + "The total cost of the tickets are $60.00\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Execute") + void testSellSeat() { + new AddCommand("Test Show|20|10/10/2030").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|A1").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|A1 A2 A3 A11").execute(model, ui, storage); + String expected = "You have successfully purchased the following seats: \n" + + "[A2, A3]\n" + + "The total cost of the tickets are $60.00\n" + + "The following seats are unavailable: \n" + + "[A1]\n" + + "The following seats do not exist: \n" + + "[A11]\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Unavailable Seats") + void testSellUnavailableSeats() { + new AddCommand("Test Show|20|10/10/2030").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|A1").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|A1").execute(model, ui, storage); + String expected = "☹ OOPS!!! All of the seats [A1] are unavailable.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Non-existent seats") + void testSellSeatsNotExist() { + new AddCommand("Test Show|20|10/10/2030").execute(model, ui, storage); + new SellSeatCommand("Test Show|10/10/2030|A11").execute(model, ui, storage); + String expected = "☹ OOPS!!! All of the seats [A11] do not exist.\n"; + assertEquals(expected, ui.getMessage()); + } + + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/seats/ViewSeatsCommandTest.java b/src/test/java/optix/commands/seats/ViewSeatsCommandTest.java new file mode 100644 index 0000000000..0560c8caf2 --- /dev/null +++ b/src/test/java/optix/commands/seats/ViewSeatsCommandTest.java @@ -0,0 +1,77 @@ +package optix.commands.seats; + +import optix.commands.shows.AddCommand; +import optix.commons.Model; +import optix.commons.Storage; +import optix.commons.model.Theatre; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ViewSeatsCommandTest { + private Ui ui = new Ui(); + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage = new Storage(filePath); + private Model model = new Model(storage); + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new AddCommand("Test Show|20|5/5/2020").execute(model, ui, storage); + new ViewSeatsCommand("").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date") + void testInvalidDate() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new ViewSeatsCommand("Test Show|2020").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Show Name Do Not Match") + void testInvalidShowName() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new ViewSeatsCommand("Test|5/5/2030").execute(model, ui, storage); + String expected = "☹ OOPS!!! Sorry the show Test cannot be found.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid Execute") + void testValidViewSeat() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new ViewSeatsCommand("Test Show|5/5/2030").execute(model, ui, storage); + Theatre show = model.getShows().get(LocalDate.of(2030, 5, 5)); + String expected = "Here is the layout of the theatre for Test Show on 5/5/2030:\n" + + show.getSeatingArrangement(); + assertEquals(expected, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} diff --git a/src/test/java/optix/commands/shows/AddCommandTest.java b/src/test/java/optix/commands/shows/AddCommandTest.java new file mode 100644 index 0000000000..7cf9702537 --- /dev/null +++ b/src/test/java/optix/commands/shows/AddCommandTest.java @@ -0,0 +1,109 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AddCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Invalid Command test") + void testParseDetails() { + AddCommand c = new AddCommand("Test Show|20"); //test has less than 3 parameter + c.execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new AddCommand("").execute(model, ui, storage); // No details + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Negative Base Price Test") + void testNegativePrice() { + AddCommand c = new AddCommand("Test Show|-5|2/12/2030"); // test has negative seat base price + c.execute(model, ui, storage); + String expected = "Seat base price cannot be negative.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Empty Seat Base Price Test") + void testEmptyPrice() { + AddCommand c = new AddCommand("Test Show||2/12/2030"); // no seat base price + c.execute(model, ui, storage); + String expected = "Please set a number for the seat base price.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Show Date Test") + void testInvalidShowDate() { + AddCommand c = new AddCommand("Test Show|20|29/2/2021|hello"); // test invalid date format + c.execute(model, ui, storage); + String expected = "☹ OOPS!!! Unable to add the following shows:\n" + + "1. Test Show (on: 29/2/2021)\n" + + "2. Test Show (on: hello)\n"; + assertEquals(expected, ui.getMessage()); + + c = new AddCommand("Test Show|20|5/5/2030"); //test date clash + c.execute(model, ui, storage); + c.execute(model, ui, storage); + expected = "☹ OOPS!!! Unable to add the following shows:\n" + + "1. Test Show (on: 5/5/2030)\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("AddCommand without exception") + void testAddShowExecute() { + AddCommand c = new AddCommand("TestShow|20|6/5/2030|7/6/2030"); //test successful execution. + c.execute(model, ui, storage); + String expected = "Noted. The following shows has been added:\n" + + "1. TestShow (on: 6/5/2030)\n" + + "2. TestShow (on: 7/6/2030)\n"; + assertEquals(expected, ui.getMessage()); + + c = new AddCommand("Test Show|20|6/5/2030|20/5/2030"); + c.execute(model, ui, storage); + expected = "Noted. The following shows has been added:\n" + + "1. Test Show (on: 20/5/2030)\n" + + "\n" + + "☹ OOPS!!! Unable to add the following shows:\n" + + "1. Test Show (on: 6/5/2030)\n"; + assertEquals(expected, ui.getMessage()); + } + + @AfterEach + void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/shows/DeleteCommandTest.java b/src/test/java/optix/commands/shows/DeleteCommandTest.java new file mode 100644 index 0000000000..2198104be5 --- /dev/null +++ b/src/test/java/optix/commands/shows/DeleteCommandTest.java @@ -0,0 +1,74 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DeleteCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Valid Deletion Test") + void testValidDelete() { // adding a show and deleting the same show + new AddCommand("Test Show 1|20|5/5/2020|6/5/2020|12/12/2020").execute(model, ui, storage); + DeleteCommand testCommand1 = new DeleteCommand("Test Show 1|5/5/2020|6/5/2020"); + testCommand1.execute(model, ui, storage); + String expected = "Noted. The following shows has been deleted:\n" + + "1. Test Show 1 (on: 5/5/2020)\n" + + "2. Test Show 1 (on: 6/5/2020)\n"; + assertEquals(expected, ui.getMessage()); + + testCommand1 = new DeleteCommand("Test Show 1|5/13/2020|12/12/2020"); + testCommand1.execute(model, ui, storage); + expected = "Noted. The following shows has been deleted:\n" + + "1. Test Show 1 (on: 12/12/2020)\n" + + "\n" + + "☹ OOPS!!! Unable to find the following shows:\n" + + "1. Test Show 1 (on: 5/13/2020)\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Deletion Test") + void testInvalidDelete() { // deleting a show that does not exist + DeleteCommand testCommand2 = new DeleteCommand("Non-existent show|4/5/2020"); + testCommand2.execute(model, ui, storage); + String expected2 = "☹ OOPS!!! Unable to find the following shows:\n" + + "1. Non-existent show (on: 4/5/2020)\n"; + assertEquals(expected2, ui.getMessage()); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new DeleteCommand("").execute(model, ui, storage); // No details + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @AfterEach + void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/shows/EditCommandTest.java b/src/test/java/optix/commands/shows/EditCommandTest.java new file mode 100644 index 0000000000..09bb5cf6d6 --- /dev/null +++ b/src/test/java/optix/commands/shows/EditCommandTest.java @@ -0,0 +1,83 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EditCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Editing non existent show") + void testNoShow() { + new EditCommand("Non existent show|5/5/2020|Test Show").execute(model, ui, storage); + String expected = "☹ OOPS!!! The show you are finding does not exist!\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Incomplete Details") + void testIncompleteDetails() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new EditCommand("Test Show").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new EditCommand("").execute(model, ui, storage); // No details + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date Details") + void testInvalidDate() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new EditCommand("Test Show|5/13/2020|Test Show 1").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid edit") + void testValidEdit() { + // add test shows + AddCommand addTestShow1 = new AddCommand("Test Show 1|20|5/5/2020"); + addTestShow1.execute(model, ui, storage); + EditCommand testCommand = new EditCommand("Test Show 1|5/5/2020|Test Show 3"); + testCommand.execute(model, ui, storage); + String expected1 = "Show has been successfully updated to Test Show 3.\n"; + assertEquals(expected1, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/shows/ListCommandTest.java b/src/test/java/optix/commands/shows/ListCommandTest.java new file mode 100644 index 0000000000..a3be4dea0c --- /dev/null +++ b/src/test/java/optix/commands/shows/ListCommandTest.java @@ -0,0 +1,60 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ListCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Empty list") + void testEmptyList() { + // testing for an empty show list + ListCommand testCommand1 = new ListCommand(); + testCommand1.execute(model, ui, storage); + String expected1 = "☹ OOPS!!! There are no shows in the near future.\n"; + assertEquals(expected1, ui.getMessage()); + } + + @Test + @DisplayName("Positive test") + void testValidList() { + // testing for a filled show list + AddCommand addShow1 = new AddCommand("dummy test 1|20|5/5/2020"); + addShow1.execute(model, ui, storage); + AddCommand addShow2 = new AddCommand("dummy test 2|20|6/5/2020"); + addShow2.execute(model, ui, storage); + ListCommand testCommand2 = new ListCommand(); + testCommand2.execute(model, ui, storage); + String expected2 = "Here are the list of shows:\n" + + "1. dummy test 1 (on: 05/05/2020)\n" + + "2. dummy test 2 (on: 06/05/2020)\n"; + assertEquals(expected2, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/shows/ListDateCommandTest.java b/src/test/java/optix/commands/shows/ListDateCommandTest.java new file mode 100644 index 0000000000..c0c5351e4f --- /dev/null +++ b/src/test/java/optix/commands/shows/ListDateCommandTest.java @@ -0,0 +1,81 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ListDateCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Test for no results") + void testNoResults() { + // try looking for a show that does not exist + ListDateCommand testCommand1 = new ListDateCommand("december 2020"); + testCommand1.execute(model, ui, storage); + String expected1 = "☹ OOPS!!! There are no shows on December 2020.\n"; + assertEquals(expected1, ui.getMessage()); + } + + @Test + @DisplayName("Positive Test") + void testValidListDate() { + // insert dummy show + AddCommand insertDummyShow1 = new AddCommand("Dummy Show|20|5/5/2020"); + insertDummyShow1.execute(model, ui, storage); + AddCommand insertDummyShow2 = new AddCommand("Dummy Show|20|6/5/2020"); + insertDummyShow2.execute(model, ui, storage); + // attempt to view dummy show. + ListDateCommand testCommand2 = new ListDateCommand("May 2020"); + testCommand2.execute(model, ui, storage); + String expected2 = "These shows are showing on May 2020: \n" + + "1. Dummy Show (on: 05/05/2020)\n" + + "2. Dummy Show (on: 06/05/2020)\n"; + assertEquals(expected2, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date") + void testInvalidDate() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new ListDateCommand("ocotber 2020").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Incomplete Details") + void testIncompleteDetails() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new ListDateCommand("May").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/shows/ListShowCommandTest.java b/src/test/java/optix/commands/shows/ListShowCommandTest.java new file mode 100644 index 0000000000..4e422faa9e --- /dev/null +++ b/src/test/java/optix/commands/shows/ListShowCommandTest.java @@ -0,0 +1,55 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ListShowCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Show Not Found") + void testShowNotFound() { + new AddCommand("Dummy Show|20|5/5/2020|6/10/2020").execute(model, ui, storage); + new ListShowCommand("").execute(model, ui, storage); //empty Show Name + assertEquals("☹ OOPS!!! The show cannot be found.\n", ui.getMessage()); + new ListShowCommand("Dummy").execute(model, ui, storage); //show cannot be found. + assertEquals("☹ OOPS!!! The show cannot be found.\n", ui.getMessage()); + } + + @Test + @DisplayName("Positive Test") + void testValidListShow() { + new AddCommand("Dummy Show|20|5/5/2020|6/10/2020").execute(model, ui, storage); + new ListShowCommand("Dummy Show").execute(model, ui, storage); + String expected = "The show Dummy Show is showing on the following following dates: \n" + + "1. 05/05/2020\n" + + "2. 06/10/2020\n"; + assertEquals(expected, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commands/shows/RescheduleCommandTest.java b/src/test/java/optix/commands/shows/RescheduleCommandTest.java new file mode 100644 index 0000000000..68a33892f1 --- /dev/null +++ b/src/test/java/optix/commands/shows/RescheduleCommandTest.java @@ -0,0 +1,83 @@ +package optix.commands.shows; + +import optix.commons.Model; +import optix.commons.Storage; +import optix.ui.Ui; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RescheduleCommandTest { + private Ui ui; + private static File currentDir = new File(System.getProperty("user.dir")); + private static File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Storage storage; + private Model model; + + @BeforeEach + void init() { + this.ui = new Ui(); + this.storage = new Storage(filePath); + this.model = new Model(storage); + } + + @Test + @DisplayName("Rescheduling non existent show") + void testNoShow() { + new RescheduleCommand("Non existent show|5/5/2020|6/5/2020").execute(model, ui, storage); + String expected = "☹ OOPS!!! The show cannot be found.\n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Incomplete Details") + void testIncompleteDetails() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new RescheduleCommand("Test Show").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("No Details Test") + void testNoDetails() { + new RescheduleCommand("").execute(model, ui, storage); // No details + String expected = "☹ OOPS!!! That is an invalid command\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Invalid Date Details") + void testInvalidDate() { + new AddCommand("Test Show|5|5/5/2030").execute(model, ui, storage); + new RescheduleCommand("Test Show|12|5/13/2020").execute(model, ui, storage); + String expected = "☹ OOPS!!! That is an invalid date.\n" + + "Please try again. \n"; + assertEquals(expected, ui.getMessage()); + } + + @Test + @DisplayName("Valid execute") + void testValidReschedule() { + // add test shows + AddCommand addTestShow1 = new AddCommand("Test Show 1|20|5/5/2020"); + addTestShow1.execute(model, ui, storage); + RescheduleCommand testCommand = new RescheduleCommand("Test Show 1|5/5/2020|6/5/2020"); + testCommand.execute(model, ui, storage); + String expected1 = "Test Show 1 has been rescheduled from 5/5/2020 to 6/5/2020.\n"; + assertEquals(expected1, ui.getMessage()); + } + + @AfterAll + static void cleanUp() { + File deletedFile = new File(filePath, "optix.txt"); + deletedFile.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commons/model/SeatTest.java b/src/test/java/optix/commons/model/SeatTest.java new file mode 100644 index 0000000000..d8c7e11882 --- /dev/null +++ b/src/test/java/optix/commons/model/SeatTest.java @@ -0,0 +1,32 @@ +package optix.commons.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SeatTest { + private Seat seat; + + @BeforeEach + void init() { + seat = new Seat("1"); + } + + @Test + void testGetSeat() { + assertEquals("[✘]", seat.getSeat()); + seat.setSold(true); + assertEquals("[✓]", seat.getSeat()); + } + + + @Test + void testGetSeatPrice() { + assertEquals(30, seat.getSeatPrice(30)); + seat.setSeatTier("2"); + assertEquals(24, seat.getSeatPrice(20)); + seat.setSeatTier("3"); + assertEquals(30, seat.getSeatPrice(20)); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commons/model/ShowMapTest.java b/src/test/java/optix/commons/model/ShowMapTest.java new file mode 100644 index 0000000000..7bd229cbab --- /dev/null +++ b/src/test/java/optix/commons/model/ShowMapTest.java @@ -0,0 +1,94 @@ +package optix.commons.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ShowMapTest { + private LocalDate date1 = LocalDate.of(2020, 10, 10); + private LocalDate date2 = LocalDate.of(2020, 11, 11); + private ShowMap shows = new ShowMap(); + + @BeforeEach + void init() { + shows.addShow("Test Show", date1, 20); + } + + @Test + void testEditShowName() { + shows.editShowName(date1, "Edited Show"); + assertEquals("Edited Show", shows.getShowName(date1)); + assertEquals(1, shows.size()); + } + + @Test + void testReschduleShow() { + shows.rescheduleShow(date1, date2); + assertFalse(shows.containsKey(date1)); + assertTrue(shows.containsKey(date2)); + } + + @Test + void testListShow() { + shows.addShow("Test Show 2", date2, 20); + String expected = "1. Test Show (on: 10/10/2020)\n" + "2. Test Show 2 (on: 11/11/2020)\n"; + assertEquals(expected, shows.listShow()); + } + + @Test + void testListFinance() { + String expected = "1. Test Show (on: 10/10/2020): $0.00\n"; + assertEquals(expected, shows.listFinance()); + } + + @Test + void testDeleteShow() { + shows.deleteShow(date1); + assertTrue(shows.isEmpty()); + } + + @Test + void testViewSeats() { + String expected = " |STAGE| \n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + "\n" + + "Tier 1 Seats (rows E and F): 20\n" + + "Tier 2 Seats (rows C and D): 20\n" + + "Tier 3 Seats (rows A and B): 20\n"; + assertEquals(expected, shows.viewSeats(date1)); + } + + @Test + void testSellSeats() { + String expected = "You have successfully purchased the following seats: \n" + + "[A1, A2]\n" + + "The total cost of the tickets are $60.00\n"; + assertEquals(expected, shows.sellSeats(date1, "A1", "A2")); + expected = "☹ OOPS!!! All of the seats [A1, A2] are unavailable.\n"; + assertEquals(expected, shows.sellSeats(date1, "A1", "A2")); + } + + @Test + void testReassignSeat() { + shows.sellSeats(date1, "A1", "A3"); + String expected = "☹ OOPS!!! Please enter valid seat numbers.\n"; + assertEquals(expected, shows.reassignSeat(date1, "A0", "A0")); + expected = "Your current seat is already A1.\n"; + assertEquals(expected, shows.reassignSeat(date1, "A1", "A1")); + expected = "The seat A2 is still available for booking.\n"; + assertEquals(expected, shows.reassignSeat(date1, "A2", "A1")); + expected = "☹ OOPS!!! Seat A1 is unavailable. Use the View Command to" + + " view the available seats.\n"; + assertEquals(expected, shows.reassignSeat(date1, "A3", "A1")); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commons/model/ShowTest.java b/src/test/java/optix/commons/model/ShowTest.java new file mode 100644 index 0000000000..189e5b522f --- /dev/null +++ b/src/test/java/optix/commons/model/ShowTest.java @@ -0,0 +1,22 @@ +package optix.commons.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ShowTest { + private Show show; + + @BeforeEach + void init() { + show = new Show("Test Show", 20); + } + + @Test + void testHasSameName() { + assertTrue(show.hasSameName("test show")); + assertFalse(show.hasSameName("testshow")); + } +} \ No newline at end of file diff --git a/src/test/java/optix/commons/model/TheatreTest.java b/src/test/java/optix/commons/model/TheatreTest.java new file mode 100644 index 0000000000..a994b3bd49 --- /dev/null +++ b/src/test/java/optix/commons/model/TheatreTest.java @@ -0,0 +1,119 @@ +package optix.commons.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TheatreTest { + private Theatre theatre; + + @BeforeEach + void init() { + theatre = new Theatre("Test Show", 20); + } + + @Test + void testCreate() { + theatre = new Theatre("Test Show", 2000, 20); + assertEquals(2000, theatre.getProfit()); + Show show = new Show("Dummy Show Name", 4000); + theatre = new Theatre(show); + assertEquals(show.getShowName(), theatre.getShowName()); + assertEquals(show.getProfit(), theatre.getProfit()); + } + + @Test + void testGetSeatingArrangement() { + String expected = " |STAGE| \n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + "\n" + + "Tier 1 Seats (rows E and F): " + theatre.getTierOneSeats() + "\n" + + "Tier 2 Seats (rows C and D): " + theatre.getTierTwoSeats() + "\n" + + "Tier 3 Seats (rows A and B): " + theatre.getTierThreeSeats() + "\n"; + System.out.println(theatre.getSeatingArrangement()); + assertEquals(expected, theatre.getSeatingArrangement()); + } + + @Test + void testSellSeats() { + theatre.sellSeats("A1", "A2"); // test sell seat function + String expected = " |STAGE| \n" + + " [✓][✓][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + "\n" + + "Tier 1 Seats (rows E and F): " + theatre.getTierOneSeats() + "\n" + + "Tier 2 Seats (rows C and D): " + theatre.getTierTwoSeats() + "\n" + + "Tier 3 Seats (rows A and B): " + theatre.getTierThreeSeats() + "\n"; + assertEquals(expected, theatre.getSeatingArrangement()); + assertEquals(60, theatre.getProfit()); + Seat[][] seats = theatre.getSeats(); + assertTrue(seats[0][0].isSold()); + assertTrue(seats[0][1].isSold()); + + theatre.sellSeats("A0"); // test none existent seat + assertEquals(expected, theatre.getSeatingArrangement()); + assertEquals(60, theatre.getProfit()); + } + + @Test + void testReassignSeat() { + theatre.sellSeats("A1"); + theatre.reassignSeat("A1", "C2"); // successful reassignment of seats. + String expected = " |STAGE| \n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✓][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + "\n" + + "Tier 1 Seats (rows E and F): " + theatre.getTierOneSeats() + "\n" + + "Tier 2 Seats (rows C and D): " + theatre.getTierTwoSeats() + "\n" + + "Tier 3 Seats (rows A and B): " + theatre.getTierThreeSeats() + "\n"; + + assertEquals(expected, theatre.getSeatingArrangement()); + theatre.reassignSeat("A1", "A0"); // unsuccessful reassignment of seats. + assertEquals(expected, theatre.getSeatingArrangement()); + } + + @Test + void testRemoveSeat() { + theatre.sellSeats("A1", "A2", "A3", "F3"); + assertEquals(110, theatre.getProfit()); + theatre.removeSeat("A3"); + String expected = " |STAGE| \n" + + " [✓][✓][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✘][✘][✘][✘][✘][✘][✘][✘]\n" + + " [✘][✘][✓][✘][✘][✘][✘][✘][✘][✘]\n" + + "\n" + + "Tier 1 Seats (rows E and F): " + theatre.getTierOneSeats() + "\n" + + "Tier 2 Seats (rows C and D): " + theatre.getTierTwoSeats() + "\n" + + "Tier 3 Seats (rows A and B): " + theatre.getTierThreeSeats() + "\n"; + assertEquals(80, theatre.getProfit()); + assertEquals(expected, theatre.getSeatingArrangement()); + assertEquals(20, theatre.removeSeat("F3")); + assertEquals(0, theatre.removeSeat("F3")); + theatre.removeSeat("A0"); + assertEquals(60, theatre.getProfit()); + } + + @Test + void testWriteToFile() { + String expected = "Test Show | 0.000000 | 20.000000\n"; + assertEquals(expected, theatre.writeToFile()); + } +} \ No newline at end of file diff --git a/src/test/java/optix/util/ParserTest.java b/src/test/java/optix/util/ParserTest.java new file mode 100644 index 0000000000..f3fd965d9a --- /dev/null +++ b/src/test/java/optix/util/ParserTest.java @@ -0,0 +1,627 @@ +package optix.util; + +import optix.commands.ByeCommand; +import optix.commands.Command; +import optix.commands.TabCommand; +import optix.commands.finance.ViewMonthlyCommand; +import optix.commands.finance.ViewProfitCommand; +import optix.commands.parser.AddAliasCommand; +import optix.commands.parser.ListAliasCommand; +import optix.commands.parser.RemoveAliasCommand; +import optix.commands.parser.ResetAliasCommand; +import optix.commands.seats.ReassignSeatCommand; +import optix.commands.seats.SellSeatCommand; +import optix.commands.seats.ViewSeatsCommand; +import optix.commands.shows.EditCommand; +import optix.commands.shows.ListCommand; +import optix.commands.shows.ListDateCommand; +import optix.commands.shows.RescheduleCommand; +import optix.commands.shows.ListShowCommand; +import optix.commands.shows.DeleteCommand; +import optix.commands.shows.AddCommand; +import optix.exceptions.OptixException; +import optix.exceptions.OptixInvalidCommandException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +//@@author OungKennedy +class ParserTest { + private static File currentDir = new File(System.getProperty("user.dir")); + private File filePath = new File(currentDir.toString() + "\\src\\test\\data\\testOptix"); + private Parser testParser; + + @BeforeEach + void init() { + testParser = new Parser(filePath); + testParser.resetPreferences(); + } + + @Test + @DisplayName("Test bye command") + void testBye() { + Command testByeCommand = null; + try { + testByeCommand = testParser.parse("bye"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(testByeCommand.getClass(), ByeCommand.class); + } + + @Test + @DisplayName("Test list command") + void testList() { + Command testListCommand = null; + try { + testListCommand = testParser.parse("list"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(testListCommand.getClass(), ListCommand.class); + } + + @Test + @DisplayName("Test reset alias command") + void testResetAlias() { + Command outputCommand = null; + try { + outputCommand = testParser.parse("reset-alias"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ResetAliasCommand.class); + } + + @Test + @DisplayName("Test list alias command") + void testListAlias() { + Command outputCommand = null; + try { + outputCommand = testParser.parse("list-alias"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ListAliasCommand.class); + } + + @Test + @DisplayName("test help") + void testHelp() { + Command outputCommand = null; + try { + outputCommand = testParser.parse("help"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), TabCommand.class); + } + + @Test + @DisplayName("test archive") + void testArchive() { + Command outputCommand = null; + try { + outputCommand = testParser.parse("archive"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), TabCommand.class); + } + + @Test + @DisplayName("test finance") + void testFinance() { + Command outputCommand = null; + try { + outputCommand = testParser.parse("finance"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), TabCommand.class); + } + + @Test + @DisplayName("invalid single word input") + void testInvalidSingleWordInput() { + String invalidInput = "invalidInput"; + Throwable e = null; + try { + testParser.parse(invalidInput); + } catch (Throwable ex) { + e = ex; + } + assertTrue(e instanceof OptixInvalidCommandException); + } + + @Test + @DisplayName("test edit command") + void testEdit() { + // input will not work but an editcommand is still returned + String testInput = "edit testShow|testDate|newShowName"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), EditCommand.class); + } + + @Test + @DisplayName("test sell command") + void testSellSeat() { + // input will not work but an actual command is still returned + String testInput = "sell input"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), SellSeatCommand.class); + } + + @Test + @DisplayName("test view command") + void testViewSeat() { + // input will not work but an actual command is still returned + String testInput = "view input"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ViewSeatsCommand.class); + } + + @Test + @DisplayName("test reschedule command") + void testReschedule() { + // input will not work but an actual command is still returned + String testInput = "reschedule input"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), RescheduleCommand.class); + } + + @Test + @DisplayName("test list date command") + void testparseListDate() { + // input will not work but an actual command is still returned + String testInput = "list Jan 2020"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ListDateCommand.class); + } + + @Test + @DisplayName("test list show command") + void testparseListShow() { + // input will not work but an actual command is still returned + String testInput = "list testShow"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ListShowCommand.class); + } + + @Test + @DisplayName("test two words bye command") + void testByeForTwoWords() { + String testInput = "bye VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ByeCommand.class); + } + + @Test + @DisplayName("test add") + void testAdd() { + String testInput = "add poto|25|25/12/2020"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), AddCommand.class); + } + + @Test + @DisplayName("test delete") + void testDelete() { + String testInput = "delete VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), DeleteCommand.class); + } + + @Test + @DisplayName("test view profit") + void testViewProfit() { + String testInput = "view-profit VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ViewProfitCommand.class); + } + + @Test + @DisplayName("test view monthly") + void testViewMonthly() { + String testInput = "view-monthly VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ViewMonthlyCommand.class); + } + + @Test + @DisplayName("test add alias command") + void testAddAliasCommand() { + String testInput = "add-alias VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), AddAliasCommand.class); + } + + @Test + @DisplayName("test remove alias command") + void testRemoveAliasCommand() { + String testInput = "remove-alias VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), RemoveAliasCommand.class); + } + + @Test + @DisplayName("test two words bye command") + void testReassignSeatCommand() { + String testInput = "reassign-seat VV"; + Command outputCommand = null; + try { + outputCommand = testParser.parse(testInput); + } catch (OptixException e) { + fail("Should not throw exception"); + } + assertEquals(outputCommand.getClass(), ReassignSeatCommand.class); + } + + @Test + @DisplayName("invalid two word input") + void testInvalidTwoWordInput() { + String invalidInput = "invalidInput VV"; + Throwable e = null; + try { + testParser.parse(invalidInput); + } catch (Throwable ex) { + e = ex; + } + assertTrue(e instanceof OptixInvalidCommandException); + } + + @Test + @DisplayName("test add alias- bye") + void testAddAliasMethod_bye() { + try { + testParser.addAlias("VV","bye"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "bye"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- list") + void testAddAliasMethod_list() { + try { + testParser.addAlias("VV","list"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "list"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- help") + void testAddAliasMethod_help() { + try { + testParser.addAlias("VV","help"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "help"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- edit") + void testAddAliasMethod_edit() { + try { + testParser.addAlias("VV","edit"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "edit"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- sell") + void testAddAliasMethod_sell() { + try { + testParser.addAlias("VV","sell"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "sell"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- view") + void testAddAliasMethod_view() { + try { + testParser.addAlias("VV","view"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "view"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- reschedule") + void testAddAliasMethod_reschedule() { + try { + testParser.addAlias("VV","reschedule"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "reschedule"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- add") + void testAddAliasMethod_add() { + try { + testParser.addAlias("VV","add"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "add"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- delete") + void testAddAliasMethod_delete() { + try { + testParser.addAlias("VV","delete"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "delete"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- reassign-seat") + void testAddAliasMethod_reassign_seat() { + try { + testParser.addAlias("VV","reassign-seat"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "reassign-seat"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- show") + void testAddAliasMethod_show() { + try { + testParser.addAlias("VV","show"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "show"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- archive") + void testAddAliasMethod_archive() { + try { + testParser.addAlias("VV","archive"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "archive"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- finance") + void testAddAliasMethod_finance() { + try { + testParser.addAlias("VV","finance"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "finance"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- view-profit") + void testAddAliasMethod_view_profit() { + try { + testParser.addAlias("VV","view-profit"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "view-profit"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- view-monthly") + void testAddAliasMethod_view_monthly() { + try { + testParser.addAlias("VV","view-monthly"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "view-monthly"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- add-alias") + void testAddAliasMethod_add_alias() { + try { + testParser.addAlias("VV","add-alias"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "add-alias"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- remove-alias") + void testAddAliasMethod_remove_alias() { + try { + testParser.addAlias("VV","remove-alias"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "remove-alias"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- list-alias") + void testAddAliasMethod_list_alias() { + try { + testParser.addAlias("VV","list-alias"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "list-alias"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("test add alias- reset-alias") + void testAddAliasMethod_reset_alias() { + try { + testParser.addAlias("VV","reset-alias"); + } catch (OptixException e) { + fail("Should not throw exception"); + } + String expected = "reset-alias"; + String actual = Parser.commandAliasMap.get("VV"); + assertEquals(expected, actual); + } + + @Test + @DisplayName("invalid add-alias: using command keyword as alias") + void testInvalidAddAliasInput1() { + Throwable e = null; + try { + testParser.addAlias("delete","add"); + } catch (Throwable ex) { + e = ex; + } + assertTrue(e instanceof OptixException); + } + + @Test + @DisplayName("invalid add-alias: using pipe symbol") + void testInvalidAddAliasInput2() { + Throwable e = null; + try { + testParser.addAlias("d|e","add"); + } catch (Throwable ex) { + e = ex; + } + assertTrue(e instanceof OptixException); + } + + @Test + @DisplayName("invalid add-alias: using existing alias") + void testInvalidAddAliasInput3() { + Throwable e = null; + try { + testParser.addAlias("a","delete"); + } catch (Throwable ex) { + e = ex; + } + assertTrue(e instanceof OptixException); + } + +} \ No newline at end of file diff --git a/text-ui-test/ACTUAL.TXT b/text-ui-test/ACTUAL.TXT new file mode 100644 index 0000000000..7132ae8f48 --- /dev/null +++ b/text-ui-test/ACTUAL.TXT @@ -0,0 +1,29 @@ +Hello from + ____ _ +| _ \ _ _| | _____ +| | | | | | | |/ / _ \ +| |_| | |_| | < __/ +|____/ \__,_|_|\_\___| + __________________________________ + Hello! I'm duke + What can I do for you? + __________________________________ + + __________________________________ +Invalid Command __________________________________ + + __________________________________ + Got it. I've added this task: + [E][?] hello (at: 13th of December 1997, 12pm) + + Now you have 3 tasks in your list. + __________________________________ + + __________________________________ + ☹ OOPS!!! This task has already been completed + __________________________________ + + __________________________________ + Bye. Hope to see you again soon! + __________________________________ + diff --git a/text-ui-test/EXPECTED.txt b/text-ui-test/EXPECTED.txt new file mode 100644 index 0000000000..0a46caaa2a --- /dev/null +++ b/text-ui-test/EXPECTED.txt @@ -0,0 +1,34 @@ +Hello from + ____ _ +| _ \ _ _| | _____ +| | | | | | | |/ / _ \ +| |_| | |_| | < __/ +|____/ \__,_|_|\_\___| + __________________________________ + Hello! I'm duke + What can I do for you? + __________________________________ + + __________________________________ + Got it. I've added this task: + [T][✘] return book + + Now you have 1 tasks in your list. + __________________________________ + + __________________________________ + Got it. I've added this task: + [E][✘] hello (at: 13th of December 1997, 12pm) + + Now you have 2 tasks in your list. + __________________________________ + + __________________________________ + Nice! I've marked this task as done: + [E][✓] birthday party (at: 12 Aug 1200) + + __________________________________ + + __________________________________ + Bye. Hope to see you again soon! + __________________________________ \ No newline at end of file diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt new file mode 100644 index 0000000000..807976abe1 --- /dev/null +++ b/text-ui-test/input.txt @@ -0,0 +1,4 @@ +todo return book +event hello /at 13/12/1997 1200 +done 2 +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat new file mode 100644 index 0000000000..cfff38eab3 --- /dev/null +++ b/text-ui-test/runtest.bat @@ -0,0 +1,21 @@ +@ECHO OFF + +REM create bin directory if it doesn't exist +if not exist ..\bin mkdir ..\bin + +REM delete output from previous run +del ACTUAL.TXT + +REM compile the code into the bin folder +javac -cp ..\bin -Xlint:none -d ..\bin @source.txt +IF ERRORLEVEL 1 ( + echo ********** BUILD FAILURE ********** + exit /b 1 +) +REM no error here, errorlevel == 0 + +REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT +java -cp ..\bin Duke < input.txt > ACTUAL.TXT + +REM compare the output to the expected output +FC ACTUAL.TXT EXPECTED.txt \ No newline at end of file diff --git a/text-ui-test/source.txt b/text-ui-test/source.txt new file mode 100644 index 0000000000..81666a79da --- /dev/null +++ b/text-ui-test/source.txt @@ -0,0 +1,19 @@ +../src/main/java/duke/Commands/AddCommand.java +../src/main/java/duke/Commands/ByeCommand.java +../src/main/java/duke/Commands/Command.java +../src/main/java/duke/Commands/DeleteCommand.java +../src/main/java/duke/Commands/DoneCommand.java +../src/main/java/duke/Commands/FindCommand.java +../src/main/java/duke/Commands/ListCommand.java +../src/main/java/duke/Constant/Duke_Response.java +../src/main/java/duke/DateFormatter.java +../src/main/java/duke/Exception/DukeException.java +../src/main/java/duke/Parser/Parser.java +../src/main/java/duke/Storage.java +../src/main/java/duke/Task/Deadline.java +../src/main/java/duke/Task/Event.java +../src/main/java/duke/Task/Task.java +../src/main/java/duke/Task/TaskList.java +../src/main/java/duke/Task/Todo.java +../src/main/java/duke/Ui.java +../src/main/java/duke.java diff --git a/text-ui-test/src/main/data/duke.txt b/text-ui-test/src/main/data/duke.txt new file mode 100644 index 0000000000..2b15b7d057 --- /dev/null +++ b/text-ui-test/src/main/data/duke.txt @@ -0,0 +1,3 @@ +E | 0 | hello | 13th of December 1997, 12pm +E | 1 | hello | 13th of December 1997, 12pm +E | 0 | hello | 13th of December 1997, 12pm diff --git a/tutorials/gradleTutorial.md b/tutorials/gradleTutorial.md index 08292b118d..3e8df2ada4 100644 --- a/tutorials/gradleTutorial.md +++ b/tutorials/gradleTutorial.md @@ -30,10 +30,10 @@ As a developer, you write a _build file_ that describes the project. A build fil git checkout master git merge gradle ``` -1. Open the `build.gradle` file in an editor. Update the following code block to point to the main class (i.e., the one containing the `main` method) of your application. The code below assumes your main class is `seedu.duke.Duke` +1. Open the `build.gradle` file in an editor. Update the following code block to point to the main class (i.e., the one containing the `main` method) of your application. The code below assumes your main class is `seedu.duke.duke` ```groovy application { - mainClassName = "seedu.duke.Duke" + mainClassName = "seedu.duke.duke" } ``` 1. To check if Gradle has been added to the project correctly, open a terminal window, navigate to the root directory of your project and run the command `gradlew run`. This should result in Gradle running the main method of your project. @@ -146,7 +146,7 @@ By convention, java tests belong in `src/test/java` folder. Create a new `test/j src ├─main │ └─java -│ └─seedu/duke/Duke.java +│ └─seedu/duke/duke.java └─test └─java └─seedu/duke/DukeTest.java diff --git a/tutorials/javaFxTutorialPart1.md b/tutorials/javaFxTutorialPart1.md index 561daeca43..58b2a705c1 100644 --- a/tutorials/javaFxTutorialPart1.md +++ b/tutorials/javaFxTutorialPart1.md @@ -44,7 +44,7 @@ javafx { ## Writing your first program -As customary, let’s start off with a simple “Hello World” program. Modify your `Duke` class to extend `javafx.application.Application`. This requires you to override the `Application#start()` method and provide a concrete implementation. Notice that the method signature for `Application#start()` has a parameter `Stage`. This is the _primary stage_ that JavaFX provides. +As customary, let’s start off with a simple “Hello World” program. Modify your `duke` class to extend `javafx.application.Application`. This requires you to override the `Application#start()` method and provide a concrete implementation. Notice that the method signature for `Application#start()` has a parameter `Stage`. This is the _primary stage_ that JavaFX provides. ```java import javafx.application.Application; @@ -52,7 +52,7 @@ import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.stage.Stage; -public class Duke extends Application { +public class duke extends Application { // ... @@ -80,7 +80,7 @@ import javafx.application.Application; */ public class Launcher { public static void main(String[] args) { - Application.launch(Duke.class, args); + Application.launch(duke.class, args); } } ``` diff --git a/tutorials/javaFxTutorialPart2.md b/tutorials/javaFxTutorialPart2.md index f24a0cd6ad..30bd16beac 100644 --- a/tutorials/javaFxTutorialPart2.md +++ b/tutorials/javaFxTutorialPart2.md @@ -1,8 +1,8 @@ -# JavaFX Tutorial Part 2 - Creating a GUI for Duke +# JavaFX Tutorial Part 2 - Creating a GUI for duke -In this tutorial, we will be creating a GUI for Duke from scratch based on the following mockup. +In this tutorial, we will be creating a GUI for duke from scratch based on the following mockup. -![Mockup for Duke](assets/DukeMockup.png) +![Mockup for duke](assets/DukeMockup.png) ## JavaFX controls @@ -34,7 +34,7 @@ But how do we get the exact layout we want in the UI? JavaFX provides that funct One way to obtain the layout in the mockup is as follows. -![Duke's layout](assets/DukeSceneGraph.png) +![duke's layout](assets/DukeSceneGraph.png) To get that layout, we create a new `AnchorPane` and add our controls to it. Similarly, we create a new `VBox` to hold the contents of the `ScrollPane`. The code should look something like this: @@ -49,7 +49,7 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; -public class Duke extends Application { +public class duke extends Application { private ScrollPane scrollPane; private VBox dialogContainer; @@ -88,7 +88,7 @@ public class Duke extends Application { Run the application and you should see something like this: -![Duke's raw layout](assets/RawLayout.png) +![duke's raw layout](assets/RawLayout.png) That is not what we were expecting, what did we forget to do? @@ -106,7 +106,7 @@ Add the following code to the bottom of the `start` method. You'll have to add ` //... //Step 2. Formatting the window to look as expected - stage.setTitle("Duke"); + stage.setTitle("duke"); stage.setResizable(false); stage.setMinHeight(600.0); stage.setMinWidth(400.0); @@ -141,7 +141,7 @@ Add the following code to the bottom of the `start` method. You'll have to add ` Run the application again. It should now look like this: -![Duke's Final layout](assets/FinalLayout.png) +![duke's Final layout](assets/FinalLayout.png) ## Exercises diff --git a/tutorials/javaFxTutorialPart3.md b/tutorials/javaFxTutorialPart3.md index a9e1bdddd3..cea5319ae6 100644 --- a/tutorials/javaFxTutorialPart3.md +++ b/tutorials/javaFxTutorialPart3.md @@ -8,7 +8,7 @@ Rather than to do everything in one try, let’s iterate and build up towards ou JavaFX has an _event-driven architecture style_. As such, we programmatically define _handler_ methods to execute as a response to certain _events_. When an event is detected, JavaFX will call the respective handlers. -For Duke, there are two events that we want to respond to, namely the user pressing `Enter` in the `TextField` and left-clicking the `Button`. These are the `onAction` event for the `TextField` and the `onMouseClicked` event for the `Button`. +For duke, there are two events that we want to respond to, namely the user pressing `Enter` in the `TextField` and left-clicking the `Button`. These are the `onAction` event for the `TextField` and the `onMouseClicked` event for the `Button`. For now, let’s have the application add a new `Label` with the text from the `TextField`. Update the `Main` class as follows. You'll need to add an `import javafx.scene.control.Label;` too. ```java @@ -103,7 +103,7 @@ import javafx.scene.image.ImageView; ``` Next, add two images to the `main/resources/images` folder. -For this tutorial, we have two images `DaUser.png` and `DaDuke.png` to represent the user avatar and Duke's avatar respectively but you can use any image you want. +For this tutorial, we have two images `DaUser.png` and `DaDuke.png` to represent the user avatar and duke's avatar respectively but you can use any image you want. Image|Filename ---|--- @@ -112,7 +112,7 @@ Image|Filename ```java -public class Duke extends Application { +public class duke extends Application { // ... private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); @@ -124,7 +124,7 @@ Add a new method to handle user input: ```java /** * Iteration 2: - * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * Creates two dialog boxes, one echoing user input and the other containing duke's reply and then appends them to * the dialog container. Clears the user input after processing. */ private void handleUserInput() { @@ -142,7 +142,7 @@ private void handleUserInput() { * Replace this stub with your completed method. */ private String getResponse(String input) { - return "Duke heard: " + input; + return "duke heard: " + input; } ``` @@ -170,7 +170,7 @@ Run the program and see how it works. ## Iteration 3 – Adding custom behavior to DialogBox -One additional benefit of defining a custom control is that we can add behavior specific to our `DialogBox`. Let’s add a method to flip a dialog box such that the image on the left to differentiate between user input and Duke’s output. +One additional benefit of defining a custom control is that we can add behavior specific to our `DialogBox`. Let’s add a method to flip a dialog box such that the image on the left to differentiate between user input and duke’s output. ```java /** @@ -224,7 +224,7 @@ Run the application and play around with it. ![DialogBoxes Iteration 3](assets/DialogBoxesIteration3.png) Congratulations! -You have successfully implemented a fully functional GUI for Duke! +You have successfully implemented a fully functional GUI for duke! ## Exercises diff --git a/tutorials/javaFxTutorialPart4.md b/tutorials/javaFxTutorialPart4.md index 0e0ab280c4..d30e57e8d0 100644 --- a/tutorials/javaFxTutorialPart4.md +++ b/tutorials/javaFxTutorialPart4.md @@ -29,7 +29,7 @@ FXML is a XML-based language that allows us to define our user interface. Proper The FXML snippet define a TextField similar to the one that we programmatically defined previous in Tutorial 2. Notice how concise FXML is compared to the plain Java version. -Let's return to Duke and convert it to use FXML instead. +Let's return to duke and convert it to use FXML instead. # Rebuilding the Scene using FXML @@ -48,15 +48,15 @@ Create the following files in `src/main/resources/view`: - - -