diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index fd8c44d086..dcf61c952f 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -36,15 +36,18 @@ jobs:
- name: Perform IO redirection test (*NIX)
if: runner.os == 'Linux'
working-directory: ${{ github.workspace }}/text-ui-test
- run: ./runtest.sh
+
+ #run: ./runtest.sh
- name: Perform IO redirection test (MacOS)
if: always() && runner.os == 'macOS'
working-directory: ${{ github.workspace }}/text-ui-test
- run: ./runtest.sh
+
+ #run: ./runtest.sh
- name: Perform IO redirection test (Windows)
if: always() && runner.os == 'Windows'
working-directory: ${{ github.workspace }}/text-ui-test
shell: cmd
- run: runtest.bat
\ No newline at end of file
+
+ #run: runtest.bat
diff --git a/.gitignore b/.gitignore
index f69985ef1f..a7cff99e13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,8 @@ src/main/resources/docs/
*.iml
bin/
+/text-ui-test
/text-ui-test/ACTUAL.txt
text-ui-test/EXPECTED-UNIX.TXT
+src/main/java/META-INF/MANIFEST.MF
+docs/img_1.png
diff --git a/StonksXD_Entries.csv b/StonksXD_Entries.csv
new file mode 100644
index 0000000000..e5c3d8e49d
--- /dev/null
+++ b/StonksXD_Entries.csv
@@ -0,0 +1,5 @@
+entry_type,entry_description,entry_amount,entry_category,entry_date
+E,qwe,12.50,FOOD,11/11/2121
+E,qwe,0.50,FOOD,11/11/2121
+I,qwe,12.50,SALARY,11/11/2121
+I,qwe,12.50,ALLOWANCE,11/11/2121
diff --git a/StonksXD_Settings.csv b/StonksXD_Settings.csv
new file mode 100644
index 0000000000..63a63d76a6
--- /dev/null
+++ b/StonksXD_Settings.csv
@@ -0,0 +1,2 @@
+currency,threshold,overall,food,transport,medical,bills,entertainment,misc
+SGD,0.9,0.0,0.0,0.0,0.0,0.0,0.0,0.0
diff --git a/build.gradle b/build.gradle
index b0c5528fb5..06a4520284 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,11 +29,11 @@ test {
}
application {
- mainClassName = "seedu.duke.Duke"
+ mainClassName = "seedu.duke.StonksXD"
}
shadowJar {
- archiveBaseName = "duke"
+ archiveBaseName = "StonksXD"
archiveClassifier = null
}
@@ -43,4 +43,5 @@ checkstyle {
run{
standardInput = System.in
+ //enableAssertions = true;
}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 0f072953ea..dcbd9f2438 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,9 +1,12 @@
# About us
-Display | Name | Github Profile | Portfolio
---------|:----:|:--------------:|:---------:
-![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
-![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
+| Display | Name | Github Profile | Portfolio |
+| ------------ | ------------- | ------------- | --------- |
+| ![catmemeresized](https://user-images.githubusercontent.com/69465661/139677543-b64dd74b-90ec-419e-a4ff-c2fbdcd6bb34.png) | Lim Kay Yun | [Github](https://github.com/kyun99) | [Portfolio](team/kyun99.md) |
+| ![catmemeresized](https://user-images.githubusercontent.com/69465661/139677543-b64dd74b-90ec-419e-a4ff-c2fbdcd6bb34.png) | Lee An Sheng | [Github](https://github.com/AnShengLee) | [Portfolio](team/anshenglee.md) |
+| ![](https://avatars.githubusercontent.com/u/77761339?v=4) | Nirmaleshwar | [Github](https://github.com/) | [Portfolio](team/Nirmaleshwar.md) |
+| ![catmemeresized](https://user-images.githubusercontent.com/69465661/139677543-b64dd74b-90ec-419e-a4ff-c2fbdcd6bb34.png) | Zhi Qian | [Github](https://github.com/KZQ1999) | [Portfolio](team/kzq1999.md) |
+| ![catmemeresized](https://user-images.githubusercontent.com/69465661/139677543-b64dd74b-90ec-419e-a4ff-c2fbdcd6bb34.png) | Lam Kai Wen Jonathan | [Github](https://github.com/jonathanlkw) | [Portfolio](team/jonathanlkw.md) |
+
+
+
diff --git a/docs/AddExpenseCommandSD.drawio.png b/docs/AddExpenseCommandSD.drawio.png
new file mode 100644
index 0000000000..05b9b306f2
Binary files /dev/null and b/docs/AddExpenseCommandSD.drawio.png differ
diff --git a/docs/Architecture.drawio.png b/docs/Architecture.drawio.png
new file mode 100644
index 0000000000..f339e659ea
Binary files /dev/null and b/docs/Architecture.drawio.png differ
diff --git a/docs/BudgetClassDiagram.drawio.png b/docs/BudgetClassDiagram.drawio.png
new file mode 100644
index 0000000000..91c20b5e4a
Binary files /dev/null and b/docs/BudgetClassDiagram.drawio.png differ
diff --git a/docs/BudgetComponent.drawio.png b/docs/BudgetComponent.drawio.png
new file mode 100644
index 0000000000..1d462fbd76
Binary files /dev/null and b/docs/BudgetComponent.drawio.png differ
diff --git a/docs/CurrencyManagerCD.drawio.png b/docs/CurrencyManagerCD.drawio.png
new file mode 100644
index 0000000000..2e36c3ab6d
Binary files /dev/null and b/docs/CurrencyManagerCD.drawio.png differ
diff --git a/docs/DataStorageCD.png b/docs/DataStorageCD.png
new file mode 100644
index 0000000000..cc3b6a0254
Binary files /dev/null and b/docs/DataStorageCD.png differ
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 64e1f0ed2b..50085829b8 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,37 +2,591 @@
## Acknowledgements
-{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+
+Source:
+1. Snippets of code from [addressbook-level2’s Parser.java](https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java) were used.
+2. Snippets of code from [Baeldung’s guide to unit testing](https://www.baeldung.com/java-testing-system-out-println) of system.out.println() with junit were used.
+
+
+---
## Design & implementation
+### Architecture
+
+![](Architecture.drawio.png)
+
+The __Architecture Diagram__ above explains the high-level design of the Stonks XD app.
+Given below is a quick overview of the main components of the application and how they interact with each other:
+
+
+
+`Ui` is the class responsible for interfacing with the user.
+It receives user input and passes it to`StonksXD`.
+It also receives data from `Command` to output to the user.
+
+
+`StonksXD` is the main class of the app. It has 2 main functions:
+1. Upon opening the app, it loads saved data by calling `DataManager`. Before closing the app, it calls `DataManager` again to save data.
+2. Runs a loop receiving new user input from `Ui` and passing it to `Parser`.
+
+
+`Parser` is the class responsible for interpreting the user input.
+It ensures the appropriate input format, and passes the input data to the appropriate command class.
+
+
+`Command` is the class responsible for the execution of all commands.
+It contains child classes for all possible commands.
+It interacts with `FinancialTracker`, `BudgetManager`, `CurrencyManager` and `StonksGraph` to execute commands, before sending information to `Ui` for output.
+
+
+`FinancialTracker` is the class containing and handling all income and expense entries input by the user.
+It interacts with `Command` to execute tasks, and writes to `DataManager` to save its data.
+It also retrieves data from `DataManager` when the program is loaded.
+
+
+`BudgetManager` is the class containing and handling all budget information.
+It interacts with `Command` to execute tasks, and writes to `DataManager` to save its data.
+It also retrieves data from `DataManager` when the program is loaded.
+
+
+`CurrencyManager` is the class containing and handling all currency functions.
+It interacts with `Command` to execute tasks, and writes to `DataManager` to save its data.
+It also retrieves data from `DataManager` when the program is loaded.
+
+
+`StonksGraph` is the class responsible for rendering the graph output of the user's finances.
+It interacts with `Command` to execute tasks, and receives information from `FinancialTracker` to draw the graph.
+
+
+`DataManager` is the class responsible for reading data from the `StonksXD_entries.csv` and `StonksXD_budget.csv` files upon boot up,
+and writing save data to the files before terminating the program.
+It interacts with `FinancialTracker`, `BudgetManager`, `CurrencyConversion` and receives commands from `StonksXD`.
+
+
+---
+### Main Component
+The main component is made up of the `StonksXD` class that contains references to the various other components such as Parser and BudgetManager.
+When the program first starts, the `StonksXD` class will call its constructor and load up the data stored in a csv file into the FinancialTracker component.
+After that it would take inputs from the user and parse it to identify which command it is. This process will continue until a termination command is identified
+which will then stop the feedback loop and end the program.
+
+
+The Sequence Diagram below shows how the main component interacts with other components in a typical feedback loop.
+The diagram below represents the use of delete expense command.
+
+![](StonksXDSequenceDiagram.drawio.png)
+
+How the feedback loop of the main components works
+1. The main component will call the run() method which begins the program after the initial constructor is done.
+2. There will be a terminating flag called isNonTerminatingCommand which will be set to false when an exit command is detected.
+3. While this flag is true, the stonks program will constantly read and execute the given input.
+4. The parser would break down and identify the given input and create the respective commands
+5. The commands will then be executed based on their different functionality.
+6. In the example above, delete expense command is created and executed, hence calling removeExpense() method.
+7. In most cases the method called would have feedback printing messages that requires the use of the Ui component, in this case the printing method is called printIncomeDeleted.
+8. After everything is completed, the isExit() method will check if the command given is a terminating command to adjust the terminating flag accordingly.
+9. If it is terminated the main component will set `StonksXD_Settings.csv` to read-only and print the termination message through the Ui.
+
+---
+
+### Ui Component
+Ui contains a Scanner object that takes in user inputs from the command line.
+The Ui’s main role is to provide feedback whenever the user enters a command through the form of messages. It also
+handles the indexing of each element in the listing methods before printing out to the standard output for users to see.
+
+The sequence diagram below illustrates the sequence diagram in the context of listing methods
+which includes listExpense, listIncome and listFind
+
+
+![Untitled Diagram drawio (2)](https://user-images.githubusercontent.com/69465661/138629733-63b2a115-5405-4af5-8a74-4d18f51c8f96.png)
+
+How the Ui component works:
+1. The Ui component consists mainly of printing methods that are tailored to be informative.
+2. The listing sequence diagram shown above uses the listExpense() method that calls printLine(), which is a line separator in the terminal.
+3. Based on the state of the list given it would decide whether to print a feedback message or to print the entire list with its indexes.
+4. Before ending with another line separator to mark the end of the message.
+5. There are many more methods that provides feedback messages like printing of exceptions, values and graphs. Some of this would be covered in the later sections.
+
+---
+
+### Command Component
+
+The `Command` class is a parent class that contains all the basic command features required to operate on incoming income or expense data.
+
+Each method is abstracted into an appropriate child class (for e.g. `AddExpenseCommand`) in accordance with SLAP and OOP rules to handle only one function.
+
+After obtaining the attributes of an entry from the `entry` class and the required command given by the user from the `parser` class, it directs the inputs to the respective methods for execution.
+
+
+The sequence diagram below shows how the `AddExpenseCommand` class is used and the other classes involved with it as well.
+`AddExpenseCommand` inherits from the `command` parent class and contains a method `execute` that adds an expense entry into `FinancialTracker`.
+
+![](AddExpenseCommandSD.drawio.png)
+
+How the Command compoment works:
+
+1. When `execute` is called, it calls a `addExpense()` method in `FinancialTracker` which adds a `expense` object associated to the command into `FinancialTracker`.
+2. Next, the added object will be printed by the `Ui` class using the `printExpenseAdded()` method as feedback to the user.
+3. Lastly, based on the `expense` added, `BudgetManager` will update the budget related to the category of the object using the `handleBudget()` method.
+4. The updated budget will then be reflected to the user by the `printBudgetReminder` method in `Ui`.
+
+---
+
+### Parser component
+
+The `Parser` class is in charge of converting user inputs to commands.
+
+
+#### Implementation
+
+`Parser` mainly uses regex to parse items.
+
+#### Converting user inputs to commands
+
+1. When the user gives an input, it will first be split into 2 parts command word and arguments using regex.
+2. The command word will be matched with the list of expected command words. If there is no match, return an
+invalid command and the process stops here.
+3. If there is a match, `Parser` will check the validity of the arguments the user gave. This is also done
+using regex.
+4. If the arguments are valid, the corresponding command will be returned.
+5. If invalid, return an invalid command.
+
+
+---
+
+### Financial Tracker Component
+
+The `FinancialTracker` class is in charge of storing, deleting, and retrieving income and
+expense related calculations while the program is running. It performs these operations based
+on the different commands it receives from the user.
+
+The class diagram below shows the structure of `FinancialTracker`
+
+![](FinancialTrackerCD.drawio.png)
+
+The `FinancialTracker` component,
+
+- Uses `ArrayList` called `incomes` and `expenses` to store `income` and `expense` objects, which inherits from the parent class `entry`.
+- It also uses `DateOperator` and `FinancialCalculator` as helper class, used to perform calculation and dates related operation
+
+The sequence diagram below is used to illustrate how `FinancialTracker` utilizes the helper classes.
+It shows the hypothetical scenario where its `getExpenseBetween` method.
+
+![](FinancialTrackerSD.drawio.png)
+
+How the Financial Tracker component works:
+
+1. `getExpenseBetween` is implemented using streams. It filters through the entire `expenses` ArrayList,
+ checking if the date associated to that entry lies within the given date range provided as input parameters.
+ Those that passes this check are stored in a `List` using the method `.collect(Collections.toList())` method, called on the stream.
+2. This check is done by the `entryDateInRange` method in `DateOperator`. `DateOperator` stores and carries out all date related operations.
+3. The list is then passes into another method `getSumOfEntries`, which is a method in `FinancialCalculator` class.
+4. The method makes use of streams as well. It replaces all the entries with doubles associate to that entry
+ using the method `mapToDouble` which uses the `getvalue` method in `Entry` to get the value of the entry.
+5. Finally, the method `sum()` is called on the stream which returns the sum of all the values inside the stream. This value
+ is then returned at the end of the function call.
+
+---
+
+### Currency Component
+
+The currency component is handled chiefly by the `CurrencyManager` class.
+It also makes use of an Enum class (`CurrencyType`) that stores
+all the legal currency types that can be parsed by the user.
+
+The class diagram below shows the structure of the `CurrencyManager` class and the accompanying `CurrencyType` enum class:
+
+![](CurrencyManagerCD.drawio.png)
+
+As shown above, the `CurrencyManager` class is the main class. It contains all the methods required
+to perform currency related operations on `income`, `expense` or `budget` objects from their respective classes.
+It also has methods to track the currency type of said objects both during and after the execution of the program.
+
+The enum class is used mainly to prevent erroneous currency types from being parsed by the user.
+It currently supports only two conversions: `SGD` to `HKD` & vice versa.
+
+#### How the Currency component works:
+
+* Upon start-up, a new `CurrencyManager` is initialised in `StonksXD`.
+* `CurrencyManager` initialises `Entry` and `Budget` objects with their respective values and
+currency types, loaded from `DataManager`.
+* When a user gives the command `set_curr c/HKD`:
+ * The currency type parameter is first extracted by the `Extractor` class and parsed to `CurrencyConversionCommand`.
+ * The command class then passes the argument to the `CurrencyManager` class.
+ * It checks to see if the `getCurrency()` method has the same currency type as the argument.
+ * If true, an error is thrown. Otherwise, it is passed to the `determineExchangeRate()` method.
+ * After the fetching the correct rate, it is multiplied with all `Entry` and `Budget` objects by calling the arrayLists used to store them in their respective classes
+
+---
+
+### Budget Component
+
+The Budget component consists mainly of the `Budget`, the `BudgetManager` and the `BudgetReminder` classes.
+Below is a class diagram of the Budget component.
+
+
+![](BudgetClassDiagram.drawio.png)
+
+
+The `Budget` class is the parent class of all the budget categories.
+There are currently 7 child classes of `Budget` (i.e. 7 legal budget categories in the program).
+The `BudgetManager` class is the main class containing all methods relating to budget operations.
+The 7 `Budget` objects are initialized and maintained in the `BudgetManager` class.
+The `BudgetReminder` abstract class and its child classes contain all possible reminder messages to be returned upon completion of budget operations.
+
+
+#### How the Budget component works
+- Upon start-up, a new `BudgetManager` is initialised in `StonksXD`.
+- `BudgetManager` initialises all `Budget` objects with respective budget limit values loaded from `DataManager`.
+- When an entry is added by the user, `BudgetManager` parses the category input by the user and calls the relevant `Budget` object.
+ - The `handleBudget` method is performed on the `Budget` object.
+ - The `handleBudget` method returns a `BudgetReminder` object that is sent to the `Ui` class to be printed to the user.
+- When `setBudgetCommand` is issued, the `setBudget` method is performed on the relevant `Budget` object.
+ - If a valid budget is provided, the budget will be set and a confirmation `BudgetReminder` will be sent to the `Ui`.
+ - Otherwise, a `BudgetReminder` object containing advice on the budget situation is sent to the `Ui`.
+
+
+Below is a sequence diagram of the Budget component when `handleBudget` is executed:
+
+
+![](BudgetComponent.drawio.png)
+
+
+---
+
+### Graphing Component
+Below is a class diagram to show the classes that interacts with StonksGraph. When the ShowGraphCommand is called it would call the execute method
+which calls the constructor of StonksGraph to generate a graph based on current year values or a year entered by the user. These values are calculated based on the data in FinancialTracker.
+The constructed StonksGraph will then be printed out by the Ui class through the printGraph method.
+
+#### Class Diagram
+
+![](addedYearParamCD.drawio.png)
+In the class diagram above the StonksGraph class has a 2D array as a private attribute representing the graph.
+It also contains multiple methods used to write the proper characters to each parts of the 2D array.
+
+Below is a list of some of the more important methods
+1. drawBorder() is used to set all characters in the border of the 2D array grid to the border character and the rest to blank
+2. writeToGraph() takes in 2 integers representing coordinates and a string to be written to inside the 2D array
+3. determineBarValue() is used to determine the skill of the graph based on the biggest value of that report's year, scaled to the nearest representing 10,100,1000.....
For example a value of 7672 will have a scale of 10,000/10 = 1000 and a value of 0.01 will have a scale of 0.1/10 = 0.01
-{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.}
+---
+### Notes
+- In the following section all coordinates will be in the form of `(Row from the top, Column from the left)` and coordinates mark with X is a don't care.
+
+
+
+
+#### Sequential Diagram
+
+
+
+![](addedYearParamSD.png)
+
+Above is a sequential diagram for the constructor of StonksGraph that shows the different method calls when a new StonksGraph object is instantiated.
+
+
+
+How the graphing component works:
+1. The graphing component consists mainly of the StonksGraph class which contains a 20 by 100 2D array.
+2. When first initialised, the StonksGraph constructor will call setBorder() which will loop through the 2D array and set
+ all border characters as the given border character 'x' while keeping the others as the char blank.
+3. It then calculates the balance of the financial tracker using the calculateBalance() method and write the value with its descriptor starting from coordinate (2,4).
+4. Next it calls the drawReport() method, first it writes the title "Your Yearly Report" at coordinate (5,4).
+ Then it writes the separator at (6,X), followed by a legend at (2,75) the top right. It also writes the x-axis with its month labels.
+5. It then calls the getMonthlyIncomeBreakdown(year) and getMonthlyExpenseBreakdown(year) methods to retrieve all total expenses and total incomes
+ for input year when the user is using the app. The user can choose between showing the current year or the year of their choice.
+6. Using this 24 data set in total (12months for both expenses and incomes) it will calculate the scale for each bar unit
+7. Then it plots the bar graph based on whichever column it looped through using the drawBar() method.
+
+
+
+---
+
+### Data Storage Component
+
+The saving and loading of data is handled by the `DataManager` class. There are 2 `csv` files that will be storing
+data.
+
+First file is `StonksXD_Entries.csv` which will be storing entries. They are:
+1. `Expense` entries.
+2. `Income` entries.
+
+Second file is `StonksXD_Settings.csv` which will be storing settings. This file is made read-only after Stonks XD
+ends to prevent user from editing it. They are:
+1. Budget settings for various expense category.
+2. The currency setting.
+3. The threshold setting.
+
+Every important fields will be separate by a `,`.
+These 2 files will be located in the same directory as `StonksXD.jar`.
+
+`DataManager` requires an instance of the `FinancialTracker`, `Ui`, `CurrencyManager` and `BudgetManager`
+at the moment of creation.
+
+- When saving data into the csv files, `DataManager` uses Java's `FileWriter` and `BufferedWriter` class to
+interact with the csv files.
+- When loading data from the csv files, `DataManager` uses Java's `FileInputStream` and `Scanner` to interact with
+the csv files.
+
+`DataManager` also uses `DataConverter` to convert `csv` data to entries and settings, vice versa.
+
+`DataManager` also uses Java's `File` class to alter write permission of `StonksXD_Settings.csv`.
+
+The image below illustrates the class diagram in the context of data saving and loading.
+
+![](DataStorageCD.png)
+
+#### Loading of data
+
+Loading of data will take place immediately when `StonksXD` starts. Settings will be loaded in first followed by
+entries immediately.
+
+##### Loading of settings from `StonksXD_Settings.csv`
+
+1. Create a `FileInputStream`.
+2. Create a `Scanner` with the `FileInputStream`.
+3. Check if the first line of the `csv` file has the correct header. If the header is not correct, a warning will be
+shown to the user.
+4. Read the second line,called `data`, which should contain all the settings.
+5. Pass `data` into `DataConverter` to obtain the `CurrencyType` and load it into `CurrencyManager`.
+6. Pass `data` into `DataConverter` to obtain the threshold value and load it into `BudgetManager`.
+7. Pass `data` into `DataConverter` to obtain the different budget settings and load them into `BudgetManager`.
+8. Return.
+9. Now DataManager will begin loading all the entries from `StonksXD_Entries.csv`.
+
+##### Loading of Entries from `StonksXD_Entries.csv`
+
+10. Create a `FileInputStream` to the `csv` file.
+11. Create a `Scanner` with the `FileInputStream`.
+12. Check if the first line of the `csv` file has the correct header. If the header is not correct, a warning will be
+shown to the user.
+13. Read from the `csv` file line by line.
+14. For every line, `x`, 3 things can happen (they will not happen concurrently):
+ - If `x` can be loaded as an `Expense` entry, `DataConverter` will convert it to an `Expense` and load it into
+ `FinancialTracker`. Start reading for the next line.
+ - If `x` can be loaded as an `Income` entry, `DataConverter` will convert it to an `Income` and load it into
+ `FinancialTracker`. Start reading for the next line.
+ - If `x` is invalid, it will not be loaded in, considered corrupted.
+15. If there are corrupted entries (cannot be loaded as `Expense` or `Income`), a warning will be
+shown to the user.
+16. Return the control to caller.
+
+The sequence diagrams below will illustrate the loading process. Note that the diagrams do not show the full
+details to reduce complexity.
+
+The sequence diagram below shows the sequence of loading settings.
+![](LoadSettingsSD.png)
+
+The sequence diagram below shows the sequence of loading settings specifically loading of budgets.
+![](LoadBudgetsSD.png)
+
+The sequence diagram below shows the sequence of loading entries.
+![](LoadEntriesSD.png)
+
+#### Saving
+
+Saving of data will take place after every user input. Entries will be saved first followed by
+settings immediately.
+
+##### Saving of entries into `StonksXD_Entries.csv`
+
+1. Create a `FileWriter` to the `csv` file.
+2. Create a `BufferedWriter` using the `FileWriter`. `BufferedWriter` is used as since we are writing many times, it
+could be the faster option.
+3. Write in the `csv` header.
+4. Obtain all `Expense` entries from `FinancialTracker`.
+5. For each `Expense`, convert it to a `String` through `DataConverter` and write the `String` to the `csv` file.
+6. Obtain all `Income` entries from `FinancialTracker`. (Will not be shown in diagram as it is similar to step 4.)
+7. For each `Income`, convert it to a `String` through `DataConverter` and write the `String` to the `csv` file.
+(Will not be shown in diagram as it is similar to step 5.)
+8. Close the buffer and return.
+9. Begin saving the settings.
+
+##### Saving of settings into `StonksXD_Settings.csv`
+
+10. Create a `FileWriter` to the `csv` file.
+11. Create a `BufferedWriter` using the `FileWriter`. `BufferedWriter` is used as since we are writing many times, it
+could be the faster option.
+12. Write in the `csv` header.
+13. Use `DataConverter` to convert all settings to a `String`.
+14. Write the `String` to the `csv` file.
+15. Close the buffer.
+16. Return the control to the caller.
+
+The sequence diagrams below will illustrate the saving process. Note that the diagrams do not show the full
+details to reduce complexity.
+
+The sequence diagram below illustrates the sequence of saving entries.
+![](SaveEntriesSD.png)
+
+The sequence diagram below illustrates the sequence of saving settings.
+![](SaveSettingsSD.png)
+
+---
## Product scope
### Target user profile
-{Describe the target user profile}
+The Stonks XD program is meant to target computing students that have trouble managing their finances and require reminders/advice to aid them in their financial journey.
+It is designed to fit the needs of students who travel frequently and prefer logging their financial records daily. Our goal as developers of this app is to provide users with the feeling of having a combination of both a journal and a snapshot.
+
### Value proposition
-{Describe the value proposition: what problem does it solve?}
+Stonks XD a global financial tracking journal, capable of both budgeting and
+analysis to serve financial needs while traveling. It is highly operable and
+intuitive command line program that is simple to use and is optimized for
+anyone on the go. Using a minimalistic command format, we aim to empower
+youth to manage their finances by making personal finance entries simple.
+
+---
## User Stories
-|Version| As a ... | I want to ... | So that I can ...|
+|Version| As a ... (role)| I want to ... (Function)| So that I can … (Benefit)|
|--------|----------|---------------|------------------|
-|v1.0|new user|see usage instructions|refer to them when I forget how to use the application|
-|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list|
+|v1.0|New User|List out all possible commands|Know what I can key into the CLI interface|
+|v1.0|User|Be able to record my expenditures|Keep track of all my expenses|
+|v1.0|User|View all expense entries|See which spending I can cut down on and better manage my finances|
+|v1.0|User|Delete my expense entries|Delete wrong entries due to possible typos|
+|v1.0|User|View total expense|See if I need to reduce my spending in the future|
+|v1.0|User|Delete my income entries|Delete wrong entries due to possible typos|
+|v1.0|Worker|Create income entries|Keep track of my total income and not spend more than that amount|
+|v1.0|User|See all income entries|Have an understanding of income history|
+|v1.0|User|View total income|Know what my spending limits are|
+|v2.0|User|Have my entries saved into the hard disk automatically|My data would not be lost when I close the application|
+|v2.0|User|Convert income / expense entries to different currencies|Do not have to manually convert currencies|
+|v2.0|User|View my expenditure in the form of bar charts|Gain more useful insights on my overall financial situation|
+|v2.0|Frugal spender|Set monthly budgets (overall budget and categorical budgets)|Plan my spending in advance|
+|v2.0|Large spender|Receive reminders when I am about to overspend|Cut back on my spending|
+|v2.0|Big spender|Get assistance readjusting my budget when I overspend|Can minimize the damage of spending too much|
+|v2.0|Financially conscious user|Receive daily tips on saving, budgeting and spending|Can be more frugal and wiser with my financial decisions|
+|v2.0|Long term user|See my account net balance|Can make appropriate plans / adjustments for the future|
+|v2.0|User|Find expense / income entry with keyword search|Narrow down the entries I want to see|
+|v2.0|User|See expenditure each month|Budget how much I need each month|
+|v2.0|User|See income earned each month|Be more motivated to save|
+|v2.0|User|Clear all my entries|Start afresh|
-## Non-Functional Requirements
+---
-{Give non-functional requirements}
+## Non-Functional Requirements
-## Glossary
+- Accessibility requirements: the application should be accessible by anyone with the `.jar` file
+- Constrains: the CSV files created by the application should be able to run on different machines running the same application
+- Fault tolerance requirements: the application should handle inputs with a reasonable amount of errors
+- Interoperability requirements: the application should run on macOS, Windows and Linux operating systems
+- Stability requirements: Application should run without internet so that user can access the application anywhere without having to connect to the internet
-* *glossary item* - Definition
+---
## Instructions for manual testing
-{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing}
+### Initial start-up guide:
+
+1. Ensure that you have Java 11 or above installed.
+
+
+2. Download the latest version of `StonksXD.jar` from [here](https://github.com/AY2122S1-CS2113T-T12-3/tp/releases).
+
+
+3. Copy the file to the folder you want to use as the home folder for your `StonksXD`.
+
+
+4. Open the Command-Line interface (CLI) and navigate to the directory where you saved the `.jar` file and run `java -jar tp.java` in the command line. `StonksXD` will start up.
+
+
+### Testing Guide:
+
+Below are a few types of testing that you can perform:
+
+- Manual Testing
+- JUnit Testing
+- Gradle Daemon Testing
+- I/O Re-direction Testing
+
+
+### Manual Testing
+
+This is a non-exhaustive list of some common manual tests that can given as commands during run-time to test code defensibility:
+
+- #### _Adding Income/ Expense entries_
+ * Test Case: `add_ex d/DESCRIPTION a/AMOUNT c/CATEGORY`.
+
+ Expected : Adds an expense item to the list. Displays confirmation message with timestamp.
+
+ * Test Case: `add_ex` but leave `d/`, `/a`, `/c` or all empty.
+
+ Expected : No item is added. Error message displayed showing correct syntax.
+
+ * Test Case: `add_ex` but give non-existent category for `/c`.
+ Expected : No item added. Error message displayed showing available categories.
+
+- #### _Delete Income/ Expense entries_
+ * Pre-requisite: List expense or income using `list_ex`/ `list_in. Must have one or more entries.
+
+ * Test Case: `del_in i/1` or `del_ex i/1`
+ Expected : Deletes the 1st entry in Income/ Expense list. Displays confirmation message.
+
+ * Test Case: `del_in i/0`, `del_in i/ABC` or `del_in i/-3`.
+ Expected : Displays error message saying invalid index.
+
+ * Test Case: `del_in i/x` where x is larger than list size.
+ Expected : Similar error message as before.
+
+- #### _List Income/ Expense entries_
+ * Test Case: `list_ex` or `list_in`
+
+ Expected : Lists all entries added so far.
+
+ * Test Case: `list_ex` or `list_in` but no items in both lists.
+
+ Expected : Displays message saying no items in list.
+
+### JUnit Testing
+
+JUnit testing modules are available in the test folder. They can be run separately or all together based on developer requirements.
+
+Below is a list of the currently available tests:
+
+- _CommandTest:_ Tests if commands like add, delete, list etc. are calling their respective methods and providing with the appropriate parameters.
+
+
+- _DataManagerTest:_ Tests the data saving function of program.
+
+
+- _DukeTest:_ Used as driver to run main().
+
+
+- _ExpenseTest:_ Tests if expense entries are processed correctly into their appropriate attributes.
+
+
+- _IncomeTest:_ Tests if income entries are processed into their appropriate attributes.
+
+
+- _FinancialTrackerTest:_
+
+### Gradle Daemon Testing
+
+Intellij comes with an in-built Gradle Daemon that can be used to run the following test:
+
+- `.\gradlew test` to check if all test files have passed.
+- `.\gradlew checkStyleTest` to check if test files comply with certain coding standards and conventions.
+- `.\gradlew checkStyleMain` to check if main program complies with all JAVA coding standards.
+
+
+### I/O Re-direction Testing
+
+This form of testing involves loading sample data stored in the `text-ui-test` folder. It can be performed as follows:
+
+1. Enter new sample data or use the pre-existing test data that can be found in the `input.txt` file.
+
+
+2. Open CLI terminal and navigate to the `text-ui-test` directory using the following command - `cd /text-ui-test`
+
+
+3. Run `.\runtest.bat` in CLI and see if you receive the message `"Test Passed!"`.
+
+
+4. The IDE will compare the output in the `EXPECTED.TXT` and `ACTUAL.TXT` files to see if they are exactly the same to pass this test.
\ No newline at end of file
diff --git a/docs/FinancialTrackerCD.drawio.png b/docs/FinancialTrackerCD.drawio.png
new file mode 100644
index 0000000000..59b05aad5c
Binary files /dev/null and b/docs/FinancialTrackerCD.drawio.png differ
diff --git a/docs/FinancialTrackerSD.drawio.png b/docs/FinancialTrackerSD.drawio.png
new file mode 100644
index 0000000000..9bb1699c2a
Binary files /dev/null and b/docs/FinancialTrackerSD.drawio.png differ
diff --git a/docs/LoadBudgetsSD.png b/docs/LoadBudgetsSD.png
new file mode 100644
index 0000000000..a27b391c3f
Binary files /dev/null and b/docs/LoadBudgetsSD.png differ
diff --git a/docs/LoadEntriesSD.png b/docs/LoadEntriesSD.png
new file mode 100644
index 0000000000..7d2f40aa5e
Binary files /dev/null and b/docs/LoadEntriesSD.png differ
diff --git a/docs/LoadSettingsSD.png b/docs/LoadSettingsSD.png
new file mode 100644
index 0000000000..edd90f53ba
Binary files /dev/null and b/docs/LoadSettingsSD.png differ
diff --git a/docs/README.md b/docs/README.md
index bbcc99c1e7..62ae2fef1c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,13 @@
-# Duke
+# Stonks XD
-{Give product intro here}
+Stonks XD is your go-to smart money management application that is able to:
+- track your daily expenses
+- set/adjust your spending limits
+- provide you with helpful financial tips and insights
+
+It is optimised as a daily journal, so you can key in your entries while you unwind at the end of the day.
+It also allows you to view your financial records in different currencies through both text and graphs,
+so you can use this application anywhere around the world!
Useful links:
* [User Guide](UserGuide.md)
diff --git a/docs/SaveEntriesSD.png b/docs/SaveEntriesSD.png
new file mode 100644
index 0000000000..176a520512
Binary files /dev/null and b/docs/SaveEntriesSD.png differ
diff --git a/docs/SaveSettingsSD.png b/docs/SaveSettingsSD.png
new file mode 100644
index 0000000000..2ec550b2f7
Binary files /dev/null and b/docs/SaveSettingsSD.png differ
diff --git a/docs/StonksXDSequenceDiagram.drawio.png b/docs/StonksXDSequenceDiagram.drawio.png
new file mode 100644
index 0000000000..fe021e20b5
Binary files /dev/null and b/docs/StonksXDSequenceDiagram.drawio.png differ
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index abd9fbe891..57db5e97eb 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,42 +1,1163 @@
-# User Guide
+# Stonks XD User Guide
+
+![](https://melmagazine.com/wp-content/uploads/2019/07/Stonks_Meme.jpg)
+
+---
## Introduction
+
+Stonks XD is your go-to smart money management application that is able to:
+- Track your daily expenses
+- Set/adjust your spending limits
+- Provide you with helpful financial tips and insights
+
+It is optimised as a daily journal, so you can key in your entries while you unwind at the end of the day.
+It also allows you to view your financial records in different currencies through both text and graphs,
+so you can use this application anywhere around the world!
+
+With this guide, you will be able to learn how to use all the functionalities of this application through step-by-step instructions
+without having to learn how to write a single line of code.
+
+Before you learn how to use the app, take a look at the [Quick Start](#1-quick-start) guide on how to install and prepare the application for use.
+
+---
+
+## Table of Contents
+1. [Quick Start](#1-quick-start)
+
+2. [Features](#2-features)
+
+ 2.1 Help
+ * [View all possible commands: `help`](#view-all-possible-commands-help)
+
+ 2.2 Add / Delete Entries
+ * [Create expense entry: `add_ex`](#create-expense-entry-add_ex)
+ * [Create expense entry with date: `add_ex_d`](#create-expense-entry-with-date-add_ex_d)
+ * [Delete expense entry: `del_ex`](#delete-expense-entry-del_ex)
+ * [Create income entry: `add_in`](#create-income-entry-add_in)
+ * [Create income entry with date: `add_in_d`](#create-income-entry-with-date-add_in_d)
+ * [Delete income entry: `del_in`](#delete-income-entry-del_in)
+
+ 2.3 List / View Information
+ * [List all expense entries: `list_ex`](#list-all-expense-entries-list_ex)
+ * [List all income entries: `list_in`](#list-all-income-entries-list_in)
+ * [View total expense: `total_ex`](#view-total-expense-total_ex)
+ * [View total income: `total_in`](#view-total-income-total_in)
+ * [View total balance: `balance`](#view-total-balance-balance)
+ * [Show total expense between 2 dates: `btw_ex`](#show-total-expense-between-2-dates-btw_ex)
+ * [Show total income between 2 dates: `btw_in `](#show-total-income-between-2-dates-btw_in)
+
+ 2.4 Finding Entries
+ * [Find entry using date: `find DATE`](#find-entry-using-date-find-ddmmyyyy)
+ * [Find entry using keyword: `find KEYWORD`](#find-entry-using-keyword-find-keyword)
+
+ 2.5 Budget Setting
+ * [Set budget: `set_budget`](#set-budget-set_budget)
+ * [Check budget: `check_budget`](#check-budget-check_budget)
+ * [Set_threshold: `set_threshold`](#set-threshold-set_threshold)
+
+ 2.6 Currency Conversion
+ * [Set currency: `set_curr`](#set-currency-set_curr)
+ * [Check current currency: `check_curr`](#check-current-currency-check_curr)
+ * [List available currencies: `list_curr`](#list-available-currencies-list_curr)
+
+ 2.7 Graphing
+ * [View yearly report: `show_graph`](#view-yearly-report-show_graph)
+
+ 2.8 Clear All Entries
+ * [Clear all entries: `clear_all_entries`](#clear-all-entries-clear_all_entries)
+
+ 2.9 Terminating Program
+ * [Terminate program: `end`](#terminate-program-end)
+
+ 2.10 Saving of Data
+ * [Saving of data](#saving-of-data)
+
+3. [FAQ](#3-faq)
-{Give a product intro}
-## Quick Start
+4. [Command Summary](#4-command-summary)
-{Give steps to get started quickly}
+---
+
+## 1. Quick Start
1. Ensure that you have Java 11 or above installed.
-1. Down the latest version of `Duke` from [here](http://link.to/duke).
-## Features
-{Give detailed description of each feature}
+2. Download the latest version of `StonksXD.jar` from [here](https://github.com/AY2122S1-CS2113T-T12-3/tp/releases/tag/v2.1).
+
+
+3. Copy the file to the folder you want to use as the home folder for your `Stonks XD`.
+
+
+4. Open the Command-Line interface (CLI) and navigate to the directory where you saved the `.jar` file and run
+`java -jar StonksXD.jar` in the command line. `Stonks XD` will start up.
+
+
+5. Once the program is up and running, the image shown below is what you can expect the program should look like!
+
+![image](https://user-images.githubusercontent.com/77761339/140724500-ea056343-86d5-4a9c-ba82-db8de51dd595.png)
+
+6. If you are a first time user, the first command you want to key in is the [help](#view-all-possible-commands-help) command.
+It will guide you to the possible commands you can enter into `Stonks XD`. (Tip! Refer to the [Features](#2-features) below for details of each command.)
+
+---
+
+## 2. Features
+
+### Notes:
+
+- Words in `UPPER_CASE` are the parameters to be supplied by you, the user.
+
+ e.g. in add `a/AMOUNT`, `AMOUNT` is a parameter which can be typed as `a/12.30`.
+
+
+- Parameters surrounded with `[` and `]` are optional parameters which you might consider including to your input.
+ e.g. in the show graph feature: `show_graph [Y/YEAR]`, an optional `YEAR` parameter may be added to show graphs of different years.
+
+
+- Most features below have a collapsible section that allows you to see the run time output. Do check them out if you want to visualize what the product looks like!
+e.g. in add `a/AMOUNT`, `AMOUNT` is a parameter which can be typed as `a/12.30`.
+
+
+- Parameters can be in any order.
+e.g. if the command specifies `c/CATEGORY a/AMOUNT`, `a/AMOUNT c/CATEGORY` is also acceptable.
+
+
+- If a parameter is expected only once in the command, but you specified it multiple times, only the last occurrence
+of the parameter will be taken. e.g. if you gave `a/100 a/1000`, only `a/1000` will be read in.
+
+
+---
+### 2.1 Help
+
+### View all possible commands: `help`
+This shows a list of all possible commands.
+If you are a first time user, this is the perfect opportunity for you to get familiarize with the application.
+Feel free to use this command whenever you are lost while using the app!
+
+Format: `help`
+
+
+
+ ▼ Expected output in run window (Click to expand!)
+
+-----------------------------------------------------------------------------------------------------
+This is a list of commands and their format!
+-----------------------------------------------------------------------------------------------------
+List Out All Commands: help
+Adding Expense: add_ex d/DESCRIPTION a/AMOUNT c/CATEGORY
+Adding Expense With Date (Date Format: DD/MM/YYYY): add_ex_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE
+Deleting Expense: del_ex i/INDEX
+Adding Income: add_in d/DESCRIPTION a/AMOUNT c/CATEGORY
+Adding Income With Date (Date Format: DD/MM/YYYY): add_in_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE
+Deleting Income: del_in i/INDEX
+Listing Expense: list_ex
+Listing Income: list_in
+Show Total Expense: total_ex
+Show Total Income: total_in
+To Display Total Balance: balance
+Show Total Expense between 2 dates (Date Format: DD/MM/YYYY): btw_ex s/START_DATE e/END_DATE
+Show Total Income between 2 dates (Date Format: DD/MM/YYYY): btw_in s/START_DATE e/END_DATE
+To Find Using Date: find DD/MM/YYYY
+To Find Based On Keyword: find KEYWORD
+To Set Budgets(Overall, Food, Transport, Medical, Bills, Entertainment, Misc): set_budget c/CATEGORY a/AMOUNT
+To Check Budgets: check_budget c/CATEGORY
+To Set Threshold Value for Reminders: set_threshold t/THRESHOLD
+To change entries into a different currency: set_curr c/CURRENCY
+To check the currency that entries are currently in: check_curr
+Lists available currency types for conversion: list_curr
+To View Your Year Report (Year format: YYYY): show_graph [Y/YEAR]
+To Clear All Expense And Income Entries: clear_all_entries
+To Terminate The Program: end
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+
+### 2.2 Add / Delete Entries
+
+### Create expense entry: `add_ex`
+This command adds an expense entry to your list. Use this to keep track of your daily expenses!
+
+Format: `add_ex d/DESCRIPTION a/AMOUNT c/CATEGORY`
+
+
+Note:
+
+- `DESCRIPTION` has to be non-empty.
+- `AMOUNT` can only have a maximum of 2 decimal points.
+- `AMOUNT` must be more than or equal to 0.05.
+- `AMOUNT` can only have a maximum value of 1,000,000 (1 Million).
+- The sum of all your entries cannot be more than 100,000,000,000 (100 Billion).
+- `CATEGORY` has to be either `food`, `transport`, `bills`, `medical`, `entertainment`, or `misc`.
+- The default date of the added expense will be the date in which the expense is added.
+
+Examples:
+
+- Let's say you just had KFC for lunch today, and you want to log it down. Type in
+ `add_ex d/KFC lunch a/10.20 c/food` into the command line. This adds an expense entry regarding lunch that costs $10.20.
+
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Your most recent spending:
+[E] KFC lunch - $10.20 (19/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+Note: Budget reminders of different kinds might also appear when expenses are added!
+They might look something like this.
+
+-----------------------------------------------------------------------------------------------------
+Exceeded both NOVEMBER FOOD budget ($35.50/$30.00) and NOVEMBER OVERALL budget ($50.50/$50.00).
+Consider adjusting your OVERALL budget to $50.50 before adjusting your FOOD budget!
+Currently you cannot extend your FOOD budget without first extending your OVERALL budget!
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+### Create expense entry with date: `add_ex_d`
+This command adds an expense entry to your list with a date of your choice!
+
+Format: `add_ex_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE`
+
+Note:
+- `DESCRIPTION` has to be non-empty.
+- `AMOUNT` can only have a maximum of 2 decimal points.
+- `AMOUNT` must be more than or equal to 0.05.
+- `AMOUNT` can only have a maximum value of 1,000,000 (1 Million).
+- The sum of all your entries cannot be more than 100,000,000,000 (100 Billion).
+- `CATEGORY` has to be either `food`, `transport`, `bills`, `medical`, `entertainment`, or `misc`.
+- `DATE` must be in the DD/MM/YYYY format.
+
+Examples:
+
+- Let's say you had McDonald's for dinner 2 days ago, and you only remembered about it today. You can type in
+ `add_ex_d d/McDonalds dinner a/7.50 c/food D/20/10/2021` into the command line. This will adds a dinner expense
+ entry that costs $7.50 that is made on 20th October 2021.
+
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Your most recent spending:
+[E] McDonalds dinner - $7.50 (20/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+Note: Budget reminders of different kinds might also appear when expenses are added!
+They might look something like this.
+
+-----------------------------------------------------------------------------------------------------
+Exceeded both NOVEMBER FOOD budget ($35.50/$30.00) and NOVEMBER OVERALL budget ($50.50/$50.00).
+Consider adjusting your OVERALL budget to $50.50 before adjusting your FOOD budget!
+Currently you cannot extend your FOOD budget without first extending your OVERALL budget!
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+
+### Delete expense entry: `del_ex`
+
+You can delete an incorrect expense entry by providing the index of said entry.
+Index can be found via the `list_ex` command below.
+
+Format: `del_ex i/INDEX`
+
+- `INDEX` has to be non-empty.
+- `INDEX` has to be a valid non-negative integer.
+
+Examples:
+
+- Let's say you have made a mistake while entering `expense` entries, and you want to remove the entry you made.
+ What you want to do is get the list of `expense` first, to find the index corresponding to that entry.
+
+
+ ▼ Expected output in run window
+
+Before deletion, view the expense list using the list_ex
command. The expected output will be as follows:
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all of your recent spending!
+-----------------------------------------------------------------------------------------------------
+1: [E] pillow - $5.00 (18/10/2021)
+2: [E] bought cookies - $5.00 (18/01/2021)
+3: [E] bought home - $555.00 (18/07/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+You want to delete the last entry, with index 3 because the description you entered was incorrect.
+You can enter the command del_ex i/3
into the command line to delete that entry.
+
+
+When entry is deleted, you should expect the following message from the program:
+
+-----------------------------------------------------------------------------------------------------
+You removed this:
+[E] bought home - $555.00 (18/07/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+After deletion, we view the list again to see that the list no longer contains the entry you deleted!
+
+-----------------------------------------------------------------------------------------------------
+1: [E] pillow - $5.00 (18/10/2021)
+2: [E] bought cookies - $5.00 (18/01/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+### Create income entry: `add_in`
+
+This adds an income entry to your list. Use this to keep track of your income!
+
+Format: `add_in d/DESCRIPTION a/AMOUNT c/CATEGORY`
+
+Note:
+- `DESCRIPTION` has to be non-empty.
+- `AMOUNT` can only have a maximum of 2 decimal points.
+- `AMOUNT` must be more than or equal to 0.05.
+- `AMOUNT` can only have a maximum value of 1,000,000 (1 Million).
+- The sum of all your entries cannot be more than 100,000,000,000 (100 Billion).
+- `CATEGORY` has to be either `salary`, `allowance`, `others` or `adhoc`.
+- The default date of the added income will be the date in which the income is added.
+
+Examples:
+
+- Let's say you received some pocket money for the week. To log it down, type
+ `add_in d/lunch money a/1000 c/allowance` into the command line. This adds an income entry regarding a
+ lunch allowance of $1000.
+
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Your most recent earning:
+[I] lunch money - $1000.00 (19/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+### Create income entry with date: `add_in_d`
+This command adds an income entry to your list with a date of your choice!
+
+Format: `add_in_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE`
+
+Note:
+- `DESCRIPTION` has to be non-empty.
+- `AMOUNT` can only have a maximum of 2 decimal points.
+- `AMOUNT` must be more than or equal to 0.05.
+- `AMOUNT` can only have a maximum value of 1,000,000 (1 Million).
+- The sum of all your entries cannot be more than 100,000,000,000 (100 Billion).
+- `CATEGORY` has to be either `salary`, `allowance`, `others` or `adhoc`.
+- `DATE` must be in the DD/MM/YYYY format.
+
+Examples:
+
+- Let's say you received a holiday bonus of $5000, but you forgot to add it in. To log it
+ type `add_in_d/december's bonus a/5000 c/salary D/26/12/2021` into the command line. It adds an
+ income entry regarding a salary bonus of $5000 for 26th December 2021.
+
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Your most recent earning:
+[I] december's bonus - $5000.00 (26/12/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+
+### Delete income entry: `del_in`
+
+You can delete an incorrect income entry by providing the index of said entry.
+The index can be found using the `list_in` command found below.
+
+Format: `del_in i/INDEX`
+
+- `INDEX` has to be non-empty.
+- `INDEX` has to be a valid non-negative integer.
+
+Examples:
+
+- Let's say you have made a mistake while entering `income` entries, and you want to remove the entry you made.
+ What you want to do is get the list of `income` first, to find the index corresponding to that entry.
+
+
+ ▼ Expected output in run window
+
+Before deletion, view the income list using the list_in
command. The expected output will be as follows:
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all of your recent earnings!
+-----------------------------------------------------------------------------------------------------
+1: [I] tuition salary - $800.00 (18/10/2021)
+2: [I] November allowance - $300.00 (18/11/2021)
+3: [I] Christmas gift money - $50.00 (18/12/2021)
+4: [I] Christmas gift money - $50000.00 (18/12/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+You want to delete the last entry, with index 4 because the value you entered was too large.
+You can enter the command del_in i/4
to delete that entry.
+
+
+Once the delete is completed, you get the following message:
+
+-----------------------------------------------------------------------------------------------------
+You removed this:
+[I] Christmas gift money - $50000.00 (18/12/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+After deletion, we can see the list that the entry we want to remove is removed!
+
+-----------------------------------------------------------------------------------------------------
+1: [I] tuition salary - $800.00 (18/10/2021)
+2: [I] November allowance - $300.00 (18/11/2021)
+3: [I] Christmas gift money - $50.00 (18/12/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+---
+### 2.3 List / View Information
+
+### List all expense entries: `list_ex`
+
+This displays the previously added expense entries in the form of a list.
+Use this to find the index of an entry you want to delete.
+
+Format: `list_ex`
+
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all of your recent spending!
+-----------------------------------------------------------------------------------------------------
+1: [E] bought cookies - $500.00 (18/01/2021)
+2: [E] bought home - $555.00 (18/07/2021)
+3: [E] bought car - $4777.00 (18/06/2021)
+4: [E] bought condo - $87654888878.00 (18/05/2021)
+5: [E] KFC lunch - $10.20 (19/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+### List all income entries: `list_in`
+
+This displays the previously added income entries in the form of a list.
+Use this to find the index of the entries you want to delete.
+
+Format: `list_in`
+
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all of your recent earnings!
+-----------------------------------------------------------------------------------------------------
+1: [I] rob a church - $300.00 (18/11/2021)
+2: [I] rob a car - $400.00 (18/12/2021)
+3: [I] rob a home - $500.00 (18/09/2021)
+4: [I] rob a child - $600.00 (18/08/2021)
+5: [I] lunch money - $1000.00 (19/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+### View total expense: `total_ex`
+
+This displays the total amount of all expenses in your list.
+This is great for giving you a quick snapshot of how much you've spent!
+
+Format: `total_ex`
+
+Note:
+
+- `total_ex` command is only able to print values less than 100,000,000,000 (100Billion).
+
+Examples:
+
+-
+Let's say you have made expense entries consistently for a couple of months since you downloaded the application and you want to know the total expense you have logged.
+
+
+
+ ▼ Expected output in run window
+
+These are the expense entries you have made in the past couple of months.
+
+-----------------------------------------------------------------------------------------------------
+1: [E] Lunch with Friends - $22.00 (02/01/2021)
+2: [E] Movie date - $50.00 (07/01/2021)
+3: [E] ORD PARTY!! - $150.00 (12/01/2021)
+4: [E] Top up my bus card for the month of Feb - $150.00 (02/02/2021)
+5: [E] Roses - $30.00 (14/02/2021)
+6: [E] Dinner with Friends - $37.00 (24/02/2021)
+7: [E] Mum's birthday treat - $120.00 (07/03/2021)
+8: [E] Grab food delivery - $45.00 (24/03/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+Simply type in the command total_ex
, and the program will display the total expense entries you have made:
+
+-----------------------------------------------------------------------------------------------------
+Your total income is: $604.00
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+### View total income: `total_in`
+
+This will give you the sum of all income entries in you have made in your list.
+This is great for giving you a quick snapshot of how much you've earned!
+
+
+Format: `total_in`
+
+Note:
+
+- `total_in` command is only able to print values less than 100,000,000,000 (100Billion).
+
+Examples:
+
+-
+Let's say you have made income entries consistently for a couple of months since you downloaded the application and you want to know the total income you have logged.
+
+
+
+ ▼ Expected output in run window
+
+These are the income entries you have made in the past couple of months.
+
+-----------------------------------------------------------------------------------------------------
+1: [I] Allowance for the month of Jan - $250.00 (02/01/2021)
+2: [I] Allowance for the month of Feb - $250.00 (04/02/2021)
+3: [I] Money from teaching tuition - $800.00 (07/03/2021)
+4: [I] Part time job - $1000.00 (20/04/2021)
+5: [I] Part time job - $1000.00 (20/05/2021)
+6: [I] Part time job - $1000.00 (20/06/2021)
+7: [I] Part time job - $1000.00 (20/07/2021)
+8: [I] Allowance for the month of Aug - $350.00 (01/08/2021)
+9: [I] Allowance for the month of Sept - $350.00 (11/09/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+Simply type in the command total_in
, and the program will display the total income entries you have made:
+
+-----------------------------------------------------------------------------------------------------
+Your total income is: $6000.00
+-----------------------------------------------------------------------------------------------------
+
+
+
+
-### Adding a todo: `todo`
-Adds a new item to the list of todo items.
+### View total balance: `balance`
-Format: `todo n/TODO_NAME d/DEADLINE`
+This shows the net balance you have after subtracting your expenses from your incomes.
+Use this to get a snapshot of whether you're in the red!
-* The `DEADLINE` can be in a natural language format.
-* The `TODO_NAME` cannot contain punctuation.
+Format: `balance`
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+Your current balance is: $-87654891720.20
+-----------------------------------------------------------------------------------------------------
+
+
+
-Example of usage:
+### Show total expense between 2 dates: `btw_ex`
-`todo n/Write the rest of the User Guide d/next week`
+This shows the sum of all the expense you had accumulated between a given date range.
+This is great for checking your total weekly, monthly, or yearly expenses!
-`todo n/Refactor the User Guide to remove passive voice d/13/04/2020`
+Format: `btw_ex s/START_DATE e/END_DATE`
-## FAQ
+- `START_DATE` & `END_DATE` must be in the DD/MM/YYYY format.
+- `START_DATE` & `END_DATE` have to be valid and non-empty.
+- `START_DATE` must be before or the same as `END_DATE`.
+
+Examples:
+- Let's you have have made a bunch of expense entries in the past 2 weeks, from 10th August 2021 and 23rd August 2021, and you want to find out how much you have spent during this time period.
+
- Simply key in two dates using the command format
btw_ex s/10/08/2021 e/23/08/2021
to get the total you have spent in those 2 weeks!
+
+ ▼ Expected output in run window
+
+These are the expense entries you have made in the past couple of months.
+
+-----------------------------------------------------------------------------------------------------
+1: [E] Lunch with Friends - $22.00 (09/07/2021)
+2: [E] Movie date - $50.00 (20/07/2021)
+3: [E] ORD PARTY!! - $150.00 (12/08/2021)
+4: [E] Top up my bus card for the month of Aug - $150.00 (15/08/2021)
+5: [E] Hostel fees - $1873.00 (15/08/2021)
+6: [E] Dinner with Friends - $37.00 (17/08/2021)
+7: [E] Mum's birthday treat - $120.00 (23/08/2021)
+8: [E] Grab food delivery - $45.00 (23/08/2021)
+-----------------------------------------------------------------------------------------------------
+
+Your total expense that you have logged for the past 2 weeks will be shown:
+
+-----------------------------------------------------------------------------------------------------
+Your total expense between 10 Aug 2021 and 23 Aug 2021 is : $2375.00
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+### Show total income between 2 dates: `btw_in`
+
+This shows the total income you have accumulated between a given date range.
+This is great for checking your total weekly, monthly, or yearly expenses!
+
+Format: `btw_in s/START_DATE e/END_DATE`
+
+- `START_DATE` & `END_DATE` must be in the DD/MM/YYYY format.
+- `START_DATE` & `END_DATE` have to be valid and non-empty.
+- `START_DATE` must be before or the same as `END_DATE`.
+
+Examples:
+- Let's you have have made a bunch of income entries in the past 2 weeks, from 10th August 2021 and 23rd August 2021, and you want to find out how much you have received during this time period.
+
- Simply key in two dates using the command format
btw_in s/10/08/2021 e/23/08/2021
to get the total income you have gotten in those 2 weeks!
+
+ ▼ Expected output in run window
+
+These are the income entries you have made in the past couple of months.
+
+-----------------------------------------------------------------------------------------------------
+1: [I] Allowance for the month of Jul - $350.00 (02/07/2021)
+2: [I] Allowance for the month of Aug - $350.00 (04/08/2021)
+3: [I] Money from teaching tuition - $800.00 (11/08/2021)
+4: [I] Part time job - $1000.00 (15/08/2021)
+5: [I] Friend finally paid me back for lunch last week -.-" - $23.00 (15/08/2021)
+6: [I] Mahjong ZMMT :) - $50.00 (20/08/2021)
+-----------------------------------------------------------------------------------------------------
+
+Your total income that you have logged for the past 2 weeks will be shown:
+
+-----------------------------------------------------------------------------------------------------
+Your total income between 10 Aug 2021 and 23 Aug 2021 is : $1873.00
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.4 Finding Entries
+
+
+### Find entry using date: `find DD/MM/YYYY`
+
+You can use this command to find and display the income or expense entries with the given date.
+
+Format: `find DD/MM/YYYY`
+
+- If the date given is not in the recognised format, a keyword search would be done instead.
+
+Examples:
+
+- `find 19/10/2021` returns income and expense entries with the given date.
+
+
+ ▼ Expected output in run window
+
+If you enter find 19/10/2021
, it will find the entry recorded on that date:
+
+-----------------------------------------------------------------------------------------------------
+Your most recent earning:
+[I] Birthday Money! - $200.00 (19/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+### Find entry using keyword: `find KEYWORD`
+
+This finds and displays the income or expense entries that contain the given keyword.
+
+Format: `find KEYWORD`
+
+- `KEYWORD` has to be a non-empty
+- `KEYWORD` can be any alpha-numeric string
+
+Examples:
+
+- `find FOOD` returns income and expense entries that contain the keyword `FOOD` in their description or categories.
+
+
+ ▼ Expected output in run window
+
+If you wish to search based on category, for e.g. all food
expenses:
+
+- Give the command
find food
and it will return the following:
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all your findings!
+-----------------------------------------------------------------------------------------------------
+1: [E] KFC lunch - $10.20 (19/10/2021)
+-----------------------------------------------------------------------------------------------------
+
+If you wish to search based on description, for e.g. all entries that contain the keyword bought
:
+- Give the command
find bought
and it will return the following:
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all your findings!
+-----------------------------------------------------------------------------------------------------
+1: [E] bought cookies - $500.00 (18/01/2021)
+2: [E] bought home - $555.00 (18/07/2021)
+3: [E] bought car - $4777.00 (18/06/2021)
+4: [E] bought condo - $87654888878.00 (18/05/2021)
+-----------------------------------------------------------------------------------------------------
+
+If you wish to search based on value, for e.g. all entries that contain the value 5
:
+- Give the command
find 5
and it will return the following:
+
+-----------------------------------------------------------------------------------------------------
+Below is a list of all your findings!
+-----------------------------------------------------------------------------------------------------
+1: [E] bought cookies - $500.00 (18/01/2021)
+2: [E] bought home - $555.00 (18/07/2021)
+3: [E] bought condo - $87654888878.00 (18/05/2021)
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.5 Budget Setting
+
+### Set budget: `set_budget`
+
+This sets a budget limit for one of the many preset expense categories.
+Reminders will be given when your spending approaches the budget limit!
+From here onwards, sub-budgets refer to the `food`, `transport`, `bills`, `medical`, `entertainment` and `misc` budgets.
+
+To help you better manage your budgets, the following features are in place for setting budgets:
+1. The new budget limit for each sub-budget must be greater than the current month's expenses for that sub-budget.
+2. The sum of all the sub-budget limits must be less than the `overall` budget limit.
+3. In the event that a sub-budget's expenses exceed its budget limit, the total expenses for that sub-budget will be used for the calculation in point 2 instead.
+
+Example:
+
+| | Food | Transport | Bills | Medical | Entertainment | Misc | Overall |
+| ------------------ | ---- | --------- | ----- | ------- | ------------- | ---- | ------- |
+| **Expenses** | $100 | **$1** | $100 | $100 | $100 | $100 | |
+| **Budget Limit** | $100 | $100 | $100 | $100 | $100 | $100 | **>$600** |
+
+In the example above, none of the sub-budget expenses exceed their budget limits.
+The allowable overall limit must be greater than $100 + **$100** + $100 + $100 + $100 + $100 = $600, which is the sum of all the sub-budget limits.
+
+
+However, in the following example, the transport expense exceeds the transport budget.
+The allowable overall limit must be greater than $100 + **$150** + $100 + $100 + $100 + $100 = $650.
+
+| | Food | Transport | Bills | Medical | Entertainment | Misc | Overall |
+| ------------------ | ---- | --------- | ----- | ------- | ------------- | ---- | ------- |
+| **Expenses** | $100 | **$150** | $100 | $100 | $100 | $100 | |
+| **Budget Limit** | $100 | $100 | $100 | $100 | $100 | $100 | **>$650** |
+
+
+
+Format: `set_budget c/CATEGORY a/AMOUNT`
+
+- `CATEGORY` has to be one of `food`, `transport`, `bills`, `medical`, `entertainment`, `misc` or `overall`.
+- `AMOUNT` must be more than or equal to 0.05.
+- `AMOUNT` has to be less than 100,000,000,000 (100 Billion).
+- `AMOUNT` has to be in 2 decimal places.
+
+Examples:
+
+- `set_budget c/bills a/100` Sets the `bills` budget to $100.
+
+
+ ▼ Expected output in run window
+
+When command set_budget c/bills a/100
is given, you get the following message if the budget is set successfully:
+
+-----------------------------------------------------------------------------------------------------
+BILLS budget set to $100.00
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+
+### Check budget: `check_budget`
+
+This checks the budget limit of one of the many preset expense categories.
+Use this when you forget your budget limits!
+
+Format: `check_budget c/CATEGORY`
+
+- `CATEGORY` has to be one of `food`, `transport`, `bills`, `medical`, `entertainment`, `misc` or `overall`.
+
+Examples:
+
+- `check_budget c/bills` checks the budget limit of the `bills` budget.
+
+
+ ▼ Expected output in run window
+
+When command check_budget c/bills
is given, you get the following message:
+
+-----------------------------------------------------------------------------------------------------
+Current BILLS limit is $100.00
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+### Set threshold: `set_threshold`
+
+This sets the threshold beyond which reminders will be given when approaching the budget limit.
+
+Format: `set_threshold t/THRESHOLD`
+
+- `THRESHOLD` has to be a value between 0 and 1.
+- Setting `THRESHOLD` to 0.9 produces reminders when you have used up more than 90% of your budget!
+
+Examples:
+
+- `set_threshold t/0.8` sets the threshold value of all budget categories to 80%.
+
+
+ ▼ Expected output in run window
+
+When command set_threshold t/0.8
is given, you get the following message:
+
+-----------------------------------------------------------------------------------------------------
+Threshold for budget reminders set to 0.8
+We'll warn you when you spend 80.0% of your budget!
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.6 Currency Conversion
+
+### Set currency: `set_curr`
+
+This allows you to see everything money-related in a different currency. Any money-related amount you key in from now
+onwards will be treated as the new currency set.
+
+Format: `set_curr c/CURRENCY`
+
+- As of v2.0, Stonks XD supports 2 different currencies: SGD and HKD.
+- If you try to set currency to currency you're already using, a warning will be shown.
+
+
+ ▼ Expected output in run window
+
+Let's take the following budget limit for FOOD
expenses as an example:
+
+-----------------------------------------------------------------------------------------------------
+Current FOOD limit is $12.50
+-----------------------------------------------------------------------------------------------------
+
+
+If you wish to convert the above (and all entries) to HKD, simply enter set_curr c/HKD
. You will receive the following confirmation message:
+
+-----------------------------------------------------------------------------------------------------
+All entries have been converted to HKD!
+-----------------------------------------------------------------------------------------------------
+
+
+If we check our budget limit once again, we see that it has now been converted to the appropriate value in HKD!
+
+-----------------------------------------------------------------------------------------------------
+Current FOOD limit is $62.50
+-----------------------------------------------------------------------------------------------------
+
+
+To convert back to SGD, just enter set_curr c/SGD
and all entries will revert back to their original denominations.
+
+
+
+### Check current currency: `check_curr`
+
+This shows you what currency setting you are currently on.
+
+Format: `check_curr`
+
+
+ ▼ Expected output in run window
+
+If you are unsure what currency your values are in, just enter check_curr
and it will show the following message:
+
+-----------------------------------------------------------------------------------------------------
+You currency setting currently: SGD
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+### List available currencies: `list_curr`
+
+This shows you what currency setting you are currently on.
+
+Format: `list_curr`
+
+
+ ▼ Expected output in run window
+
+If you are unsure as to what currency types you can convert to, just enter list_curr
and it will show the following message:
+
+-----------------------------------------------------------------------------------------------------
+Here is a list of available currencies you can convert to!
+1. HKD
+2. SGD
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.7 Graphing
+
+### View yearly report: `show_graph`
+
+This shows the monthly breakdown of the finances in a Yearly Report which also
+includes current month spending and earnings and your overall account balance regardless of year.
+We recommend using this function after your daily logging of expenses for a one-stop check-in on the state of your finances!
+
+
+#### Note
+1. The graph scales according to your largest monthly total, the scale currently used by the graph will be shown to you on the top right. 1.0E9 for example would mean 1,000,000,000, E stands for exponential.
+
+
+2. It would be ideal not to have entries with big differences as the Stonks XD app is meant for daily logging.
+
+Format: `show_graph [Y/YEAR]`
+
+- `YEAR` is an optional input which you may include. It will show the graph that corresponds to the given year. It must be in the YYYY format
+
+
+ ▼ Expected output in run window
+
+show_graph Y/2121
+-----------------------------------------------------------------------------------------------------
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+x x
+x Account Balance: $12.00 Legend: x
+x Current month (NOVEMBER 2021) total expense: $0.00 # is Expense x
+x Current month (NOVEMBER 2021) total income: $0.00 o is Income x
+x Year 2121 Report Unit: 10.0 x
+x ------------------------------------------------------------------------------------------------ x
+x x
+x x
+x x
+x x
+x x
+x x
+x x
+x x
+x o x
+x #o x
+x ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x
+x Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec x
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.8 Clear all entries
+
+### Clear all entries: `clear_all_entries`
+
+Clears all the income and expense entries Stonks XD is currently keeping track of.
+Use this when you want to start Stonks XD afresh. Be careful when you use it though!
+This cannot be undone!
+
+Format: `clear_all_entries`
+
+
+ ▼ Expected output in run window
+
+If you wish to clear all your entries and start afresh:
+
+- Give the command
clear_all_entries
+
+-----------------------------------------------------------------------------------------------------
+All your entries have been cleared!
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.9 Terminating Program
+
+### Terminate program: `end`
+
+This exits the program when you are done using it. A random tip will be shown along with the "BYE" message shown below.
+
+Format: `end`
+
+ ▼ Expected output in run window
+
+-----------------------------------------------------------------------------------------------------
+██████ ██ ██ ███████ ██
+██ ██ ██ ██ ██ ██ ██
+██████ ████ █████ ██
+██ ██ ██ ██ ██ ██
+██████ ██ ███████ ██
+Here's our tip for the day: Try using the 50/30/20 rule to budget. 50% for needs, 30% for wants and 20% for savings
+-----------------------------------------------------------------------------------------------------
+
+
+
+
+---
+### 2.10 Saving of Data
+
+### Saving of data
+
+Stonks XD will save / load your data from `StonksXD_Entries.csv` and `StonksXD_Settings.csv`. This ensures that you
+will not lose important data when Stonks XD closes.
+
+The 2 `csv` files will be stored in the same directory as `StonksXD.jar`.
+
+`StonksXD_Entries.csv` is an editable file. It stores the following content:
+1. `Expense` entries.
+2. `Income` entries.
+
+`StonksXD_Settings.csv` a **read-only file**. It stores all the important settings:
+1. Currency setting.
+2. Threshold setting.
+3. Budget settings for different expense categories.
+
+All important fields will be separated by a `,`.
+
+The reason for data files being in `.csv` format is for you can have an easier time editing / viewing the data
+in Excel when **not** using the program. It is easy to make mistakes when editing the `.csv` files manually so
+do be careful.
+
+
+
+#### When editing / viewing csv files
+
+- `.csv` files **must not** be open concurrently while Stonks XD is running. In other words, never have two programs
+writing / reading the `.csv` files at the same time. This will very likely cause saving / loading errors and
+loss of data.
+
+
+- `StonksXD_Settings.csv` is **read-only**. This is because changing the settings will result in many unexpected
+ results. For example, changing the currency setting in the file would result in the amounts in `StonksXD_Entries.csv`
+ being recognised as another currency.
+
+
+- Restrictions and rules for different variables are the same as how you would enter them in the Command Line
+Interface, as specified above.
+
+
+- Do not alter / delete the headers of `.csv` files. Stonks XD is able to minimise the damage if you do so but to
+ensure your data is saved / loaded properly, please do not edit anything unexpected.
+
+
+- Stonks XD expects the dates in `StonksXD_Entries.csv` to be in `DD/MM/YYYY` format i.e., `11/12/2021` when loading
+data. When opening `StonksXD_Entries.csv` in Excel, Excel might change the format of the dates. Do ensure Excel's date
+format is in `DD/MM/YYYY` when dealing with `StonksXD_Entries.csv`. Entries with a different date format will be
+considered corrupted and will not be loaded into Stonks XD.
+
+
+- Do not edit the amount of your entries **drastically** such that they exceed the max amount per entry or the
+total limit of 100,000,000,000 for expenses / incomes. Entries that cause you to exceed either of the 2 limit will be
+considered corrupted and not be loaded.
+
+
+
+#### In the event of corrupted data
+
+When you run Stonks XD, it will immediately start to load all the data from both `.csv` files. You might run into
+messages telling you that there are corrupted data, and they will not be loaded. This is likely because you have
+edited things you are not suppose to, or you have edited wrongly. There is a way to minimise this damage (This is
+optional, and you do not have to do this if you are fine with losing data).
+Here are the steps:
+1. When you have terminated Stonks XD but want to edit the `.csv` files, make a copy of them first.
+
+
+2. If you run Stonks XD and receive corruption errors, end the program.
+
+
+3. Copy and paste the contents from your copies back into `StonksXD_Entries.csv` and `StonksXD_Settings.csv`. This
+means all your edits are gone. But, this will ensure that there will be no data is lost.
+
+
+---
+
+## 3. FAQ
**Q**: How do I transfer my data to another computer?
-**A**: {your answer here}
+**A**: Your data is saved in 2 `.csv` files called `StonksXD_Entries.csv` and `StonksXD_Budget.csv`. To transfer the data, make a copy of these files and paste them in the same directory as the `.jar` file on your new machine.
+
+**Q**: What if my program terminates unexpectedly?
+
+**A**: All data will be stored inside the respective `.csv` files.
+
+---
+
+## 4. Command Summary
+
+| Action | Format | Examples |
+| ------------ | ------------- | ------------- |
+| View all possible commands | `help` | - |
+| Create expense entry | `add_ex d/DESCRIPTION a/AMOUNT c/CATEGORY` | `add_ex d/KFC lunch a/10.20 c/food` |
+| Create expense entry with date | `add_ex_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE` | `add_ex_d d/KFC lunch a/10.20 c/food D/20/08/2021` |
+| Delete expense entry | `del_ex i/INDEX` | `del_ex i/3` |
+| Create income entry | `add_in d/DESCRIPTION a/AMOUNT c/CATEGORY` | `add_in d/work a/3200 c/salary` |
+| Create income entry with date | `add_in_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE` | `add_in_d d/work a/10.20 c/salary D/20/08/2021` |
+| Delete income entry | `del_in i/INDEX` | `del_in i/2` |
+| List all expense entries | `list_ex` | - |
+| List all income entries | `list_in` | - |
+| View total expense | `total_ex` | - |
+| View total income | `total_in` | - |
+| View total balance | `balance` | - |
+| Show total expense between 2 dates | `btw_ex s/START_DATE e/END_DATE` | `btw_ex s/10/07/2021 e/23/10/2021` |
+| Show total income between 2 dates | `btw_in s/START_DATE e/END_DATE` | `btw_in s/10/07/2021 e/23/10/2021` |
+| Find entry using date | `find YYYY-MM-DD` | `find 19/10/2021` |
+| Find entry using keyword | `find KEYWORD` | `find food`
`find 3` |
+| Set budget | `set_budget c/CATEGORY a/AMOUNT` | `set_budget c/bills a/100` |
+| Check budget | `check_budget c/CATEGORY` | `check_budget c/bills` |
+| Set threshold | `set_threshold t/THRESHOLD` | `set_threshold t/0.2` |
+| Set currency | `set_curr c/CURRENCY` | `set_curr c/hkd` |
+| Check current currency | `check_curr` | - |
+| List all currency conversions | `list_curr` | - |
+| View Yearly Report |`show_graph Y/YYYY`|`show_graph Y/2023`|
+| Clear all entries | `clear_all_entries` | - |
+| To terminate program | `end` | - |
-## Command Summary
-{Give a 'cheat sheet' of commands here}
-* Add todo `todo n/TODO_NAME d/DEADLINE`
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..c4192631f2
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/docs/addedYearParamCD.drawio.png b/docs/addedYearParamCD.drawio.png
new file mode 100644
index 0000000000..7bd1195100
Binary files /dev/null and b/docs/addedYearParamCD.drawio.png differ
diff --git a/docs/addedYearParamSD.png b/docs/addedYearParamSD.png
new file mode 100644
index 0000000000..d958be96de
Binary files /dev/null and b/docs/addedYearParamSD.png differ
diff --git a/docs/team/Nirmaleshwar.md b/docs/team/Nirmaleshwar.md
new file mode 100644
index 0000000000..1eaec80494
--- /dev/null
+++ b/docs/team/Nirmaleshwar.md
@@ -0,0 +1,121 @@
+# Nirmaleshwar Sathiya Moorthi
+## Project Portfolio
+
+---
+## Overview
+Stonks XD is an expense managing software that aims to simplify the process of keeping track of one's finances.
+The target users for this app are computing students that travels frequently and prefer logging their finances.
+The app is able to track your daily expenses, set and adjust your spending limits and give advice based on daily expenses.
+It is also able to give visual representations of financial data through bar graphs with currency conversion capabilities.
+---
+
+### Summary of Contributions
+
+* **Classes:** `Currency Conversion` & `Currency Manager`
+ * **Purpose:** Handles all currency related functions that support the execution of commands such as `list_curr`, `set_curr` and `check_curr`.
+ * **Justifications:** Allows users to use Stonks XD in their native currency thus, giving them a better grasp of their finances and ensuring a more personalised experience.
+ * **Significance:** This enhancement requires proper understanding of what needs to be shown, the indexing, different enums, format specifiers like 2 decimal place for money related entries.
+
+
+
+* **Feature:** `check_curr`
+ * **Purpose:** Allow users to check the current currency mode of Stonks XD.
+ * **Justifications:** Sometimes user can forget what currency mode the entries are in e.g. when user re-opens Stonks XD. This commands allows the user to check the currency type at any point during execution.
+ * **Significance:** Requires tracking the currency state at all times, even when switching to other modes or after saving the program.
+
+
+
+* **Feature:** `list_curr`
+ * **Purpose:** Allow users to list available currency conversion modes.
+ * **Justifications:** When using Stonks XD for the first time, users can check the available types at their convenience without referring to the UG.
+ * **Significance:** Used an array store and loop through enum class of currency types and print them out. If newer types are added, changes have to be made only to the enum class.
+
+
+
+* **Feature:** `set_curr`
+ * **Purpose:** Converts all entries in Stonks XD to the given currency type.
+ * **Justifications:** Users can visualize their expenses or income in their native currency. Since the purpose of Stonks XD is to not only track but also to save money for users, converting to a currency they are familiar with will enable them to achieve the aforementioned goals.
+ * **Significance:** The currency types are stored in an enum class and when a user requests a change, it obtains all the entries from `income`, `expense`, `budget`, `balance` and multiplies them all with the correct exchange rate. If returning to default (SGD), then the original entries are returned.
+
+
+
+* **Feature:** `add_ex` & `add_in` commands.
+ * **Purpose** Adds user expense and income entries to their appropriate lists.
+ * **Justifications:** Users can manage two separate lists and interact with them by adding items.
+ * **Significance:** It is the most instrumental function to the tracker, so it involves managing the different attributes that the user can parse.
+
+
+
+* **Feature:** `del_ex`& `del_in` commands.
+ * **Purpose:** Allows users to delete entries from their appropriate lists.
+ * **Justifications:** Users might have unwanted or erroneous entries that they might wish to delete.
+ * **Significance:** Similar to the add function, it is instrumental to the tracker. Ensuring the deletion of the correct entry is a bit more crucial. For this purpose, `parser` is made a lot more strict in what is acceptable as a proper index for deletion.
+
+
+
+* **Link to code contribution:** [Reposense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=Nirmaleshwar&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=Nirmaleshwar&tabRepo=AY2122S1-CS2113T-T12-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false)
+
+---
+
+### Enhancement to existing features:
+
+* Added JUnit testing for command class [#92](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/92)
+* Added JUnit testing for currencyManager class [#276](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/276)
+* Refactored Currency commands into separate classes [#213](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/213)
+* Refactored `Parser` to include separate case handling for currency commands [#213](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/213)
+* Added Javadoc comments to improve code readability [#270](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/270)
+
+
+
+### Contributions to the UG:
+
+* Created the entire UG for v1.0 release [#76](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/76/files)
+* Implemented drop-down box style descriptions [#76](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/76/files)
+* Included guides for currency related commands for v2.0 release [#137](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/137/files)
+* Made improvements to the structuring and readability of UG [#137](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/137/files)
+
+
+
+### Contributions to the DG:
+
+* Created section pertaining to command class for v1.0 release [#83](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/83)
+* Created entire manual testing section for v1.0 release [#83](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/83)
+* Created section for Currency Conversion class [#276](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/276)
+* Included both class and sequence diagrams [#276](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/276)
+
+
+
+### Contributions to team-based tasks:
+
+ * Worked together with teammates for the UG, DG and features for v1.0.
+ * Discussed with teammates regularly on ideas for the development of the product.
+ * Involved in creating releases for `v1.0` & `v2.0`
+ * Handled issues, user stories and PR's during project development.
+ * Facilitated Zoom calls by creating rooms whenever required and taking meeting minutes.
+ * Tested extensively and fixed bugs before final release.
+
+
+
+### Review/mentoring contributions:
+
+* Links to PR's reviewed by me:
+ * [#20](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/20)
+ * [#27](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/27)
+ * [#45](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/45)
+ * [#49](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/49)
+ * [#86](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/86)
+ * [#122](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/122)
+ * [#134](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/134)
+ * [#143](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/143)
+ * [#212](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/212)
+ * [#213](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/213)
+ * [#215](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/215)
+
+
+
+### Other notable contributions not listed above:
+
+* Created the skeleton of entire command class for Stonks XD before refactoring [#20](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/20)
+* Fixed the loss of precision when converting between currencies when they ae of type double [#279](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/137)
+
+
\ No newline at end of file
diff --git a/docs/team/anshenglee.md b/docs/team/anshenglee.md
new file mode 100644
index 0000000000..38f426a976
--- /dev/null
+++ b/docs/team/anshenglee.md
@@ -0,0 +1,135 @@
+# Lee An Sheng - Project Portfolio Page
+
+## Overview
+Stonks XD is an expense managing software that aims to simplify the process of keeping track of one's finances.
+The target users for this app are computing students that travels frequently and prefer logging their finances.
+Stonks XD allows you to:
+- Add income, add daily expenses and track them.
+- Set budgets for different expense categories and receive financial advices.
+- Receive a snapshot of your spending in the form of bar graphs.
+- See your entries in different currencies.
+
+### Summary of Contributions
+
+#### Code contributed
+
+Link to code contribution: [RepoSense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=AnShengLee&tabRepo=AY2122S1-CS2113T-T12-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+#### Enhancements implemented
+
+- **New Feature**: Added `Parser` class.
+ - What it does: Parses user inputs and determine the command user is trying to call.
+ - Justification: This is required in order for our program to understand what the user is trying to achieve.
+ - Highlights: `Parser` uses regex to do its job, verifying the format of inputs given and splitting user inputs to
+ smaller chunks that the program can understand. Thus, this feature requires a decent understanding of regex, good
+ understanding of `String` manipulation and also error handling especially when the user types in unexpected inputs.
+
+
+- **New Feature**: Added `DataManager` class.
+ - What it does: Saves and loads user's entries and settings into `csv` files.
+ - Justification: This is required so that user's data (such as expenses) will not be lost when the program closes.
+ When the user starts the program again, all the previously saved data will be loaded.
+ - Highlights: Uses classes like `BufferedWriter` and `Scanner` to write and read files. This feature requires an
+ understanding of file writing and reading libraries. Also, need to understand how other classes work as this
+ class interacts with many other classes such as `FinancialManager` and `BudgerManager`.
+
+
+- **New Feature**: Added mechanism to allow user to input their parameters in any order.
+ - What it does: For a command that takes in 2 parameters, A and B, user can give A first followed by B or
+ vice versa.
+ - Justification: This is to increase the user-friendly factor of our product.
+ - Highlights: This is implemented through regex. Requires good understanding of regex.
+
+
+- **New Feature**: Added `Messages` class.
+ - What it does: Stores all possible messages that the program could show to the user.
+ - Justification: This is done to fit the OOP paradigm and increase readability.
+ - Highlights: Good understanding of OOP.
+
+
+- **New Feature**: Added `CommandKeywords` class.
+ - What it does: Stores all possible commands the user can give.
+ - Justification: This is done to fit the OOP paradigm and increase readability.
+ - Highlights: Good understanding of OOP.
+
+
+- **New Feature**: Included date mechanism to the program.
+ - What it does: Allows entries entered by the user to have a date tagged to them.
+ - Justification: We can group entries up by month, year, etc. This then allows us to calculate things like
+ total expense in a month.
+ - Highlights: This requires an understanding of the `LocalDate` and `DateTimeFormatter` libraries.
+
+
+- **New Feature**: Added `ClearAllEntriesCommand` class.
+ - What it does: Allows all entries to be cleared.
+ - Justification: Mainly just a quality of life feature. If the user wants to start with a clear list of entries, they
+ can just use this command to start afresh. This command is useful during testing as well.
+ - Highlights: Understand how other classes work as this class interacts with other classes.
+
+#### Contributions to UG
+
+- The basic UG structure when the product was in v1.0.
+ - Such as the introduction.
+ - Table of contents.
+ - Hyperlinks.
+ - Quick start.
+ - Command summary.
+- The `Notes` portion under `Features`.
+- Data saving feature.
+- Fix typos and small format errors.
+
+#### Contributions to DG
+
+- Fix typos and small format errors.
+- Parser component.
+- Data saving component.
+ - Contributed **1** class diagram and **5** sequence diagrams.
+ Link to PR: [#277](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/277)
+- Added user stories for v2.0.
+
+#### Contributions to team-based tasks
+
+- Attended all team meetings.
+- Brainstormed features and user stories for v1.0 and v2.0.
+- Discussed format and content for both UG and DG.
+- Ensured forking workflow.
+- Managed milestones. Ensured everything was done before deadlines. Adjusted milestone deadlines when needed.
+- Managed issues. Ensured duplicate issues are marked. Labelled issues with the appropriate labels.
+- Fixed bugs throughout the project.
+- Fixed multiple bugs found during PED
+ - **6** PED bugs were fixed. Here are the issue links:
+ [#192](https://github.com/AY2122S1-CS2113T-T12-3/tp/issues/192)
+ , [#173](https://github.com/AY2122S1-CS2113T-T12-3/tp/issues/173)
+ , [#170](https://github.com/AY2122S1-CS2113T-T12-3/tp/issues/170)
+ , [#167](https://github.com/AY2122S1-CS2113T-T12-3/tp/issues/167)
+ , [#157](https://github.com/AY2122S1-CS2113T-T12-3/tp/issues/157)
+ , [#155](https://github.com/AY2122S1-CS2113T-T12-3/tp/issues/155)
+
+#### Review/mentoring contributions during team project
+
+- Total PRs reviewed: 34
+- Links to PRs with non-trivial reviews:
+ [#210](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/210)
+ , [#208](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/208)
+ , [#122](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/122)
+ , [#56](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/56)
+ , [#49](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/49)
+ , [#45](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/45)
+ , [#32](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/32)
+ , [#27](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/27)
+ , [#20](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/20)
+
+- Other forms of help:
+ - Helped other teammates through streaming channels like Zoom and Discord.
+ - Helped through telegram group chat.
+
+#### Contributions beyond the project team
+
+- Links to PRs reviewed during iP:
+ - [[Silin Chen] iP #192](https://github.com/nus-cs2113-AY2122S1/ip/pull/192)
+ - [[Swati] iP #10](https://github.com/nus-cs2113-AY2122S1/ip/pull/10)
+
+- Links to PRs reviewed during tP:
+ - [[CS2113-F11-3] TourPlanner #42](https://github.com/nus-cs2113-AY2122S1/tp/pull/42)
+ - [[CS2113T-W12-4] Fridget #3](https://github.com/nus-cs2113-AY2122S1/tp/pull/3)
+
\ No newline at end of file
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index ab75b391b8..0000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# John Doe - Project Portfolio Page
-
-## Overview
-
-
-### Summary of Contributions
diff --git a/docs/team/jonathanlkw.md b/docs/team/jonathanlkw.md
new file mode 100644
index 0000000000..eb71bef2e8
--- /dev/null
+++ b/docs/team/jonathanlkw.md
@@ -0,0 +1,78 @@
+# Lam Kai Wen Jonathan - Project Portfolio Page
+
+---
+## Overview
+Stonks XD is an expense managing software that aims to simplify the process of keeping track of one's finances.
+The target users for this app are computing students that travels frequently and prefer logging their finances.
+The app is able to track your daily expenses, set and adjust your spending limits and give advice based on daily expenses.
+It is also able to give visual representations of financial data through bar graphs with currency conversion capabilities.
+
+---
+
+### Summary of Contributions
+
+- **New Feature**: Added `Entry`, `Expense` and `Income` classes.
+ - What it does: These form the base class structure for all added expense and income entries.
+ - Justification: Each instance of the `Expense` and `Income` class contains important information about the entries in a concise manner.
+ - Highlights: This feature requires an understanding of class inheritance to implement. Furthermore, `Expense` and `Income` classes form the basis for most implementations in `FinancialTracker`.
+
+
+- **New Feature**: Added `Budget` class and its child classes.
+ - What it does: Stores every possible budget in the program as a child class of `Budget`.
+ - Justification: Information about the respective budgets (budget limits, current spending), and their methods can be stored an accessed in an elegant manner.
+ - Highlights: This feature requires an understanding of class and method inheritance in order to efficiently set up multiple budgets.
+
+
+- **New Feature**: Added `BudgetManager` class.
+ - What it does: This is the main processor of all budget and budget reminder operations. (Budget reminders are given when the user approaches/exceeds an active budget, and prompts are given to the user on how to refactor the budget.)
+ - Justification: Contains all budget and budget reminder methods. Isolates budget operations from other operations in the program.
+ - Highlights: The logic behind which budget reminders to give was quite challenging as it contained many scenarios.
+
+- **New Feature** Added `BudgetReminder` class.
+ - What it does: This class represents all the possible budget reminders given in different scenarios.
+ - Justification: Organizes the large number of reminder information in a neat manner.
+ - Highlights: The budget reminders had many variations and required much data from various places to print the reminders accurately.
+
+
+- **New Feature**: Added `set_budget` command.
+ - What it does: Receives input from the user and sets the budget limit for the appropriate budget. Budget limits of 0 represent deactivated budgets.
+ - Justification: Allows user to set his budgets as a way to manage his finances.
+ - Highlights: This feature required interaction between `Parser`, `Command` and `Ui` classes. Understanding of the program execution flow was required to implement correctly. Furthermore, invalid inputs by users had to be properly handled with error messages shown.
+
+
+- **New Feature**: Added `check_budget` command.
+ - What it does: Shows the user the current budget limit of a requested budget.
+ - Justification: Allows the user to check and understand his current budget limits and plan appropriately.
+ - Highlights: This feature required interaction between `Parser`, `Command` and `Ui` classes. Understanding of the program execution flow was required to implement correctly. Furthermore, invalid budgets inputted had to be properly handled with error messages shown.
+
+
+- **New Feature**: Added `set_threshold` command.
+ - What it does: Receives input from the user and sets the threshold limit beyond which budget reminders will be given.
+ - Justification: Allows user to customise when he wants to receive warnings.
+ - Highlights: This feature required understand of the interaction between `Parser`, `Command` and `Ui` classes. Invalid inputs of more than 2dp had to be handled and inputs below zero and above 1 had to be rejected.
+
+Link to code contribution: [RepoSense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=jonathanlkw&tabRepo=AY2122S1-CS2113T-T12-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+- **Project Management**:
+ - Managed release `v1.0` on GitHub.
+
+
+- **Enhancement to existing features**:
+ - Wrote JUnit tests for `BudgetManager` class (Pull requests [#84](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/84), [#127](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/127), [#243](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/243))
+ - Added additional JUnit tests for `Ui` class (Pull request [#127](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/127))
+ - Bug fixes. (Pull requests [#42](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/42), [#44](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/44), [#135](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/135), [#254](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/254), [#284](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/284))
+
+
+- **Documentation**:
+ - User Guide:
+ - Contributed to the writing of the introduction to StonksXD (Pull requests [#86](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/86/files), [#129](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/129))
+ - Added write-up for `set_budget`, `check_budget` and `set_threshold` commands (Pull request [#129](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/129), [#281](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/281))
+ - Made edits for grammar and friendlier tone (Pull request [#129](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/129))
+ - Developer Guide:
+ - Added Architecture Component write-up and architecture diagram (Pull requests [#85](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/85), [#89](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/89), [#133](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/133), [#273](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/273))
+ - Added Budget Component write-up and sequence diagram (Pull request [#133](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/133), [#273](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/273))
+
+
+- **Community**:
+ - PRs reviewed (with non-trivial comments): [#27](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/27), [#83](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/83), [#122](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/122), [#201](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/201), [#211](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/211), [#213](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/213)
+ - Contributed to the discussion of solutions to PE-D bugs via zoom meetings.
diff --git a/docs/team/kyun99.md b/docs/team/kyun99.md
new file mode 100644
index 0000000000..d05d671f3c
--- /dev/null
+++ b/docs/team/kyun99.md
@@ -0,0 +1,90 @@
+# Lim Kay Yun's Project Portfolio Page
+
+## Project: StonksXD
+
+Stonks XD - It is an expense managing software that aims to simplify the process of keeping track of ones' s finances.
+The target users for this app are computing students that travels frequently and prefer logging their finances.
+By simplifying the commands, we made logging financial information easy, lowering the barrier of entry for users to build
+positive financial habits
+
+
+Given below are my contributions to the project.
+
+- **New Feature**: Added StonksXD class.
+ - What it does: It is the main class of the entire program which interacts with all the main components of the program
+ - Justification: This feature acts as the central processor of the program
+ - Highlights: The components used in the program used this class a point of entry. New components are added accordingly in this class.
+ Good understanding of OOP is required, and the overall structure of the program.
+ - Credits: Structure of code is adapted from [addressbook-level2](https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/Main.java)
+
+
+- **New Feature**: Added command and exit command class
+ - What is does: Command is the parent class which all other commands inherit from and ExitCommand is the only class that inherits
+ overrides isExit method of Command, which is used to terminate the program. It provides the framework for other existing command classes.
+ - Justification: Command specifies the structure of the other command classes so that they can integrate well with the main class.
+ Only the exit command should terminate the program. Other classes which inherits from Command should not be able to do so.
+ - Highlights: All commands used in the program inherits from the Command class. If there is any change in behavior require to be done for
+ all commands, it should be done in Command. Good understanding of OOP is required.
+
+
+- **New Feature**: Added commands to get the total expense/income entries.
+ - What it does: It calculates the sum of all the amounts in each expense/income entry.
+ - Justification: Provides insights on the expense/income of the user
+ - Highlights: This feature is used as a foundation for another command that gets total expense/income given a specified date range.
+
+
+- **New Feature**: Added commands to get the total expense/income entries between 2 dates.
+ - What it does: It calculates the sum of all the amounts in each expense/income entry based on a pair of dates.
+ - Justification: Provides insights on the expense/income of the user
+ - Highlights: This feature is uses streams and LocalDate heavily so understanding of Java 8 streams is necessary.
+
+- **New Feature**: Added optional year parameter that will show graph according to the year given by user
+ - What is does: The program shows a graph according to all the months in the year, given by the user
+ - Justification: Since the program allows user to input entries by date, they should also be able to view entries of that year
+ - Highlights: This feature required good understanding of DateTimeFormatter since it does not accept year-only formats.
+
+- **New Feature**: Added Financial Advisor that will offer finance tips to the user when the program terminates
+ - What is does: The program prints a random message everytime the user ends the program
+ - Justification: It is part of our user story, to offer tips to the user, so he can manage their finances better.
+ - Highlights: It utilizes Random class which deals with random number generators.
+
+- **Code contributed**: [RepoSense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=t12-3&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=kyun99&tabRepo=AY2122S1-CS2113T-T12-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&zFR=false&until=2021-11-07)
+
+
+- **Project management**:
+ - Managed release v2.0 on GitHub
+ - Ensured forking workflow
+ - Brainstormed for features and ideas as a project
+
+
+- **Enhancements to existing features**:
+ - Set limit to the sum of total expense and income to prevent rounding-off error.
+ - Set limit to amount user can add for each entry.
+ - Added multiple helper classes used within the program.
+ - Wrote additional tests for existing features. [#166](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/116), [#202](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/202)
+ - Added Categories and Date fields to Expense and Income.[#56](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/56)
+ - Added method to get data for printing in graph command. [#66](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/66)
+ - Refactored financial tracker class to use 2 array list instead of 1, making the code cleaner and more readable. [#45](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/45)
+ - Refactored parser code using SLAP and exceptions. [#97](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/97)
+
+- **Documentation**:
+ - User Guide:
+ - Supplemented total_in, total_ex, btw_in, btw_ex commands. [#217](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/217)
+ - Supplemented add_in, add_ex commands. [#208](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/208)
+ - Added FAQ. [#105](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/105)
+ - Developer Guide:
+ - Add Financial Tracker component [#253](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/253)
+ - Add StonksXD sequence diagram
+ - Add Command component sequence diagram
+
+- **Community**:
+ - PRs reviewed:
+ [#49](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/49)
+ , [213](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/213)
+ , [#64](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/64)
+ , [#48](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/48)
+ , [#32](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/32)
+ - Helped with reading through and debugging code.
+
+
+
diff --git a/docs/team/kzq1999.md b/docs/team/kzq1999.md
new file mode 100644
index 0000000000..4ac9926932
--- /dev/null
+++ b/docs/team/kzq1999.md
@@ -0,0 +1,118 @@
+# Zhi Qian - Project Portfolio Page
+
+---
+## 1. Overview
+Stonks XD is an expense managing software that aims to simplify the process of keeping track of one's finances.
+The target users for this app are computing students that travels frequently and prefer logging their finances.
+The app is able to track your daily expenses, set and adjust your spending limits and give advice based on daily expenses.
+It is also able to give visual representations of financial data through bar graphs with currency conversion capabilities.
+
+---
+
+### 2. Summary of Contributions
+* `Ui class`
+ * What it does: Handles all the feedback messages and listing message of the program.
+ * Justifications: Users requires some sort of feedback to know what they are doing in the program, things include error messages, alerting of missing parameters, indexing and organising of different messages.
+ * Highlights: This enhancement requires proper understanding of what needs to be shown, the indexing, different enums, format specifiers like 2 decimal place for money related entries.
+
+
+
+* `Help command`
+ * What it does: Listing all possible commands for the users
+ * Justifications: Users need to have something to remind them of what commands exist
+ * Highlights: Uses the List library to add every help command and its format into an array, command and its format are all represented as CONSTANTS for easy reconfiguration.
+
+
+
+* `List commands`
+ * What it does: List out the expenses entries, income entries or entries that the user searched for using find command
+ * Justifications: Users that use the Stonks XD program might want to view all the things that they added, so having the capability to list would be a very helpful addition to the program.
+ * Highlights: Requires handling of multiple ArrayList of FinancialTracker.
+
+
+
+* `Find command`
+ * What it does: Allow users to search through dates, keywords, value and category
+ * Justifications: There may be too many entries and having a find function that can filter through all these entries would be very helpful.
+Users might want to search not only based on description but also based on dates, values and categories.
+ * Highlights: Requires parsing of values from double to string, dates to string and use of try- catch to handle any exceptions thrown. Enums were used in finding categories in place of original version of String.
+
+
+
+* `Balance command`
+ * What it does: Allow users to see the net balance of all expenses and incomes in the program
+ * Justifications: Users might want to know how much they have right now so they can decide how much to spend in the future
+ * Highlights: Balance is dynamic and it depends on the entries given in the program, so using clear_all_entries would wipe all balance too.
+
+
+
+* `StonksGraph Class`
+ * What it does: Graphing of the various financial calculations into a snapshot with bargraphs
+ * Justifications: Users might need some sort of visualisation of their financial data, as numbers by itself are not that helpful. With this feature they will be able to view their monthly expenses throughout the year with ease and identify point of major spending or earning.
+ * Highlights: Uses 2D array to represent the graph and uses date library to constantly plot the graph based on current year and current month.
+
+
+
+* Link to code contribution: [Reposense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=KZQ1999&tabRepo=AY2122S1-CS2113T-T12-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false)
+
+* Enhancement to existing features
+ * Added Junit testing for testing outputs to terminal [#25](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/25) [#40](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/40) [#80](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/80) [#93](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/93)
+ * Refractoring and cleaning of code [#47](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/47) [#91](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/91)
+ * Javadocs [#125](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/125)
+
+
+
+* Contributions to the UG:
+ * Content page organisation [#201](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/201)
+ * `Graphing` feature in the UG [#104](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/104)
+ * `Find,help and end` feature in the UG [#205](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/205)
+ * Fixed UG deployment issue [#113](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/113)
+ * Contributed to the command summary table as part of the team [#205](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/205)
+
+
+
+* Contributions to the DG:
+ * `Ui` component and user stories [#80](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/80)
+ * Added `listing` Sequence Diagram for the `Ui` component [#104](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/104)
+ * Added Sequence Diagram for the `graphing` component: [#205](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/205) [#210](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/210)
+
+
+
+* Contributions to team-based tasks
+ * Creating Team Repository
+ * Worked as a team to come out with User Guide, Features for V1.0 through many team meetings
+ * Brainstormed for user stories as a team
+ * Creating jar file for `v1.0`
+ * Issue, Milestone handling in Github issue tracker.
+
+
+
+* Review/mentoring contributions:
+ * Links to PR reviewed:
+ [#11](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/11)
+ [#20](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/20)
+ [#27](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/27)
+ [#49](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/49)
+ [#56](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/56)
+ [#110](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/110)
+ [#123](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/123)
+ [#201](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/201)
+ [#206](https://github.com/AY2122S1-CS2113T-T12-3/tp/pull/206)
+
+ * Helped in other ways:
+ * Contributed to zoom calls to help teammates in trouble.
+ * Contributed to discussions and call for help whenever a teammate is in trouble.
+
+
+
+
+* Evidence of helping others:
+ * [PE-DryRun](https://github.com/KZQ1999/ped/tree/main/files)
+ * [DG-Review](https://github.com/nus-cs2113-AY2122S1/tp/pull/28/files/b7ab96268157fb2dd4459dd973ac30ded04712c0)
+
+
+
+
+* Evidence of technical accomplishments
+ * Graphing is a component that is quite tricky as it involves 2d arrays, plotting, incorporating different methods to generate each part of the graph. Scaling of graph is another major component that is quite difficult to accomplish.
+
\ No newline at end of file
diff --git a/src/main/java/seedu/budget/BillsBudget.java b/src/main/java/seedu/budget/BillsBudget.java
new file mode 100644
index 0000000000..fcd549d5e1
--- /dev/null
+++ b/src/main/java/seedu/budget/BillsBudget.java
@@ -0,0 +1,19 @@
+package seedu.budget;
+
+import seedu.entry.ExpenseCategory;
+
+/**
+ * Class representing the bills budget category.
+ */
+public class BillsBudget extends Budget {
+
+ /**
+ * Constructor initializing category, name and limit of the bills budget.
+ * @param limit Limit of the budget category
+ */
+ public BillsBudget(double limit) {
+ this.category = ExpenseCategory.BILLS;
+ this.name = ExpenseCategory.BILLS.toString();
+ this.limit = limit;
+ }
+}
diff --git a/src/main/java/seedu/budget/Budget.java b/src/main/java/seedu/budget/Budget.java
new file mode 100644
index 0000000000..82b7ca694e
--- /dev/null
+++ b/src/main/java/seedu/budget/Budget.java
@@ -0,0 +1,69 @@
+package seedu.budget;
+
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+/**
+ * Abstract class representing all possible budgets in the system.
+ */
+public abstract class Budget {
+ protected ExpenseCategory category;
+ protected String name;
+ protected double limit;
+
+ /**
+ * Returns the category of the specified budget.
+ * @return Category of the budget
+ */
+ public ExpenseCategory getCategory() {
+ return this.category;
+ }
+
+ /**
+ * Returns the name of the specified budget.
+ * @return Name of the budget
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Sets the budget limit to specified value.
+ * @param amount New budget limit to be set
+ */
+ public void setLimit(double amount) {
+ this.limit = amount;
+ }
+
+ /**
+ * Returns the budget limit of the specified budget.
+ * @return Budget limit of budget
+ */
+ public double getLimit() {
+ return this.limit;
+ }
+
+ /**
+ * Returns the sum of all expenses in the budget category for the current month.
+ * @param entries ArrayList of expenses from FinacialTracker
+ * @param date Date containing the current month and year
+ * @return Sum of all expenses in the budget category for the current month
+ */
+ public double calAmount(ArrayList entries, LocalDate date) {
+ double amount = 0;
+ for (Expense expense : entries) {
+ if ((expense.getCategory() == this.category) & isCorrectMonthYear(expense, date)) {
+ amount += expense.getValue();
+ }
+ }
+ assert amount >= 0;
+ return amount;
+ }
+
+ protected boolean isCorrectMonthYear(Expense expense, LocalDate date) {
+ return expense.getDate().getMonth() == date.getMonth() & expense.getDate().getYear() == date.getYear();
+ }
+}
diff --git a/src/main/java/seedu/budget/EntertainmentBudget.java b/src/main/java/seedu/budget/EntertainmentBudget.java
new file mode 100644
index 0000000000..47deed59ea
--- /dev/null
+++ b/src/main/java/seedu/budget/EntertainmentBudget.java
@@ -0,0 +1,18 @@
+package seedu.budget;
+
+import seedu.entry.ExpenseCategory;
+
+/**
+ * Class representing the entertainment budget category.
+ */
+public class EntertainmentBudget extends Budget {
+ /**
+ * Constructor initializing category, name and limit of the entertainment budget.
+ * @param limit Limit of the budget category
+ */
+ public EntertainmentBudget(double limit) {
+ this.category = ExpenseCategory.ENTERTAINMENT;
+ this.name = ExpenseCategory.ENTERTAINMENT.toString();
+ this.limit = limit;
+ }
+}
diff --git a/src/main/java/seedu/budget/FoodBudget.java b/src/main/java/seedu/budget/FoodBudget.java
new file mode 100644
index 0000000000..011afdf44e
--- /dev/null
+++ b/src/main/java/seedu/budget/FoodBudget.java
@@ -0,0 +1,19 @@
+package seedu.budget;
+
+import seedu.entry.ExpenseCategory;
+
+/**
+ * Class representing the food budget category.
+ */
+public class FoodBudget extends Budget {
+
+ /**
+ * Constructor initializing category, name and limit of the food budget.
+ * @param limit Limit of the budget category
+ */
+ public FoodBudget(double limit) {
+ this.category = ExpenseCategory.FOOD;
+ this.name = ExpenseCategory.FOOD.toString();
+ this.limit = limit;
+ }
+}
diff --git a/src/main/java/seedu/budget/MedicalBudget.java b/src/main/java/seedu/budget/MedicalBudget.java
new file mode 100644
index 0000000000..d1901ecef7
--- /dev/null
+++ b/src/main/java/seedu/budget/MedicalBudget.java
@@ -0,0 +1,19 @@
+package seedu.budget;
+
+import seedu.entry.ExpenseCategory;
+
+/**
+ * Class representing the medical budget category.
+ */
+public class MedicalBudget extends Budget {
+
+ /**
+ * Constructor initializing category, name and limit of the medical budget.
+ * @param limit Limit of the budget category
+ */
+ public MedicalBudget(double limit) {
+ this.category = ExpenseCategory.MEDICAL;
+ this.name = ExpenseCategory.MEDICAL.toString();
+ this.limit = limit;
+ }
+}
diff --git a/src/main/java/seedu/budget/MiscBudget.java b/src/main/java/seedu/budget/MiscBudget.java
new file mode 100644
index 0000000000..987c67ef50
--- /dev/null
+++ b/src/main/java/seedu/budget/MiscBudget.java
@@ -0,0 +1,19 @@
+package seedu.budget;
+
+import seedu.entry.ExpenseCategory;
+
+/**
+ * Class representing the misc budget category.
+ */
+public class MiscBudget extends Budget {
+
+ /**
+ * Constructor initializing category, name and limit of the misc budget.
+ * @param limit Limit of the budget category
+ */
+ public MiscBudget(double limit) {
+ this.category = ExpenseCategory.MISC;
+ this.name = ExpenseCategory.MISC.toString();
+ this.limit = limit;
+ }
+}
diff --git a/src/main/java/seedu/budget/OverallBudget.java b/src/main/java/seedu/budget/OverallBudget.java
new file mode 100644
index 0000000000..6b6977d7b4
--- /dev/null
+++ b/src/main/java/seedu/budget/OverallBudget.java
@@ -0,0 +1,41 @@
+package seedu.budget;
+
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+/**
+ * Class representing the overall budget category.
+ */
+public class OverallBudget extends Budget {
+
+ /**
+ * Constructor initializing category, name and limit of the overall budget.
+ * @param limit Limit of budget category
+ */
+ public OverallBudget(double limit) {
+ this.category = ExpenseCategory.OVERALL;
+ this.name = "OVERALL";
+ this.limit = limit;
+ }
+
+ /**
+ * Returns the sum of all expenses in the current month.
+ * @param entries ArrayList of expenses from FinacialTracker
+ * @param date Date containing the current month and year
+ * @return Sum of all expenses in the current month
+ */
+ @Override
+ public double calAmount(ArrayList entries, LocalDate date) {
+ double amount = 0;
+ for (Expense expense : entries) {
+ if (isCorrectMonthYear(expense, date)) {
+ amount += expense.getValue();
+ }
+ }
+ assert amount >= 0;
+ return amount;
+ }
+}
diff --git a/src/main/java/seedu/budget/TransportBudget.java b/src/main/java/seedu/budget/TransportBudget.java
new file mode 100644
index 0000000000..83377c74a2
--- /dev/null
+++ b/src/main/java/seedu/budget/TransportBudget.java
@@ -0,0 +1,19 @@
+package seedu.budget;
+
+import seedu.entry.ExpenseCategory;
+
+/**
+ * Class representing the transport budget category.
+ */
+public class TransportBudget extends Budget {
+
+ /**
+ * Constructor initializing category, name and limit of the transport budget.
+ * @param limit Limit of the budget category
+ */
+ public TransportBudget(double limit) {
+ this.category = ExpenseCategory.TRANSPORT;
+ this.name = ExpenseCategory.TRANSPORT.toString();
+ this.limit = limit;
+ }
+}
diff --git a/src/main/java/seedu/commands/Command.java b/src/main/java/seedu/commands/Command.java
new file mode 100644
index 0000000000..66bcc7816f
--- /dev/null
+++ b/src/main/java/seedu/commands/Command.java
@@ -0,0 +1,15 @@
+package seedu.commands;
+
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public abstract class Command {
+ public abstract void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager);
+
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/commands/InvalidCommand.java b/src/main/java/seedu/commands/InvalidCommand.java
new file mode 100644
index 0000000000..7042c5e8f3
--- /dev/null
+++ b/src/main/java/seedu/commands/InvalidCommand.java
@@ -0,0 +1,25 @@
+package seedu.commands;
+
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class InvalidCommand extends Command {
+
+ private final String message;
+
+ public InvalidCommand(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ui.printError(message);
+ }
+}
diff --git a/src/main/java/seedu/commands/budget/BalanceCommand.java b/src/main/java/seedu/commands/budget/BalanceCommand.java
new file mode 100644
index 0000000000..f16ce68068
--- /dev/null
+++ b/src/main/java/seedu/commands/budget/BalanceCommand.java
@@ -0,0 +1,25 @@
+package seedu.commands.budget;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+/**
+ * The command that prints the balance of the financial tracker to the output.
+ */
+public class BalanceCommand extends Command {
+
+ /**
+ * Prints the balance of the financial tracker to the standard output.
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations/
+ * @param currencyManager The currency manager that manages the currency state of the financial tracker.
+ */
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ui.printBalance(finances.calculateBalance());
+ }
+}
diff --git a/src/main/java/seedu/commands/budget/CheckBudgetCommand.java b/src/main/java/seedu/commands/budget/CheckBudgetCommand.java
new file mode 100644
index 0000000000..10f0144be0
--- /dev/null
+++ b/src/main/java/seedu/commands/budget/CheckBudgetCommand.java
@@ -0,0 +1,39 @@
+package seedu.commands.budget;
+
+import seedu.commands.Command;
+import seedu.entry.ExpenseCategory;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+/**
+ * Class representing the check budget command.
+ * Checks the current limit of the specified budget.
+ */
+public class CheckBudgetCommand extends Command {
+ ExpenseCategory category;
+
+ /**
+ * Constructor that initializes the category of the budget to be checked.
+ * @param category Category of budget to be checked
+ */
+ public CheckBudgetCommand(ExpenseCategory category) {
+ this.category = category;
+ }
+
+ /**
+ * Obtains the budget limit of the specified budget and prints it to the Ui with an appropriate message.
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency conversion related operations.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ double budgetLimit = budgetManager.getBudget(category);
+ ui.printBudget(category, budgetLimit);
+ }
+
+}
diff --git a/src/main/java/seedu/commands/budget/SetBudgetCommand.java b/src/main/java/seedu/commands/budget/SetBudgetCommand.java
new file mode 100644
index 0000000000..7aa50acae3
--- /dev/null
+++ b/src/main/java/seedu/commands/budget/SetBudgetCommand.java
@@ -0,0 +1,44 @@
+package seedu.commands.budget;
+
+import seedu.commands.Command;
+import seedu.entry.ExpenseCategory;
+import seedu.reminder.BudgetReminder;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+/**
+ * Class representing the set budget command.
+ * Sets the limit of the specified budget if the new limit is valid.
+ * If invalid, an appropriate reminder is returned, with advice on how to handle the budget.
+ */
+public class SetBudgetCommand extends Command {
+ ExpenseCategory category;
+ double amount;
+
+ /**
+ * Constructor that initializes the category and the amount of the budget to be set.
+ * @param category Category of the budget
+ * @param amount New budget limit to be set
+ */
+ public SetBudgetCommand(ExpenseCategory category, double amount) {
+ this.category = category;
+ this.amount = amount;
+ }
+
+ /**
+ * Sets the limit of the specified budget if the new limit is valid.
+ * If invalid, an appropriate reminder is printed to the Ui.
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency conversion related operations.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ BudgetReminder reminder = budgetManager.setBudget(amount, category, finances.getExpenses());
+ ui.printSetBudgetReminder(reminder);
+ }
+}
diff --git a/src/main/java/seedu/commands/budget/SetThresholdCommand.java b/src/main/java/seedu/commands/budget/SetThresholdCommand.java
new file mode 100644
index 0000000000..e88a8b2d7a
--- /dev/null
+++ b/src/main/java/seedu/commands/budget/SetThresholdCommand.java
@@ -0,0 +1,37 @@
+package seedu.commands.budget;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+/**
+ * Class representing the set threshold command.
+ * Sets the threshold value to the specified value.
+ */
+public class SetThresholdCommand extends Command {
+ double threshold;
+
+ /**
+ * Constructor initializing the threshold value to be set.
+ * @param threshold Threshold value to be set
+ */
+ public SetThresholdCommand(double threshold) {
+ this.threshold = threshold;
+ }
+
+ /**
+ * Sets the threshold value and prints a confirmation to the Ui.
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency conversion related operations.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ budgetManager.setThreshold(threshold);
+ ui.printThresholdConfirmation(threshold);
+ }
+}
diff --git a/src/main/java/seedu/commands/currency/CheckCurrentCurrencyCommand.java b/src/main/java/seedu/commands/currency/CheckCurrentCurrencyCommand.java
new file mode 100644
index 0000000000..b806758df9
--- /dev/null
+++ b/src/main/java/seedu/commands/currency/CheckCurrentCurrencyCommand.java
@@ -0,0 +1,26 @@
+//@@author Nirmaleshwar
+
+package seedu.commands.currency;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class CheckCurrentCurrencyCommand extends Command {
+
+ /**
+ * Executes checking of current currency types of entries.
+ * @param finances object from FinancialTracker
+ * @param ui object from Ui
+ * @param budgetManager object from BudgetManager
+ * @param currencyManager object from CurrencyManager
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ CurrencyType currency = currencyManager.getCurrency();
+ ui.printCurrentCurrency(currency);
+ }
+}
diff --git a/src/main/java/seedu/commands/currency/CurrencyConversionCommand.java b/src/main/java/seedu/commands/currency/CurrencyConversionCommand.java
new file mode 100644
index 0000000000..41af8b2e2a
--- /dev/null
+++ b/src/main/java/seedu/commands/currency/CurrencyConversionCommand.java
@@ -0,0 +1,38 @@
+package seedu.commands.currency;
+
+import seedu.commands.Command;
+import seedu.exceptions.SameCurrencyTypeException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class CurrencyConversionCommand extends Command {
+ protected CurrencyType from;
+ protected CurrencyType to;
+
+ /**
+ * Constructor used to assign old and new currency types.
+ * @param to new currency type that user wishes to convert to
+ */
+ public CurrencyConversionCommand(CurrencyType to) {
+ this.to = to;
+ this.from = CurrencyType.SGD;
+ }
+
+
+ /**
+ * Executes conversion of all entries into given currency type.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ try {
+ currencyManager.currencyConvertor(from, to, finances, budgetManager);
+ ui.printCurrencyChangedConfirmation(to);
+ } catch (SameCurrencyTypeException e) {
+ ui.printSameCurrencyTypeMessage(to);
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/commands/currency/CurrencyType.java b/src/main/java/seedu/commands/currency/CurrencyType.java
new file mode 100644
index 0000000000..d7dd43e0a8
--- /dev/null
+++ b/src/main/java/seedu/commands/currency/CurrencyType.java
@@ -0,0 +1,8 @@
+package seedu.commands.currency;
+
+/**
+ * Enumeration of all valid currency types in the system.
+ */
+public enum CurrencyType {
+ HKD, SGD
+}
diff --git a/src/main/java/seedu/commands/currency/ListCurrencyTypesCommand.java b/src/main/java/seedu/commands/currency/ListCurrencyTypesCommand.java
new file mode 100644
index 0000000000..011725ed9f
--- /dev/null
+++ b/src/main/java/seedu/commands/currency/ListCurrencyTypesCommand.java
@@ -0,0 +1,23 @@
+package seedu.commands.currency;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class ListCurrencyTypesCommand extends Command {
+
+ /**
+ * Executes listing of available currency types.
+ * @param finances object from FinancialTracker
+ * @param ui object from Ui
+ * @param budgetManager object from BudgetManager
+ * @param currencyManager object from CurrencyManager
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ui.printCurrencyTypes(currencyManager.getCurrencyTypes());
+ }
+}
diff --git a/src/main/java/seedu/commands/expense/AddExpenseCommand.java b/src/main/java/seedu/commands/expense/AddExpenseCommand.java
new file mode 100644
index 0000000000..28527a098a
--- /dev/null
+++ b/src/main/java/seedu/commands/expense/AddExpenseCommand.java
@@ -0,0 +1,46 @@
+//@@author Nirmaleshwar
+
+package seedu.commands.expense;
+
+import seedu.commands.Command;
+import seedu.entry.Expense;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.reminder.BudgetReminder;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import java.time.LocalDate;
+
+public class AddExpenseCommand extends Command {
+ private Expense expense;
+
+ /**
+ * Constructor for add expense command.
+ * @param expense object to be added
+ */
+ public AddExpenseCommand(Expense expense) {
+ this.expense = expense;
+ }
+
+ /**
+ * Executes adding of expense item.
+ * @param finances object from FinancialTracker
+ * @param ui object from Ui
+ * @param budgetManager object from BudgetManager
+ * @param currencyManager object from CurrencyManager
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ try {
+ finances.addExpense(expense);
+ ui.printExpenseAdded(expense);
+ BudgetReminder reminder = budgetManager.handleBudget(expense, finances.getExpenses());
+ ui.printBudgetReminder(reminder);
+ } catch (ExpenseOverflowException e) {
+ ui.printError(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/seedu/commands/expense/DeleteExpenseCommand.java b/src/main/java/seedu/commands/expense/DeleteExpenseCommand.java
new file mode 100644
index 0000000000..58db9bc4e8
--- /dev/null
+++ b/src/main/java/seedu/commands/expense/DeleteExpenseCommand.java
@@ -0,0 +1,41 @@
+//@@author Nirmaleshwar
+
+package seedu.commands.expense;
+
+import seedu.commands.Command;
+import seedu.entry.Expense;
+import seedu.exceptions.ExpenseEntryNotFoundException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class DeleteExpenseCommand extends Command {
+ private int expenseNumber;
+
+ /**
+ * Constructor for delete expense command.
+ * @param expenseNumber index of item to be deleted
+ */
+ public DeleteExpenseCommand(int expenseNumber) {
+ this.expenseNumber = expenseNumber;
+ }
+
+ /**
+ * Executes deletion of expense item.
+ * @param finances object from FinancialTracker
+ * @param ui object from Ui
+ * @param budgetManager object from BudgetManager
+ * @param currencyManager object from CurrencyManager
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ try {
+ Expense deletedExpense = finances.removeExpense(expenseNumber);
+ ui.printExpenseDeleted(deletedExpense);
+ } catch (ExpenseEntryNotFoundException e) {
+ ui.printError(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/seedu/commands/expense/ListExpenseCommand.java b/src/main/java/seedu/commands/expense/ListExpenseCommand.java
new file mode 100644
index 0000000000..533bba8b09
--- /dev/null
+++ b/src/main/java/seedu/commands/expense/ListExpenseCommand.java
@@ -0,0 +1,19 @@
+package seedu.commands.expense;
+
+import seedu.commands.Command;
+import seedu.entry.Expense;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import java.util.ArrayList;
+
+public class ListExpenseCommand extends Command {
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ArrayList expenses = finances.getExpenses();
+ ui.listExpense(expenses);
+ }
+}
diff --git a/src/main/java/seedu/commands/expense/TotalExpenseBetweenCommand.java b/src/main/java/seedu/commands/expense/TotalExpenseBetweenCommand.java
new file mode 100644
index 0000000000..010c5b3c4a
--- /dev/null
+++ b/src/main/java/seedu/commands/expense/TotalExpenseBetweenCommand.java
@@ -0,0 +1,30 @@
+package seedu.commands.expense;
+
+import seedu.commands.Command;
+import seedu.entry.Expense;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+import seedu.utility.tools.DateRange;
+
+import java.time.LocalDate;
+import java.util.List;
+
+public class TotalExpenseBetweenCommand extends Command {
+ private LocalDate start;
+ private LocalDate end;
+
+ public TotalExpenseBetweenCommand(DateRange dateRange) {
+ this.start = dateRange.getStartDate();
+ this.end = dateRange.getEndDate();
+ }
+
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ double totalExpenseBetween = finances.getExpenseBetween(start,end);
+ ui.printTotalExpenseBetween(totalExpenseBetween,start,end);
+ }
+}
+
diff --git a/src/main/java/seedu/commands/expense/TotalExpenseCommand.java b/src/main/java/seedu/commands/expense/TotalExpenseCommand.java
new file mode 100644
index 0000000000..9cd0aeaa5d
--- /dev/null
+++ b/src/main/java/seedu/commands/expense/TotalExpenseCommand.java
@@ -0,0 +1,18 @@
+package seedu.commands.expense;
+
+import seedu.commands.Command;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class TotalExpenseCommand extends Command {
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ double totalExpense = finances.getTotalExpense();
+ ui.printTotalExpense(totalExpense);
+
+ }
+}
diff --git a/src/main/java/seedu/commands/general/ClearAllEntriesCommand.java b/src/main/java/seedu/commands/general/ClearAllEntriesCommand.java
new file mode 100644
index 0000000000..a8c1a5b179
--- /dev/null
+++ b/src/main/java/seedu/commands/general/ClearAllEntriesCommand.java
@@ -0,0 +1,21 @@
+package seedu.commands.general;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+/**
+ * Clears all expense and income entries. This is so that users can start afresh.
+ * Also allows us developers to have easier time testing data saving capabilities.
+ */
+public class ClearAllEntriesCommand extends Command {
+
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ finances.clearAllEntries();
+ ui.printAllEntriesCleared();
+ }
+}
diff --git a/src/main/java/seedu/commands/general/ExitCommand.java b/src/main/java/seedu/commands/general/ExitCommand.java
new file mode 100644
index 0000000000..75edaa3de9
--- /dev/null
+++ b/src/main/java/seedu/commands/general/ExitCommand.java
@@ -0,0 +1,20 @@
+package seedu.commands.general;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class ExitCommand extends Command {
+
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ }
+
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/commands/general/FindCommand.java b/src/main/java/seedu/commands/general/FindCommand.java
new file mode 100644
index 0000000000..164a4420bd
--- /dev/null
+++ b/src/main/java/seedu/commands/general/FindCommand.java
@@ -0,0 +1,118 @@
+package seedu.commands.general;
+
+import seedu.commands.Command;
+import seedu.entry.Entry;
+import seedu.utility.BudgetManager;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.IncomeCategory;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import static seedu.utility.tools.DateOperator.DATE_FORMAT;
+
+/**
+ * The command that searches through the financial tracker for same keyword, value or category.
+ */
+public class FindCommand extends Command {
+ protected String keyword;
+
+ /**
+ * Constructor of FindCommand class that initialises the keyword attribute as what the user key in.
+ * @param keyword The given keyword, value or category user wants to search for.
+ */
+ public FindCommand(String keyword) {
+ this.keyword = keyword;
+ }
+
+
+ /**
+ * Filters through both the expense list and income list to search for entries with matching search keywords.
+ *
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency state of the financial tracker.
+ */
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ArrayList entries = finances.getEntries();
+ ArrayList filteredEntries = new ArrayList<>();
+
+ try {
+ filterByDate(entries, filteredEntries);
+ } catch (DateTimeParseException e) {
+ filterByKeyword(entries, filteredEntries);
+ }
+
+ ui.listFind(filteredEntries);
+ }
+
+ private void filterByDate(ArrayList entries, ArrayList filteredEntries) {
+ LocalDate localDate = LocalDate.parse(keyword, DateTimeFormatter.ofPattern(DATE_FORMAT));
+ for (Entry entry: entries) {
+ if (entry.getDate().isEqual(localDate)) {
+ filteredEntries.add(entry);
+ }
+ }
+ }
+
+ private void filterByKeyword(ArrayList entries, ArrayList filteredEntries) {
+ for (Entry entry: entries) {
+ String valueAsString = Double.toString(entry.getValue());
+ if (entry.getDescription().contains(keyword)) {
+ filteredEntries.add(entry);
+ } else if (valueAsString.contains(keyword)) {
+ filteredEntries.add(entry);
+ } else {
+ Enum filterCategory = determineCategory();
+ if (entry.getCategory().equals(filterCategory)) {
+ filteredEntries.add(entry);
+ }
+ }
+ }
+ }
+
+ private Enum determineCategory() {
+ Enum filterCategory;
+ switch (keyword.toUpperCase()) {
+ case "FOOD":
+ filterCategory = ExpenseCategory.FOOD;
+ break;
+ case "TRANSPORT":
+ filterCategory = ExpenseCategory.TRANSPORT;
+ break;
+ case "MEDICAL":
+ filterCategory = ExpenseCategory.MEDICAL;
+ break;
+ case "BILLS":
+ filterCategory = ExpenseCategory.BILLS;
+ break;
+ case "ENTERTAINMENT":
+ filterCategory = ExpenseCategory.ENTERTAINMENT;
+ break;
+ case "MISC":
+ filterCategory = ExpenseCategory.MISC;
+ break;
+ case "SALARY":
+ filterCategory = IncomeCategory.SALARY;
+ break;
+ case "ALLOWANCE":
+ filterCategory = IncomeCategory.ALLOWANCE;
+ break;
+ case "ADHOC":
+ filterCategory = IncomeCategory.ADHOC;
+ break;
+ case "OTHERS":
+ filterCategory = IncomeCategory.OTHERS;
+ break;
+ default:
+ filterCategory = ExpenseCategory.NULL;
+ }
+ return filterCategory;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/commands/general/HelpCommand.java b/src/main/java/seedu/commands/general/HelpCommand.java
new file mode 100644
index 0000000000..b3bd11f744
--- /dev/null
+++ b/src/main/java/seedu/commands/general/HelpCommand.java
@@ -0,0 +1,27 @@
+package seedu.commands.general;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+/**
+ * The command that shows all possible commands and their formats, it is an inherited class of the Command class.
+ */
+public class HelpCommand extends Command {
+
+ /**
+ * Prints the list of commands and their formats to the output.
+ *
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency state of the financial tracker.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ui.printHelp();
+ }
+}
diff --git a/src/main/java/seedu/commands/general/ShowGraphByYearCommand.java b/src/main/java/seedu/commands/general/ShowGraphByYearCommand.java
new file mode 100644
index 0000000000..f91e3081d9
--- /dev/null
+++ b/src/main/java/seedu/commands/general/ShowGraphByYearCommand.java
@@ -0,0 +1,43 @@
+package seedu.commands.general;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.StonksGraph;
+import seedu.utility.Ui;
+
+import java.time.LocalDate;
+
+
+/**
+ * The command that shows a graph based on the input year, it is an inherited class of the Command class.
+ */
+public class ShowGraphByYearCommand extends Command {
+
+ private int year;
+
+
+ /**
+ * Constructor of the ShowGraphByYearCommand that initialises the input year as its attribute.
+ * @param year The input year given by the user.
+ */
+ public ShowGraphByYearCommand(LocalDate year) {
+ this.year = year.getYear();
+ }
+
+
+ /**
+ * Prints the graph representing the financial tracker of the input year given by the user.
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency state of the financial tracker.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ StonksGraph stonksGraph = new StonksGraph(finances, year);
+ ui.printGraph(stonksGraph);
+ }
+}
diff --git a/src/main/java/seedu/commands/general/ShowGraphCommand.java b/src/main/java/seedu/commands/general/ShowGraphCommand.java
new file mode 100644
index 0000000000..4079b89e73
--- /dev/null
+++ b/src/main/java/seedu/commands/general/ShowGraphCommand.java
@@ -0,0 +1,50 @@
+
+package seedu.commands.general;
+
+import seedu.commands.Command;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.StonksGraph;
+import seedu.utility.Ui;
+
+import java.time.LocalDate;
+
+/**
+ * The command that shows a graph based on the current year, it is an inherited class of the Command class.
+ */
+public class ShowGraphCommand extends Command {
+ private int year;
+
+
+ /**
+ * Constructor of the ShowGraphCommand that initialises the current year as its attribute.
+ */
+ public ShowGraphCommand() {
+ this.year = currentYear();
+ }
+
+ //@@author KZQ1999
+ private int currentYear() {
+ LocalDate currentDate = LocalDate.now();
+ return currentDate.getYear();
+ }
+ //@@author KZQ1999
+
+
+ /**
+ * Prints the graph representing the financial tracker of the current year.
+ * @param finances The financial tracker containing all the entries.
+ * @param ui The user interface which provide feedback to the user.
+ * @param budgetManager The budgeting manager that manages all the budget related operations.
+ * @param currencyManager The currency manager that manages the currency state of the financial tracker.
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ StonksGraph stonksGraph = new StonksGraph(finances, year);
+ ui.printGraph(stonksGraph);
+ }
+}
+
+
diff --git a/src/main/java/seedu/commands/income/AddIncomeCommand.java b/src/main/java/seedu/commands/income/AddIncomeCommand.java
new file mode 100644
index 0000000000..1e81442273
--- /dev/null
+++ b/src/main/java/seedu/commands/income/AddIncomeCommand.java
@@ -0,0 +1,42 @@
+//@@author Nirmaleshwar
+
+package seedu.commands.income;
+
+import seedu.commands.Command;
+import seedu.entry.Income;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class AddIncomeCommand extends Command {
+ private Income income;
+
+ /**
+ * Constructor for add income command.
+ * @param income object to be added
+ */
+ public AddIncomeCommand(Income income) {
+ this.income = income;
+ }
+
+ /**
+ * Executes adding of income item.
+ * @param finances object from FinancialTracker
+ * @param ui object from Ui
+ * @param budgetManager object from BudgetManager
+ * @param currencyManager object from CurrencyManager
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ try {
+ finances.addIncome(income);
+ ui.printIncomeAdded(income);
+ } catch (IncomeOverflowException e) {
+ ui.printError(e.getMessage());
+ }
+
+ }
+}
diff --git a/src/main/java/seedu/commands/income/DeleteIncomeCommand.java b/src/main/java/seedu/commands/income/DeleteIncomeCommand.java
new file mode 100644
index 0000000000..4ae6744209
--- /dev/null
+++ b/src/main/java/seedu/commands/income/DeleteIncomeCommand.java
@@ -0,0 +1,41 @@
+//@@author Nirmaleshwar
+
+package seedu.commands.income;
+
+import seedu.commands.Command;
+import seedu.entry.Income;
+import seedu.exceptions.IncomeEntryNotFoundException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class DeleteIncomeCommand extends Command {
+ private int incomeNumber;
+
+ /**
+ * Constructor for delete income command.
+ * @param incomeNumber index of item to be deleted
+ */
+ public DeleteIncomeCommand(int incomeNumber) {
+ this.incomeNumber = incomeNumber;
+ }
+
+ /**
+ * Executes deletion of income item.
+ * @param finances object from FinancialTracker
+ * @param ui object from Ui
+ * @param budgetManager object from BudgetManager
+ * @param currencyManager object from CurrencyManager
+ */
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ try {
+ Income deletedIncome = finances.removeIncome(incomeNumber);
+ ui.printIncomeDeleted(deletedIncome);
+ } catch (IncomeEntryNotFoundException e) {
+ ui.printError(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/seedu/commands/income/ListIncomeCommand.java b/src/main/java/seedu/commands/income/ListIncomeCommand.java
new file mode 100644
index 0000000000..dd0509d9c2
--- /dev/null
+++ b/src/main/java/seedu/commands/income/ListIncomeCommand.java
@@ -0,0 +1,19 @@
+package seedu.commands.income;
+
+import seedu.commands.Command;
+import seedu.entry.Income;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import java.util.ArrayList;
+
+public class ListIncomeCommand extends Command {
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ ArrayList incomes = finances.getIncomes();
+ ui.listIncome(incomes);
+ }
+}
diff --git a/src/main/java/seedu/commands/income/TotalIncomeBetweenCommand.java b/src/main/java/seedu/commands/income/TotalIncomeBetweenCommand.java
new file mode 100644
index 0000000000..a3cfbd9d3e
--- /dev/null
+++ b/src/main/java/seedu/commands/income/TotalIncomeBetweenCommand.java
@@ -0,0 +1,30 @@
+package seedu.commands.income;
+
+import seedu.commands.Command;
+import seedu.entry.Expense;
+import seedu.entry.Income;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+import seedu.utility.tools.DateRange;
+
+import java.time.LocalDate;
+
+public class TotalIncomeBetweenCommand extends Command {
+ private LocalDate start;
+ private LocalDate end;
+
+ public TotalIncomeBetweenCommand(DateRange dateRange) {
+ this.start = dateRange.getStartDate();
+ this.end = dateRange.getEndDate();
+ }
+
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ double totalIncomeBetween = finances.getIncomeBetween(start,end);
+ ui.printTotalIncomeBetween(totalIncomeBetween,start,end);
+ }
+}
+
diff --git a/src/main/java/seedu/commands/income/TotalIncomeCommand.java b/src/main/java/seedu/commands/income/TotalIncomeCommand.java
new file mode 100644
index 0000000000..56fe35e157
--- /dev/null
+++ b/src/main/java/seedu/commands/income/TotalIncomeCommand.java
@@ -0,0 +1,17 @@
+package seedu.commands.income;
+
+import seedu.commands.Command;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+public class TotalIncomeCommand extends Command {
+ @Override
+ public void execute(FinancialTracker finances, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ double totalIncome = finances.getTotalIncome();
+ ui.printTotalIncome(totalIncome);
+ }
+}
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/main/java/seedu/duke/StonksXD.java b/src/main/java/seedu/duke/StonksXD.java
new file mode 100644
index 0000000000..2f355707f4
--- /dev/null
+++ b/src/main/java/seedu/duke/StonksXD.java
@@ -0,0 +1,73 @@
+package seedu.duke;
+
+import seedu.commands.Command;
+
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.storage.DataManager;
+import seedu.utility.FinancialTracker;
+
+import seedu.utility.Parser;
+import seedu.utility.Ui;
+
+/**
+ * A command line interfaced program that can store your financial entries and provide other insights and analytical
+ * services.
+ */
+public class StonksXD {
+ private final Ui ui;
+ private final FinancialTracker finances;
+ private final Parser parser;
+ private final DataManager dataManager;
+ private final BudgetManager budgetManager;
+ private final CurrencyManager currencyManager;
+ private boolean isNonTerminatingCommand = true;
+
+ /**
+ * Constructor for StonksXD. It instantiates all the components used and are crucial to the functioning of the
+ * program.
+ */
+ public StonksXD() {
+ this.ui = new Ui();
+ this.parser = new Parser();
+ this.budgetManager = new BudgetManager();
+ this.currencyManager = new CurrencyManager();
+ this.finances = new FinancialTracker(currencyManager);
+ this.dataManager = new DataManager(finances, ui, budgetManager, currencyManager);
+ dataManager.loadAll();
+ dataManager.setSettingsToWritable();
+ }
+
+ /**
+ * Handles the lifecycle and the general logic of the program. It reads users input and performs actions
+ * based on it.
+ */
+ public void run() {
+ ui.printWelcome();
+
+ while (isNonTerminatingCommand) {
+ String fullCommand = ui.readCommand();
+ Command command = parser.parseCommand(fullCommand);
+ command.execute(finances, ui, budgetManager, currencyManager);
+ if (command.isExit()) {
+ terminateStonksXD();
+ }
+ dataManager.saveAll();
+ }
+ dataManager.setSettingsToReadOnly();
+ ui.printBye();
+ }
+
+ private void terminateStonksXD() {
+ isNonTerminatingCommand = false;
+ }
+
+ /**
+ * Point of entry for the program.
+ *
+ * @param args No input parameters is expected.
+ */
+ public static void main(String[] args) {
+ new StonksXD().run();
+ }
+}
diff --git a/src/main/java/seedu/entry/Entry.java b/src/main/java/seedu/entry/Entry.java
new file mode 100644
index 0000000000..56e346b9b9
--- /dev/null
+++ b/src/main/java/seedu/entry/Entry.java
@@ -0,0 +1,63 @@
+package seedu.entry;
+
+import java.time.LocalDate;
+
+/**
+ * Entry is the parent class that Expense and Income inherits from.
+ */
+public abstract class Entry {
+ protected String description;
+ protected double value;
+ protected LocalDate date;
+ protected static final String DATE_FORMAT = "dd/MM/yyyy";
+
+ /**
+ * Gets the description of the entry.
+ *
+ * @return A String storing information on the entry.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /**
+ * Gets the value of the entry.
+ *
+ * @return A double storing the value of the entry.
+ */
+ public double getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets a value for an entry.
+ *
+ * @param newValue A double that will replace the original value.
+ */
+ public void setValue(double newValue) {
+ this.value = newValue;
+ }
+
+ /**
+ * Get the date information of the entry.
+ *
+ * @return A LocalDate which is associated with the entry.
+ */
+ public LocalDate getDate() {
+ return this.date;
+ }
+
+ /**
+ * Get the category associated with the entry.
+ *
+ * @return A enum which the entry is categorised under.
+ */
+ public abstract Enum getCategory();
+
+ /**
+ * Converts the object into a string to be printed.
+ *
+ * @return A string of information associated to the entry.
+ */
+ public abstract String toString();
+}
diff --git a/src/main/java/seedu/entry/Expense.java b/src/main/java/seedu/entry/Expense.java
new file mode 100644
index 0000000000..d25cd2f44f
--- /dev/null
+++ b/src/main/java/seedu/entry/Expense.java
@@ -0,0 +1,58 @@
+package seedu.entry;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Expense is an object that stores the description, value, date and category of an expense entry added by the user.
+ */
+public class Expense extends Entry {
+ private ExpenseCategory category;
+
+ /**
+ * Constructor for Expense that initializes description, value and category of the expense entry.
+ * Date is set to current date.
+ * @param description Description of the expense entry
+ * @param value Dollar value of the expense entry
+ * @param category Category of the expense. It must be one of the 7 valid pre-programmed expense categories.
+ */
+ public Expense(String description, double value, ExpenseCategory category) {
+ this.description = description;
+ this.value = value;
+ this.date = LocalDate.now();
+ this.category = category;
+ }
+
+ /**
+ * Constructor for Expense that initializes description, value, date and category fo the expense entry.
+ * @param description Description of the expense entry
+ * @param value Dollar value of the expense entry
+ * @param category Category of the expense. It must be one of the 7 valid pre-programmed expense categories.
+ * @param date Date that the expense entry was made.
+ */
+ public Expense(String description, double value, ExpenseCategory category, LocalDate date) {
+ this.description = description;
+ this.value = value;
+ this.date = date;
+ this.category = category;
+ }
+
+ /**
+ * Returns the category of the expense.
+ * @return Category of the expense
+ */
+ @Override
+ public ExpenseCategory getCategory() {
+ return category;
+ }
+
+ /**
+ * Returns the expense entry as a string to be printed.
+ * @return String format of the expense
+ */
+ @Override
+ public String toString() {
+ String expenseDate = date.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ return String.format("[E] %s - $%.2f (%s)", description, value, expenseDate);
+ }
+}
diff --git a/src/main/java/seedu/entry/ExpenseCategory.java b/src/main/java/seedu/entry/ExpenseCategory.java
new file mode 100644
index 0000000000..122cba2e03
--- /dev/null
+++ b/src/main/java/seedu/entry/ExpenseCategory.java
@@ -0,0 +1,8 @@
+package seedu.entry;
+
+/**
+ * Enumeration of all valid expense categories in the system (NULL category is invalid).
+ */
+public enum ExpenseCategory {
+ OVERALL, FOOD, TRANSPORT, MEDICAL, BILLS, ENTERTAINMENT, MISC, NULL
+}
diff --git a/src/main/java/seedu/entry/Income.java b/src/main/java/seedu/entry/Income.java
new file mode 100644
index 0000000000..9a21fa47c9
--- /dev/null
+++ b/src/main/java/seedu/entry/Income.java
@@ -0,0 +1,58 @@
+package seedu.entry;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Income is an object that stores the description, value, date, and category of an income entry inputted by the user.
+ */
+public class Income extends Entry {
+ IncomeCategory category;
+
+ /**
+ * Constructor that initializes description, value and category of the income entry.
+ * Date is set to current date
+ * @param description Description of the income entry
+ * @param value Dollar value of the income entry
+ * @param category Category of the income entry. It must be one of the 4 valid pre-programmed expense categories.
+ */
+ public Income(String description, double value, IncomeCategory category) {
+ this.description = description;
+ this.value = value;
+ this.date = LocalDate.now();
+ this.category = category;
+ }
+
+ /**
+ * Constructor that initializes description, value, category and date of the income entry.
+ * @param description Description of the income entry
+ * @param value Dollar value of the income entry
+ * @param category Category of the income entry. It must be one of the 4 valid pre-programmed expense categories.
+ * @param date Date that the income entry was made.
+ */
+ public Income(String description, double value, IncomeCategory category, LocalDate date) {
+ this.description = description;
+ this.value = value;
+ this.date = date;
+ this.category = category;
+ }
+
+ /**
+ * Returns the category of the income entry.
+ * @return Category of the incoem entry
+ */
+ @Override
+ public IncomeCategory getCategory() {
+ return category;
+ }
+
+ /**
+ * Returns the income entry as a string to be printed.
+ * @return String format of the income entry
+ */
+ @Override
+ public String toString() {
+ String incomeDate = date.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ return String.format("[I] %s - $%.2f (%s)", description, value, incomeDate);
+ }
+}
diff --git a/src/main/java/seedu/entry/IncomeCategory.java b/src/main/java/seedu/entry/IncomeCategory.java
new file mode 100644
index 0000000000..36af92b81e
--- /dev/null
+++ b/src/main/java/seedu/entry/IncomeCategory.java
@@ -0,0 +1,8 @@
+package seedu.entry;
+
+/**
+ * Enumeration of all valid income categories in the system (NULL category is invalid).
+ */
+public enum IncomeCategory {
+ SALARY, ALLOWANCE, ADHOC, OTHERS, NULL
+}
diff --git a/src/main/java/seedu/exceptions/BlankCategoryException.java b/src/main/java/seedu/exceptions/BlankCategoryException.java
new file mode 100644
index 0000000000..5f9c56f601
--- /dev/null
+++ b/src/main/java/seedu/exceptions/BlankCategoryException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown if required Category parameter is added by user.
+ */
+public class BlankCategoryException extends InputException {
+ public BlankCategoryException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/BlankCurrencyTypeException.java b/src/main/java/seedu/exceptions/BlankCurrencyTypeException.java
new file mode 100644
index 0000000000..2d6f590815
--- /dev/null
+++ b/src/main/java/seedu/exceptions/BlankCurrencyTypeException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+public class BlankCurrencyTypeException extends InputException {
+ public BlankCurrencyTypeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/EntryAmountBelowMinException.java b/src/main/java/seedu/exceptions/EntryAmountBelowMinException.java
new file mode 100644
index 0000000000..7132e78579
--- /dev/null
+++ b/src/main/java/seedu/exceptions/EntryAmountBelowMinException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when entry amount given is less than 0.05.
+ */
+public class EntryAmountBelowMinException extends InputException {
+ public EntryAmountBelowMinException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/EntryAmountExceedLimitException.java b/src/main/java/seedu/exceptions/EntryAmountExceedLimitException.java
new file mode 100644
index 0000000000..ba65489e31
--- /dev/null
+++ b/src/main/java/seedu/exceptions/EntryAmountExceedLimitException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when user adds an entry that exceeds 1000000.
+ */
+public class EntryAmountExceedLimitException extends InputException {
+ public EntryAmountExceedLimitException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/ExpenseEntryNotFoundException.java b/src/main/java/seedu/exceptions/ExpenseEntryNotFoundException.java
new file mode 100644
index 0000000000..120d061c50
--- /dev/null
+++ b/src/main/java/seedu/exceptions/ExpenseEntryNotFoundException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the index given by the user that corresponds to the index of an expense entry does not exist.
+ */
+public class ExpenseEntryNotFoundException extends Exception {
+ public ExpenseEntryNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/ExpenseOverflowException.java b/src/main/java/seedu/exceptions/ExpenseOverflowException.java
new file mode 100644
index 0000000000..943136b627
--- /dev/null
+++ b/src/main/java/seedu/exceptions/ExpenseOverflowException.java
@@ -0,0 +1,11 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when user attempts to add an expense to the FinancialManager that causes the total sum of expenses to be more
+ * than 100000000000.
+ */
+public class ExpenseOverflowException extends Exception {
+ public ExpenseOverflowException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/IncomeEntryNotFoundException.java b/src/main/java/seedu/exceptions/IncomeEntryNotFoundException.java
new file mode 100644
index 0000000000..b4e4c17538
--- /dev/null
+++ b/src/main/java/seedu/exceptions/IncomeEntryNotFoundException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the index given by the user that corresponds to the index of an income entry does not exist.
+ */
+public class IncomeEntryNotFoundException extends Exception {
+ public IncomeEntryNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/IncomeOverflowException.java b/src/main/java/seedu/exceptions/IncomeOverflowException.java
new file mode 100644
index 0000000000..4281620dcc
--- /dev/null
+++ b/src/main/java/seedu/exceptions/IncomeOverflowException.java
@@ -0,0 +1,11 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when user attempts to add an income to the FinancialManager that causes the total sum of incomes to be more
+ * than 100000000000.
+ */
+public class IncomeOverflowException extends Exception {
+ public IncomeOverflowException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InputException.java b/src/main/java/seedu/exceptions/InputException.java
new file mode 100644
index 0000000000..50663bf617
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InputException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * This is the parent class for all user input related exception.
+ */
+public class InputException extends Exception {
+ public InputException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidAmountException.java b/src/main/java/seedu/exceptions/InvalidAmountException.java
new file mode 100644
index 0000000000..015112b9dd
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidAmountException.java
@@ -0,0 +1,11 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the user attempts to add in an input that contains a number that does not match the intended
+ * requirements.
+ */
+public class InvalidAmountException extends InputException {
+ public InvalidAmountException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidBudgetAmountException.java b/src/main/java/seedu/exceptions/InvalidBudgetAmountException.java
new file mode 100644
index 0000000000..ffd18bdef7
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidBudgetAmountException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+public class InvalidBudgetAmountException extends InputException {
+ public InvalidBudgetAmountException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidCurrencyTypeException.java b/src/main/java/seedu/exceptions/InvalidCurrencyTypeException.java
new file mode 100644
index 0000000000..c31cf0cb55
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidCurrencyTypeException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+public class InvalidCurrencyTypeException extends InputException {
+ public InvalidCurrencyTypeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidDateException.java b/src/main/java/seedu/exceptions/InvalidDateException.java
new file mode 100644
index 0000000000..28011f1846
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidDateException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+public class InvalidDateException extends InputException {
+ public InvalidDateException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidDescriptionException.java b/src/main/java/seedu/exceptions/InvalidDescriptionException.java
new file mode 100644
index 0000000000..ff83d37e91
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidDescriptionException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the supposed description is blank.
+ */
+public class InvalidDescriptionException extends InputException {
+ public InvalidDescriptionException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidExpenseCategoryException.java b/src/main/java/seedu/exceptions/InvalidExpenseCategoryException.java
new file mode 100644
index 0000000000..03cbe31ff0
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidExpenseCategoryException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the supposed ExpenseCategory given is not supported / recognised.
+ */
+public class InvalidExpenseCategoryException extends InputException {
+ public InvalidExpenseCategoryException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidExpenseDataFormatException.java b/src/main/java/seedu/exceptions/InvalidExpenseDataFormatException.java
new file mode 100644
index 0000000000..23022f7498
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidExpenseDataFormatException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the csv String for Expense data does not fit the expected format.
+ */
+public class InvalidExpenseDataFormatException extends Exception {
+}
diff --git a/src/main/java/seedu/exceptions/InvalidIncomeCategoryException.java b/src/main/java/seedu/exceptions/InvalidIncomeCategoryException.java
new file mode 100644
index 0000000000..2ca5016214
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidIncomeCategoryException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the supposed IncomeCategory given is not supported / recognised.
+ */
+public class InvalidIncomeCategoryException extends InputException {
+ public InvalidIncomeCategoryException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidIncomeDataFormatException.java b/src/main/java/seedu/exceptions/InvalidIncomeDataFormatException.java
new file mode 100644
index 0000000000..756dac25db
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidIncomeDataFormatException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the csv String for Income data does not fit the expected format.
+ */
+public class InvalidIncomeDataFormatException extends Exception {
+}
diff --git a/src/main/java/seedu/exceptions/InvalidIndexException.java b/src/main/java/seedu/exceptions/InvalidIndexException.java
new file mode 100644
index 0000000000..0bf1e07360
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidIndexException.java
@@ -0,0 +1,10 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the supposed index cannot be converted from String to int. Or when the index is out of expected range.
+ */
+public class InvalidIndexException extends InputException {
+ public InvalidIndexException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/InvalidSettingsDataFormatException.java b/src/main/java/seedu/exceptions/InvalidSettingsDataFormatException.java
new file mode 100644
index 0000000000..b0f2b28b87
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidSettingsDataFormatException.java
@@ -0,0 +1,8 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the csv String for settings data does not fit the expected format.
+ */
+public class InvalidSettingsDataFormatException extends Exception {
+
+}
diff --git a/src/main/java/seedu/exceptions/InvalidThresholdValueException.java b/src/main/java/seedu/exceptions/InvalidThresholdValueException.java
new file mode 100644
index 0000000000..d7814367a1
--- /dev/null
+++ b/src/main/java/seedu/exceptions/InvalidThresholdValueException.java
@@ -0,0 +1,11 @@
+package seedu.exceptions;
+
+/**
+ * Thrown when the threshold value given cannot be parsed to a double, or when it is not between 0 and 1 exclusive,
+ * or when it is not in 2 decimal places.
+ */
+public class InvalidThresholdValueException extends InputException {
+ public InvalidThresholdValueException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/exceptions/SameCurrencyTypeException.java b/src/main/java/seedu/exceptions/SameCurrencyTypeException.java
new file mode 100644
index 0000000000..266f5c15b1
--- /dev/null
+++ b/src/main/java/seedu/exceptions/SameCurrencyTypeException.java
@@ -0,0 +1,7 @@
+package seedu.exceptions;
+
+public class SameCurrencyTypeException extends InputException {
+ public SameCurrencyTypeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/reminder/BudgetReminder.java b/src/main/java/seedu/reminder/BudgetReminder.java
new file mode 100644
index 0000000000..0ce51a7e4f
--- /dev/null
+++ b/src/main/java/seedu/reminder/BudgetReminder.java
@@ -0,0 +1,28 @@
+package seedu.reminder;
+
+/**
+ * Abstract class representing the reminder messages returned in the BudgetManager class.
+ */
+public abstract class BudgetReminder {
+ protected String month;
+ protected String budgetName;
+ protected double currBudgetAmount;
+ protected double budgetLimit;
+
+ /**
+ * Returns a string of a value rounded to two decimal places.
+ * @param value Double value to be rounded
+ * @return String of a value with two decimal places
+ */
+ protected String roundTwoDecimalPlace(double value) {
+ String displayValue = String.format("%.2f", value);
+ return displayValue;
+ }
+
+ /**
+ * Abstract method to convert the BudgetReminder object to a string.
+ * @return String of the BudgetReminder object
+ */
+ @Override
+ public abstract String toString();
+}
diff --git a/src/main/java/seedu/reminder/BudgetSetReminder.java b/src/main/java/seedu/reminder/BudgetSetReminder.java
new file mode 100644
index 0000000000..ba1381f4b1
--- /dev/null
+++ b/src/main/java/seedu/reminder/BudgetSetReminder.java
@@ -0,0 +1,28 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder message when a budget is successfully set.
+ */
+public class BudgetSetReminder extends BudgetReminder {
+
+ /**
+ * Constructor initializing the month, name, current amount and the limit of the budget.
+ * @param budgetName Name of the budget that was set
+ * @param budgetLimit Limit of the budget that was set
+ */
+ public BudgetSetReminder(String budgetName, double budgetLimit) {
+ this.month = null;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = 0;
+ this.budgetLimit = budgetLimit;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return Reminder as a string.
+ */
+ @Override
+ public String toString() {
+ return budgetName + " budget set to $" + roundTwoDecimalPlace(budgetLimit);
+ }
+}
diff --git a/src/main/java/seedu/reminder/DoubleExceededBudgetReminder.java b/src/main/java/seedu/reminder/DoubleExceededBudgetReminder.java
new file mode 100644
index 0000000000..044147b34c
--- /dev/null
+++ b/src/main/java/seedu/reminder/DoubleExceededBudgetReminder.java
@@ -0,0 +1,45 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder message when both the sub-budget and the overall budget are exceeded.
+ */
+public class DoubleExceededBudgetReminder extends DoubleReminder {
+
+ /**
+ * Constructor initializing the reminder message with all relevant fields.
+ * @param month Current month
+ * @param budgetName Name of the budget that was exceeded
+ * @param currBudgetAmount Current amount in the exceeded budget
+ * @param budgetLimit Current limit of the exceeded budget
+ * @param currOverallAmount Current amount in the overall budget
+ * @param overallLimit Current overall budget limit
+ * @param totalBudget Sum of all sub-budgets and expenses
+ */
+ public DoubleExceededBudgetReminder(String month, String budgetName, double currBudgetAmount,
+ double budgetLimit, double currOverallAmount,
+ double overallLimit, double totalBudget) {
+ this.month = month;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = budgetLimit;
+ this.currOverallAmount = currOverallAmount;
+ this.overallLimit = overallLimit;
+ this.totalBudget = totalBudget;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return Reminder as a string.
+ */
+ @Override
+ public String toString() {
+ return "Exceeded both " + month + " " + budgetName + " budget ($"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit)
+ + ") and " + month + " OVERALL budget ($" + roundTwoDecimalPlace(currOverallAmount)
+ + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nConsider adjusting your OVERALL budget to $" + roundTwoDecimalPlace(totalBudget)
+ + " before adjusting your " + budgetName + " budget!"
+ + "\nCurrently you cannot extend your " + budgetName
+ + " budget without first extending your OVERALL budget!";
+ }
+}
diff --git a/src/main/java/seedu/reminder/DoubleNearingBudgetReminder.java b/src/main/java/seedu/reminder/DoubleNearingBudgetReminder.java
new file mode 100644
index 0000000000..253dcc692a
--- /dev/null
+++ b/src/main/java/seedu/reminder/DoubleNearingBudgetReminder.java
@@ -0,0 +1,59 @@
+package seedu.reminder;
+
+import seedu.utility.BudgetManager;
+
+/**
+ * Class representing the reminder message when both the sug-budget and the overall budget are nearing the limit.
+ */
+public class DoubleNearingBudgetReminder extends DoubleReminder {
+
+ /**
+ * Constructor initializing the reminder message with all relevant fields.
+ * @param month Current month
+ * @param budgetName Name of the budget that was exceeded
+ * @param currBudgetAmount Current amount in the exceeded budget
+ * @param budgetLimit Current limit of the exceeded budget
+ * @param currOverallAmount Current amount in the overall budget
+ * @param overallLimit Current overall budget limit
+ * @param totalBudget Sum of all sub-budgets and expenses
+ */
+ public DoubleNearingBudgetReminder(String month, String budgetName, double currBudgetAmount, double budgetLimit,
+ double currOverallAmount, double overallLimit, double totalBudget) {
+ this.month = month;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = budgetLimit;
+ this.currOverallAmount = currOverallAmount;
+ this.overallLimit = overallLimit;
+ this.totalBudget = totalBudget;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * If the sub-budget can be extended, the amount it can be extended up till is provided.
+ * @return Reminder as a string
+ */
+ @Override
+ public String toString() {
+ double extendedBudget = overallLimit - totalBudget + budgetLimit;
+ if (extendedBudget == budgetLimit) {
+ return "Nearing both " + month + " " + budgetName + " budget ($"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit)
+ + ") and " + month + " OVERALL budget ($" + roundTwoDecimalPlace(currOverallAmount)
+ + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nConsider adjusting your OVERALL budget before adjusting your " + budgetName + " budget!"
+ + "\nCurrently you cannot extend your " + budgetName
+ + " budget without first extending your OVERALL budget!";
+ } else {
+ return "Nearing both " + month + " " + budgetName + " budget ($"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit)
+ + ") and " + month + " OVERALL budget ($" + roundTwoDecimalPlace(currOverallAmount)
+ + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nConsider adjusting your OVERALL budget before adjusting your " + budgetName + " budget!"
+ + "\nCurrently you can extend your " + budgetName
+ + " budget up until $" + roundTwoDecimalPlace(extendedBudget) + "!";
+ }
+
+ }
+
+}
diff --git a/src/main/java/seedu/reminder/DoubleReminder.java b/src/main/java/seedu/reminder/DoubleReminder.java
new file mode 100644
index 0000000000..7ffa5e1324
--- /dev/null
+++ b/src/main/java/seedu/reminder/DoubleReminder.java
@@ -0,0 +1,12 @@
+package seedu.reminder;
+
+import seedu.budget.Budget;
+
+/**
+ * Abstract class representing reminders dealing with both the sub-budget and the overall budget.
+ */
+public abstract class DoubleReminder extends BudgetReminder {
+ protected double currOverallAmount;
+ protected double overallLimit;
+ protected double totalBudget;
+}
diff --git a/src/main/java/seedu/reminder/ExceededBudgetNearingOverallReminder.java b/src/main/java/seedu/reminder/ExceededBudgetNearingOverallReminder.java
new file mode 100644
index 0000000000..749f88cbd7
--- /dev/null
+++ b/src/main/java/seedu/reminder/ExceededBudgetNearingOverallReminder.java
@@ -0,0 +1,56 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder message when the sub-budget is exceeded and the overall budget is nearing its limit.
+ */
+public class ExceededBudgetNearingOverallReminder extends DoubleReminder {
+
+ /**
+ * Constructor initializing the reminder message with all relevant fields.
+ * @param month Current month
+ * @param budgetName Name of the budget that was exceeded
+ * @param currBudgetAmount Current amount in the exceeded budget
+ * @param budgetLimit Current limit of the exceeded budget
+ * @param currOverallAmount Current amount in the overall budget
+ * @param overallLimit Current overall budget limit
+ * @param totalBudget Sum of all sub-budgets and expenses
+ */
+ public ExceededBudgetNearingOverallReminder(String month, String budgetName, double currBudgetAmount,
+ double budgetLimit, double currOverallAmount,
+ double overallLimit, double totalBudget) {
+ this.month = month;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = budgetLimit;
+ this.currOverallAmount = currOverallAmount;
+ this.overallLimit = overallLimit;
+ this.totalBudget = totalBudget;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * If the sub-budget can be extended, the amount it can be extended until is provided.
+ * @return Reminder as a string.
+ */
+ @Override
+ public String toString() {
+ double extendedBudget = overallLimit - totalBudget + currBudgetAmount;
+ if (extendedBudget < currBudgetAmount) {
+ return "Exceeded " + month + " " + budgetName + " budget ($"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit)
+ + ") and nearing " + month + " OVERALL budget ($" + roundTwoDecimalPlace(currOverallAmount)
+ + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nConsider adjusting your OVERALL budget before adjusting your " + budgetName + " budget!"
+ + "\nCurrently you cannot extend your " + budgetName
+ + " budget without first extending your OVERALL budget!";
+ } else {
+ return "Exceeded " + month + " " + budgetName + " budget ($"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit)
+ + ") and nearing " + month + " OVERALL budget ($" + roundTwoDecimalPlace(currOverallAmount)
+ + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nConsider adjusting your OVERALL budget before adjusting your " + budgetName + " budget!"
+ + "\nCurrently you can extend your " + budgetName
+ + " budget up until $" + roundTwoDecimalPlace(extendedBudget) + "!";
+ }
+ }
+}
diff --git a/src/main/java/seedu/reminder/NoReminder.java b/src/main/java/seedu/reminder/NoReminder.java
new file mode 100644
index 0000000000..6a2a9294a0
--- /dev/null
+++ b/src/main/java/seedu/reminder/NoReminder.java
@@ -0,0 +1,15 @@
+package seedu.reminder;
+
+public class NoReminder extends BudgetReminder {
+ public NoReminder() {
+ this.month = null;
+ this.budgetName = null;
+ this.currBudgetAmount = 0;
+ this.budgetLimit = 0;
+ }
+
+ @Override
+ public String toString() {
+ return "Budget reminders are only given for current month!";
+ }
+}
diff --git a/src/main/java/seedu/reminder/SingleExceededReminder.java b/src/main/java/seedu/reminder/SingleExceededReminder.java
new file mode 100644
index 0000000000..513f0c12b8
--- /dev/null
+++ b/src/main/java/seedu/reminder/SingleExceededReminder.java
@@ -0,0 +1,32 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder when only a single budget is exceeded.
+ */
+public class SingleExceededReminder extends BudgetReminder {
+
+ /**
+ * Constructor initializing the reminder with all relevant fields.
+ * @param month Current month
+ * @param budgetName Name of budget that was exceeded
+ * @param currBudgetAmount Current amount in the budget
+ * @param budgetLimit Current limit of the budget
+ */
+ public SingleExceededReminder(String month, String budgetName, double currBudgetAmount, double budgetLimit) {
+ this.month = month;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = budgetLimit;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return Reminder as a string
+ */
+ @Override
+ public String toString() {
+ return "You have exceeded the " + month + " " + budgetName + " budget: $"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/" + "$" + roundTwoDecimalPlace(budgetLimit)
+ + "\nConsider readjusting your " + month + " " + budgetName + " budget!";
+ }
+}
diff --git a/src/main/java/seedu/reminder/SingleNearingReminder.java b/src/main/java/seedu/reminder/SingleNearingReminder.java
new file mode 100644
index 0000000000..12de6e4c8c
--- /dev/null
+++ b/src/main/java/seedu/reminder/SingleNearingReminder.java
@@ -0,0 +1,34 @@
+package seedu.reminder;
+
+import java.time.LocalDate;
+
+/**
+ * Class representing the reminder message when a single budget is nearing its budget limit.
+ */
+public class SingleNearingReminder extends BudgetReminder {
+
+ /**
+ * Constructor initializing the reminder with all relevant fields.
+ * @param month Current month
+ * @param budgetName Name of budget that was exceeded
+ * @param currBudgetAmount Current amount in the budget
+ * @param budgetLimit Current limit of the budget
+ */
+ public SingleNearingReminder(String month, String budgetName, double currBudgetAmount, double budgetLimit) {
+ this.month = month;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = budgetLimit;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return Reminder as a string
+ */
+ @Override
+ public String toString() {
+ return "You are almost reaching the " + month + " " + budgetName + " budget: $"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit)
+ + "\nConsider readjusting your " + month + " " + budgetName + " budget!";
+ }
+}
diff --git a/src/main/java/seedu/reminder/SingleReminder.java b/src/main/java/seedu/reminder/SingleReminder.java
new file mode 100644
index 0000000000..84c30cd725
--- /dev/null
+++ b/src/main/java/seedu/reminder/SingleReminder.java
@@ -0,0 +1,32 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder message when an expense is added.
+ */
+public class SingleReminder extends BudgetReminder {
+
+ /**
+ * Constructor initializing the reminder with all relevant fields.
+ * @param month Current month
+ * @param budgetName Name of budget that was exceeded
+ * @param currBudgetAmount Current amount in the budget
+ * @param budgetLimit Current limit of the budget
+ */
+ public SingleReminder(String month, String budgetName, double currBudgetAmount, double budgetLimit) {
+ this.month = month;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = budgetLimit;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return Reminder as a string
+ */
+ @Override
+ public String toString() {
+ return "Current " + month + " " + budgetName + " budget: $"
+ + roundTwoDecimalPlace(currBudgetAmount) + "/$" + roundTwoDecimalPlace(budgetLimit);
+ }
+
+}
diff --git a/src/main/java/seedu/reminder/UnableToSetBudgetReminder.java b/src/main/java/seedu/reminder/UnableToSetBudgetReminder.java
new file mode 100644
index 0000000000..d2aa031673
--- /dev/null
+++ b/src/main/java/seedu/reminder/UnableToSetBudgetReminder.java
@@ -0,0 +1,53 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder message when budget cannot be set.
+ */
+public class UnableToSetBudgetReminder extends BudgetReminder {
+ private double overallLimit;
+ private double newBudgetLimit;
+ private double newTotalBudget;
+
+ /**
+ * Constructor initializing message with all relevant fields.
+ * @param budgetName Name of the budget
+ * @param currBudgetAmount Current amount in the budget
+ * @param overallLimit Current limit of the budget
+ * @param newBudgetLimit New limit to be set
+ * @param newTotalBudget New sum of all sub-budgets and expenses if new limit is successfully set
+ */
+ public UnableToSetBudgetReminder(String budgetName, double currBudgetAmount, double overallLimit,
+ double newBudgetLimit, double newTotalBudget) {
+ this.month = null;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = currBudgetAmount;
+ this.budgetLimit = 0;
+ this.overallLimit = overallLimit;
+ this.newBudgetLimit = newBudgetLimit;
+ this.newTotalBudget = newTotalBudget;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return The reminder as a string.
+ */
+ @Override
+ public String toString() {
+ if (newBudgetLimit < currBudgetAmount && newTotalBudget > overallLimit) {
+ return budgetName + " budget must be greater than current " + budgetName + " spending of $"
+ + roundTwoDecimalPlace(currBudgetAmount) + "."
+ + "\nWith new " + budgetName
+ + " budget, total of all sub-budgets/spending will exceed OVERALL budget ($"
+ + roundTwoDecimalPlace(newTotalBudget) + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nIncrease your OVERALL budget first!";
+ } else if (newBudgetLimit < currBudgetAmount) {
+ return budgetName + " budget must be greater than current " + budgetName + " spending of $"
+ + roundTwoDecimalPlace(currBudgetAmount) + ".";
+ } else {
+ return "With new " + budgetName
+ + " budget, total of all sub-budgets/spending will exceed OVERALL budget ($"
+ + roundTwoDecimalPlace(newTotalBudget) + "/$" + roundTwoDecimalPlace(overallLimit) + ")."
+ + "\nIncrease your OVERALL budget first!";
+ }
+ }
+}
diff --git a/src/main/java/seedu/reminder/UnableToSetOverallBudgetReminder.java b/src/main/java/seedu/reminder/UnableToSetOverallBudgetReminder.java
new file mode 100644
index 0000000000..858d6fb537
--- /dev/null
+++ b/src/main/java/seedu/reminder/UnableToSetOverallBudgetReminder.java
@@ -0,0 +1,32 @@
+package seedu.reminder;
+
+/**
+ * Class representing the reminder message when the overall budget cannot be set.
+ */
+public class UnableToSetOverallBudgetReminder extends BudgetReminder {
+ private double totalBudget;
+
+ /**
+ * Constructor initializing message with all relevant fields.
+ * @param budgetName Name of the budget
+ * @param budgetLimit Limit of the budget
+ * @param totalBudget Sum of all sub-budgets and expenses
+ */
+ public UnableToSetOverallBudgetReminder(String budgetName, double budgetLimit, double totalBudget) {
+ this.month = null;
+ this.budgetName = budgetName;
+ this.currBudgetAmount = 0;
+ this.budgetLimit = budgetLimit;
+ this.totalBudget = totalBudget;
+ }
+
+ /**
+ * Returns the reminder as a string.
+ * @return Reminder as a string.
+ */
+ @Override
+ public String toString() {
+ return "OVERALL budget must be greater than all sub-budgets and spending!"
+ + "\nCurrently sum of all sub-budgets/spending: $" + roundTwoDecimalPlace(totalBudget);
+ }
+}
diff --git a/src/main/java/seedu/utility/BudgetManager.java b/src/main/java/seedu/utility/BudgetManager.java
new file mode 100644
index 0000000000..60c1fdbe45
--- /dev/null
+++ b/src/main/java/seedu/utility/BudgetManager.java
@@ -0,0 +1,267 @@
+package seedu.utility;
+
+import seedu.budget.BillsBudget;
+import seedu.budget.Budget;
+import seedu.budget.EntertainmentBudget;
+import seedu.budget.FoodBudget;
+import seedu.budget.MedicalBudget;
+import seedu.budget.MiscBudget;
+import seedu.budget.OverallBudget;
+import seedu.budget.TransportBudget;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.reminder.BudgetReminder;
+import seedu.reminder.BudgetSetReminder;
+import seedu.reminder.DoubleExceededBudgetReminder;
+import seedu.reminder.DoubleNearingBudgetReminder;
+import seedu.reminder.ExceededBudgetNearingOverallReminder;
+import seedu.reminder.NoReminder;
+import seedu.reminder.SingleExceededReminder;
+import seedu.reminder.SingleNearingReminder;
+import seedu.reminder.SingleReminder;
+import seedu.reminder.UnableToSetBudgetReminder;
+import seedu.reminder.UnableToSetOverallBudgetReminder;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+/**
+ * BudgetManager is the class initializes and maintains all budget objects.
+ * Handles the setting of budgets and the provision of appropriate budget reminders.
+ */
+public class BudgetManager {
+ private double threshold;
+ private final OverallBudget overallBudget = new OverallBudget(0);
+ private final FoodBudget foodBudget = new FoodBudget(0);
+ private final TransportBudget transportBudget = new TransportBudget(0);
+ private final MedicalBudget medicalBudget = new MedicalBudget(0);
+ private final BillsBudget billsBudget = new BillsBudget(0);
+ private final EntertainmentBudget entertainmentBudget = new EntertainmentBudget(0);
+ private final MiscBudget miscBudget = new MiscBudget(0);
+
+ private ArrayList budgets = new ArrayList<>();
+
+ /**
+ * Constructor that initializes the threshold value to a default of 0.9 and adds all budgets to an ArrayList.
+ */
+ public BudgetManager() {
+ this.threshold = 0.9;
+ budgets.add(overallBudget);
+ budgets.add(foodBudget);
+ budgets.add(transportBudget);
+ budgets.add(medicalBudget);
+ budgets.add(billsBudget);
+ budgets.add(entertainmentBudget);
+ budgets.add(miscBudget);
+ }
+
+ /**
+ * Returns an appropriate BudgetReminder object after determining the state of the budgets.
+ * @param expense Expense object that was just added
+ * @param expenses ArrayList of expenses in FinancialTracker
+ * @return
+ */
+ public BudgetReminder handleBudget(Expense expense, ArrayList expenses) {
+ Budget budget = expenseCategoryToBudget(expense.getCategory());
+ LocalDate date = expense.getDate();
+ String month = date.getMonth().toString();
+ double currBudgetAmount = budget.calAmount(expenses, date);
+ double currOverallAmount = overallBudget.calAmount(expenses, date);
+
+ if (isNotCurrentMonth(date)) {
+ return new NoReminder();
+ }
+
+ if (isNearingLimit(budget, currBudgetAmount) && isNearingLimit(overallBudget, currOverallAmount)) {
+
+ return new DoubleNearingBudgetReminder(month, budget.getName(), currBudgetAmount, budget.getLimit(),
+ currOverallAmount, overallBudget.getLimit(), getTotalBudget(expenses, date));
+
+ } else if (isExceededLimit(budget, currBudgetAmount) && isExceededLimit(overallBudget, currOverallAmount)) {
+
+ return new DoubleExceededBudgetReminder(month, budget.getName(), currBudgetAmount, budget.getLimit(),
+ currOverallAmount, overallBudget.getLimit(), getTotalBudget(expenses, date));
+
+ } else if (isExceededLimit(budget, currBudgetAmount) && isNearingLimit(overallBudget, currOverallAmount)) {
+
+ return new ExceededBudgetNearingOverallReminder(month, budget.getName(), currBudgetAmount,
+ budget.getLimit(), currOverallAmount, overallBudget.getLimit(), getTotalBudget(expenses, date));
+
+ } else {
+
+ if (isNearingLimit(budget, currBudgetAmount)) {
+ return new SingleNearingReminder(month, budget.getName(), currBudgetAmount, budget.getLimit());
+ } else if (isExceededLimit(budget, currBudgetAmount)) {
+ return new SingleExceededReminder(month, budget.getName(), currBudgetAmount, budget.getLimit());
+ } else {
+ return new SingleReminder(month, budget.getName(), currBudgetAmount, budget.getLimit());
+ }
+
+ }
+ }
+
+ /**
+ * Returns true if the date is in the current month.
+ * @param date Date containing month and year information
+ */
+ private boolean isNotCurrentMonth(LocalDate date) {
+ return date.getMonth() != LocalDate.now().getMonth() | date.getYear() != LocalDate.now().getYear();
+ }
+
+ /**
+ * Returns the maximum amount left in the budget before budget warnings must be given.
+ * @param budgetLimit Limit of specific budget
+ * @return maximum amount left before budget warnings must be given.
+ */
+ private double getThresholdLimit(double budgetLimit) {
+ return (1 - threshold) * budgetLimit;
+ }
+
+ /**
+ * Returns true if approaching the budget limit (diff is smaller than thresholdLimit).
+ * @param budget Budget to check
+ * @param currBudgetAmount Current amount spent in the budget
+ * @return boolean signifying if approaching budget limit
+ */
+ private boolean isNearingLimit(Budget budget, double currBudgetAmount) {
+ double diff = budget.getLimit() - currBudgetAmount;
+ double thresholdLimit = getThresholdLimit(budget.getLimit());
+ return (diff > 0) && (diff <= thresholdLimit);
+ }
+
+ /**
+ * Returns true if budget limit is exceeded.
+ * @param budget Budget to check
+ * @param currBudgetAmount Current amount spent in the budget
+ * @return boolean signifying if budget limit is exceeded
+ */
+ private boolean isExceededLimit(Budget budget, double currBudgetAmount) {
+ double diff = budget.getLimit() - currBudgetAmount;
+ return diff <= 0;
+ }
+
+ /**
+ * Sets threshold value to specified value.
+ * Threshold value of 0.9 means budget warnings will be given when 90% of the budget limit is reached.
+ * @param threshold double value between 0 and 1
+ */
+ public void setThreshold(double threshold) {
+ assert (threshold >= 0) && (threshold <= 1);
+ this.threshold = threshold;
+ }
+
+ /**
+ * Returns the threshold value.
+ * @return threshold value
+ */
+ public double getThreshold() {
+ return this.threshold;
+ }
+
+ /**
+ * Sets a new budget limit for a specified budget if the new limit is valid.
+ * If invalid, returns a BudgetReminder explaining the problem.
+ * New budget limit must be greater than spending in the budget category.
+ * Sum of all spending in sub-budgets and current sub-budget limits must be smaller than overall budget limit.
+ * @param amount New budget limit to be set
+ * @param category Budget category that new limit is to be imposed upon
+ * @param expenses ArrayList of expenses from FinancialTracker
+ * @return BudgetReminder object containing explanations and advice on the current budget situation.
+ */
+ public BudgetReminder setBudget(double amount, ExpenseCategory category, ArrayList expenses) {
+ assert amount >= 0;
+ assert category != ExpenseCategory.NULL;
+ Budget budget = expenseCategoryToBudget(category);
+ LocalDate date = LocalDate.now();
+ if (budget == overallBudget) {
+ if (amount >= getTotalBudget(expenses, date)) {
+ budget.setLimit(amount);
+ return new BudgetSetReminder(budget.getName(), budget.getLimit());
+ } else {
+ return new UnableToSetOverallBudgetReminder(budget.getName(),
+ budget.getLimit(), getTotalBudget(expenses, date));
+ }
+ } else {
+ double oldBudget = budget.getLimit();
+ budget.setLimit(amount);
+ double newTotalBudget = getTotalBudget(expenses, date);
+ if (amount >= budget.calAmount(expenses, date) && newTotalBudget <= overallBudget.getLimit()) {
+ return new BudgetSetReminder(budget.getName(), budget.getLimit());
+ } else {
+ budget.setLimit(oldBudget);
+ return new UnableToSetBudgetReminder(budget.getName(), budget.calAmount(expenses, date),
+ overallBudget.getLimit(), amount, newTotalBudget);
+ }
+ }
+ }
+
+ /**
+ * Returns the current limit of a budget category.
+ * @param category Specified budget category
+ * @return Budget limit
+ */
+ public double getBudget(ExpenseCategory category) {
+ assert category != ExpenseCategory.NULL;
+ Budget budget = expenseCategoryToBudget(category);
+ return budget.getLimit();
+ }
+
+ /**
+ * Returns an ArrayList containing all the budget objects for easy access.
+ * @return ArrayList of budget objects
+ */
+ public ArrayList getBudgets() {
+ return budgets;
+ }
+
+ /**
+ * Returns the sum of all sub-budgets (excluding overall budget).
+ * If the current expenses in a sub-budget is greater than its budget limit,
+ * the current expenses value will be summed instead.
+ * @param expenses ArrayList of expenses from FinancialTracker
+ * @param date Date object containing the month for which total budget will be calculated.
+ * @return sum of all sub-budgets (or expenses)
+ */
+ public double getTotalBudget(ArrayList expenses, LocalDate date) {
+ double total = 0;
+ for (Budget budget : budgets) {
+ if (budget == overallBudget) {
+ continue;
+ } else if (budget.getLimit() >= budget.calAmount(expenses, date)) {
+ total += budget.getLimit();
+ } else {
+ total += budget.calAmount(expenses, date);
+ }
+ }
+ return total;
+ }
+
+ private Budget expenseCategoryToBudget(ExpenseCategory category) {
+ assert category != ExpenseCategory.NULL;
+ Budget budget;
+ switch (category) {
+ case FOOD:
+ budget = foodBudget;
+ break;
+ case TRANSPORT:
+ budget = transportBudget;
+ break;
+ case MEDICAL:
+ budget = medicalBudget;
+ break;
+ case BILLS:
+ budget = billsBudget;
+ break;
+ case ENTERTAINMENT:
+ budget = entertainmentBudget;
+ break;
+ case MISC:
+ budget = miscBudget;
+ break;
+ default:
+ budget = overallBudget;
+ break;
+ }
+ return budget;
+ }
+}
diff --git a/src/main/java/seedu/utility/CommandFormat.java b/src/main/java/seedu/utility/CommandFormat.java
new file mode 100644
index 0000000000..875546899f
--- /dev/null
+++ b/src/main/java/seedu/utility/CommandFormat.java
@@ -0,0 +1,50 @@
+package seedu.utility;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CommandFormat {
+ private static final String HELP_FORMAT = "List Out All Commands: help";
+ private static final String ADD_EXPENSE_FORMAT = "Adding Expense: add_ex d/DESCRIPTION "
+ + "a/AMOUNT c/CATEGORY";
+ private static final String ADD_EXPENSE_WITH_DATE_FORMAT = "Adding Expense With Date (Date Format: DD/MM/YYYY):"
+ + " add_ex_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE";
+ private static final String DEL_EXPENSE_FORMAT = "Deleting Expense: del_ex i/INDEX";
+ private static final String LIST_EXPENSE_FORMAT = "Listing Expense: list_ex";
+ private static final String TOTAL_EXPENSE_FORMAT = "Show Total Expense: total_ex";
+ private static final String ADD_INCOME_FORMAT = "Adding Income: add_in d/DESCRIPTION "
+ + "a/AMOUNT c/CATEGORY";
+ private static final String ADD_INCOME_WITH_DATE_FORMAT = "Adding Income With Date (Date Format: DD/MM/YYYY):"
+ + " add_in_d d/DESCRIPTION a/AMOUNT c/CATEGORY D/DATE";
+ private static final String DEL_INCOME_FORMAT = "Deleting Income: del_in i/INDEX";
+ private static final String LIST_INCOME_FORMAT = "Listing Income: list_in";
+ private static final String TOTAL_INCOME_FORMAT = "Show Total Income: total_in";
+ private static final String EXPENSE_BETWEEN_FORMAT = "Show Total Expense between 2 dates (Date Format: DD/MM/YYYY)"
+ + ": btw_ex s/START_DATE e/END_DATE";
+ private static final String INCOME_BETWEEN_FORMAT = "Show Total Income between 2 dates (Date Format: DD/MM/YYYY)"
+ + ": btw_in s/START_DATE e/END_DATE";
+ private static final String END_FORMAT = "To Terminate The Program: end";
+ private static final String FIND_FORMAT = "To Find Using Date: find DD/MM/YYYY\n"
+ + "To Find Based On Keyword: find KEYWORD";
+ private static final String BALANCE_FORMAT = "To Display Total Balance: balance";
+ private static final String SET_BUDGET_FORMAT = "To Set Budgets(Overall, Food, Transport, Medical, Bills,"
+ + " Entertainment, Misc): set_budget c/CATEGORY a/AMOUNT";
+ private static final String CHECK_BUDGET_FORMAT = "To Check Budgets: check_budget c/CATEGORY";
+ private static final String SET_THRESHOLD_FORMAT = "To Set Threshold Value for Reminders: "
+ + "set_threshold t/THRESHOLD";
+ private static final String CLEAR_FORMAT = "To Clear All Expense And Income Entries: clear_all_entries";
+ private static final String GRAPH_FORMAT = "To View Your Year Report (Year format: YYYY): show_graph [Y/YEAR]";
+ private static final String CONVERT_CURRENCY_FORMAT
+ = "To change entries into a different currency: set_curr c/CURRENCY";
+ private static final String CHECK_CURRENCY_FORMAT
+ = "To check the currency that entries are currently in: check_curr";
+ private static final String LIST_CURRENCY_TYPES_FORMAT
+ = "Lists available currency types for conversion: list_curr";
+
+ public static final List commands = Arrays.asList(HELP_FORMAT, ADD_EXPENSE_FORMAT,
+ ADD_EXPENSE_WITH_DATE_FORMAT, DEL_EXPENSE_FORMAT, ADD_INCOME_FORMAT, ADD_INCOME_WITH_DATE_FORMAT,
+ DEL_INCOME_FORMAT, LIST_EXPENSE_FORMAT, LIST_INCOME_FORMAT, TOTAL_EXPENSE_FORMAT, TOTAL_INCOME_FORMAT,
+ BALANCE_FORMAT, EXPENSE_BETWEEN_FORMAT, INCOME_BETWEEN_FORMAT, FIND_FORMAT, SET_BUDGET_FORMAT,
+ CHECK_BUDGET_FORMAT, SET_THRESHOLD_FORMAT, CONVERT_CURRENCY_FORMAT, CHECK_CURRENCY_FORMAT,
+ LIST_CURRENCY_TYPES_FORMAT, GRAPH_FORMAT, CLEAR_FORMAT, END_FORMAT);
+}
diff --git a/src/main/java/seedu/utility/CommandKeywords.java b/src/main/java/seedu/utility/CommandKeywords.java
new file mode 100644
index 0000000000..2f92706d03
--- /dev/null
+++ b/src/main/java/seedu/utility/CommandKeywords.java
@@ -0,0 +1,31 @@
+package seedu.utility;
+
+/**
+ * Contains all the possible command words that users can give.
+ */
+public class CommandKeywords {
+ public static final String HELP_COMMAND_KEYWORD = "help";
+ public static final String ADD_EXPENSE_KEYWORD = "add_ex";
+ public static final String ADD_EXPENSE_WITH_DATE_KEYWORD = "add_ex_d";
+ public static final String ADD_INCOME_KEYWORD = "add_in";
+ public static final String ADD_INCOME_WITH_DATE_KEYWORD = "add_in_d";
+ public static final String DELETE_EXPENSE_KEYWORD = "del_ex";
+ public static final String DELETE_INCOME_KEYWORD = "del_in";
+ public static final String LIST_EXPENSE_KEYWORD = "list_ex";
+ public static final String LIST_INCOME_KEYWORD = "list_in";
+ public static final String TOTAL_EXPENSE_KEYWORD = "total_ex";
+ public static final String TOTAL_INCOME_KEYWORD = "total_in";
+ public static final String FIND_KEYWORD = "find";
+ public static final String BALANCE_KEYWORD = "balance";
+ public static final String EXIT_KEYWORD = "end";
+ public static final String EXPENSE_RANGE_KEYWORD = "btw_ex";
+ public static final String INCOME_RANGE_KEYWORD = "btw_in";
+ public static final String SHOW_GRAPH_KEYWORD = "show_graph";
+ public static final String CLEAR_ALL_ENTRIES_KEYWORD = "clear_all_entries";
+ public static final String SET_BUDGET_KEYWORD = "set_budget";
+ public static final String CHECK_BUDGET_KEYWORD = "check_budget";
+ public static final String SET_THRESHOLD_KEYWORD = "set_threshold";
+ public static final String CONVERT_CURRENCY_KEYWORD = "set_curr";
+ public static final String CHECK_CURRENT_CURRENCY_KEYWORD = "check_curr";
+ public static final String LIST_CURRENCY_TYPES_KEYWORD = "list_curr";
+}
diff --git a/src/main/java/seedu/utility/CurrencyManager.java b/src/main/java/seedu/utility/CurrencyManager.java
new file mode 100644
index 0000000000..1591e9c218
--- /dev/null
+++ b/src/main/java/seedu/utility/CurrencyManager.java
@@ -0,0 +1,139 @@
+package seedu.utility;
+
+import seedu.budget.Budget;
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.Entry;
+import seedu.exceptions.SameCurrencyTypeException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * CurrencyManager class maintains all methods required by currency related commands.
+ * Performs currency conversion operations on income, expense and budget objects and tracks those changes.
+ */
+public class CurrencyManager {
+
+ protected double exchangeRate;
+ private CurrencyType currency = CurrencyType.SGD;
+ private final ArrayList currencyTypes = new ArrayList<>();
+
+
+ /**
+ * Converts all entries and budgets into given currency type.
+ * @param from base currency type
+ * @param to new currency type that user wishes to convert to
+ * @param finances FinancialTracker object that allows access to all stored entries
+ * @param budgetManager BudgetManager object that allows access to stored budgets
+ * @throws SameCurrencyTypeException throws error is user tries to convert to same currency type again
+ */
+ public void currencyConvertor(CurrencyType from, CurrencyType to, FinancialTracker finances,
+ BudgetManager budgetManager) throws SameCurrencyTypeException {
+ if (getCurrency() == to) {
+ throw new SameCurrencyTypeException(Messages.SAME_CURRENCY_TYPE_MESSAGE);
+ } else {
+ ArrayList entries = finances.getEntries();
+ ArrayList budgets = budgetManager.getBudgets();
+ convertEntries(entries, from, to);
+ convertBudgets(budgets, from, to);
+ }
+ }
+
+ /**
+ * Returns the current currency state of all entries.
+ * @return currency
+ */
+ public CurrencyType getCurrency() {
+ assert currency != null;
+ return currency;
+ }
+
+ /**
+ * Updates currency type to new currency.
+ * @param currency tracks currency type of entries
+ */
+ public void setCurrency(CurrencyType currency) {
+ this.currency = currency;
+ }
+
+ /**
+ * Creates arrayList with enums contained in currencyType class.
+ * @return currencyTypes
+ */
+ public ArrayList getCurrencyTypes() {
+ currencyTypes.addAll(Arrays.asList(CurrencyType.values()));
+ return currencyTypes;
+ }
+
+ /**
+ * Returns exchange rate of given currency type.
+ * @param to new currency type that user wishes to convert to
+ * @return exchangeRate
+ */
+ public double determineExchangeRate(CurrencyType to) {
+ switch (to) {
+ case HKD:
+ return exchangeRate = 5.00;
+ case SGD:
+ return exchangeRate = (1.00 / 5.00);
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Checks if given currency type is equal to SGD.
+ * @param from base currency type
+ * @return false if equals SGD
+ */
+ public boolean isBaseCurrency(CurrencyType from) {
+ return !from.equals(CurrencyType.SGD);
+ }
+
+ /**
+ * Converts all entry objects values into equivalent value in new currency type.
+ * @param entries income and expense objects stored in FinancialTracker
+ * @param from base currency type
+ * @param to new currency type that user wishes to convert to
+ */
+ public void convertEntries(ArrayList entries, CurrencyType from, CurrencyType to) {
+ for (Entry entry : entries) {
+ double newValue = convertItem(from, to, entry.getValue());
+ assert newValue >= 0;
+ entry.setValue(newValue);
+ }
+ setCurrency(to);
+ }
+
+ /**
+ * Converts all budget objects values into equivalent value in new currency type.
+ * @param budgets budget objects stored in BudgetManager
+ * @param from base currency type
+ * @param to new currency type that user wishes to convert to
+ */
+ public void convertBudgets(ArrayList budgets, CurrencyType from, CurrencyType to) {
+ for (Budget budget : budgets) {
+ double newValue = convertItem(from, to, budget.getLimit());
+ budget.setLimit(newValue);
+ }
+ setCurrency(to);
+ }
+
+ /**
+ * Returns new values of entries after multiplying with correct exchangeRate.
+ * @param from base currency type
+ * @param to new currency type that user wishes to convert to
+ * @param value value of object being parsed
+ * @return value
+ */
+ public double convertItem(CurrencyType from, CurrencyType to, double value) {
+ if (isBaseCurrency(from)) {
+ double fromRate = determineExchangeRate(from);
+ assert fromRate >= 0;
+ value = (value / fromRate);
+ }
+ double toRate = determineExchangeRate(to);
+ assert toRate >= 0;
+ return (value * toRate);
+ }
+}
diff --git a/src/main/java/seedu/utility/FinancialTracker.java b/src/main/java/seedu/utility/FinancialTracker.java
new file mode 100644
index 0000000000..0e13ee5e14
--- /dev/null
+++ b/src/main/java/seedu/utility/FinancialTracker.java
@@ -0,0 +1,291 @@
+package seedu.utility;
+
+import seedu.entry.Entry;
+import seedu.entry.Expense;
+import seedu.entry.Income;
+import seedu.exceptions.ExpenseEntryNotFoundException;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeEntryNotFoundException;
+import seedu.exceptions.IncomeOverflowException;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+import static seedu.utility.tools.DateOperator.entryDateInRange;
+import static seedu.utility.tools.DateOperator.sameEntryYear;
+import static seedu.utility.tools.FinancialCalculator.getSumOfEntries;
+import static seedu.utility.tools.FinancialCalculator.sortEntriesByMonth;
+
+
+/**
+ * A Financial tracker that contains 2 separate list of income and expense entries and a net balance.
+ */
+public class FinancialTracker {
+ private final ArrayList expenses;
+ private final ArrayList incomes;
+ private final CurrencyManager currencyManager;
+
+ public static final double TOTAL_ENTRIES_LIMIT = 100000000000.00;
+
+ /**
+ * Constructor for financial tracker initialises two empty ArrayList, one for expenses and one for incomes.
+ */
+ public FinancialTracker(CurrencyManager currencyManager) {
+ this.expenses = new ArrayList<>();
+ this.incomes = new ArrayList<>();
+ this.currencyManager = currencyManager;
+ }
+
+ /**
+ * Returns balance of the financial tracker which is totalincome - totalexpense.
+ *
+ * @return Balance of the financial tracker.
+ */
+ public double calculateBalance() {
+ return getTotalIncome() - getTotalExpense();
+ }
+
+ /**
+ * Adds an Expense object into the expenses ArrayList of FinancialTracker.
+ *
+ * @param expense Expense object we want to add into the ArrayList.
+ * @throws ExpenseOverflowException Thrown if the sum of expense exceeds a fixed limit.
+ */
+ public void addExpense(Expense expense) throws ExpenseOverflowException {
+ int expenseSize = 0;
+ assert (expenseSize = expenses.size()) >= 0;
+ if (isOverflowedExpense(expense)) {
+ throw new ExpenseOverflowException(Messages.EXPENSE_OVERFLOW_ERROR);
+ }
+ expenses.add(expense);
+ assert !expenses.isEmpty();
+ assert expenses.size() > expenseSize;
+ }
+
+ private boolean isOverflowedExpense(Expense expense) {
+ return expense.getValue() + getTotalExpense() > TOTAL_ENTRIES_LIMIT;
+ }
+
+ /**
+ * Adds an Income object into the income ArrayList of FinancialTracker.
+ *
+ * @param income Income object we want to add into the ArrayList.
+ * @throws IncomeOverflowException Thrown if the sum of income exceeds a fixed limit.
+ */
+ public void addIncome(Income income) throws IncomeOverflowException {
+ int incomeSize = 0;
+ assert (incomeSize = incomes.size()) >= 0;
+ if (isOverflowedIncome(income)) {
+ throw new IncomeOverflowException(Messages.INCOME_OVERFLOW_ERROR);
+ }
+ incomes.add(income);
+ assert !incomes.isEmpty();
+ assert incomes.size() > incomeSize;
+ }
+
+ private boolean isOverflowedIncome(Income income) {
+ return income.getValue() + getTotalIncome() > TOTAL_ENTRIES_LIMIT;
+ }
+
+ private int indexOffset(int index) {
+ return index - 1;
+ }
+
+ /**
+ * Removes an expense entry based on its index.
+ *
+ * @param expenseIndex Index of deleted expense entry.
+ * @return Deleted expense object.
+ * @throws ExpenseEntryNotFoundException Thrown if no entry can be found in the given index.
+ */
+ public Expense removeExpense(int expenseIndex) throws ExpenseEntryNotFoundException {
+ try {
+ Expense removedExpense = expenses.remove(indexOffset(expenseIndex));
+ assert expenses.stream().noneMatch(expense -> expense == removedExpense);
+ return removedExpense;
+ } catch (IndexOutOfBoundsException e) {
+ throw new ExpenseEntryNotFoundException(Messages.UNABLE_TO_DELETE_MESSAGE);
+ }
+ }
+
+ /**
+ * Removes an income entry based on its index.
+ *
+ * @param incomeIndex Index of deleted income entry.
+ * @return Deleted income object.
+ * @throws IncomeEntryNotFoundException Thrown if no entry can be found in the given index.
+ */
+ public Income removeIncome(int incomeIndex) throws IncomeEntryNotFoundException {
+ try {
+ Income removedIncome = incomes.remove(indexOffset(incomeIndex));
+ assert incomes.stream().noneMatch(expense -> expense == removedIncome);
+ return removedIncome;
+ } catch (IndexOutOfBoundsException e) {
+ throw new IncomeEntryNotFoundException(Messages.UNABLE_TO_DELETE_MESSAGE);
+ }
+ }
+
+ /**
+ * Returns an ArrayList called expenses from FinancialTracker.
+ *
+ * @return Returns ArrayList with only expense entries inside.
+ */
+ public ArrayList getExpenses() {
+ assert expenses != null;
+ return expenses;
+ }
+
+ /**
+ * Returns an ArrayList called incomes from FinancialTracker.
+ *
+ * @return Returns ArrayList with only income entries inside.
+ */
+ public ArrayList getIncomes() {
+ assert incomes != null;
+ return incomes;
+ }
+
+ /**
+ * Returns an ArrayList of Entry elements, which include all incomes and expenses entries in the financial tracker.
+ *
+ * @return ArrayList of Entry elements.
+ */
+ public ArrayList getEntries() {
+ ArrayList entries = new ArrayList<>();
+ entries.addAll(getExpenses());
+ entries.addAll(getIncomes());
+ return entries;
+ }
+
+ /**
+ * Returns the total expense of all expenses in the financial tracker.
+ *
+ * @return Total expense of all expenses in the financial tracker.
+ */
+ public double getTotalExpense() {
+ double totalExpense = 0;
+ for (Expense expense : expenses) {
+ assert expense.getValue() >= 0;
+ totalExpense += expense.getValue();
+ }
+ assert totalExpense >= 0;
+ assert totalExpense <= TOTAL_ENTRIES_LIMIT;
+ return totalExpense;
+ }
+
+ /**
+ * Returns the total income of all incomes in the financial tracker.
+ *
+ * @return Total income of all incomes in the financial tracker.
+ */
+ public double getTotalIncome() {
+ double totalIncome = 0;
+ for (Income income : incomes) {
+ assert income.getValue() >= 0;
+ totalIncome += income.getValue();
+ }
+ assert totalIncome >= 0;
+ assert totalIncome <= TOTAL_ENTRIES_LIMIT;
+ return totalIncome;
+ }
+
+ /**
+ * Returns total expense between two given dates.
+ *
+ * @param startDate Starting date (Left boundary).
+ * @param endDate End Date (Right boundary).
+ * @return Total expense between two given dates.
+ */
+ public double getExpenseBetween(LocalDate startDate, LocalDate endDate) {
+ List accumulatedExpense = expenses.stream()
+ .filter(entryDateInRange(startDate, endDate))
+ .collect(toList());
+ return getSumOfEntries(accumulatedExpense);
+ }
+
+ /**
+ * Returns an ArrayList of size 12, where each element stores the total expense of that month in the given year.
+ *
+ * @param inputYear Year which the monthly breakdown is based on.
+ * @return ArrayList of elements representing total expense in each month.
+ */
+ public ArrayList getMonthlyExpenseBreakdown(int inputYear) {
+ List yearlyAccumulatedExpense = expenses.stream()
+ .filter(sameEntryYear(inputYear))
+ .collect(toList());
+ return sortEntriesByMonth(yearlyAccumulatedExpense);
+ }
+
+ /**
+ * Returns total income between two given dates.
+ *
+ * @param startDate Starting date (Left boundary).
+ * @param endDate End Date (Right boundary).
+ * @return Total income between two given dates.
+ */
+ public double getIncomeBetween(LocalDate startDate, LocalDate endDate) {
+ List accumulatedIncome = incomes.stream()
+ .filter(entryDateInRange(startDate, endDate))
+ .collect(toList());
+ return getSumOfEntries(accumulatedIncome);
+ }
+
+ /**
+ * Returns an ArrayList of size 12, where each element stores the total income of that month in the given year.
+ *
+ * @param inputYear Year which the monthly breakdown is based on.
+ * @return ArrayList of elements representing total income in each month.
+ */
+ public ArrayList getMonthlyIncomeBreakdown(int inputYear) {
+ List yearlyAccumulatedIncome = incomes.stream()
+ .filter(sameEntryYear(inputYear))
+ .collect(toList());
+ return sortEntriesByMonth(yearlyAccumulatedIncome);
+ }
+
+ /**
+ * Returns the size of the expenses ArrayList.
+ *
+ * @return Size of the expenses array list.
+ */
+ public int getExpenseSize() {
+ return expenses.size();
+ }
+
+ /**
+ * Returns the size of the incomes ArrayList.
+ *
+ * @return Size of the incomes array list.
+ */
+ public int getIncomeSize() {
+ return incomes.size();
+ }
+
+ /**
+ * Returns true if expenses list of financial tracker is empty.
+ *
+ * @return Whether the expense list is empty.
+ */
+ public boolean isExpensesEmpty() {
+ return expenses.isEmpty();
+ }
+
+ /**
+ * Returns true if incomes list of financial tracker is empty.
+ *
+ * @return Whether the income list is empty.
+ */
+ public boolean isIncomesEmpty() {
+ return incomes.isEmpty();
+ }
+
+ /**
+ * Delete all entries from both expenses and income list in financial tracker and set balance to zero.
+ */
+ public void clearAllEntries() {
+ expenses.clear();
+ incomes.clear();
+ }
+}
diff --git a/src/main/java/seedu/utility/Messages.java b/src/main/java/seedu/utility/Messages.java
new file mode 100644
index 0000000000..3d3ccada6f
--- /dev/null
+++ b/src/main/java/seedu/utility/Messages.java
@@ -0,0 +1,96 @@
+package seedu.utility;
+
+/**
+ * Contains all the warning messages to be sent to the user when an invalid input is given.
+ */
+public class Messages {
+ public static final String INVALID_COMMAND_MESSAGE =
+ "Invalid command. Use \"help\" to show the list of possible commands.";
+ public static final String NON_NUMERIC_AMOUNT_MESSAGE = "Only numeric inputs are allowed for amount.";
+ public static final String NON_NUMERIC_THRESHOLD_MESSAGE = "Threshold value given is either out of range or "
+ + "non-numeric. Please try again.";
+ public static final String NON_POSITIVE_AMOUNT_MESSAGE = "Only positive values are allowed for amount.";
+ public static final String NON_POSITIVE_INTEGER_INDEX_MESSAGE = "Only positive integers are allowed for index.";
+ public static final String BLANK_AMOUNT_MESSAGE = "No amount inputted!";
+ public static final String UNABLE_TO_DELETE_MESSAGE = "Entry not deleted because entry not found!";
+ public static final String BLANK_DESCRIPTION_MESSAGE = "Your description is empty!";
+ public static final String HAS_CORRUPTED_DATA_ENTRIES = "StonksXD_Entries.csv has corrupted entries, "
+ + "those corrupted entries will be discarded and not be loaded.";
+ public static final String HAS_CORRUPTED_SETTINGS = "StonksXD_Settings.csv has corrupted settings, "
+ + "all settings will be reset.";
+ public static final String UNABLE_TO_FIND_DATA_FILE = "Unable to find StonksXD_Entries.csv, a new one "
+ + "has been made.";
+ public static final String UNABLE_TO_FIND_SETTINGS_FILE = "Unable to find StonksXD_Settings.csv, a new one "
+ + "has been made.";
+ public static final String SEARCH_NO_MATCH_MESSAGE = "Your search did not match any of the entries!";
+ public static final String PROMPTING_MESSAGE = "Would you like to set your budget before you begin?\n"
+ + "You can use the set budget commands shown in the help command!";
+ public static final String HELP_COMMAND_MESSAGE = "This is a list of commands and their format!";
+ public static final String LISTING_EXPENSE_MESSAGE = "Below is a list of all of your recent spending!";
+ public static final String LISTING_INCOME_MESSAGE = "Below is a list of all of your recent earnings!";
+ public static final String EMPTY_INCOME_MESSAGE = "You have not entered any income!";
+ public static final String EMPTY_EXPENSE_MESSAGE = "You have not spent anything!";
+ public static final String FOUND_LIST_MESSAGE = "Below is a list of all your findings!";
+ public static final String BLANK_CATEGORY_MESSAGE = "Your category is empty!";
+ public static final String DATE_FORMAT_MESSAGE = "Your start and end dates must be in a DD/MM/YYYY format!";
+ public static final String ALL_DATA_CLEARED = "All your entries have been cleared!";
+ public static final String INVALID_EXPENSE_CATEGORY_MESSAGE = "Input only 1 of these 6 categories: Food, "
+ + "Transport, Bills, Medical, Entertainment or Misc.";
+ public static final String INVALID_INCOME_CATEGORY_MESSAGE = "Input only 1 of these 4 categories: Salary, "
+ + "Allowance, Adhoc, or Others.";
+ public static final String INVALID_BUDGET_CATEGORY_MESSAGE = "Input only 1 of these 7 categories: Food, "
+ + "Transport, Bills, Medical, Entertainment, Misc or Overall";
+ public static final String INVALID_THRESHOLD_MESSAGE = "Threshold value should between 0 and 1.";
+ public static final String INVALID_CURRENCY_TYPE_MESSAGE = "Please enter a valid currency for conversion!";
+ public static final String BLANK_CURRENCY_TYPE_MESSAGE = "You have not entered any currency type!";
+ public static final String SAME_CURRENCY_TYPE_MESSAGE = "Your lists are already in the requested currency type!";
+ public static final String AVAILABLE_CURRENCIES_MESSAGE
+ = "Here is a list of available currencies you can convert to!";
+
+ public static final String SEPARATOR_MESSAGE = "----------------------------------------------------------------"
+ + "-------------------------------------";
+
+ public static final String BYE_MESSAGE = "██████ ██ ██ ███████ ██ \n"
+ + "██ ██ ██ ██ ██ ██ ██ \n"
+ + "██████ ████ █████ ██ \n"
+ + "██ ██ ██ ██ ██ ██ \n"
+ + "██████ ██ ███████ ██ ";
+
+ public static final String LOGO_MESSAGE = "███████ ████████ ██████ ███ ██ ██ ██ ███████"
+ + " ██ ██ ██████ \n██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ \n"
+ + "███████ ██ ██ ██ ██ ██ ██ █████ ███████ ███ ██ ██ \n"
+ + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ \n"
+ + "███████ ██ ██████ ██ ████ ██ ██ ███████ ██ ██ ██████ ";
+
+ public static final String TIP_HEADER = "Here's our tip for the day: ";
+ public static final String DISPLAY_ADVICE_ERROR = "Sorry there is no advice for you at this moment >.<";
+ public static final String CURRENT_CURRENCY_MESSAGE = "Your currency setting currently: ";
+ public static final String CURRENCY_CONVERSION_SUCCESSFUL_MESSAGE = "All entries have been converted to ";
+ public static final String INVALID_DATE_RANGE_MESSAGE = "Make sure your start date is before your end date";
+ public static final String PARAMETERS_ERROR_MESSAGE = "You have missing or invalid parameters. Use help to view "
+ + "commands again!";
+ public static final String INVALID_EXPENSE_VALUE = "Wow! That's a lot of money. "
+ + "Are you sure you have spent that much?";
+ public static final String INVALID_BUDGET_VALUE = "Wow! That's a lot of money."
+ + "I don't think we can handle that much money";
+ public static final String INCOMPLETE_ENTRIES_CSV_HEADER_MESSAGE = "The CSV header for StonksXD_Entries.csv "
+ + "seems to be incomplete or missing.\nDo not worry, we will put back the CSV header.\nNote: If you "
+ + "replaced the CSV header with an entry, that entry will not be loaded.";
+ public static final String INCOMPLETE_SETTINGS_CSV_HEADER_MESSAGE = "The CSV header for StonksXD_Settings.csv "
+ + "seems to be incomplete or missing.\nDo not worry, we will put back the CSV header.\nNote: If you "
+ + "replaced the CSV header with the settings, those settings will not be loaded.";
+ public static final String EXPENSE_OVERFLOW_ERROR = "Your total expense will exceed $100000000000. "
+ + "Are you sure you have spent this much?";
+ public static final String INCOME_OVERFLOW_ERROR = "Your total income will exceed $100000000000. "
+ + "Are you sure you have gotten this much?";
+ public static final String ERROR_SAVING_ENTRIES_MESSAGE = "There is trouble saving entries into "
+ + "StonksXD_Entries.csv, some or all entries maybe lost.";
+ public static final String ERROR_SAVING_SETTINGS_MESSAGE = "There is trouble saving entries into "
+ + "StonksXD_Settings.csv, some or all settings maybe lost.";
+ public static final String TOO_MANY_DP_MESSAGE = "Only 2 or less decimal places are allowed!";
+ public static final String INVALID_DATE_FORMAT = "Your date format is invalid. Please use DD/MM/YYYY";
+ public static final String INVALID_INDEX_MESSAGE = "Index given is either out of range or not an integer.";
+ public static final String INVALID_YEAR_MESSAGE = "The given year must be in the YYYY format.";
+ public static final String INVALID_DATE_ERROR = "This date is invalid. Try another date!";
+ public static final String AMOUNT_BELOW_MIN_MESSAGE = "The amount you have given is less than 0.05!";
+}
diff --git a/src/main/java/seedu/utility/Parser.java b/src/main/java/seedu/utility/Parser.java
new file mode 100644
index 0000000000..1635ec6f1a
--- /dev/null
+++ b/src/main/java/seedu/utility/Parser.java
@@ -0,0 +1,656 @@
+package seedu.utility;
+
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+
+import seedu.commands.Command;
+import seedu.commands.expense.AddExpenseCommand;
+import seedu.commands.income.AddIncomeCommand;
+import seedu.commands.expense.DeleteExpenseCommand;
+import seedu.commands.income.DeleteIncomeCommand;
+import seedu.commands.expense.ListExpenseCommand;
+import seedu.commands.income.ListIncomeCommand;
+import seedu.commands.currency.CheckCurrentCurrencyCommand;
+import seedu.commands.general.ClearAllEntriesCommand;
+import seedu.commands.general.ExitCommand;
+import seedu.commands.general.FindCommand;
+import seedu.commands.general.HelpCommand;
+import seedu.commands.general.ShowGraphByYearCommand;
+import seedu.commands.general.ShowGraphCommand;
+import seedu.commands.budget.CheckBudgetCommand;
+import seedu.commands.currency.CurrencyConversionCommand;
+import seedu.commands.currency.CurrencyType;
+import seedu.commands.InvalidCommand;
+import seedu.commands.budget.SetBudgetCommand;
+import seedu.commands.budget.SetThresholdCommand;
+import seedu.commands.expense.TotalExpenseBetweenCommand;
+import seedu.commands.expense.TotalExpenseCommand;
+import seedu.commands.income.TotalIncomeBetweenCommand;
+import seedu.commands.income.TotalIncomeCommand;
+import seedu.commands.budget.BalanceCommand;
+import seedu.commands.currency.ListCurrencyTypesCommand;
+
+import seedu.exceptions.InputException;
+import seedu.exceptions.InvalidBudgetAmountException;
+import seedu.exceptions.InvalidDateException;
+import seedu.exceptions.InvalidIndexException;
+import seedu.exceptions.InvalidThresholdValueException;
+import seedu.utility.tools.DateRange;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static seedu.utility.CommandKeywords.ADD_EXPENSE_KEYWORD;
+import static seedu.utility.CommandKeywords.ADD_EXPENSE_WITH_DATE_KEYWORD;
+import static seedu.utility.CommandKeywords.ADD_INCOME_KEYWORD;
+import static seedu.utility.CommandKeywords.ADD_INCOME_WITH_DATE_KEYWORD;
+import static seedu.utility.CommandKeywords.BALANCE_KEYWORD;
+import static seedu.utility.CommandKeywords.CHECK_BUDGET_KEYWORD;
+import static seedu.utility.CommandKeywords.CHECK_CURRENT_CURRENCY_KEYWORD;
+import static seedu.utility.CommandKeywords.CLEAR_ALL_ENTRIES_KEYWORD;
+import static seedu.utility.CommandKeywords.CONVERT_CURRENCY_KEYWORD;
+import static seedu.utility.CommandKeywords.DELETE_EXPENSE_KEYWORD;
+import static seedu.utility.CommandKeywords.DELETE_INCOME_KEYWORD;
+import static seedu.utility.CommandKeywords.EXIT_KEYWORD;
+import static seedu.utility.CommandKeywords.EXPENSE_RANGE_KEYWORD;
+import static seedu.utility.CommandKeywords.FIND_KEYWORD;
+import static seedu.utility.CommandKeywords.HELP_COMMAND_KEYWORD;
+import static seedu.utility.CommandKeywords.INCOME_RANGE_KEYWORD;
+import static seedu.utility.CommandKeywords.LIST_CURRENCY_TYPES_KEYWORD;
+import static seedu.utility.CommandKeywords.LIST_EXPENSE_KEYWORD;
+import static seedu.utility.CommandKeywords.LIST_INCOME_KEYWORD;
+import static seedu.utility.CommandKeywords.SET_BUDGET_KEYWORD;
+import static seedu.utility.CommandKeywords.SET_THRESHOLD_KEYWORD;
+import static seedu.utility.CommandKeywords.SHOW_GRAPH_KEYWORD;
+import static seedu.utility.CommandKeywords.TOTAL_EXPENSE_KEYWORD;
+import static seedu.utility.CommandKeywords.TOTAL_INCOME_KEYWORD;
+import static seedu.utility.tools.DateOperator.extractDate;
+import static seedu.utility.tools.DateOperator.extractStartAndEndDate;
+import static seedu.utility.tools.DateOperator.getYearFormat;
+import static seedu.utility.tools.DateOperator.isValidDateRange;
+import static seedu.utility.tools.Extractor.extractAmount;
+import static seedu.utility.tools.Extractor.extractBudgetAmount;
+import static seedu.utility.tools.Extractor.extractCurrencyType;
+import static seedu.utility.tools.Extractor.extractDescription;
+import static seedu.utility.tools.Extractor.extractExpenseCategory;
+import static seedu.utility.tools.Extractor.extractIncomeCategory;
+import static seedu.utility.tools.Extractor.extractIndex;
+import static seedu.utility.tools.Extractor.extractThresholdValue;
+
+/**
+ * Parses user given inputs. Takes in user inputs and attempt to convert it to an appropriate command.
+ * Some parts of the code were adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+public class Parser {
+
+ /**
+ * Used for initial separation of command word and args.
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
+
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private static final Pattern ADD_EXPENSE_ARGUMENT_FORMAT =
+ Pattern.compile("^(?= [cda]/)(?=.* d/(?.+?)( [ca]/|$))"
+ + "(?=.* a/(?.+?)( [dc]/|$))"
+ + "(?=.* c/(?.+?)( [da]/|$)).*$");
+
+ private static final Pattern ADD_EXPENSE_ARGUMENT_FORMAT_WITH_DATE =
+ Pattern.compile("^(?= [cdaD]/)(?=.* d/(?.+?)( [caD]/|$))"
+ + "(?=.* a/(?.+?)( [dcD]/|$))"
+ + "(?=.* c/(?.+?)( [daD]/|$))"
+ + "(?=.* D/(?.+?)( [dac]/|$)).*$");
+
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private static final Pattern ADD_INCOME_ARGUMENT_FORMAT =
+ Pattern.compile("^(?= [cda]/)(?=.* d/(?.+?)( [ca]/|$))"
+ + "(?=.* a/(?.+?)( [dc]/|$))"
+ + "(?=.* c/(?.+?)( [da]/|$)).*$");
+
+ private static final Pattern ADD_INCOME_ARGUMENT_FORMAT_WITH_DATE =
+ Pattern.compile("^(?= [cdaD]/)(?=.* d/(?.+?)( [caD]/|$))"
+ + "(?=.* a/(?.+?)( [dcD]/|$))"
+ + "(?=.* c/(?.+?)( [daD]/|$))"
+ + "(?=.* D/(?.+?)( [dac]/|$)).*$");
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private static final Pattern DELETE_EXPENSE_ARGUMENT_FORMAT =
+ Pattern.compile("i/(?[^/]+)");
+
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private static final Pattern DELETE_INCOME_ARGUMENT_FORMAT =
+ Pattern.compile("i/(?[^/]+)");
+
+ private static final Pattern DATE_RANGE_ARGUMENT_FORMAT =
+ Pattern.compile("^(?= [se]/)(?=.* s/(?.+?)( [e]/|$))"
+ + "(?=.* e/(?.+?)( [s]/|$)).*$");
+
+ private static final Pattern SET_BUDGET_ARGUMENT_FORMAT =
+ Pattern.compile("^(?= [ca]/)(?=.* c/(?.+?)( [a]/|$))"
+ + "(?=.* a/(?.+?)( [c]/|$)).*$");
+
+ private static final Pattern CHECK_BUDGET_ARGUMENT_FORMAT =
+ Pattern.compile("c/(?[^/]+)");
+
+ private static final Pattern SET_THRESHOLD_ARGUMENT_FORMAT =
+ Pattern.compile("t/(?[^/]+)");
+
+ private static final Pattern CURRENCY_CONVERSION_FORMAT =
+ Pattern.compile("c/(?.+)");
+
+ private static final Pattern SHOW_GRAPH_BY_YEAR_FORMAT =
+ Pattern.compile("Y/(?.+)");
+
+ /**
+ * Parses user input into command for execution.
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ */
+ public Command parseCommand(String userInput) {
+ final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ final String commandWord = matcher.group("commandWord");
+ String arguments = matcher.group("arguments").trim();
+
+ if (isExpenseRelatedCommand(commandWord)) {
+ return prepareExpenseRelatedCommand(commandWord, arguments);
+ } else if (isIncomeRelatedCommand(commandWord)) {
+ return prepareIncomeRelatedCommand(commandWord, arguments);
+ } else if (isGeneralRelatedCommand(commandWord)) {
+ return prepareGeneralRelatedCommand(commandWord, arguments);
+ } else if (isBudgetRelatedCommand(commandWord)) {
+ return prepareBudgetRelatedCommand(commandWord, arguments);
+ } else if (isCurrencyRelatedCommand(commandWord)) {
+ return prepareCurrencyRelatedCommand(commandWord, arguments);
+ } else {
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ }
+
+ private boolean isGeneralRelatedCommand(String commandWord) {
+ return (commandWord.equals(HELP_COMMAND_KEYWORD) || commandWord.equals(FIND_KEYWORD)
+ || commandWord.equals(EXIT_KEYWORD) || commandWord.equals(SHOW_GRAPH_KEYWORD)
+ || commandWord.equals(CLEAR_ALL_ENTRIES_KEYWORD));
+ }
+
+ private boolean isExpenseRelatedCommand(String commandWord) {
+ return (commandWord.equals(ADD_EXPENSE_KEYWORD) || commandWord.equals(DELETE_EXPENSE_KEYWORD)
+ || commandWord.equals(LIST_EXPENSE_KEYWORD) || commandWord.equals(TOTAL_EXPENSE_KEYWORD)
+ || commandWord.equals(EXPENSE_RANGE_KEYWORD) || commandWord.equals(ADD_EXPENSE_WITH_DATE_KEYWORD));
+ }
+
+ private boolean isIncomeRelatedCommand(String commandWord) {
+ return (commandWord.equals(ADD_INCOME_KEYWORD) || commandWord.equals(DELETE_INCOME_KEYWORD)
+ || commandWord.equals(LIST_INCOME_KEYWORD) || commandWord.equals(TOTAL_INCOME_KEYWORD)
+ || commandWord.equals(INCOME_RANGE_KEYWORD) || commandWord.equals(ADD_INCOME_WITH_DATE_KEYWORD));
+ }
+
+ private boolean isBudgetRelatedCommand(String commandWord) {
+ return (commandWord.equals(BALANCE_KEYWORD) || commandWord.equals(SET_BUDGET_KEYWORD)
+ || commandWord.equals(CHECK_BUDGET_KEYWORD) || commandWord.equals(SET_THRESHOLD_KEYWORD));
+ }
+
+ private boolean isCurrencyRelatedCommand(String commandWord) {
+ return (commandWord.equals(CHECK_CURRENT_CURRENCY_KEYWORD) || commandWord.equals(CONVERT_CURRENCY_KEYWORD)
+ || commandWord.equals(LIST_CURRENCY_TYPES_KEYWORD));
+ }
+
+ private Command prepareGeneralRelatedCommand(String commandWord, String arguments) {
+ switch (commandWord) {
+ case HELP_COMMAND_KEYWORD:
+ return prepareHelp(arguments);
+ case FIND_KEYWORD:
+ return prepareFind(arguments);
+ case EXIT_KEYWORD:
+ return prepareExit(arguments);
+ case SHOW_GRAPH_KEYWORD:
+ return prepareShowGraph(arguments);
+ case CLEAR_ALL_ENTRIES_KEYWORD:
+ return prepareClearAllEntries(arguments);
+ default:
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ }
+
+ private Command prepareExpenseRelatedCommand(String commandWord, String arguments) {
+ switch (commandWord) {
+ case ADD_EXPENSE_KEYWORD:
+ return prepareAddExpenseWithoutDate(arguments);
+ case ADD_EXPENSE_WITH_DATE_KEYWORD:
+ return prepareAddExpenseWithDate(arguments);
+ case DELETE_EXPENSE_KEYWORD:
+ return prepareDeleteExpense(arguments);
+ case LIST_EXPENSE_KEYWORD:
+ return prepareListExpense(arguments);
+ case TOTAL_EXPENSE_KEYWORD:
+ return prepareTotalExpense(arguments);
+ case EXPENSE_RANGE_KEYWORD:
+ return prepareExpenseRange(arguments);
+ default:
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ }
+
+ private Command prepareIncomeRelatedCommand(String commandWord, String arguments) {
+ switch (commandWord) {
+ case ADD_INCOME_KEYWORD:
+ return prepareAddIncomeWithoutDate(arguments);
+ case ADD_INCOME_WITH_DATE_KEYWORD:
+ return prepareAddIncomeWithDate(arguments);
+ case DELETE_INCOME_KEYWORD:
+ return prepareDeleteIncome(arguments);
+ case LIST_INCOME_KEYWORD:
+ return prepareListIncome(arguments);
+ case TOTAL_INCOME_KEYWORD:
+ return prepareTotalIncome(arguments);
+ case INCOME_RANGE_KEYWORD:
+ return prepareIncomeRange(arguments);
+ default:
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ }
+
+ private Command prepareBudgetRelatedCommand(String commandWord, String arguments) {
+ switch (commandWord) {
+ case BALANCE_KEYWORD:
+ return prepareBalance(arguments);
+ case SET_BUDGET_KEYWORD:
+ return prepareSetBudget(arguments);
+ case CHECK_BUDGET_KEYWORD:
+ return prepareCheckBudget(arguments);
+ case SET_THRESHOLD_KEYWORD:
+ return prepareSetThreshold(arguments);
+ default:
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ }
+
+ private Command prepareCurrencyRelatedCommand(String commandWord, String arguments) {
+ switch (commandWord) {
+ case CHECK_CURRENT_CURRENCY_KEYWORD:
+ return prepareCheckCurrentCurrency(arguments);
+ case CONVERT_CURRENCY_KEYWORD:
+ return prepareConvertCurrency(arguments);
+ case LIST_CURRENCY_TYPES_KEYWORD:
+ return prepareListCurrencyTypes(arguments);
+ default:
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ }
+
+ private Command prepareIncomeRange(String arguments) {
+ try {
+ final Matcher matcher = DATE_RANGE_ARGUMENT_FORMAT.matcher(" " + arguments);
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+ DateRange dateRange = extractStartAndEndDate(matcher);
+ if (isValidDateRange(dateRange)) {
+ return new TotalIncomeBetweenCommand(dateRange);
+ }
+ return new InvalidCommand(Messages.INVALID_DATE_RANGE_MESSAGE);
+ } catch (DateTimeParseException e) {
+ return new InvalidCommand(Messages.DATE_FORMAT_MESSAGE);
+ } catch (InvalidDateException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ }
+
+ private Command prepareExpenseRange(String arguments) {
+ try {
+ final Matcher matcher = DATE_RANGE_ARGUMENT_FORMAT.matcher(" " + arguments);
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+ DateRange dateRange = extractStartAndEndDate(matcher);
+ if (isValidDateRange(dateRange)) {
+ return new TotalExpenseBetweenCommand(dateRange);
+ }
+ return new InvalidCommand(Messages.INVALID_DATE_RANGE_MESSAGE);
+ } catch (DateTimeParseException e) {
+ return new InvalidCommand(Messages.DATE_FORMAT_MESSAGE);
+ } catch (InvalidDateException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ }
+
+ private Command prepareBalance(String arguments) {
+ if (arguments.isBlank()) {
+ return new BalanceCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareFind(String arguments) {
+ if (arguments.isBlank()) {
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+ return new FindCommand(arguments);
+ }
+
+ private Command prepareHelp(String arguments) {
+ if (arguments.isBlank()) {
+ return new HelpCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private Command prepareAddExpenseWithoutDate(String arguments) {
+ final Matcher matcher = ADD_EXPENSE_ARGUMENT_FORMAT.matcher(" " + arguments);
+ if (matcher.matches()) {
+ try {
+ double expenseAmount = extractAmount(matcher);
+ String expenseDescription = extractDescription(matcher);
+ ExpenseCategory expenseCategory = extractExpenseCategory(matcher);
+ Expense expense = new Expense(expenseDescription, expenseAmount, expenseCategory);
+ assert !expenseCategory.equals(ExpenseCategory.NULL);
+ return new AddExpenseCommand(expense);
+ } catch (InputException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ } else {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+ }
+
+ private Command prepareAddExpenseWithDate(String arguments) {
+ final Matcher matcher = ADD_EXPENSE_ARGUMENT_FORMAT_WITH_DATE.matcher(" " + arguments);
+ if (matcher.matches()) {
+ try {
+ double expenseAmount = extractAmount(matcher);
+ String expenseDescription = extractDescription(matcher);
+ ExpenseCategory expenseCategory = extractExpenseCategory(matcher);
+ LocalDate expenseDate = extractDate(matcher);
+ Expense expense = new Expense(expenseDescription, expenseAmount, expenseCategory, expenseDate);
+ assert !expenseCategory.equals(ExpenseCategory.NULL);
+ return new AddExpenseCommand(expense);
+ } catch (InputException e) {
+ return new InvalidCommand(e.getMessage());
+ } catch (DateTimeParseException de) {
+ return new InvalidCommand(Messages.INVALID_DATE_FORMAT);
+ }
+ } else {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+ }
+
+ private Command prepareAddIncomeWithoutDate(String arguments) {
+ final Matcher matcher = ADD_INCOME_ARGUMENT_FORMAT.matcher(" " + arguments);
+ if (matcher.matches()) {
+ try {
+ double incomeAmount = extractAmount(matcher);
+ String incomeDescription = extractDescription(matcher);
+ IncomeCategory incomeCategory = extractIncomeCategory(matcher);
+ Income income = new Income(incomeDescription, incomeAmount, incomeCategory);
+ assert !incomeCategory.equals(IncomeCategory.NULL);
+ return new AddIncomeCommand(income);
+ } catch (InputException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ } else {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+ }
+
+ private Command prepareAddIncomeWithDate(String arguments) {
+ final Matcher matcher = ADD_INCOME_ARGUMENT_FORMAT_WITH_DATE.matcher(" " + arguments);
+ if (matcher.matches()) {
+ try {
+ double incomeAmount = extractAmount(matcher);
+ String incomeDescription = extractDescription(matcher);
+ IncomeCategory incomeCategory = extractIncomeCategory(matcher);
+ LocalDate incomeDate = extractDate(matcher);
+ Income income = new Income(incomeDescription, incomeAmount, incomeCategory, incomeDate);
+ assert !incomeCategory.equals(IncomeCategory.NULL);
+ return new AddIncomeCommand(income);
+ } catch (InputException e) {
+ return new InvalidCommand(e.getMessage());
+ } catch (DateTimeParseException de) {
+ return new InvalidCommand(Messages.INVALID_DATE_FORMAT);
+ }
+ } else {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private Command prepareDeleteExpense(String arguments) {
+ final Matcher matcher = DELETE_EXPENSE_ARGUMENT_FORMAT.matcher(arguments);
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+
+ String userGivenIndex = matcher.group("index").trim();
+ int deleteExpenseIndex;
+ try {
+ deleteExpenseIndex = extractIndex(userGivenIndex);
+ } catch (InvalidIndexException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ assert deleteExpenseIndex >= 1;
+
+ return new DeleteExpenseCommand(deleteExpenseIndex);
+ }
+
+ /**
+ * This was adapted from addressbook-level2 source code here:
+ * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/parser/Parser.java
+ */
+ private Command prepareDeleteIncome(String arguments) {
+ final Matcher matcher = DELETE_INCOME_ARGUMENT_FORMAT.matcher(arguments);
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+
+ String userGivenIndex = matcher.group("index").trim();
+ int deleteIncomeIndex;
+ try {
+ deleteIncomeIndex = extractIndex(userGivenIndex);
+ } catch (InvalidIndexException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ assert deleteIncomeIndex >= 1;
+
+ return new DeleteIncomeCommand(deleteIncomeIndex);
+ }
+
+ private Command prepareListExpense(String arguments) {
+ if (arguments.isBlank()) {
+ return new ListExpenseCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareListIncome(String arguments) {
+ if (arguments.isBlank()) {
+ return new ListIncomeCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareTotalExpense(String arguments) {
+ if (arguments.isBlank()) {
+ return new TotalExpenseCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareTotalIncome(String arguments) {
+ if (arguments.isBlank()) {
+ return new TotalIncomeCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareClearAllEntries(String arguments) {
+ if (arguments.isBlank()) {
+ return new ClearAllEntriesCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareShowGraph(String arguments) {
+ if (arguments.isBlank()) {
+ return new ShowGraphCommand();
+ }
+ final Matcher matcher = SHOW_GRAPH_BY_YEAR_FORMAT.matcher(arguments);
+ if (matcher.matches()) {
+ try {
+ DateTimeFormatter yearFormat = getYearFormat();
+ String userGivenYear = matcher.group("year").trim();
+ LocalDate year = LocalDate.parse(userGivenYear, yearFormat);
+ return new ShowGraphByYearCommand(year);
+ } catch (DateTimeParseException e) {
+ return new InvalidCommand(Messages.INVALID_YEAR_MESSAGE);
+ }
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareCheckCurrentCurrency(String arguments) {
+ if (arguments.isBlank()) {
+ return new CheckCurrentCurrencyCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareExit(String arguments) {
+ if (arguments.isBlank()) {
+ return new ExitCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareSetBudget(String arguments) {
+ final Matcher matcher = SET_BUDGET_ARGUMENT_FORMAT.matcher(" " + arguments);
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+
+ double budgetAmount;
+ try {
+ budgetAmount = extractBudgetAmount(matcher);
+ } catch (InvalidBudgetAmountException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+
+ String expenseCategory = matcher.group("category").trim().toUpperCase();
+ switch (expenseCategory) {
+ case "FOOD":
+ return new SetBudgetCommand(ExpenseCategory.FOOD, budgetAmount);
+ case "TRANSPORT":
+ return new SetBudgetCommand(ExpenseCategory.TRANSPORT, budgetAmount);
+ case "MEDICAL":
+ return new SetBudgetCommand(ExpenseCategory.MEDICAL, budgetAmount);
+ case "BILLS":
+ return new SetBudgetCommand(ExpenseCategory.BILLS, budgetAmount);
+ case "ENTERTAINMENT":
+ return new SetBudgetCommand(ExpenseCategory.ENTERTAINMENT, budgetAmount);
+ case "MISC":
+ return new SetBudgetCommand(ExpenseCategory.MISC, budgetAmount);
+ case "OVERALL":
+ return new SetBudgetCommand(ExpenseCategory.OVERALL, budgetAmount);
+ default:
+ return new InvalidCommand(Messages.INVALID_BUDGET_CATEGORY_MESSAGE);
+ }
+ }
+
+ private Command prepareCheckBudget(String arguments) {
+ final Matcher matcher = CHECK_BUDGET_ARGUMENT_FORMAT.matcher(arguments.trim());
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+
+ String expenseCategory = matcher.group("category").trim().toUpperCase();
+ if (expenseCategory.isBlank()) {
+ return new InvalidCommand(Messages.BLANK_CATEGORY_MESSAGE);
+ }
+
+ switch (expenseCategory) {
+ case "FOOD":
+ return new CheckBudgetCommand(ExpenseCategory.FOOD);
+ case "TRANSPORT":
+ return new CheckBudgetCommand(ExpenseCategory.TRANSPORT);
+ case "MEDICAL":
+ return new CheckBudgetCommand(ExpenseCategory.MEDICAL);
+ case "BILLS":
+ return new CheckBudgetCommand(ExpenseCategory.BILLS);
+ case "ENTERTAINMENT":
+ return new CheckBudgetCommand(ExpenseCategory.ENTERTAINMENT);
+ case "MISC":
+ return new CheckBudgetCommand(ExpenseCategory.MISC);
+ case "OVERALL":
+ return new CheckBudgetCommand(ExpenseCategory.OVERALL);
+ default:
+ return new InvalidCommand(Messages.INVALID_BUDGET_CATEGORY_MESSAGE);
+ }
+ }
+
+ private Command prepareSetThreshold(String arguments) {
+ final Matcher matcher = SET_THRESHOLD_ARGUMENT_FORMAT.matcher(arguments.trim());
+ if (!matcher.matches()) {
+ return new InvalidCommand(Messages.PARAMETERS_ERROR_MESSAGE);
+ }
+
+ double thresholdValue;
+ try {
+ thresholdValue = extractThresholdValue(matcher);
+ } catch (InvalidThresholdValueException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+
+ return new SetThresholdCommand(thresholdValue);
+ }
+
+ private Command prepareConvertCurrency(String arguments) {
+ final Matcher matcher = CURRENCY_CONVERSION_FORMAT.matcher(arguments);
+ if (matcher.matches()) {
+ try {
+ CurrencyType newCurrencyType = extractCurrencyType(matcher);
+ return new CurrencyConversionCommand(newCurrencyType);
+ } catch (InputException e) {
+ return new InvalidCommand(e.getMessage());
+ }
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+
+ private Command prepareListCurrencyTypes(String arguments) {
+ if (arguments.isBlank()) {
+ return new ListCurrencyTypesCommand();
+ }
+ return new InvalidCommand(Messages.INVALID_COMMAND_MESSAGE);
+ }
+}
diff --git a/src/main/java/seedu/utility/StonksGraph.java b/src/main/java/seedu/utility/StonksGraph.java
new file mode 100644
index 0000000000..ed63ab5e5c
--- /dev/null
+++ b/src/main/java/seedu/utility/StonksGraph.java
@@ -0,0 +1,298 @@
+package seedu.utility;
+
+
+import seedu.utility.tools.DateOperator;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class StonksGraph {
+ private static final int ROWS = 20;
+ private static final int COLS = 100;
+ private static final int ROWS_OFFSET = ROWS - 1;
+ private static final int COLS_OFFSET = COLS - 1;
+ private static final char INCOME_BAR = 'o';
+ private static final char EXPENSE_BAR = '#';
+ private static final char X_AXIS_CHAR = '~';
+ private static final char SEPARATOR_CHAR = '-';
+ private static final char BORDER_CHAR = 'x';
+ private static final char NON_BORDER_CHAR = ' ';
+ private static final double TOTAL_LIMIT = 100000000000.00;
+ private final char[][] grid = new char [ROWS][COLS];
+
+ /**
+ * Constructor for the StonksGraph which firsts set the border, then the balance and finally the report itself.
+ */
+ public StonksGraph(FinancialTracker finances, int year) {
+ drawBorder();
+ drawBalance(finances.calculateBalance());
+ drawReport(finances, year);
+ }
+
+ @Override
+ public String toString() {
+ return convertGridToString();
+ }
+
+ /**
+ * Returns true it's within the rows where the barchart is supposed to be.
+ */
+ private boolean isWithinRowsConsistingOfBarGraph(int x) {
+ return x >= 7 && x < 17;
+ }
+
+ private boolean isBorder(int x, int y) {
+ return x == 0 || y == 0 || x == ROWS_OFFSET || y == COLS_OFFSET;
+ }
+
+ /**
+ * Returns true if column is that of each month's expense bar.
+ */
+ private boolean isExpenseBar(int y) {
+ return y == 4 || y == 12 || y == 20 || y == 28 || y == 36 || y == 44 || y == 52 || y == 60
+ || y == 68 || y == 76 || y == 84 || y == 92;
+ }
+
+ /**
+ * Returns true if column is that of each month's income bar.
+ */
+ private boolean isIncomeBar(int y) {
+ return y == 5 || y == 13 || y == 21 || y == 29 || y == 37 || y == 45 || y == 53 || y == 61
+ || y == 69 || y == 77 || y == 85 || y == 93;
+ }
+
+
+ private void drawBorder() {
+ for (int x = 0; x < ROWS; x++) {
+ for (int y = 0; y < COLS; y++) {
+ if (isBorder(x, y)) {
+ grid[x][y] = BORDER_CHAR;
+ } else {
+ grid[x][y] = NON_BORDER_CHAR;
+ }
+ }
+ }
+ }
+
+
+ private void drawBalance(double balance) {
+ String stringAmount = String.format("$%.2f", balance);
+
+ assert (stringAmount.length() <= 30) : "Size should be <= 30";
+ writeToGraph(2,4,"Account Balance: ");
+ writeToGraph(2,21, stringAmount);
+ }
+
+
+
+ private void writeToGraph(int rowCount, int colCount, String toAdd) {
+ int stringLength = toAdd.length();
+
+ int i = 0;
+ while (i < stringLength) {
+ grid[rowCount][colCount] = toAdd.charAt(i);
+ colCount++;
+ i++;
+ }
+
+ }
+
+ /**
+ * Converts the 2d array grid into a String.
+ *
+ * @return A string which represents the 2D grid in 1D form.
+ */
+ private String convertGridToString() {
+ StringBuilder convertedString = new StringBuilder();
+ for (int x = 0; x < ROWS; x++) {
+ for (int y = 0; y < COLS; y++) {
+ convertedString.append(grid[x][y]);
+ }
+ convertedString.append(System.lineSeparator());
+ }
+ return convertedString.toString();
+ }
+
+
+ /**
+ * Returns month as an int based on which column it is at.
+ *
+ * @param colCount The column count of the grid.
+ * @return Returns an integer that represents the month.
+ */
+ private int getMonth(int colCount) {
+ if (colCount >= 4 && colCount <= 6) {
+ return 1;
+ } else if (colCount >= 12 && colCount <= 14) {
+ return 2;
+ } else if (colCount >= 20 && colCount <= 22) {
+ return 3;
+ } else if (colCount >= 28 && colCount <= 30) {
+ return 4;
+ } else if (colCount >= 36 && colCount <= 38) {
+ return 5;
+ } else if (colCount >= 44 && colCount <= 46) {
+ return 6;
+ } else if (colCount >= 52 && colCount <= 54) {
+ return 7;
+ } else if (colCount >= 60 && colCount <= 62) {
+ return 8;
+ } else if (colCount >= 68 && colCount <= 70) {
+ return 9;
+ } else if (colCount >= 76 && colCount <= 78) {
+ return 10;
+ } else if (colCount >= 84 && colCount <= 86) {
+ return 11;
+ } else {
+ return 12;
+ }
+ }
+
+ /**
+ * Draw the total expenses and incomes for the current month in the yearly report.
+ *
+ * @param currIncomeBreakdowns ArrayList containing all the totalIncomes for each month of the current year.
+ * @param currExpenseBreakdowns ArrayList containing all the totalExpenses for each month of the current year.
+ */
+ private void drawCurrentMonth(ArrayList currIncomeBreakdowns, ArrayList currExpenseBreakdowns) {
+ int currentYear = LocalDate.now().getYear();
+ Month currentMonth = DateOperator.currentMonth();
+ int currentMonthInIndex = DateOperator.currentMonthInIndex();
+ String currentExpenseString = "Current month (" + currentMonth + " " + currentYear + ") total expense: ";
+ String currentIncomeString = "Current month (" + currentMonth + " " + currentYear + ") total income: ";
+
+ double currentMonthExpense = currExpenseBreakdowns.get(currentMonthInIndex);
+ writeToGraph(3,4, currentExpenseString);
+ String stringCurrentMonthExpense = String.format("$%.2f", currentMonthExpense);
+ writeToGraph(3, 49, stringCurrentMonthExpense);
+
+ double currentMonthIncome = currIncomeBreakdowns.get(currentMonthInIndex);
+ writeToGraph(4,4, currentIncomeString);
+ String stringCurrentMonthIncome = String.format("$%.2f", currentMonthIncome);
+ writeToGraph(4, 48, stringCurrentMonthIncome);
+
+ }
+
+ private void drawLegendAndTitle(int year) {
+ writeToGraph(5,4, String.format("Year %d Report", year));
+ writeToGraph(2, 75, "Legend:");
+ writeToGraph(3, 80, " # is Expense");
+ writeToGraph(4, 80, " o is Income ");
+ }
+
+ private void drawXAxisLabels() {
+ writeToGraph(18,4,"Jan");
+ writeToGraph(18,12,"Feb");
+ writeToGraph(18,20,"Mar");
+ writeToGraph(18,28,"Apr");
+ writeToGraph(18,36,"May");
+ writeToGraph(18,44,"Jun");
+ writeToGraph(18,52,"Jul");
+ writeToGraph(18,60,"Aug");
+ writeToGraph(18,68,"Sept");
+ writeToGraph(18,76,"Oct");
+ writeToGraph(18,84,"Nov");
+ writeToGraph(18,92,"Dec");
+ }
+
+ private void drawXAxis() {
+ for (int i = 2;i < 98; i++) {
+ grid[17][i] = X_AXIS_CHAR;
+ }
+ }
+
+ private void drawSeparator() {
+ for (int i = 2;i < 98; i++) {
+ grid[6][i] = SEPARATOR_CHAR;
+ }
+ }
+
+ private void drawIncomeBar(int x, int y, int incomeBar) {
+ if (x >= 17 - incomeBar && x < 17) {
+ grid[x][y] = INCOME_BAR;
+ }
+ }
+
+ private void drawExpenseBar(int x, int y, int expenseBar) {
+ if (x >= 17 - expenseBar && x < 17) {
+ grid[x][y] = EXPENSE_BAR;
+ }
+ }
+
+
+ private void drawBar(int x, int y, int noOfIncomeBar, int noOfExpenseBar) {
+ if (isWithinRowsConsistingOfBarGraph(x)) {
+ if (isExpenseBar(y)) {
+ drawExpenseBar(x, y, noOfExpenseBar);
+ } else if (isIncomeBar(y)) {
+ drawIncomeBar(x, y, noOfIncomeBar);
+ }
+ }
+ }
+
+
+
+ /**
+ * Draws the yearly report shown in show_graph command depending on year user enter as input.
+ * Yearly report includes the legend, current month totals , monthly breakdown, axes and the title.
+ *
+ * @param finances The financial tracker with all the various entries.
+ * @param year The year given as input by user.
+ */
+ private void drawReport(FinancialTracker finances, int year) {
+ drawSeparator();
+ drawLegendAndTitle(year);
+ drawXAxisLabels();
+ drawXAxis();
+ ArrayList searchedIncomeBreakdowns = finances.getMonthlyIncomeBreakdown(year);
+ ArrayList searchedExpenseBreakdowns = finances.getMonthlyExpenseBreakdown(year);
+
+ ArrayList values = new ArrayList<>();
+ values.addAll(searchedExpenseBreakdowns);
+ values.addAll(searchedIncomeBreakdowns);
+ double biggestTotal = Collections.max(values);
+ assert (biggestTotal <= TOTAL_LIMIT) : "Total income/expense should be less than equal to 100Bil";
+ double barValue = determineBarValue(biggestTotal);
+
+ ArrayList currentIncomeBreakdowns = finances.getMonthlyIncomeBreakdown(LocalDate.now().getYear());
+ ArrayList currentExpenseBreakdowns = finances.getMonthlyExpenseBreakdown(LocalDate.now().getYear());
+ drawCurrentMonth(currentIncomeBreakdowns, currentExpenseBreakdowns);
+
+
+ for (int x = 0; x < ROWS; x++) {
+ for (int y = 0; y < COLS; y++) {
+ int monthIndex = getMonth(y) - 1;
+ int noOfIncomeBar = (int)(searchedIncomeBreakdowns.get(monthIndex) / barValue);
+ int noOfExpenseBar = (int)(searchedExpenseBreakdowns.get(monthIndex) / barValue);
+ drawBar(x, y, noOfIncomeBar, noOfExpenseBar);
+ }
+ }
+ }
+
+ private double determineBarValue(double totalValue) {
+ boolean isBetweenZeroPointOneAndOne = totalValue >= 0.1 && totalValue < 1;
+ boolean isSmallerThanZeroPointOne = totalValue < 0.1;
+
+ if (isBetweenZeroPointOneAndOne) {
+ writeToGraph(5, 75, "Unit: 0.1");
+ return 0.1;
+ } else if (isSmallerThanZeroPointOne) {
+ writeToGraph(5, 75, "Unit: 0.01");
+ return 0.01;
+ }
+
+ int noOfDigits = 0;
+ //Counts no of digits on the left of decimal point
+ while (totalValue >= 1) {
+ totalValue = totalValue / 10;
+ noOfDigits++;
+ }
+ double barValue = Math.pow(10, noOfDigits - 1);
+
+ writeToGraph(5, 75, "Unit: " + barValue);
+ return barValue;
+ }
+
+}
diff --git a/src/main/java/seedu/utility/Ui.java b/src/main/java/seedu/utility/Ui.java
new file mode 100644
index 0000000000..d560ff9fb2
--- /dev/null
+++ b/src/main/java/seedu/utility/Ui.java
@@ -0,0 +1,451 @@
+package seedu.utility;
+
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.Entry;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.reminder.BudgetReminder;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+import static seedu.utility.tools.DateOperator.DATE_FORMAT;
+import static seedu.utility.tools.FinancialAdvisor.getRandomAdvice;
+
+/**
+ * Represents a user interface where feedbacks are given in response to user input.
+ */
+public class Ui {
+ private final Scanner in;
+ private static final String NEW_LINE = System.lineSeparator();
+
+
+ /**
+ * Constructor for Ui of the program with a Scanner object that is able to read user inputs.
+ */
+ public Ui() {
+ this.in = new Scanner(System.in);
+ }
+
+ /**
+ * Reads a new command from the user through the standard input and trim trailing spaces at the back.
+ */
+ public String readCommand() {
+ return in.nextLine().trim();
+ }
+
+ /**
+ * Prints the welcoming message for users that have entered the program.
+ */
+ public void printWelcome() {
+ printLine();
+ System.out.println(Messages.LOGO_MESSAGE);
+ printLine();
+ System.out.println(Messages.PROMPTING_MESSAGE);
+ printLine();
+ }
+
+ private void printLine() {
+ System.out.println(Messages.SEPARATOR_MESSAGE);
+ }
+
+ /**
+ * Prints the given list of expenses to the standard output, else if its empty print the empty expense list message.
+ *
+ * @param expenses An ArrayList of expense elements.
+ */
+ public void listExpense(ArrayList expenses) {
+ printLine();
+ if (expenses.isEmpty()) {
+ printEmptyExpenseListMessage();
+ } else {
+ printExpenseList(expenses);
+ }
+ printLine();
+ }
+
+ /**
+ * Prints the given list of incomes else if its empty print the empty income list message.
+ *
+ * @param incomes An ArrayList of income elements.
+ */
+ public void listIncome(ArrayList incomes) {
+ printLine();
+ if (incomes.isEmpty()) {
+ printEmptyIncomeListMessage();
+ } else {
+ printIncomeList(incomes);
+ }
+ printLine();
+ }
+
+
+ /**
+ * Prints the given list of entries else if its empty print the no match found message.
+ *
+ * @param filteredEntries The entries that got filtered out from searching through the financial tracker.
+ */
+ public void listFind(ArrayList filteredEntries) {
+ printLine();
+ if (filteredEntries.isEmpty()) {
+ printNoMatchFoundMessage();
+ } else {
+ printFilteredList(filteredEntries);
+ }
+ printLine();
+ }
+
+ private void printNoMatchFoundMessage() {
+ System.out.println(Messages.SEARCH_NO_MATCH_MESSAGE);
+ }
+
+ private void printEmptyIncomeListMessage() {
+ System.out.println(Messages.EMPTY_INCOME_MESSAGE);
+ }
+
+ private void printEmptyExpenseListMessage() {
+ System.out.println(Messages.EMPTY_EXPENSE_MESSAGE);
+ }
+
+ private void printFilteredList(ArrayList filteredEntries) {
+ assert filteredEntries.size() > 0;
+ System.out.println(Messages.FOUND_LIST_MESSAGE);
+ printLine();
+
+ int i = 1;
+ for (Entry entry : filteredEntries) {
+ System.out.print(i + ": " + entry + NEW_LINE);
+ i++;
+ }
+ }
+
+ private void printIncomeList(ArrayList incomes) {
+ assert incomes.size() > 0;
+ System.out.println(Messages.LISTING_INCOME_MESSAGE);
+ printLine();
+
+ int i = 1;
+ for (Income income : incomes) {
+ System.out.print(i + ": " + income + NEW_LINE);
+ i++;
+ }
+ }
+
+
+
+ private void printExpenseList(ArrayList expenses) {
+ assert expenses.size() > 0;
+ System.out.println(Messages.LISTING_EXPENSE_MESSAGE);
+ printLine();
+
+ int i = 1;
+ for (Expense expense : expenses) {
+ System.out.print(i + ": " + expense + NEW_LINE);
+ i++;
+ }
+ }
+
+ /**
+ * Prints the total expense of all expenses in the financial tracker to the standard output.
+ *
+ * @param totalExpense The total value of the expenses in the financial tracker.
+ */
+ public void printTotalExpense(double totalExpense) {
+ assert totalExpense >= 0;
+ printLine();
+ System.out.printf("Your total expense is: $%.2f" + NEW_LINE, totalExpense);
+ printLine();
+ }
+
+ /**
+ * Prints the total income of the financial tracker to the standard output.
+ *
+ * @param totalIncome The total value of the incomes in the financial tracker.
+ */
+ public void printTotalIncome(double totalIncome) {
+ assert totalIncome >= 0;
+ printLine();
+ System.out.printf("Your total income is: $%.2f" + NEW_LINE, totalIncome);
+ printLine();
+ }
+
+ /**
+ * Prints the balance of the financial tracker to the standard output.
+ *
+ * @param balance The balance which is the net value of totalIncome and totalExpense in the financial tracker.
+ */
+ public void printBalance(double balance) {
+ printLine();
+ System.out.printf("Your current balance is: $%.2f" + NEW_LINE, balance);
+ printLine();
+ }
+
+ /**
+ * Prints the feedback message for adding an expense to the financial tracker.
+ *
+ * @param expense The expense to be added to the financial tracker.
+ */
+ public void printExpenseAdded(Expense expense) {
+ printLine();
+ System.out.println("Your most recent spending: " + NEW_LINE + expense);
+ printLine();
+ }
+
+ /**
+ * Prints the feedback message for deleting an expense from the financial tracker.
+ *
+ * @param expense The expense to be deleted from the financial tracker.
+ */
+ public void printExpenseDeleted(Expense expense) {
+ printLine();
+ System.out.println("You removed this: " + NEW_LINE + expense);
+ printLine();
+ }
+
+ /**
+ * Prints the feedback message for adding an income to the financial tracker.
+ *
+ * @param income The income to be added to the financial tracker.
+ */
+ public void printIncomeAdded(Income income) {
+ printLine();
+ System.out.println("Your most recent earning: " + NEW_LINE + income);
+ printLine();
+ }
+
+ /**
+ * Prints the feedback message for deleting an income from the financial tracker.
+ *
+ * @param income The income to be deleted from the financial tracker.
+ */
+ public void printIncomeDeleted(Income income) {
+ printLine();
+ System.out.println("You removed this: " + NEW_LINE + income);
+ printLine();
+ }
+
+ /**
+ * Prints the total expense between two dates.
+ * If totalExpense is 0 print the no expense between two dates message.
+ *
+ * @param totalExpense The total value of the expenses in the financial tracker.
+ * @param start The starting date (Left boundary).
+ * @param end The ending date (Right boundary).
+ */
+ public void printTotalExpenseBetween(double totalExpense, LocalDate start, LocalDate end) {
+ printLine();
+ if (totalExpense == 0) {
+ printNoExpenseBetweenMessage(start, end);
+ } else {
+ printExpenseBetweenMessage(totalExpense, start, end);
+ }
+ printLine();
+ }
+
+ /**
+ * Prints the total income between two dates.
+ * If totalIncome is 0 print the no income between two dates message.
+ *
+ * @param totalIncome The total value of the incomes in the financial tracker.
+ * @param start The starting date (Left boundary).
+ * @param end The ending date (Right boundary).
+ */
+ public void printTotalIncomeBetween(double totalIncome, LocalDate start, LocalDate end) {
+ printLine();
+ if (totalIncome == 0) {
+ printNoIncomeBetweenMessage(start, end);
+ } else {
+ printIncomeBetweenMessage(totalIncome, start, end);
+ }
+ printLine();
+ }
+
+ private void printExpenseBetweenMessage(double totalExpense, LocalDate start, LocalDate end) {
+ String startString = start.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ String endString = end.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ System.out.printf("Your total expense between %s and %s is $%.2f", startString, endString, totalExpense);
+ System.out.print(NEW_LINE);
+ }
+
+ private void printNoExpenseBetweenMessage(LocalDate start, LocalDate end) {
+ String startString = start.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ String endString = end.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ System.out.printf("You do not have any expense between %s and %s", startString, endString);
+ System.out.print(NEW_LINE);
+ }
+
+ private void printIncomeBetweenMessage(double totalExpense, LocalDate start, LocalDate end) {
+ String startString = start.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ String endString = end.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ System.out.printf("Your total income between %s and %s is $%.2f", startString, endString, totalExpense);
+ System.out.print(NEW_LINE);
+ }
+
+ private void printNoIncomeBetweenMessage(LocalDate start, LocalDate end) {
+ String startString = start.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ String endString = end.format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ System.out.printf("You do not have any income between %s and %s", startString, endString);
+ System.out.print(NEW_LINE);
+
+ }
+
+ /**
+ * Prints the list of commands and their respective format to the standard output.
+ */
+ public void printHelp() {
+ printLine();
+ System.out.println(Messages.HELP_COMMAND_MESSAGE);
+ printLine();
+ for (String command:CommandFormat.commands) {
+ System.out.println(command);
+ }
+ printLine();
+ }
+
+ /**
+ * Prints the termination message of the STONKS XD program.
+ *
+ */
+ public void printBye() {
+ printLine();
+ System.out.println(Messages.BYE_MESSAGE);
+ System.out.println(NEW_LINE);
+ System.out.println(Messages.TIP_HEADER + getRandomAdvice());
+ printLine();
+ }
+
+ /**
+ * Prints the error message as feedback through the standard output.
+ *
+ * @param errorMessage The error message to be printed out due to certain exceptions or invalid inputs.
+ */
+ public void printError(String errorMessage) {
+ printLine();
+ System.out.println(errorMessage);
+ printLine();
+ }
+
+ /**
+ * Prints the error message as feedback through the standard output, specifically for the loading of data process.
+ *
+ * @param errorMessage The error message to be printed out due to certain exceptions or invalid inputs.
+ */
+ public void printLoadingError(String errorMessage) {
+ printLine();
+ System.out.println(errorMessage);
+ }
+
+ /**
+ * Prints a message to user through standard output confirming all entries have been cleared.
+ */
+ public void printAllEntriesCleared() {
+ printLine();
+ System.out.println(Messages.ALL_DATA_CLEARED);
+ printLine();
+ }
+
+ /**
+ * Prints the graph representing the yearly report.
+ *
+ * @param stonksGraph The graph representing the yearly report of the financial tracker.
+ */
+ public void printGraph(StonksGraph stonksGraph) {
+ printLine();
+ System.out.print(stonksGraph);
+ printLine();
+ }
+
+ /**
+ * Prints the budget reminder after an expense is added.
+ * @param reminder BudgetReminder object containing appropriate reminder.
+ */
+ public void printBudgetReminder(BudgetReminder reminder) {
+ System.out.println(reminder);
+ printLine();
+ }
+
+ /**
+ * Prints the budget setting advice after an attempt is made to set a new budget.
+ * @param reminder BudgetReminder object containing appropriate advice
+ */
+ public void printSetBudgetReminder(BudgetReminder reminder) {
+ printLine();
+ System.out.println(reminder);
+ printLine();
+ }
+
+ /**
+ * Prints the budget for the given category.
+ *
+ * @param category The category of expense.
+ * @param budgetLimit The budget limit for the given category.
+ */
+ public void printBudget(ExpenseCategory category, double budgetLimit) {
+ printLine();
+ System.out.printf("Current %s limit is $%.2f", category.toString(), budgetLimit);
+ System.out.print(NEW_LINE);
+ printLine();
+ }
+
+ /**
+ * Prints the threshold set feedback for setting budgets.
+ *
+ * @param threshold The threshold for the budget.
+ */
+ public void printThresholdConfirmation(double threshold) {
+ printLine();
+ System.out.println("Threshold for budget reminders set to " + threshold);
+ System.out.printf("We'll warn you when you spend %.1f%% of your budget!", threshold * 100);
+ System.out.print(NEW_LINE);
+ printLine();
+ }
+
+ /**
+ * Prints confirmation message upon successful conversion of entries.
+ * @param currency tracks currency type of entries
+ */
+ public void printCurrencyChangedConfirmation(CurrencyType currency) {
+ printLine();
+ System.out.println(Messages.CURRENCY_CONVERSION_SUCCESSFUL_MESSAGE + currency + "!");
+ printLine();
+ }
+
+
+ /**
+ * Prints error message if trying to convert again to the same currency type.
+ * @param currency tracks currency type of entries
+ */
+ public void printSameCurrencyTypeMessage(CurrencyType currency) {
+ printLine();
+ System.out.println(Messages.SAME_CURRENCY_TYPE_MESSAGE + "- " + currency);
+ printLine();
+ }
+
+ /**
+ * Prints the current currency type of all entries.
+ * @param currency tracks currency type of entries
+ */
+ public void printCurrentCurrency(CurrencyType currency) {
+ printLine();
+ System.out.println(Messages.CURRENT_CURRENCY_MESSAGE + currency.toString());
+ printLine();
+ }
+
+ /**
+ * Prints out list of available currency conversions.
+ * @param currencyTypes enum of all allowed currency types
+ */
+ public void printCurrencyTypes(ArrayList currencyTypes) {
+ printLine();
+ System.out.println(Messages.AVAILABLE_CURRENCIES_MESSAGE);
+ int i = 0;
+ for (CurrencyType currencyType : currencyTypes) {
+ System.out.println(i + 1 + ". " + currencyType);
+ i++;
+ }
+ printLine();
+ }
+}
diff --git a/src/main/java/seedu/utility/storage/DataConverter.java b/src/main/java/seedu/utility/storage/DataConverter.java
new file mode 100644
index 0000000000..76be2fed2f
--- /dev/null
+++ b/src/main/java/seedu/utility/storage/DataConverter.java
@@ -0,0 +1,227 @@
+package seedu.utility.storage;
+
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.BlankCurrencyTypeException;
+import seedu.exceptions.InputException;
+import seedu.exceptions.InvalidBudgetAmountException;
+import seedu.exceptions.InvalidCurrencyTypeException;
+import seedu.exceptions.InvalidExpenseDataFormatException;
+import seedu.exceptions.InvalidIncomeDataFormatException;
+import seedu.exceptions.InvalidSettingsDataFormatException;
+import seedu.exceptions.InvalidThresholdValueException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static seedu.utility.tools.DateOperator.DATE_FORMAT;
+import static seedu.utility.tools.DateOperator.extractDate;
+import static seedu.utility.tools.Extractor.extractAmountForLoading;
+import static seedu.utility.tools.Extractor.extractBudgetAmountForLoading;
+import static seedu.utility.tools.Extractor.extractCurrencyType;
+import static seedu.utility.tools.Extractor.extractDescription;
+import static seedu.utility.tools.Extractor.extractExpenseCategory;
+import static seedu.utility.tools.Extractor.extractIncomeCategory;
+import static seedu.utility.tools.Extractor.extractThresholdValue;
+
+/**
+ * Converts entries (expense entries and income entries) to a csv String, vice versa.
+ * Also converts settings to a csv String, vice verse.
+ * This allows important user information to be stored in a csv file for easy access and reading. All the
+ * information will not be lost when the program closes.
+ */
+public abstract class DataConverter {
+
+ private static final String DATA_SEPARATOR = ",";
+ private static final Pattern EXPENSE_DATA_FORMAT
+ = Pattern.compile("E" + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR
+ + "(?.+)" + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR + "(?.+)");
+ private static final Pattern INCOME_DATA_FORMAT
+ = Pattern.compile("I" + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR
+ + "(?.+)" + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR + "(?.+)");
+ private static final Pattern SETTINGS_DATA_FORMAT = Pattern.compile("(?.+)" + DATA_SEPARATOR
+ + "(?.+)" + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR + "(?.+)"
+ + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR + "(?.+)"
+ + DATA_SEPARATOR + "(?.+)" + DATA_SEPARATOR + "(?.+)"
+ + DATA_SEPARATOR + "(?.+)");
+
+ /**
+ * Converts Expense entries to a csv String. This is so that the String can be stored in a csv file.
+ *
+ * @param expense The Expense entry to be converted.
+ * @return A csv String.
+ */
+ public static String convertExpenseToData(Expense expense) {
+ String expenseInTwoDecimalPlace = String.format("%.2f",expense.getValue());
+ return "E" + DATA_SEPARATOR + expense.getDescription() + DATA_SEPARATOR + expenseInTwoDecimalPlace
+ + DATA_SEPARATOR + expense.getCategory() + DATA_SEPARATOR
+ + expense.getDate().format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ }
+
+ /**
+ * Converts Income entries to a csv String. This is so that the String can be stored in a csv file.
+ *
+ * @param income The Income entry to be converted.
+ * @return A csv String.
+ */
+ public static String convertIncomeToData(Income income) {
+ String incomeInTwoDecimalPlace = String.format("%.2f",income.getValue());
+ return "I" + DATA_SEPARATOR + income.getDescription() + DATA_SEPARATOR + incomeInTwoDecimalPlace
+ + DATA_SEPARATOR + income.getCategory() + DATA_SEPARATOR
+ + income.getDate().format(DateTimeFormatter.ofPattern(DATE_FORMAT));
+ }
+
+ /**
+ * Converts a csv String to an Expense entry. This is done during the loading of data phrase when the program
+ * starts.
+ *
+ * @param data The csv String.
+ * @return An Expense entry.
+ * @throws InputException If any value in the csv String cannot be parsed to their expected
+ * variable format. For example, the amount value cannot be
+ * parsed to a double.
+ * @throws InvalidExpenseDataFormatException If the format of csv String received is not in the expected format.
+ * @throws DateTimeParseException If the date value in the csv String cannot be parsed as a LocalDate.
+ */
+ public static Expense convertDataToExpense(String data) throws InputException, InvalidExpenseDataFormatException,
+ DateTimeParseException {
+ final Matcher matcher = EXPENSE_DATA_FORMAT.matcher(data.trim());
+ if (matcher.matches()) {
+ String expenseDescription = extractDescription(matcher);
+ double expenseAmount = extractAmountForLoading(matcher);
+ ExpenseCategory expenseCategory = extractExpenseCategory(matcher);
+ LocalDate expenseDate = extractDate(matcher);
+ assert expenseAmount > 0;
+ assert !expenseDescription.isBlank();
+ return new Expense(expenseDescription, expenseAmount, expenseCategory, expenseDate);
+ } else {
+ throw new InvalidExpenseDataFormatException();
+ }
+ }
+
+ /**
+ * Converts a csv String to an Income entry. This is done during the loading of data phrase when the program
+ * starts.
+ *
+ * @param data The csv String.
+ * @return An Income entry.
+ * @throws InputException If any value in the csv String cannot be parsed to their expected
+ * variable format. For example, the amount value cannot be parsed to
+ * a double.
+ * @throws InvalidIncomeDataFormatException If the format of csv String received is not in the expected format.
+ * @throws DateTimeParseException If the date value in the csv String cannot be parsed as a LocalDate.
+ */
+ public static Income convertDataToIncome(String data) throws InputException, InvalidIncomeDataFormatException,
+ DateTimeParseException {
+ final Matcher matcher = INCOME_DATA_FORMAT.matcher(data.trim());
+ if (matcher.matches()) {
+ String incomeDescription = extractDescription(matcher);
+ double incomeAmount = extractAmountForLoading(matcher);
+ IncomeCategory incomeCategory = extractIncomeCategory(matcher);
+ LocalDate incomeDate = extractDate(matcher);
+ assert incomeAmount > 0;
+ assert !incomeDescription.isBlank();
+ return new Income(incomeDescription, incomeAmount, incomeCategory, incomeDate);
+ } else {
+ throw new InvalidIncomeDataFormatException();
+ }
+ }
+
+ /**
+ * Converts user settings to a csv String.
+ *
+ * @param budgetManager Provides the threshold value and the budget for the different expense category.
+ * @param currencyManager Provides the currency setting also known as CurrencyType.
+ * @return A csv String.
+ */
+ public static String convertSettingsToData(BudgetManager budgetManager, CurrencyManager currencyManager) {
+ CurrencyType currency = currencyManager.getCurrency();
+ StringBuilder data = new StringBuilder(currency.toString() + ",");
+ data.append(budgetManager.getThreshold()).append(",");
+ for (ExpenseCategory category : ExpenseCategory.values()) {
+ // NULL is the category after MISC. We do not expect NULL to have a value thus we break here.
+ if (category == ExpenseCategory.MISC) {
+ data.append(budgetManager.getBudget(category));
+ break;
+ }
+ data.append(budgetManager.getBudget(category));
+ data.append(DATA_SEPARATOR);
+ }
+ return data.toString();
+ }
+
+ /**
+ * Converts a csv String to CurrencyType.
+ *
+ * @param data The csv String.
+ * @return A CurrencyType.
+ * @throws InvalidCurrencyTypeException If the CurrencyType cannot be recognised / not supported.
+ * @throws BlankCurrencyTypeException If the CurrencyType is blank.
+ * @throws InvalidSettingsDataFormatException If the format of the csv String is not the same as the expected
+ * format.
+ */
+ public static CurrencyType convertDataToCurrencySetting(String data) throws InvalidCurrencyTypeException,
+ BlankCurrencyTypeException, InvalidSettingsDataFormatException {
+ final Matcher matcher = SETTINGS_DATA_FORMAT.matcher(data.trim());
+ if (matcher.matches()) {
+ return extractCurrencyType(matcher);
+ }
+ throw new InvalidSettingsDataFormatException();
+
+ }
+
+ /**
+ * Converts a csv String to the threshold value.
+ *
+ * @param data The csv String.
+ * @return The threshold value.
+ * @throws InvalidThresholdValueException If the threshold value is not acceptable. For example, it is out
+ * of range.
+ * @throws InvalidSettingsDataFormatException If the format of the csv String is not the same as the
+ * expected format.
+ */
+ public static double convertDataToThresholdSetting(String data) throws InvalidThresholdValueException,
+ InvalidSettingsDataFormatException {
+ final Matcher matcher = SETTINGS_DATA_FORMAT.matcher(data.trim());
+ if (matcher.matches()) {
+ return extractThresholdValue(matcher);
+ }
+ throw new InvalidSettingsDataFormatException();
+ }
+
+ /**
+ * Converts a csv String to the budget settings.
+ *
+ * @param data The csv String.
+ * @return An ArrayList of double. Each representing a budget value for a particular ExpenseCategory.
+ * @throws NumberFormatException A threshold value in the csv String cannot be parsed as a double.
+ * @throws NullPointerException No threshold value give in the csv String.
+ * @throws InvalidSettingsDataFormatException If the format of the csv String is not the same as the
+ * expected format.
+ */
+ public static ArrayList convertDataToBudgetSettings(String data) throws NumberFormatException,
+ NullPointerException, InvalidSettingsDataFormatException, InvalidBudgetAmountException {
+ ArrayList budgetSettings = new ArrayList<>();
+ final Matcher matcher = SETTINGS_DATA_FORMAT.matcher(data.trim());
+ if (matcher.matches()) {
+ for (ExpenseCategory category : ExpenseCategory.values()) {
+ // Not expected to have a budget related to NULL
+ if (category == ExpenseCategory.NULL) {
+ break;
+ }
+ budgetSettings.add(extractBudgetAmountForLoading(matcher.group(category.toString().toLowerCase())));
+ }
+ return budgetSettings;
+ }
+ throw new InvalidSettingsDataFormatException();
+ }
+}
diff --git a/src/main/java/seedu/utility/storage/DataManager.java b/src/main/java/seedu/utility/storage/DataManager.java
new file mode 100644
index 0000000000..51192c3776
--- /dev/null
+++ b/src/main/java/seedu/utility/storage/DataManager.java
@@ -0,0 +1,301 @@
+package seedu.utility.storage;
+
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.exceptions.BlankCurrencyTypeException;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.exceptions.InputException;
+import seedu.exceptions.InvalidBudgetAmountException;
+import seedu.exceptions.InvalidCurrencyTypeException;
+import seedu.exceptions.InvalidExpenseDataFormatException;
+import seedu.exceptions.InvalidIncomeDataFormatException;
+import seedu.exceptions.InvalidSettingsDataFormatException;
+import seedu.exceptions.InvalidThresholdValueException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Messages;
+import seedu.utility.Ui;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+import static seedu.utility.storage.DataConverter.convertDataToBudgetSettings;
+import static seedu.utility.storage.DataConverter.convertDataToCurrencySetting;
+import static seedu.utility.storage.DataConverter.convertDataToExpense;
+import static seedu.utility.storage.DataConverter.convertDataToIncome;
+import static seedu.utility.storage.DataConverter.convertDataToThresholdSetting;
+import static seedu.utility.storage.DataConverter.convertExpenseToData;
+import static seedu.utility.storage.DataConverter.convertIncomeToData;
+import static seedu.utility.storage.DataConverter.convertSettingsToData;
+
+/**
+ * Saves entries (expenses and incomes) Stonks XD is tracking into a csv file.
+ * Also saves Stonks XD settings into another csv file. Settings include: currency state, amount for each budget
+ * category and the reminder threshold value.
+ */
+public class DataManager {
+
+ private static final String NEWLINE = System.lineSeparator();
+ private static final String ENTRIES_FILE_NAME = "./StonksXD_Entries.csv";
+ private static final String ENTRIES_CSV_HEADER = "entry_type,entry_description,entry_amount,"
+ + "entry_category,entry_date";
+ private static final String SETTINGS_FILE_NAME = "./StonksXD_Settings.csv";
+ private static final String SETTINGS_CSV_HEADER = "currency,threshold,overall,food,transport,medical,"
+ + "bills,entertainment,misc";
+ private final Ui ui;
+ private final FinancialTracker financialTracker;
+ private final CurrencyManager currencyManager;
+ private final BudgetManager budgetManager;
+
+ /**
+ * Constructs an instance of DataManager.
+ *
+ * @param financialTracker The financialTracker will provide all the entries Stonks XD is tracking currently.
+ * @param ui The ui will be used to print out any warnings or messages to the user.
+ * @param budgetManager The budgetManager will provide all the budget settings to be saved / loaded.
+ * @param currencyManager The currencyManager will provide the currency setting.
+ */
+ public DataManager(FinancialTracker financialTracker, Ui ui, BudgetManager budgetManager,
+ CurrencyManager currencyManager) {
+ this.financialTracker = financialTracker;
+ this.ui = ui;
+ this.budgetManager = budgetManager;
+ this.currencyManager = currencyManager;
+ }
+
+ /**
+ * Saves all entries and settings.
+ * This method will be used more frequently as we typically want to save both entries and settings together.
+ */
+ public void saveAll() {
+ saveEntries();
+ saveSettings();
+ }
+
+ /**
+ * Loads all entries and settings.
+ * This method will be used more frequently as we typically want to load both entries and settings together.
+ */
+ public void loadAll() {
+ loadSettings();
+ loadEntries();
+ }
+
+ /**
+ * Sets the settings file, StonksXD_Settings.csv, to read-only.
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void setSettingsToReadOnly() {
+ File settingsFile = new File(SETTINGS_FILE_NAME);
+ settingsFile.setReadOnly();
+ }
+
+ /**
+ * Sets the settings file, StonksXD_Settings.csv, to writable.
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void setSettingsToWritable() {
+ File settingsFile = new File(SETTINGS_FILE_NAME);
+ settingsFile.setWritable(true);
+ }
+
+ /**
+ * Saves all entries Stonks XD is currently tracking into a csv file.
+ * This allows users to not lose all their entries when program closes.
+ */
+ private void saveEntries() {
+ try {
+ FileWriter writer = new FileWriter(ENTRIES_FILE_NAME);
+ BufferedWriter buffer = new BufferedWriter(writer);
+
+ buffer.write(ENTRIES_CSV_HEADER);
+ buffer.write(NEWLINE);
+ writeExpenses(buffer);
+ writeIncomes(buffer);
+ buffer.close();
+ } catch (IOException e) {
+ ui.printError(Messages.ERROR_SAVING_ENTRIES_MESSAGE);
+ }
+ }
+
+ private void writeIncomes(BufferedWriter buffer) throws IOException {
+ String data;
+ ArrayList incomes = financialTracker.getIncomes();
+ for (Income income : incomes) {
+ data = convertIncomeToData(income);
+ buffer.write(data);
+ buffer.write(NEWLINE);
+ }
+ }
+
+ private void writeExpenses(BufferedWriter buffer) throws IOException {
+ String data;
+ ArrayList expenses = financialTracker.getExpenses();
+ for (Expense expense : expenses) {
+ data = convertExpenseToData(expense);
+ buffer.write(data);
+ buffer.write(NEWLINE);
+ }
+ }
+
+ /**
+ * Loads all entries from StonksXD_Entries.csv into StonksXD.
+ * This allows users to not lose all their entries when the previous instance of Stonks XD closed.
+ */
+ private void loadEntries() {
+ FileInputStream fis;
+ try {
+ fis = new FileInputStream(ENTRIES_FILE_NAME);
+ } catch (FileNotFoundException e) {
+ ui.printLoadingError(Messages.UNABLE_TO_FIND_DATA_FILE);
+ return;
+ }
+ boolean hasCorruptedLines = false;
+ Scanner sc = new Scanner(fis);
+ checkForEntriesFileHeader(sc);
+ while (sc.hasNextLine()) {
+ String data = sc.nextLine();
+ try {
+ loadAsExpense(data);
+ } catch (InputException | InvalidExpenseDataFormatException | DateTimeParseException
+ | ExpenseOverflowException e) {
+ try {
+ loadAsIncome(data);
+ } catch (InputException | InvalidIncomeDataFormatException | DateTimeParseException
+ | IncomeOverflowException ie) {
+ hasCorruptedLines = true;
+ }
+ }
+ }
+ if (hasCorruptedLines) {
+ ui.printLoadingError(Messages.HAS_CORRUPTED_DATA_ENTRIES);
+ }
+ }
+
+ private void loadAsIncome(String data) throws InputException, InvalidIncomeDataFormatException,
+ IncomeOverflowException {
+ Income income = convertDataToIncome(data);
+ financialTracker.addIncome(income);
+ }
+
+ private void loadAsExpense(String data) throws InputException, InvalidExpenseDataFormatException,
+ ExpenseOverflowException {
+ Expense expense = convertDataToExpense(data);
+ financialTracker.addExpense(expense);
+ }
+
+ private void checkForEntriesFileHeader(Scanner sc) {
+ String data;
+ try {
+ data = sc.nextLine();
+ } catch (NoSuchElementException e) {
+ ui.printLoadingError(Messages.INCOMPLETE_ENTRIES_CSV_HEADER_MESSAGE);
+ return;
+ }
+
+ if (!data.equals(ENTRIES_CSV_HEADER)) {
+ ui.printLoadingError(Messages.INCOMPLETE_ENTRIES_CSV_HEADER_MESSAGE);
+ }
+ }
+
+ private void checkForSettingsFileHeader(Scanner sc) {
+ String data;
+ try {
+ data = sc.nextLine();
+ } catch (NoSuchElementException e) {
+ ui.printLoadingError(Messages.INCOMPLETE_SETTINGS_CSV_HEADER_MESSAGE);
+ return;
+ }
+
+ if (!data.equals(SETTINGS_CSV_HEADER)) {
+ ui.printLoadingError(Messages.INCOMPLETE_SETTINGS_CSV_HEADER_MESSAGE);
+ }
+ }
+
+ /**
+ * Saves all settings into a csv file.
+ * This allows users to not lose all their settings when program closes.
+ */
+ private void saveSettings() {
+ try {
+ FileWriter writer = new FileWriter(SETTINGS_FILE_NAME);
+ BufferedWriter buffer = new BufferedWriter(writer);
+
+ buffer.write(SETTINGS_CSV_HEADER);
+ buffer.write(NEWLINE);
+ writeSettings(buffer);
+ buffer.close();
+ } catch (IOException e) {
+ ui.printError(Messages.ERROR_SAVING_SETTINGS_MESSAGE);
+ }
+ }
+
+ private void writeSettings(BufferedWriter buffer) throws IOException {
+ String data;
+ data = convertSettingsToData(budgetManager, currencyManager);
+ buffer.write(data);
+ buffer.write(NEWLINE);
+ }
+
+ /**
+ * Loads all settings from StonksXD_Settings.csv into StonksXD.
+ * This allows users to not lose all their settings when the previous instance of Stonks XD closed.
+ */
+ private void loadSettings() {
+ FileInputStream fis;
+ try {
+ fis = new FileInputStream(SETTINGS_FILE_NAME);
+ } catch (FileNotFoundException e) {
+ ui.printLoadingError(Messages.UNABLE_TO_FIND_SETTINGS_FILE);
+ return;
+ }
+
+ Scanner sc = new Scanner(fis);
+ try {
+ checkForSettingsFileHeader(sc);
+ String data = sc.nextLine();
+ CurrencyType currency = convertDataToCurrencySetting(data);
+ loadCurrencySetting(currency);
+ double thresholdValue = convertDataToThresholdSetting(data);
+ loadThresholdSetting(thresholdValue);
+ ArrayList budgetSettings = convertDataToBudgetSettings(data);
+ loadBudgetSettings(budgetSettings);
+ } catch (NullPointerException | NumberFormatException | InvalidSettingsDataFormatException
+ | InvalidCurrencyTypeException | BlankCurrencyTypeException | NoSuchElementException
+ | InvalidThresholdValueException | InvalidBudgetAmountException e) {
+ ui.printLoadingError(Messages.HAS_CORRUPTED_SETTINGS);
+ }
+ }
+
+ private void loadCurrencySetting(CurrencyType currency) {
+ currencyManager.setCurrency(currency);
+ }
+
+ private void loadBudgetSettings(ArrayList budgetSettings) {
+ int budgetIndex = 0;
+ for (ExpenseCategory category : ExpenseCategory.values()) {
+ // Not expected to have a budget related to NULL
+ if (category == ExpenseCategory.NULL) {
+ break;
+ }
+ budgetManager.setBudget(budgetSettings.get(budgetIndex), category, financialTracker.getExpenses());
+ budgetIndex += 1;
+ }
+ }
+
+ private void loadThresholdSetting(double thresholdValue) {
+ budgetManager.setThreshold(thresholdValue);
+ }
+}
diff --git a/src/main/java/seedu/utility/tools/DateOperator.java b/src/main/java/seedu/utility/tools/DateOperator.java
new file mode 100644
index 0000000000..10a68b99a2
--- /dev/null
+++ b/src/main/java/seedu/utility/tools/DateOperator.java
@@ -0,0 +1,172 @@
+package seedu.utility.tools;
+
+import seedu.entry.Entry;
+import seedu.exceptions.InvalidDateException;
+import seedu.utility.Messages;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+
+import static java.util.stream.Collectors.toList;
+
+
+/**
+ * DateOperator is stores useful date manipulation methods which are used for processing data of FinancialTracker.java.
+ */
+public abstract class DateOperator {
+ public static final String DATE_FORMAT = "dd/MM/yyyy";
+
+ /**
+ * Checks if the date of a given entry is within a valid date range,
+ * where startDate is earlier than or the same as endDate.
+ *
+ * @param startDate The lower bound of the valid date range.
+ * @param endDate The upper bound of the valid date range.
+ * @param item The entry which date is of interest.
+ * @return True if the date of the item lies within a valid range else returns false.
+ */
+ public static boolean isBetweenStartAndEndDates(LocalDate startDate, LocalDate endDate, Entry item) {
+ LocalDate date = item.getDate();
+ return ((date.isAfter(startDate) || date.isEqual(startDate))
+ && (date.isBefore(endDate) || date.isEqual(endDate)));
+ }
+
+ /**
+ * Check if 2 dates form a valid date range.
+ *
+ * @param dateRange Object containing the date range
+ * @return True if the startDate is earlier than endDate.
+ */
+ public static boolean isValidDateRange(DateRange dateRange) {
+ LocalDate startDate = dateRange.getStartDate();
+ LocalDate endDate = dateRange.getEndDate();
+ return ((startDate.isBefore(endDate) || startDate.isEqual(endDate)));
+ }
+
+ /**
+ * Check if the year associated with the item matches the input year.
+ *
+ * @param inputYear Year used to compare item year with
+ * @param item The item whose year we want to compare
+ * @return True if year of both inputYear and item's year are the same, false if different.
+ */
+ public static boolean isSameYear(int inputYear, Entry item) {
+ return item.getDate().getYear() == inputYear;
+ }
+
+ /**
+ * Check if the month associated with the item matches the input year.
+ *
+ * @param inputMonth Year used to compare item month with
+ * @param item The item whose month we want to compare
+ * @return True if year of both inputMonth and item's month are the same, false if different.
+ */
+ public static boolean isSameMonth(int inputMonth, Entry item) {
+ return item.getDate().getMonthValue() == inputMonth;
+ }
+
+ /**
+ * Create a DateTime format that only accounts for year. It is used to compare with user input
+ *
+ * @return DateTimeFormatter object that compares year.
+ */
+ public static DateTimeFormatter getYearFormat() {
+ return new DateTimeFormatterBuilder().appendPattern("yyyy")
+ .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
+ .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
+ .toFormatter();
+ }
+
+ /**
+ * Creates a lambda that compares if the date associated to an item falls between 2 given dates.
+ *
+ * @param startDate The lower bound of the valid date range.
+ * @param endDate The upper bound of the valid date range.
+ * @return A lambda that can compare item's date to a valid date range.
+ */
+ public static Predicate entryDateInRange(LocalDate startDate, LocalDate endDate) {
+ return item -> isBetweenStartAndEndDates(startDate, endDate, item);
+ }
+
+ /**
+ * Creates a lambda that checks if the month associated to an item is the same as an input month.
+ *
+ * @param inputMonth The month which we compare the item month to.
+ * @return A lambda that can compare if item's month and input month is the same.
+ */
+ public static Predicate sameEntryMonth(int inputMonth) {
+ return item -> isSameMonth(inputMonth, item);
+ }
+
+ /**
+ * Creates a lambda that checks if the year associated to an item is the same as an input year.
+ *
+ * @param inputYear The month which we compare the item month to.
+ * @return A lambda that can compare if item's year and input year is the same.
+ */
+ public static Predicate sameEntryYear(int inputYear) {
+ return item -> isSameYear(inputYear, item);
+ }
+
+
+ /**
+ * Returns the current month as an index.
+ * @return The current month as an index of data type int.
+ */
+ public static int currentMonthInIndex() {
+ LocalDate currentDate = LocalDate.now();
+ return currentDate.getMonthValue() - 1;
+ }
+
+ /**
+ * Returns the current month.
+ * @return The current month as an object.
+ */
+ public static Month currentMonth() {
+ LocalDate currentDate = LocalDate.now();
+ return currentDate.getMonth();
+ }
+
+ public static DateRange extractStartAndEndDate(Matcher matcher) throws InvalidDateException {
+ String start = matcher.group("start").trim();
+ String end = matcher.group("end").trim();
+ if (isInvalidDate(start) || isInvalidDate(end)) {
+ throw new InvalidDateException(Messages.INVALID_DATE_ERROR);
+ }
+ LocalDate startDate = LocalDate.parse(start, DateTimeFormatter.ofPattern(DATE_FORMAT));
+ LocalDate endDate = LocalDate.parse(end, DateTimeFormatter.ofPattern(DATE_FORMAT));
+
+ return new DateRange(startDate,endDate);
+ }
+
+ //@@author AnShengLee
+ public static LocalDate extractDate(Matcher matcher) throws DateTimeParseException, InvalidDateException {
+ String date = matcher.group("date").trim();
+ if (isInvalidDate(date)) {
+ throw new InvalidDateException(Messages.INVALID_DATE_ERROR);
+ }
+ return LocalDate.parse(date, DateTimeFormatter.ofPattern(DATE_FORMAT));
+ }
+ //@@author AnShengLee
+
+ private static boolean isInvalidDate(String inputDate) {
+ ArrayList date = new ArrayList<>(Arrays.asList(inputDate.split("/")));
+ List fields = date.stream().mapToInt(Integer::parseInt).boxed().collect(toList());
+ return fields.get(0) == 29 && fields.get(1) == 2 && isNormalYear(date.get((2)));
+ }
+
+ private static boolean isNormalYear(String inputYear) {
+ DateTimeFormatter yearFormat = getYearFormat();
+ LocalDate year = LocalDate.parse(inputYear, yearFormat);
+ return !year.isLeapYear();
+ }
+}
diff --git a/src/main/java/seedu/utility/tools/DateRange.java b/src/main/java/seedu/utility/tools/DateRange.java
new file mode 100644
index 0000000000..9ff4602234
--- /dev/null
+++ b/src/main/java/seedu/utility/tools/DateRange.java
@@ -0,0 +1,24 @@
+package seedu.utility.tools;
+
+import java.time.LocalDate;
+
+/**
+ * Class that stores 2 LocalDate attributes which is used for date operations.
+ */
+public class DateRange {
+ private LocalDate startDate;
+ private LocalDate endDate;
+
+ public DateRange(LocalDate startDate, LocalDate endDate) {
+ this.startDate = startDate;
+ this.endDate = endDate;
+ }
+
+ public LocalDate getStartDate() {
+ return startDate;
+ }
+
+ public LocalDate getEndDate() {
+ return endDate;
+ }
+}
diff --git a/src/main/java/seedu/utility/tools/Extractor.java b/src/main/java/seedu/utility/tools/Extractor.java
new file mode 100644
index 0000000000..7daee0de8c
--- /dev/null
+++ b/src/main/java/seedu/utility/tools/Extractor.java
@@ -0,0 +1,313 @@
+package seedu.utility.tools;
+
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.BlankCategoryException;
+import seedu.exceptions.BlankCurrencyTypeException;
+import seedu.exceptions.EntryAmountBelowMinException;
+import seedu.exceptions.EntryAmountExceedLimitException;
+import seedu.exceptions.InvalidAmountException;
+import seedu.exceptions.InvalidBudgetAmountException;
+import seedu.exceptions.InvalidCurrencyTypeException;
+import seedu.exceptions.InvalidDescriptionException;
+import seedu.exceptions.InvalidExpenseCategoryException;
+import seedu.exceptions.InvalidIncomeCategoryException;
+import seedu.exceptions.InvalidIndexException;
+import seedu.exceptions.InvalidThresholdValueException;
+import seedu.utility.Messages;
+
+import java.util.regex.Matcher;
+
+/**
+ * Converts Strings given to a variable format that the program understands and can do meaningful work on it.
+ */
+public abstract class Extractor {
+
+ private static final double LOAD_AMOUNT_MIN = 0.01;
+ private static final double ENTRY_AMOUNT_MIN = 0.05;
+ private static final double ENTRY_AMOUNT_LIMIT = 1000000;
+ private static final double BUDGET_AMOUNT_LIMIT = 100000000000.00;
+
+ /**
+ * Converts the String given to an index which is an int.
+ *
+ * @param userGivenIndex The String that should contain user-specified index.
+ * @return An int which represents the index user specify.
+ * @throws InvalidIndexException If the String cannot be parsed into an int.
+ */
+ public static int extractIndex(String userGivenIndex) throws InvalidIndexException {
+ try {
+ int deleteIndex = Integer.parseInt(userGivenIndex);
+ if (deleteIndex <= 0) {
+ throw new InvalidIndexException(Messages.NON_POSITIVE_INTEGER_INDEX_MESSAGE);
+ }
+ return deleteIndex;
+ } catch (NumberFormatException e) {
+ throw new InvalidIndexException(Messages.INVALID_INDEX_MESSAGE);
+ }
+ }
+
+ /**
+ * Checks if the String given for Entry description is valid. Returns the String if it's valid.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The description as a String.
+ * @throws InvalidDescriptionException If the description is blank.
+ */
+ public static String extractDescription(Matcher matcher) throws
+ InvalidDescriptionException {
+ String description = matcher.group("description").trim();
+ if (description.isBlank()) {
+ throw new InvalidDescriptionException(Messages.BLANK_DESCRIPTION_MESSAGE);
+ }
+ return description;
+ }
+
+ /**
+ * Converts the String given to IncomeCategory.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The IncomeCategory.
+ * @throws BlankCategoryException If the supposed IncomeCategory is blank.
+ * @throws InvalidIncomeCategoryException If the IncomeCategory given is not expected / not supported.
+ */
+ public static IncomeCategory extractIncomeCategory(Matcher matcher) throws
+ BlankCategoryException, InvalidIncomeCategoryException {
+ String incomeCategory = matcher.group("category").trim();
+ if (incomeCategory.isBlank()) {
+ throw new BlankCategoryException(Messages.BLANK_CATEGORY_MESSAGE);
+ }
+ switch (incomeCategory.toUpperCase()) {
+ case "ALLOWANCE":
+ return IncomeCategory.ALLOWANCE;
+ case "SALARY":
+ return IncomeCategory.SALARY;
+ case "ADHOC":
+ return IncomeCategory.ADHOC;
+ case "OTHERS":
+ return IncomeCategory.OTHERS;
+ default:
+ throw new InvalidIncomeCategoryException(Messages.INVALID_INCOME_CATEGORY_MESSAGE);
+ }
+ }
+
+ /**
+ * Converts the given String to ExpenseCategory.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The ExpenseCategory.
+ * @throws BlankCategoryException If the supposed ExpenseCategory is blank.
+ * @throws InvalidExpenseCategoryException If the ExpenseCategory given is not expected / not supported.
+ */
+ public static ExpenseCategory extractExpenseCategory(Matcher matcher) throws
+ BlankCategoryException, InvalidExpenseCategoryException {
+ String expenseCategory = matcher.group("category").trim();
+ if (expenseCategory.isBlank()) {
+ throw new BlankCategoryException(Messages.BLANK_CATEGORY_MESSAGE);
+ }
+ switch (expenseCategory.toUpperCase()) {
+ case "FOOD":
+ return ExpenseCategory.FOOD;
+ case "TRANSPORT":
+ return ExpenseCategory.TRANSPORT;
+ case "MEDICAL":
+ return ExpenseCategory.MEDICAL;
+ case "BILLS":
+ return ExpenseCategory.BILLS;
+ case "ENTERTAINMENT":
+ return ExpenseCategory.ENTERTAINMENT;
+ case "MISC":
+ return ExpenseCategory.MISC;
+ default:
+ throw new InvalidExpenseCategoryException(Messages.INVALID_EXPENSE_CATEGORY_MESSAGE);
+ }
+ }
+
+ /**
+ * Converts the given String to the budget amount which is a double.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The budget amount in double format.
+ * @throws InvalidBudgetAmountException If the budget amount does not fit the expected rules.
+ * For example, if budget amount given is more than the limit.
+ */
+ public static double extractBudgetAmount(Matcher matcher) throws InvalidBudgetAmountException {
+
+ String dataAmount = matcher.group("amount").trim();
+ if (dataAmount.isBlank()) {
+ throw new InvalidBudgetAmountException(Messages.BLANK_AMOUNT_MESSAGE);
+ } else if (hasMoreThanTwoDecimalPlaces(dataAmount)) {
+ throw new InvalidBudgetAmountException(Messages.TOO_MANY_DP_MESSAGE);
+ }
+ double budgetAmount;
+ try {
+ budgetAmount = Double.parseDouble(dataAmount);
+ } catch (NumberFormatException e) {
+ throw new InvalidBudgetAmountException(Messages.NON_NUMERIC_AMOUNT_MESSAGE);
+ }
+
+ if (budgetAmount < ENTRY_AMOUNT_MIN) {
+ throw new InvalidBudgetAmountException(Messages.AMOUNT_BELOW_MIN_MESSAGE);
+ } else if (Double.isInfinite(budgetAmount) || Double.isNaN(budgetAmount)) {
+ throw new InvalidBudgetAmountException(Messages.NON_NUMERIC_AMOUNT_MESSAGE);
+ } else if (budgetAmount > BUDGET_AMOUNT_LIMIT) {
+ throw new InvalidBudgetAmountException(Messages.INVALID_BUDGET_VALUE);
+ }
+ return budgetAmount;
+ }
+
+ /**
+ * Converts the given String to the budget amount which is a double. Usually used for loading.
+ *
+ * @param amountFromData The budget amount received from csv file.
+ * @return The budget amount in double format.
+ * @throws InvalidBudgetAmountException If the budget amount does not fit the expected rules.
+ * For example, if budget amount given is more than the limit.
+ */
+ public static double extractBudgetAmountForLoading(String amountFromData) throws InvalidBudgetAmountException {
+
+ if (amountFromData.isBlank()) {
+ throw new InvalidBudgetAmountException(Messages.BLANK_AMOUNT_MESSAGE);
+ }
+ double budgetAmount;
+ try {
+ budgetAmount = Double.parseDouble(amountFromData);
+ } catch (NumberFormatException e) {
+ throw new InvalidBudgetAmountException(Messages.NON_NUMERIC_AMOUNT_MESSAGE);
+ }
+
+ if (budgetAmount < 0) {
+ throw new InvalidBudgetAmountException(Messages.NON_POSITIVE_AMOUNT_MESSAGE);
+ } else if (Double.isInfinite(budgetAmount) || Double.isNaN(budgetAmount)) {
+ throw new InvalidBudgetAmountException(Messages.NON_NUMERIC_AMOUNT_MESSAGE);
+ } else if (budgetAmount > BUDGET_AMOUNT_LIMIT) {
+ throw new InvalidBudgetAmountException(Messages.INVALID_BUDGET_VALUE);
+ }
+ return budgetAmount;
+ }
+
+ /**
+ * Converts the given String to amount which is a double.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The amount, in double format.
+ * @throws InvalidAmountException If the amount given does not match the expected guidelines.
+ * @throws EntryAmountExceedLimitException If the amount given exceeds the limit.
+ * @throws EntryAmountBelowMinException If the amount given is less than 0.05.
+ */
+ public static double extractAmount(Matcher matcher) throws InvalidAmountException,
+ EntryAmountExceedLimitException, EntryAmountBelowMinException {
+ String userGivenAmount = matcher.group("amount").trim();
+ double amount = parseAmount(userGivenAmount);
+ if (amount > ENTRY_AMOUNT_LIMIT) {
+ throw new EntryAmountExceedLimitException(Messages.INVALID_EXPENSE_VALUE);
+ } else if (amount < ENTRY_AMOUNT_MIN) {
+ throw new EntryAmountBelowMinException(Messages.AMOUNT_BELOW_MIN_MESSAGE);
+ }
+ return amount;
+ }
+
+ /**
+ * Converts the csv String to amount which is a double. Used for loading.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The amount, in double format.
+ * @throws InvalidAmountException If the amount given does not match the expected guidelines.
+ * @throws EntryAmountExceedLimitException If the amount given exceeds the limit.
+ * @throws EntryAmountBelowMinException If the amount given is less than 0.05.
+ */
+ public static double extractAmountForLoading(Matcher matcher) throws InvalidAmountException,
+ EntryAmountExceedLimitException, EntryAmountBelowMinException {
+ String userGivenAmount = matcher.group("amount").trim();
+ double amount = parseAmount(userGivenAmount);
+ if (amount > ENTRY_AMOUNT_LIMIT) {
+ throw new EntryAmountExceedLimitException(Messages.INVALID_EXPENSE_VALUE);
+ } else if (amount < LOAD_AMOUNT_MIN) {
+ throw new EntryAmountBelowMinException(Messages.AMOUNT_BELOW_MIN_MESSAGE);
+ }
+ return amount;
+ }
+
+ private static double parseAmount(String userGivenAmount) throws InvalidAmountException {
+ double amount;
+ try {
+ amount = Double.parseDouble(userGivenAmount);
+ } catch (NumberFormatException e) {
+ throw new InvalidAmountException(Messages.NON_NUMERIC_AMOUNT_MESSAGE);
+ }
+ if (hasMoreThanTwoDecimalPlaces(userGivenAmount)) {
+ throw new InvalidAmountException(Messages.TOO_MANY_DP_MESSAGE);
+ } else if (amount <= 0) {
+ throw new InvalidAmountException(Messages.NON_POSITIVE_AMOUNT_MESSAGE);
+ } else if (Double.isNaN(amount) || Double.isInfinite(amount)) {
+ throw new InvalidAmountException(Messages.NON_NUMERIC_AMOUNT_MESSAGE);
+ }
+ assert amount > 0;
+ return amount;
+ }
+
+ private static boolean hasMoreThanTwoDecimalPlaces(String userGivenAmount) {
+ boolean hasDecimal = userGivenAmount.contains(".");
+ if (hasDecimal) {
+ int indexOfDecimal = userGivenAmount.indexOf(".");
+ String decimals = userGivenAmount.substring(indexOfDecimal + 1);
+ int numOfDecimalPlaces = decimals.length();
+ return numOfDecimalPlaces > 2;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Converts the String given to the CurrencyType.
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The CurrencyType.
+ * @throws BlankCurrencyTypeException If CurrencyType given is empty.
+ * @throws InvalidCurrencyTypeException the CurrencyType given is not expected / not supported.
+ */
+ public static CurrencyType extractCurrencyType(Matcher matcher)
+ throws BlankCurrencyTypeException, InvalidCurrencyTypeException {
+ String newCurrency = matcher.group("currency").trim();
+ if (newCurrency.isBlank()) {
+ throw new BlankCurrencyTypeException(Messages.BLANK_CURRENCY_TYPE_MESSAGE);
+ }
+ switch (newCurrency.toUpperCase()) {
+ case "HKD":
+ return CurrencyType.HKD;
+ case "SGD":
+ return CurrencyType.SGD;
+ default:
+ throw new InvalidCurrencyTypeException(Messages.INVALID_CURRENCY_TYPE_MESSAGE);
+ }
+ }
+
+ /**
+ * Converts given String to threshold value.
+ *
+ * @param matcher An engine that performs match operations on a character sequence by interpreting a Pattern.
+ * @return The threshold value.
+ * @throws InvalidThresholdValueException If the threshold value given is unexpected.
+ */
+ public static double extractThresholdValue(Matcher matcher) throws InvalidThresholdValueException {
+ String userGivenThreshold = matcher.group("threshold").trim();
+ return parseThresholdValue(userGivenThreshold);
+ }
+
+ private static double parseThresholdValue(String userGivenThreshold) throws InvalidThresholdValueException {
+ double thresholdValue;
+ try {
+ thresholdValue = Double.parseDouble(userGivenThreshold);
+ } catch (NumberFormatException e) {
+ throw new InvalidThresholdValueException(Messages.NON_NUMERIC_THRESHOLD_MESSAGE);
+ }
+ if ((thresholdValue < 0) | (thresholdValue > 1)) {
+ throw new InvalidThresholdValueException(Messages.INVALID_THRESHOLD_MESSAGE);
+ } else if (Double.isNaN(thresholdValue) || Double.isInfinite(thresholdValue)) {
+ throw new InvalidThresholdValueException(Messages.NON_NUMERIC_THRESHOLD_MESSAGE);
+ } else if (hasMoreThanTwoDecimalPlaces(userGivenThreshold)) {
+ throw new InvalidThresholdValueException(Messages.TOO_MANY_DP_MESSAGE);
+ }
+ return thresholdValue;
+ }
+}
diff --git a/src/main/java/seedu/utility/tools/FinancialAdvisor.java b/src/main/java/seedu/utility/tools/FinancialAdvisor.java
new file mode 100644
index 0000000000..e79dee42fb
--- /dev/null
+++ b/src/main/java/seedu/utility/tools/FinancialAdvisor.java
@@ -0,0 +1,44 @@
+package seedu.utility.tools;
+
+import seedu.utility.Messages;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * FinancialAdvisor offers a finance related tip to the user at the end when the program terminates.
+ */
+public abstract class FinancialAdvisor {
+ private static final Random RANDOM = new Random();
+
+ private static final String ADVICE_1 = "Try using the 50/30/20 rule to budget."
+ + " 50% for needs, 30% for wants and 20% for savings";
+ private static final String ADVICE_2 = "Do you have an emergency fund? "
+ + "It should cover at least 3 to 6 months of your expense";
+ private static final String ADVICE_3 = "The Best Time To Invest Was Yesterday,"
+ + " The Second Best Time Is Today";
+ private static final String ADVICE_4 = "If you have any debts,"
+ + " try to pay off the one with the highest interest first.";
+ private static final String ADVICE_5 = "If there is something you really want to buy"
+ + ", make sure you have the price of it times 10 in your savings first";
+ private static final List ADVICES = Arrays.asList(ADVICE_1,ADVICE_2,ADVICE_3,ADVICE_4,ADVICE_5);
+
+ /**
+ * Generates a random advice.
+ *
+ * @return A string that reads a financial advice.
+ */
+ public static String getRandomAdvice() {
+ int bound = ADVICES.size();
+ if (isValidRandomRange(bound)) {
+ int adviceIndex = RANDOM.nextInt(bound);
+ return ADVICES.get(adviceIndex);
+ }
+ return Messages.DISPLAY_ADVICE_ERROR;
+ }
+
+ private static boolean isValidRandomRange(int bound) {
+ return bound > 0;
+ }
+}
diff --git a/src/main/java/seedu/utility/tools/FinancialCalculator.java b/src/main/java/seedu/utility/tools/FinancialCalculator.java
new file mode 100644
index 0000000000..9ddb35480d
--- /dev/null
+++ b/src/main/java/seedu/utility/tools/FinancialCalculator.java
@@ -0,0 +1,55 @@
+package seedu.utility.tools;
+
+import seedu.entry.Entry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static seedu.utility.FinancialTracker.TOTAL_ENTRIES_LIMIT;
+import static seedu.utility.tools.DateOperator.sameEntryMonth;
+
+
+/**
+ * This class abstracts out more complication calculations used in FinancialTracker.
+ */
+public abstract class FinancialCalculator {
+ private static final int[] MONTHS = IntStream.range(1,13).toArray();
+
+ /**
+ * Sorts an entire year's entries according to the month they are associated with.
+ *
+ * @param yearlyAccumulatedEntries A List of entries that all share the same associated year.
+ * @return A sorted ArrayList where index 1 - 12 contains the total entry corresponding to the month Jan to Dec.
+ */
+ public static ArrayList sortEntriesByMonth(List yearlyAccumulatedEntries) {
+ ArrayList monthlyBreakdown = new ArrayList<>();
+ for (int month : MONTHS) {
+ double entryForTheMonth = getMonthlyEntries(month, yearlyAccumulatedEntries);
+ monthlyBreakdown.add(entryForTheMonth);
+ }
+ return monthlyBreakdown;
+ }
+
+ private static double getMonthlyEntries(int inputMonth, List yearlyEntries) {
+ List monthlyAccumulatedEntries = yearlyEntries.stream()
+ .filter(sameEntryMonth(inputMonth))
+ .collect(Collectors.toList());
+ return getSumOfEntries(monthlyAccumulatedEntries);
+ }
+
+ /**
+ * Calculate the total amount associated to all the entries in the list.
+ *
+ * @param accumulatedEntries A list containing all the entries we want to sum the values of.
+ * @return The sum of all the entries stored as a double.
+ */
+ public static double getSumOfEntries(List accumulatedEntries) {
+ double totalEntry = accumulatedEntries.stream()
+ .mapToDouble(Entry::getValue)
+ .sum();
+ assert totalEntry <= TOTAL_ENTRIES_LIMIT;
+ return totalEntry;
+ }
+}
diff --git a/src/test/java/seedu/duke/BalanceCommandTest.java b/src/test/java/seedu/duke/BalanceCommandTest.java
new file mode 100644
index 0000000000..f5c9670362
--- /dev/null
+++ b/src/test/java/seedu/duke/BalanceCommandTest.java
@@ -0,0 +1,27 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BalanceCommandTest {
+ private CurrencyManager currencyManager = new CurrencyManager();
+ private FinancialTracker testTracker = new FinancialTracker(currencyManager);
+
+ @Test
+ public void execute_twoValidDateInputs_validTotalSum() throws IncomeOverflowException, ExpenseOverflowException {
+ Income testIncome = new Income("Salary", 400.00, IncomeCategory.SALARY);
+ Expense testExpense = new Expense("Burger", 500.00, ExpenseCategory.FOOD);
+ testTracker.addIncome(testIncome);
+ testTracker.addExpense(testExpense);
+ assertEquals(-100.00, testTracker.calculateBalance());
+ }
+}
diff --git a/src/test/java/seedu/duke/BudgetManagerTest.java b/src/test/java/seedu/duke/BudgetManagerTest.java
new file mode 100644
index 0000000000..388835a233
--- /dev/null
+++ b/src/test/java/seedu/duke/BudgetManagerTest.java
@@ -0,0 +1,158 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.reminder.BudgetReminder;
+import seedu.reminder.BudgetSetReminder;
+import seedu.reminder.DoubleExceededBudgetReminder;
+import seedu.reminder.DoubleNearingBudgetReminder;
+import seedu.reminder.ExceededBudgetNearingOverallReminder;
+import seedu.reminder.SingleExceededReminder;
+import seedu.reminder.SingleNearingReminder;
+import seedu.reminder.SingleReminder;
+import seedu.reminder.UnableToSetBudgetReminder;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BudgetManagerTest {
+
+ private static final String currentMonth =
+ LocalDate.now().getMonth().toString();
+
+ private final CurrencyManager currencyManager = new CurrencyManager();
+ private final BudgetManager budgetManager = new BudgetManager();
+ private final FinancialTracker finances = new FinancialTracker(currencyManager);
+
+ @Test
+ public void setBudget_validEntry_correctBudget() {
+ BudgetReminder reminder = budgetManager.setBudget(2000.50, ExpenseCategory.OVERALL, finances.getExpenses());
+ BudgetReminder expectedReminder = new BudgetSetReminder("OVERALL", 2000.50);
+ assertEquals(2000.50, budgetManager.getBudget(ExpenseCategory.OVERALL));
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ public void setBudget_overallBudgetLessThanBudget_warningGiven() {
+ BudgetReminder reminder = budgetManager.setBudget(2000.50, ExpenseCategory.TRANSPORT,
+ finances.getExpenses());
+ BudgetReminder expectedReminder = new UnableToSetBudgetReminder("TRANSPORT", 0,
+ 0, 2000.50, 2000.50);
+ assertEquals(0, budgetManager.getBudget(ExpenseCategory.TRANSPORT));
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ public void setBudget_budgetLessThanExpense_warningGiven() throws ExpenseOverflowException {
+ budgetManager.setBudget(20, ExpenseCategory.OVERALL, finances.getExpenses());
+ finances.addExpense(new Expense("mcdonalds", 15, ExpenseCategory.FOOD));
+ BudgetReminder reminder = budgetManager.setBudget(10, ExpenseCategory.FOOD, finances.getExpenses());
+ BudgetReminder expectedReminder = new UnableToSetBudgetReminder("FOOD", 15,
+ 20, 10, 15);
+ assertEquals(0, budgetManager.getBudget(ExpenseCategory.FOOD));
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ @Test
+ public void setThreshold_validEntry_correctThreshold() {
+ budgetManager.setThreshold(0.85);
+ assertEquals(0.85, budgetManager.getThreshold());
+ }
+
+
+ @Test
+ public void handleBudget_overallNotExceededBudgetNotExceeded_DoubleNearingBudgetReminder()
+ throws ExpenseOverflowException {
+ budgetManager.setBudget(13, ExpenseCategory.OVERALL, finances.getExpenses());
+ budgetManager.setBudget(12, ExpenseCategory.FOOD, finances.getExpenses());
+ budgetManager.setThreshold(0.8);
+ finances.addExpense(new Expense("mcdonalds", 5, ExpenseCategory.FOOD));
+ Expense testExpense = new Expense("dinner", 6, ExpenseCategory.FOOD);
+ finances.addExpense(testExpense);
+ BudgetReminder reminder = budgetManager.handleBudget(testExpense, finances.getExpenses());
+ BudgetReminder expectedReminder = new DoubleNearingBudgetReminder(currentMonth,
+ "FOOD", 11, 12, 11, 13, 12);
+ assertEquals(expectedReminder.toString(), reminder.toString());
+
+ }
+
+ @Test
+ public void handleBudget_overallNotExceededBudgetExceeded_ExceededBudgetNearingOverallReminder()
+ throws ExpenseOverflowException {
+ budgetManager.setBudget(12, ExpenseCategory.OVERALL, finances.getExpenses());
+ budgetManager.setBudget(4, ExpenseCategory.FOOD, finances.getExpenses());
+ budgetManager.setThreshold(0.9);
+ Expense testExpense = new Expense("breakfast", 11, ExpenseCategory.FOOD);
+ finances.addExpense(testExpense);
+ BudgetReminder reminder = budgetManager.handleBudget(testExpense, finances.getExpenses());
+ BudgetReminder expectedReminder = new ExceededBudgetNearingOverallReminder(currentMonth,
+ "FOOD", 11, 4, 11,12, 11);
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ @Test
+ public void handleBudget_overallExceededBudgetExceeded_DoubleExceededBudgetReminder()
+ throws ExpenseOverflowException {
+ budgetManager.setBudget(12, ExpenseCategory.OVERALL, finances.getExpenses());
+ budgetManager.setBudget(4, ExpenseCategory.FOOD, finances.getExpenses());
+ budgetManager.setThreshold(0.9);
+ Expense testExpense = new Expense("breakfast", 15, ExpenseCategory.FOOD);
+ finances.addExpense(testExpense);
+ BudgetReminder reminder = budgetManager.handleBudget(testExpense, finances.getExpenses());
+ BudgetReminder expectedReminder = new DoubleExceededBudgetReminder(currentMonth,
+ "FOOD", 15, 4, 15,12, 15);
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ @Test
+ public void handleBudget_overallNoWarningBudgetExceeded_SingleExceededReminder()
+ throws ExpenseOverflowException {
+ budgetManager.setBudget(12, ExpenseCategory.OVERALL, finances.getExpenses());
+ budgetManager.setBudget(4, ExpenseCategory.FOOD, finances.getExpenses());
+ budgetManager.setThreshold(0.9);
+ Expense testExpense = new Expense("breakfast", 5, ExpenseCategory.FOOD);
+ finances.addExpense(testExpense);
+ BudgetReminder reminder = budgetManager.handleBudget(testExpense, finances.getExpenses());
+ BudgetReminder expectedReminder = new SingleExceededReminder(currentMonth,
+ "FOOD", 5, 4);
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ @Test
+ public void handleBudget_overallNoWarningBudgetNearing_SingleNearingReminder()
+ throws ExpenseOverflowException {
+ budgetManager.setBudget(12, ExpenseCategory.OVERALL, finances.getExpenses());
+ budgetManager.setBudget(4, ExpenseCategory.FOOD, finances.getExpenses());
+ budgetManager.setThreshold(0.9);
+ Expense testExpense = new Expense("breakfast", 3.9, ExpenseCategory.FOOD);
+ finances.addExpense(testExpense);
+ BudgetReminder reminder = budgetManager.handleBudget(testExpense, finances.getExpenses());
+ BudgetReminder expectedReminder = new SingleNearingReminder(currentMonth,
+ "FOOD", 3.9, 4);
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+ @Test
+ public void handleBudget_overallNoWarningBudgetNotNearing_SingleBudgetReminder()
+ throws ExpenseOverflowException {
+ budgetManager.setBudget(12, ExpenseCategory.OVERALL, finances.getExpenses());
+ budgetManager.setBudget(4, ExpenseCategory.FOOD, finances.getExpenses());
+ budgetManager.setThreshold(0.9);
+ Expense testExpense = new Expense("breakfast", 1.20, ExpenseCategory.FOOD);
+ finances.addExpense(testExpense);
+ BudgetReminder reminder = budgetManager.handleBudget(testExpense, finances.getExpenses());
+ BudgetReminder expectedReminder = new SingleReminder(currentMonth,
+ "FOOD", 1.20, 4);
+ assertEquals(expectedReminder.toString(), reminder.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/duke/CommandTest.java b/src/test/java/seedu/duke/CommandTest.java
new file mode 100644
index 0000000000..515aa2b681
--- /dev/null
+++ b/src/test/java/seedu/duke/CommandTest.java
@@ -0,0 +1,114 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.commands.expense.AddExpenseCommand;
+import seedu.commands.income.AddIncomeCommand;
+import seedu.commands.general.ClearAllEntriesCommand;
+import seedu.commands.expense.DeleteExpenseCommand;
+import seedu.commands.income.DeleteIncomeCommand;
+import seedu.commands.expense.TotalExpenseCommand;
+import seedu.commands.income.TotalIncomeCommand;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+public class CommandTest {
+ private CurrencyManager currencyManager = new CurrencyManager();
+ private FinancialTracker testTracker = new FinancialTracker(currencyManager);
+ private Ui testUi = new Ui();
+ private BudgetManager budgetManager = new BudgetManager();
+
+ @Test
+ public void addExpenseCommand_validExpense_validExpensesSize() {
+ Expense testExpense = new Expense("Bubble Tea", 4.80, ExpenseCategory.FOOD);
+ AddExpenseCommand testCommand = new AddExpenseCommand(testExpense);
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(1, testTracker.getExpenseSize());
+ }
+
+ @Test
+ public void addIncomeCommand_validIncome_validIncomeSize() {
+ Income testIncome = new Income("Pocket Money", 100, IncomeCategory.ALLOWANCE);
+ AddIncomeCommand testCommand = new AddIncomeCommand(testIncome);
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(1, testTracker.getIncomeSize());
+ }
+
+ @Test
+ public void deleteExpenseCommand_validIndex_validIncomeRemoved() {
+ DeleteExpenseCommand testCommand = new DeleteExpenseCommand(1);
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertTrue(testTracker.isExpensesEmpty());
+ }
+
+ @Test
+ public void deleteIncomeCommand_validIndex_validExpenseRemoved() {
+ DeleteIncomeCommand testCommand = new DeleteIncomeCommand(1);
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertTrue(testTracker.isIncomesEmpty());
+ }
+
+ @Test
+ public void clearAllEntriesCommand_validEntry_validArrayListSize()
+ throws IncomeOverflowException, ExpenseOverflowException {
+ Income testIncome1 = new Income("Pocket Money", 100, IncomeCategory.ALLOWANCE);
+ Income testIncome2 = new Income("Pocket Money", 100, IncomeCategory.ALLOWANCE);
+ Expense testExpense1 = new Expense("Bubble Tea", 4.80, ExpenseCategory.FOOD);
+ Expense testExpense2 = new Expense("Bubble Tea", 4.80, ExpenseCategory.FOOD);
+ testTracker.addIncome(testIncome1);
+ testTracker.addIncome(testIncome2);
+ testTracker.addExpense(testExpense1);
+ testTracker.addExpense(testExpense2);
+ ClearAllEntriesCommand testCommand = new ClearAllEntriesCommand();
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(0, testTracker.getIncomeSize());
+ assertEquals(0, testTracker.getExpenseSize());
+ }
+
+ @Test
+ public void totalExpenseCommand_validExpenseEntry_validTotal() {
+ TotalExpenseCommand testCommand = new TotalExpenseCommand();
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(0, testTracker.getTotalIncome());
+ Expense testExpense1 = new Expense("Bubble Tea", 4.80, ExpenseCategory.FOOD);
+ Expense testExpense2 = new Expense("Chicken Rice", 3.00, ExpenseCategory.FOOD);
+ AddExpenseCommand testCommand2 = new AddExpenseCommand(testExpense1);
+ AddExpenseCommand testCommand3 = new AddExpenseCommand(testExpense2);
+ testCommand2.execute(testTracker, testUi, budgetManager, currencyManager);
+ testCommand3.execute(testTracker, testUi, budgetManager, currencyManager);
+ TotalExpenseCommand testCommand4 = new TotalExpenseCommand();
+ testCommand4.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(7.80, testTracker.getTotalExpense());
+ }
+
+ @Test
+ public void totalIncomeCommand_validIncomeEntry_validTotal() {
+ TotalIncomeCommand testCommand = new TotalIncomeCommand();
+ testCommand.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(0, testTracker.getTotalExpense());
+ Income testIncome1 = new Income("Pocket Money", 100, IncomeCategory.ALLOWANCE);
+ Income testIncome2 = new Income("Chicken Rice", 2500, IncomeCategory.SALARY);
+ AddIncomeCommand testCommand2 = new AddIncomeCommand(testIncome1);
+ AddIncomeCommand testCommand3 = new AddIncomeCommand(testIncome2);
+ testCommand2.execute(testTracker, testUi, budgetManager, currencyManager);
+ testCommand3.execute(testTracker, testUi, budgetManager, currencyManager);
+ TotalIncomeCommand testCommand4 = new TotalIncomeCommand();
+ testCommand4.execute(testTracker, testUi, budgetManager, currencyManager);
+ assertEquals(2600, testTracker.getTotalIncome());
+ }
+
+}
+
diff --git a/src/test/java/seedu/duke/CurrencyManagerStub.java b/src/test/java/seedu/duke/CurrencyManagerStub.java
new file mode 100644
index 0000000000..8066fe9357
--- /dev/null
+++ b/src/test/java/seedu/duke/CurrencyManagerStub.java
@@ -0,0 +1,11 @@
+package seedu.duke;
+
+import seedu.commands.currency.CurrencyType;
+import seedu.utility.CurrencyManager;
+
+public class CurrencyManagerStub extends CurrencyManager {
+ @Override
+ public CurrencyType getCurrency() {
+ return CurrencyType.SGD;
+ }
+}
diff --git a/src/test/java/seedu/duke/CurrencyManagerTest.java b/src/test/java/seedu/duke/CurrencyManagerTest.java
new file mode 100644
index 0000000000..91de193c69
--- /dev/null
+++ b/src/test/java/seedu/duke/CurrencyManagerTest.java
@@ -0,0 +1,79 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.commands.Command;
+import seedu.commands.currency.CurrencyConversionCommand;
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+public class CurrencyManagerTest {
+
+ @Test
+ public void checkCurrency_expectedCurrencyTypeSgd() {
+ CurrencyManager currencyManager = new CurrencyManager();
+ assertSame(currencyManager.getCurrency(), CurrencyType.SGD);
+ }
+
+ @Test
+ public void checkCurrency_expectedCurrencyTypeHdk() {
+ Ui ui = new Ui();
+ CurrencyManager currencyManager = new CurrencyManager();
+ BudgetManager budgetManager = new BudgetManager();
+ FinancialTracker testTracker = new FinancialTracker(currencyManager);
+ assertSame(currencyManager.getCurrency(), CurrencyType.SGD);
+ Command testCommand = new CurrencyConversionCommand(CurrencyType.HKD);
+ testCommand.execute(testTracker, ui, budgetManager, currencyManager);
+ assertSame(currencyManager.getCurrency(), CurrencyType.HKD);
+ }
+
+ @Test
+ public void convertCurrencyHdk_expectedCurrencyTypeHdk() {
+ Ui ui = new Ui();
+ CurrencyManager currencyManager = new CurrencyManager();
+ BudgetManager budgetManager = new BudgetManager();
+ FinancialTracker testTracker = new FinancialTracker(currencyManager);
+ Command testCommand = new CurrencyConversionCommand(CurrencyType.HKD);
+ testCommand.execute(testTracker, ui, budgetManager, currencyManager);
+ assertSame(currencyManager.getCurrency(), CurrencyType.HKD);
+ }
+
+ @Test
+ public void convertCurrencyHdkTwice_SameTypeExceptionThrown_expenseValueNoChange() {
+ Ui ui = new Ui();
+ CurrencyManager currencyManager = new CurrencyManager();
+ BudgetManager budgetManager = new BudgetManager();
+ FinancialTracker testTracker = new FinancialTracker(currencyManager);
+ Expense expense = new Expense("food", 10.0, ExpenseCategory.FOOD);
+ Command convertCurrencyCommand = new CurrencyConversionCommand(CurrencyType.HKD);
+ convertCurrencyCommand.execute(testTracker, ui, budgetManager, currencyManager);
+ assertEquals(expense.getValue(), 10.00);
+ Command convertCurrencyCommandAgain = new CurrencyConversionCommand(CurrencyType.HKD);
+ convertCurrencyCommandAgain.execute(testTracker, ui, budgetManager, currencyManager);
+ assertEquals(expense.getValue(), 10.00);
+ }
+
+ @Test
+ public void convertCurrencyHdk_expectedNewExpenseValue() throws ExpenseOverflowException {
+ Ui ui = new Ui();
+ CurrencyManager currencyManager = new CurrencyManager();
+ BudgetManager budgetManager = new BudgetManager();
+ FinancialTracker testTracker = new FinancialTracker(currencyManager);
+ Expense expense = new Expense("food", 10.0, ExpenseCategory.FOOD);
+ testTracker.addExpense(expense);
+ Command testCurrencyConversion = new CurrencyConversionCommand(CurrencyType.HKD);
+ testCurrencyConversion.execute(testTracker, ui, budgetManager, currencyManager);
+ assertEquals(expense.getValue(), 50.00);
+ Command testCurrencyConversion2 = new CurrencyConversionCommand(CurrencyType.SGD);
+ testCurrencyConversion2.execute(testTracker, ui, budgetManager, currencyManager);
+ assertEquals(expense.getValue(), 10.00);
+ }
+}
diff --git a/src/test/java/seedu/duke/DataConverterTest.java b/src/test/java/seedu/duke/DataConverterTest.java
new file mode 100644
index 0000000000..fe624040ba
--- /dev/null
+++ b/src/test/java/seedu/duke/DataConverterTest.java
@@ -0,0 +1,154 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.commands.currency.CurrencyType;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.BlankCurrencyTypeException;
+import seedu.exceptions.InputException;
+import seedu.exceptions.InvalidAmountException;
+import seedu.exceptions.InvalidBudgetAmountException;
+import seedu.exceptions.InvalidCurrencyTypeException;
+import seedu.exceptions.InvalidExpenseDataFormatException;
+import seedu.exceptions.InvalidIncomeDataFormatException;
+import seedu.exceptions.InvalidSettingsDataFormatException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static seedu.utility.storage.DataConverter.convertDataToBudgetSettings;
+import static seedu.utility.storage.DataConverter.convertDataToCurrencySetting;
+import static seedu.utility.storage.DataConverter.convertDataToExpense;
+import static seedu.utility.storage.DataConverter.convertDataToIncome;
+import static seedu.utility.storage.DataConverter.convertExpenseToData;
+import static seedu.utility.storage.DataConverter.convertIncomeToData;
+import static seedu.utility.storage.DataConverter.convertSettingsToData;
+
+public class DataConverterTest {
+ private static final String DATE_FORMAT = "dd/MM/yyyy";
+ private static final String DATA_SEPARATOR = ",";
+ private static final int TOTAL_EXPENSE_CATEGORY = 7;
+
+ @Test
+ public void convertExpenseToData_validExpense_correctDataOutput() {
+ LocalDate date = LocalDate.parse("11/11/2121", DateTimeFormatter.ofPattern(DATE_FORMAT));
+ Expense testExpense = new Expense("buy book", 12.33, ExpenseCategory.FOOD, date);
+ String testData = convertExpenseToData(testExpense);
+ assertEquals("E" + DATA_SEPARATOR + "buy book" + DATA_SEPARATOR + 12.33 + DATA_SEPARATOR + "FOOD"
+ + DATA_SEPARATOR + "11/11/2121", testData);
+ }
+
+ @Test
+ public void convertIncomeToData_validIncome_correctDataOutput() {
+ LocalDate date = LocalDate.parse("11/11/2121", DateTimeFormatter.ofPattern(DATE_FORMAT));
+ Income testIncome = new Income("job", 1233.0, IncomeCategory.ADHOC, date);
+ String testData = convertIncomeToData(testIncome);
+ assertEquals("I" + DATA_SEPARATOR + "job" + DATA_SEPARATOR + "1233.00" + DATA_SEPARATOR + "ADHOC"
+ + DATA_SEPARATOR + "11/11/2121", testData);
+ }
+
+ @Test
+ public void convertDataToExpense_validExpenseData_outputExpense() throws InputException,
+ InvalidExpenseDataFormatException {
+ Expense testExpense = convertDataToExpense("E,sfa,12.00,food,11/11/2121");
+ assertEquals("sfa", testExpense.getDescription());
+ assertEquals(12, testExpense.getValue());
+ }
+
+ @Test
+ public void convertDataToExpense_invalidExpenseDataWithBlankDescription_throwsException() {
+ assertThrows(InvalidExpenseDataFormatException.class, () -> convertDataToExpense("E, ,"));
+ }
+
+ @Test
+ public void convertDataToExpense_invalidExpenseDataWithInvalidAmount_throwsException() {
+ assertThrows(InvalidAmountException.class,
+ () -> convertDataToExpense("E, asd, 12a, qwe, 21-11-11"));
+ }
+
+ @Test
+ public void convertDataToIncome_validIncomeData_outputIncome() throws InputException,
+ InvalidIncomeDataFormatException, DateTimeException {
+ Income testIncome = convertDataToIncome("I" + DATA_SEPARATOR + "pay" + DATA_SEPARATOR
+ + 1000 + DATA_SEPARATOR + "SALARY" + DATA_SEPARATOR + "11/11/2121");
+ assertEquals("pay", testIncome.getDescription());
+ assertEquals(1000.0, testIncome.getValue());
+ }
+
+ @Test
+ public void convertDataToIncome_invalidIncomeDataWithBlankDescription_throwsException() {
+ assertThrows(InputException.class,
+ () -> convertDataToIncome("I, , 12, q, 2121-11-11"));
+ }
+
+ @Test
+ public void convertDataToIncome_invalidIncomeDataWithInvalidAmount_throwsException() {
+ assertThrows(InvalidAmountException.class,
+ () -> convertDataToIncome("I, asd, 12a, q, 2121-11-11"));
+ }
+
+ @Test
+ public void convertDataToIncome_invalidIncomeDataWithInvalidMarker_throwsException() {
+ assertThrows(InvalidIncomeDataFormatException.class,
+ () -> convertDataToIncome("E, asd, 12a, q, 2121-11-11"));
+ }
+
+ @Test
+ public void convertDataToIncome_invalidIncomeDataWithInvalidSeparator_throwsException() {
+ assertThrows(InvalidIncomeDataFormatException.class,
+ () -> convertDataToIncome("I" + DATA_SEPARATOR + "pay" + DATA_SEPARATOR + 1000 + DATA_SEPARATOR
+ + "SALARY" + "|" + "11/11/2121"));
+ }
+
+ @Test
+ public void convertDataToBudgetSettings_validData_validBudgets() throws InvalidSettingsDataFormatException,
+ InvalidBudgetAmountException {
+ String testData = "SGD,0.1,12.0,1.0,1.0,1.0,1.0,1.0,1.0";
+ ArrayList testBudgets = convertDataToBudgetSettings(testData);
+ for (int i = 0; i < TOTAL_EXPENSE_CATEGORY; i++) {
+ if (i == 0) {
+ assertEquals(12, testBudgets.get(i));
+ } else {
+ assertEquals(1, testBudgets.get(i));
+ }
+ }
+ }
+
+ @Test
+ public void convertDataToCurrencySetting_validData_validCurrency() throws InvalidCurrencyTypeException,
+ InvalidSettingsDataFormatException, BlankCurrencyTypeException {
+ String testData = "SGD,0.0,12.0,12.0,12.0,12.0,12.0,12.0,12";
+ CurrencyType currency = convertDataToCurrencySetting(testData);
+ assertEquals(currency.toString(), "SGD");
+ }
+
+ @Test
+ public void convertSettingsToData_validSettings_validData() {
+ BudgetManager testBudgetManager = new BudgetManager();
+ CurrencyManager currencyManager = new CurrencyManager();
+ FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ for (ExpenseCategory category : ExpenseCategory.values()) {
+ if (category == ExpenseCategory.NULL) {
+ break;
+ }
+ if (category == ExpenseCategory.OVERALL) {
+ testBudgetManager.setBudget(12, category, financialTracker.getExpenses());
+ } else {
+ testBudgetManager.setBudget(1, category, financialTracker.getExpenses());
+ }
+ }
+ testBudgetManager.setThreshold(0.2);
+ String testData = convertSettingsToData(testBudgetManager, currencyManager);
+ assertEquals("SGD,0.2,12.0,1.0,1.0,1.0,1.0,1.0,1.0", testData);
+
+ }
+}
diff --git a/src/test/java/seedu/duke/DataManagerTest.java b/src/test/java/seedu/duke/DataManagerTest.java
new file mode 100644
index 0000000000..ccd3006049
--- /dev/null
+++ b/src/test/java/seedu/duke/DataManagerTest.java
@@ -0,0 +1,124 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.BudgetManager;
+import seedu.utility.CurrencyManager;
+import seedu.utility.storage.DataManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Ui;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.utility.storage.DataConverter.convertSettingsToData;
+
+public class DataManagerTest {
+
+ public static final String DATE_FORMAT = "dd/MM/yyyy";
+
+ @Test
+ public void saveEntries_validEntries_correctDataFileContent()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ CurrencyManager currencyManager = new CurrencyManager();
+ FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ LocalDate date = LocalDate.parse("11/11/2121", DateTimeFormatter.ofPattern(DATE_FORMAT));
+ financialTracker.addExpense(new Expense("qwe", 12.5, ExpenseCategory.FOOD, date));
+ financialTracker.addExpense(new Expense("qwe", .5, ExpenseCategory.FOOD, date));
+ financialTracker.addIncome(new Income("qwe", 12.5, IncomeCategory.SALARY, date));
+ financialTracker.addIncome(new Income("qwe", 12.5, IncomeCategory.ALLOWANCE, date));
+ Ui ui = new Ui();
+ BudgetManager budgetManager = new BudgetManager();
+ DataManager dataManager = new DataManager(financialTracker, ui, budgetManager, currencyManager);
+ dataManager.setSettingsToWritable();
+ dataManager.saveAll();
+ }
+
+ @Test
+ public void loadEntries_validDataFileContent_correctEntries()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ saveEntries_validEntries_correctDataFileContent();
+ CurrencyManager currencyManager = new CurrencyManager();
+ FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ Ui ui = new Ui();
+ BudgetManager budgetManager = new BudgetManager();
+ DataManager dataManager = new DataManager(financialTracker, ui, budgetManager, currencyManager);
+ dataManager.loadAll();
+ assertEquals(12.5, financialTracker.getExpenses().get(0).getValue());
+ assertEquals("qwe", financialTracker.getExpenses().get(0).getDescription());
+ assertEquals(ExpenseCategory.FOOD, financialTracker.getExpenses().get(0).getCategory());
+
+ assertEquals(.5, financialTracker.getExpenses().get(1).getValue());
+ assertEquals("qwe", financialTracker.getExpenses().get(1).getDescription());
+
+ assertEquals(12.5, financialTracker.getIncomes().get(0).getValue());
+ assertEquals("qwe", financialTracker.getIncomes().get(0).getDescription());
+
+ assertEquals(12.5, financialTracker.getIncomes().get(1).getValue());
+ assertEquals("qwe", financialTracker.getIncomes().get(1).getDescription());
+ }
+
+ @Test
+ public void loadEntries_invalidDataFileContent_detectInvalidDataEntriesAndOutputWarningMessages()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ CurrencyManager currencyManager = new CurrencyManager();
+ FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ LocalDate date = LocalDate.parse("11/11/2121", DateTimeFormatter.ofPattern(DATE_FORMAT));
+ financialTracker.addExpense(new Expense("qwe", 12.5, ExpenseCategory.FOOD, date));
+ financialTracker.addIncome(new Income("qwe", 12.5, IncomeCategory.ALLOWANCE, date));
+ financialTracker.addIncome(new Income("", 12.5, IncomeCategory.ALLOWANCE, date));
+ Ui ui = new Ui();
+ BudgetManager budgetManager = new BudgetManager();
+ DataManager dataManager = new DataManager(financialTracker, ui, budgetManager, currencyManager);
+ dataManager.saveAll();
+ dataManager.loadAll();
+ }
+
+ @Test
+ public void saveSettings_validSettings_validSettingData() {
+ CurrencyManager currencyManager = new CurrencyManager();
+ FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ Ui ui = new Ui();
+ BudgetManager budgetManager = new BudgetManager();
+ DataManager dataManager = new DataManager(financialTracker, ui, budgetManager, currencyManager);
+ int i = 80;
+ for (ExpenseCategory category : ExpenseCategory.values()) {
+ if (category == ExpenseCategory.NULL) {
+ break;
+ }
+ budgetManager.setBudget(i, category, financialTracker.getExpenses());
+ i /= 2;
+ }
+ budgetManager.setThreshold(0.5);
+ dataManager.setSettingsToWritable();
+ dataManager.saveAll();
+ String testData = convertSettingsToData(budgetManager, currencyManager);
+ String expectedData = "SGD,0.5,80.0,40.0,20.0,10.0,5.0,2.0,1.0";
+ assertEquals(expectedData, testData);
+ }
+
+ @Test
+ public void loadAll_validBudgetData_validBudgets() {
+ saveSettings_validSettings_validSettingData();
+ CurrencyManager currencyManager = new CurrencyManager();
+ FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ Ui ui = new Ui();
+ BudgetManager budgetManager = new BudgetManager();
+ DataManager dataManager = new DataManager(financialTracker, ui, budgetManager, currencyManager);
+ dataManager.loadAll();
+ int i = 80;
+ for (ExpenseCategory category : ExpenseCategory.values()) {
+ if (category == ExpenseCategory.NULL) {
+ break;
+ }
+ assertEquals(budgetManager.getBudget(category), i);
+ i /= 2;
+ }
+ }
+}
diff --git a/src/test/java/seedu/duke/ExpenseTest.java b/src/test/java/seedu/duke/ExpenseTest.java
new file mode 100644
index 0000000000..4197287313
--- /dev/null
+++ b/src/test/java/seedu/duke/ExpenseTest.java
@@ -0,0 +1,22 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ExpenseTest {
+
+ @Test
+ public void getDescription_stringDescription_returnsDescription() {
+ Expense e1 = new Expense("Bubble tea", 4.8, ExpenseCategory.FOOD);
+ assertEquals("Bubble tea", e1.getDescription());
+ }
+
+ @Test
+ public void getValue_valueInputOfTypeDouble_returnsValueInput() {
+ Expense e1 = new Expense("Bubble tea", 4.8, ExpenseCategory.FOOD);
+ assertEquals(4.8, e1.getValue());
+ }
+}
diff --git a/src/test/java/seedu/duke/FinancialTrackerTest.java b/src/test/java/seedu/duke/FinancialTrackerTest.java
new file mode 100644
index 0000000000..04c684aff7
--- /dev/null
+++ b/src/test/java/seedu/duke/FinancialTrackerTest.java
@@ -0,0 +1,112 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.ExpenseEntryNotFoundException;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeEntryNotFoundException;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+
+public class FinancialTrackerTest {
+
+ @Test
+ public void addEntry_twoExpenseObjects_expectSizeOfListToBeTwo() throws ExpenseOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addExpense(new Expense("Lunch", 5.20, ExpenseCategory.FOOD));
+ testTracker.addExpense(new Expense("Dinner", 20, ExpenseCategory.FOOD));
+ assertEquals(2, testTracker.getExpenseSize());
+ }
+
+ @Test
+ public void addIncome_twoIncomeObjects_expectSizeOfListToBeTwo() throws IncomeOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addIncome(new Income("pocket money", 5.20, IncomeCategory.ADHOC));
+ testTracker.addIncome(new Income("salary", 20, IncomeCategory.ADHOC));
+ assertEquals(2, testTracker.getIncomeSize());
+ }
+
+ @Test
+ public void removeExpense_emptyExpenseList_expectExpenseEntryNotFoundException() throws ExpenseOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addExpense(new Expense("Lunch", 5.20, ExpenseCategory.FOOD));
+ assertThrows(ExpenseEntryNotFoundException.class, () -> {
+ testTracker.removeExpense(4);
+ });
+ }
+
+ @Test
+ public void removeIncome_emptyIncomeList_expectIncomeEntryNotFoundException() throws IncomeOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addIncome(new Income("pocket money", 5.20, IncomeCategory.ADHOC));
+ assertThrows(IncomeEntryNotFoundException.class, () -> {
+ testTracker.removeIncome(4);
+ });
+ }
+
+ @Test
+ public void getMonthlyIncomeBreakdown_IncomeList_expectTotalAccumulatedIncome() throws IncomeOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addIncome(new Income("pocket money", 5.20, IncomeCategory.ALLOWANCE));
+ testTracker.addIncome(new Income("salary", 100, IncomeCategory.ADHOC));
+ ArrayList totalIncome = testTracker.getMonthlyIncomeBreakdown(2021);
+ assertEquals(105.20, totalIncome.get(LocalDate.now().getMonthValue() - 1));
+ }
+
+ @Test
+ public void getMonthlyExpenseBreakdown_ExpenseList_expectTotalAccumulatedExpense() throws ExpenseOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addExpense(new Expense("lunch", 5.20, ExpenseCategory.FOOD));
+ testTracker.addExpense(new Expense("dinner", 100, ExpenseCategory.FOOD));
+ ArrayList totalExpense = testTracker.getMonthlyExpenseBreakdown(2021);
+ System.out.println(totalExpense);
+ assertEquals(105.20, totalExpense.get(LocalDate.now().getMonthValue() - 1));
+ }
+
+ @Test
+ public void addIncome_InvalidLargeIncomeValue_expectIncomeOverflowException() throws IncomeOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addIncome(new Income("salary", 50000000000.0, IncomeCategory.SALARY));
+ assertThrows(IncomeOverflowException.class, () -> {
+ testTracker.addIncome(new Income("salary", 50000000001.0, IncomeCategory.SALARY));
+ });
+ }
+
+ @Test
+ public void addExpense_InvalidLargeExpenseValue_expectExpenseOverflowException() throws ExpenseOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addExpense(new Expense("food", 50000000000.0, ExpenseCategory.FOOD));
+ assertThrows(ExpenseOverflowException.class, () -> {
+ testTracker.addExpense(new Expense("food", 50000000001.0, ExpenseCategory.FOOD));
+ });
+ }
+
+ @Test
+ public void addExpense_LargeExpenseValue_validTotalExpense() throws ExpenseOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addExpense(new Expense("food", 50000000000.0, ExpenseCategory.FOOD));
+ testTracker.addExpense(new Expense("food", 10.0, ExpenseCategory.FOOD));
+ testTracker.addExpense(new Expense("food", 5.0, ExpenseCategory.FOOD));
+ assertEquals(50000000015.00, testTracker.getTotalExpense());
+ }
+
+ @Test
+ public void addIncome_LargeIncomeValue_validTotalIncome() throws IncomeOverflowException {
+ FinancialTracker testTracker = new FinancialTracker(new CurrencyManager());
+ testTracker.addIncome(new Income("Income", 90000000000.0, IncomeCategory.SALARY));
+ testTracker.addIncome(new Income("Income", 9999999998.0, IncomeCategory.SALARY));
+ testTracker.addIncome(new Income("Income", 1, IncomeCategory.SALARY));
+ assertEquals(99999999999.00, testTracker.getTotalIncome());
+ }
+}
diff --git a/src/test/java/seedu/duke/IncomeTest.java b/src/test/java/seedu/duke/IncomeTest.java
new file mode 100644
index 0000000000..96462cd3f2
--- /dev/null
+++ b/src/test/java/seedu/duke/IncomeTest.java
@@ -0,0 +1,22 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class IncomeTest {
+
+ @Test
+ public void getDescription_stringDescription_returnsDescription() {
+ Income i1 = new Income("Jan Allowance", 480.00, IncomeCategory.ALLOWANCE);
+ assertEquals("Jan Allowance", i1.getDescription());
+ }
+
+ @Test
+ public void getValue_valueInputOfTypeDouble_returnsValueInput() {
+ Income i1 = new Income("Jan Allowance", 480.00, IncomeCategory.ALLOWANCE);
+ assertEquals(480.00, i1.getValue());
+ }
+}
diff --git a/src/test/java/seedu/duke/ParserTest.java b/src/test/java/seedu/duke/ParserTest.java
new file mode 100644
index 0000000000..4592d62f11
--- /dev/null
+++ b/src/test/java/seedu/duke/ParserTest.java
@@ -0,0 +1,265 @@
+package seedu.duke;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.junit.jupiter.api.Test;
+import seedu.commands.Command;
+import seedu.commands.budget.BalanceCommand;
+import seedu.commands.budget.SetThresholdCommand;
+import seedu.commands.expense.AddExpenseCommand;
+import seedu.commands.general.ClearAllEntriesCommand;
+import seedu.commands.general.ShowGraphByYearCommand;
+import seedu.commands.general.ShowGraphCommand;
+import seedu.commands.income.AddIncomeCommand;
+import seedu.commands.income.DeleteIncomeCommand;
+import seedu.commands.general.HelpCommand;
+import seedu.commands.InvalidCommand;
+import seedu.commands.expense.ListExpenseCommand;
+import seedu.commands.income.ListIncomeCommand;
+import seedu.commands.income.TotalIncomeCommand;
+import seedu.utility.Messages;
+import seedu.utility.Parser;
+
+public class ParserTest {
+ @Test
+ public void parseCommand_validHelpCommand_returnHelpCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("help");
+ assertSame(underTest.getClass(), HelpCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidListCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("list_in d");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_validListIncomeCommand_returnListIncomeCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("list_in ");
+ assertSame(underTest.getClass(), ListIncomeCommand.class);
+ }
+
+ @Test
+ public void parseCommand_validListExpenseCommand_returnListIncomeCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("list_ex");
+ assertSame(underTest.getClass(), ListExpenseCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("tfshsdfh");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidAddExpenseCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex d/tfshsdfh a/12a c/qewew");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidAddExpenseCommandWithEmptyDescription_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex d/a/12a");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_validAddExpenseCommand_returnInvalidExpenseCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex d/ tfshsdfh a/ 123 c/2wq2");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidDeleteIncomeCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("del_in i/12a ");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidDeleteExpenseCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("del_ex i/12a ");
+ InvalidCommand test = (InvalidCommand) underTest;
+ assertSame("Index given is either out of range or not an integer.", test.getMessage());
+ }
+
+ @Test
+ public void parseCommand_validDeleteIncomeCommand_returnDeleteIncomeCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("del_in i/ 12 ");
+ assertSame(underTest.getClass(), DeleteIncomeCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidTotalIncomeCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("total_in fddgf");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidTotalExpenseCommand_returnInvalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("total_ex fddgf");
+ assertSame(underTest.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseCommand_validTotalIncomeCommand_returnTotalIncomeCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("total_in ");
+ assertSame(underTest.getClass(), TotalIncomeCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidInput_correctOutputWarningMessage() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("gfsbsfbgfsbfgs");
+ InvalidCommand test = (InvalidCommand) underTest;
+ assertSame("Invalid command. Use \"help\" to show the list of possible commands.", test.getMessage());
+ }
+
+ @Test
+ public void parseCommand_invalidDeleteIncomeCommand_correctOutputWarningMessage() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("del_in i/aa");
+ InvalidCommand test = (InvalidCommand) underTest;
+ assertSame("Index given is either out of range or not an integer.", test.getMessage());
+ }
+
+ @Test
+ public void parseCommand_invalidAddIncomeCommand_correctOutputWarningMessage() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_in d/buy book a/aa c/qwqe");
+ InvalidCommand test = (InvalidCommand) underTest;
+ assertSame("Only numeric inputs are allowed for amount.", test.getMessage());
+ }
+
+ @Test
+ public void parseCommand_invalidExitCommand_correctOutputWarningMessage() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("bye");
+ InvalidCommand test = (InvalidCommand) underTest;
+ assertSame("Invalid command. Use \"help\" to show the list of possible commands.", test.getMessage());
+ }
+
+ @Test
+ public void parseCommand_expenseInputWithDate_validCommand() {
+ Parser testParser = new Parser();
+ String userInput = "add_ex_d D/11/11/2121 d/asf a/10 c/food";
+ Command testCommand = testParser.parseCommand(userInput);
+ assertEquals(testCommand.getClass(), AddExpenseCommand.class);
+ }
+
+ @Test
+ public void parseCommand_incomeInputWithoutDate_validCommand() {
+ Parser testParser = new Parser();
+ String userInput = "add_in c/salary d/a/g/adg/ad/gd/fag/ a/10";
+ Command testCommand = testParser.parseCommand(userInput);
+ assertEquals(testCommand.getClass(), AddIncomeCommand.class);
+ }
+
+ @Test
+ public void betweenIncome_invalidDateRange_invalidCommand() {
+ Parser testParser = new Parser();
+ String userInput = "btw_in s/11/12/2100 e/11/11/2100";
+ Command testCommand = testParser.parseCommand(userInput);
+ assertEquals(testCommand.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void betweenExpense_invalidDateRange_invalidCommand() {
+ Parser testParser = new Parser();
+ String userInput = "btw_ex s/11/12/2100 e/11/11/2100";
+ Command testCommand = testParser.parseCommand(userInput);
+ assertEquals(testCommand.getClass(), InvalidCommand.class);
+ }
+
+ @Test
+ public void parseAddExpenseCommand_invalidExpenseValue_invalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex d/food a/1000000.001 c/food");
+ assertSame(InvalidCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseAddIncomeCommand_invalidIncomeValue_invalidCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_in d/salary a/1000000.0011 c/salary");
+ assertSame(InvalidCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validThresholdInput_correctCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("set_threshold t/0.5");
+ assertSame(SetThresholdCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validClearAllCommand_correctCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("clear_all_entries");
+ assertSame(ClearAllEntriesCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validShowGraphCommand_correctCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("show_graph");
+ assertSame(ShowGraphCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validYearInputShowGraphCommand_correctCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("show_graph Y/ 2021 ");
+ assertSame(ShowGraphByYearCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_invalidYearInputShowGraphCommand_correctCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("show_graph Y/2023 12as d v ");
+ assertEquals(Messages.INVALID_YEAR_MESSAGE,((InvalidCommand)underTest).getMessage());
+ }
+
+ @Test
+ public void parseCommand_validExpenseInputD_C_A_validCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex d//fa/gd/ff/s/f/sf/s/f/ c/food a/100");
+ assertEquals(AddExpenseCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validExpenseInputC_D_A_validCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex c/food d//fa/gd/ff/s/f/sf/s/f/ a/100");
+ assertEquals(AddExpenseCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validExpenseInputA_C_D_validCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("add_ex a/100 c/food d//fa/gd/ff/s/f/sf/s/f/");
+ assertEquals(AddExpenseCommand.class, underTest.getClass());
+ }
+
+ @Test
+ public void parseCommand_validBalanceCommand_validBalanceCommand() {
+ Parser testParser = new Parser();
+ Command underTest = testParser.parseCommand("balance");
+ assertEquals(BalanceCommand.class, underTest.getClass());
+ }
+}
diff --git a/src/test/java/seedu/duke/TotalExpenseBetweenCommandTest.java b/src/test/java/seedu/duke/TotalExpenseBetweenCommandTest.java
new file mode 100644
index 0000000000..45b08a075c
--- /dev/null
+++ b/src/test/java/seedu/duke/TotalExpenseBetweenCommandTest.java
@@ -0,0 +1,41 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Expense;
+import seedu.entry.ExpenseCategory;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TotalExpenseBetweenCommandTest {
+ CurrencyManager currencyManager = new CurrencyManagerStub();
+ FinancialTracker testTracker = new FinancialTracker(currencyManager);
+
+ @Test
+ public void execute_twoValidDateInputs_validTotalSum() throws ExpenseOverflowException {
+ LocalDate startDate = LocalDate.parse("20/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ LocalDate endDate = LocalDate.parse("29/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ Expense testExpense1 = new Expense("Salary", 400.00, ExpenseCategory.FOOD, startDate);
+ Expense testExpense2 = new Expense("Allowance", 400.00, ExpenseCategory.MISC, endDate);
+ testTracker.addExpense(testExpense1);
+ testTracker.addExpense(testExpense2);
+ assertEquals(800.00, testTracker.getExpenseBetween(startDate,endDate));
+ }
+
+ @Test
+ public void execute_twoValidDateInputs_TotalSumZero() throws ExpenseOverflowException {
+ LocalDate startDate = LocalDate.parse("28/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ LocalDate endDate = LocalDate.parse("29/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ Expense testExpense1 = new Expense("Salary", 400.00, ExpenseCategory.FOOD);
+ Expense testExpense2 = new Expense("Allowance", 400.00, ExpenseCategory.MISC);
+ testTracker.addExpense(testExpense1);
+ testTracker.addExpense(testExpense2);
+ assertEquals(0.00, testTracker.getExpenseBetween(startDate,endDate));
+ }
+
+}
diff --git a/src/test/java/seedu/duke/TotalIncomeBetweenCommandTest.java b/src/test/java/seedu/duke/TotalIncomeBetweenCommandTest.java
new file mode 100644
index 0000000000..62b3c72e55
--- /dev/null
+++ b/src/test/java/seedu/duke/TotalIncomeBetweenCommandTest.java
@@ -0,0 +1,41 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.Test;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TotalIncomeBetweenCommandTest {
+ CurrencyManager currencyManager = new CurrencyManagerStub();
+ FinancialTracker testTracker = new FinancialTracker(currencyManager);
+
+ @Test
+ public void execute_twoValidDateInputs_validTotalSum() throws IncomeOverflowException {
+ LocalDate startDate = LocalDate.parse("20/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ LocalDate endDate = LocalDate.parse("29/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+
+ Income testIncome1 = new Income("Salary", 400.00, IncomeCategory.ALLOWANCE, startDate);
+ Income testIncome2 = new Income("Allowance", 400.00, IncomeCategory.ALLOWANCE, endDate);
+ testTracker.addIncome(testIncome1);
+ testTracker.addIncome(testIncome2);
+ assertEquals(800.00, testTracker.getIncomeBetween(startDate,endDate));
+ }
+
+ @Test
+ public void execute_twoValidDateInputs_TotalSumZero() throws IncomeOverflowException {
+ LocalDate startDate = LocalDate.parse("28/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ LocalDate endDate = LocalDate.parse("29/10/2021", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ Income testIncome1 = new Income("Salary", 400.00, IncomeCategory.ALLOWANCE);
+ Income testIncome2 = new Income("Allowance", 400.00, IncomeCategory.ALLOWANCE);
+ testTracker.addIncome(testIncome1);
+ testTracker.addIncome(testIncome2);
+ assertEquals(0.00, testTracker.getIncomeBetween(startDate,endDate));
+ }
+}
diff --git a/src/test/java/seedu/duke/UiTest.java b/src/test/java/seedu/duke/UiTest.java
new file mode 100644
index 0000000000..a3ce389023
--- /dev/null
+++ b/src/test/java/seedu/duke/UiTest.java
@@ -0,0 +1,483 @@
+package seedu.duke;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.commands.currency.CheckCurrentCurrencyCommand;
+import seedu.commands.currency.CurrencyConversionCommand;
+import seedu.commands.currency.CurrencyType;
+import seedu.commands.currency.ListCurrencyTypesCommand;
+import seedu.commands.general.FindCommand;
+import seedu.entry.Entry;
+import seedu.entry.Income;
+import seedu.entry.IncomeCategory;
+import seedu.entry.ExpenseCategory;
+import seedu.entry.Expense;
+import seedu.exceptions.ExpenseOverflowException;
+import seedu.exceptions.IncomeOverflowException;
+import seedu.utility.CurrencyManager;
+import seedu.utility.FinancialTracker;
+import seedu.utility.Messages;
+import seedu.utility.StonksGraph;
+import seedu.utility.Ui;
+import seedu.utility.BudgetManager;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class UiTest {
+ private final PrintStream standardOut = System.out;
+ private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
+ private final String newLine = System.lineSeparator();
+
+
+
+ @BeforeEach
+ public void setUp() {
+ System.setOut(new PrintStream(outputStreamCaptor));
+ }
+
+ @AfterEach
+ public void tearDown() {
+ System.setOut(standardOut);
+ }
+
+ private static final String SEPARATOR_LINE = "-------------------------------------------------------------------"
+ + "----------------------------------";
+ private static final String currentDate =
+ "(" + LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) + ")";
+ private static final String DATE_FORMAT = "dd/MM/yyyy";
+
+
+ private final Ui testUI = new Ui();
+ private CurrencyManager currencyManager = new CurrencyManager();
+ private final FinancialTracker financialTracker = new FinancialTracker(currencyManager);
+ private BudgetManager budgetManager = new BudgetManager();
+
+ public void initialiseFinancialTracker() throws IncomeOverflowException, ExpenseOverflowException {
+ financialTracker.addIncome(new Income("Paycheck August", 25.0, IncomeCategory.SALARY));
+ financialTracker.addExpense(new Expense("Bought a game", 19.73, ExpenseCategory.FOOD));
+ financialTracker.addExpense(new Expense("Bought cookies", 5.0, ExpenseCategory.FOOD));
+ financialTracker.addExpense(new Expense("Bought cakes", 7.0, ExpenseCategory.FOOD));
+ financialTracker.addIncome(new Income("Rob a bank", 2000.0, IncomeCategory.ADHOC));
+ financialTracker.addIncome(new Income("Paycheck July", 25.0, IncomeCategory.SALARY));
+ }
+
+ @Test
+ public void listExpense_validFinancialTracker_filteredExpenses()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ initialiseFinancialTracker();
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all of your recent spending!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + "2: [E] Bought cookies - $5.00 " + currentDate + newLine
+ + "3: [E] Bought cakes - $7.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+
+ testUI.listExpense(financialTracker.getExpenses());
+
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void listIncome_validFinancialTracker_filteredIncomes()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ initialiseFinancialTracker();
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all of your recent earnings!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [I] Paycheck August - $25.00 " + currentDate + newLine
+ + "2: [I] Rob a bank - $2000.00 " + currentDate + newLine
+ + "3: [I] Paycheck July - $25.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+
+ testUI.listIncome(financialTracker.getIncomes());
+
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void listFind_givenFilteredList_printFilteredList()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ initialiseFinancialTracker();
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + Messages.FOUND_LIST_MESSAGE + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + "2: [E] Bought cookies - $5.00 " + currentDate + newLine
+ + "3: [E] Bought cakes - $7.00 " + currentDate + newLine
+ + "4: [I] Paycheck August - $25.00 " + currentDate + newLine
+ + "5: [I] Rob a bank - $2000.00 " + currentDate + newLine
+ + "6: [I] Paycheck July - $25.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+
+ testUI.listFind(financialTracker.getEntries());
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printExpenseAdded_addedOneItem_expenseAddedFeedback() {
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your most recent spending: " + newLine
+ + "[E] Bought cookies - $5.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+
+ testUI.printExpenseAdded(new Expense("Bought cookies", 5.0, ExpenseCategory.FOOD));
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printIncomeAdded_addedOneItem_incomeAddedFeedback() {
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your most recent earning: " + newLine
+ + "[I] Salary August - $5.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ testUI.printIncomeAdded(new Income("Salary August", 5.0, IncomeCategory.SALARY));
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printBalance_oneExpenseOneIncome_printNetBalance()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ initialiseFinancialTracker();
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your current balance is: $2018.27" + newLine
+ + SEPARATOR_LINE;
+
+ testUI.printBalance(financialTracker.calculateBalance());
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void listExpense_emptyFinancialTracker_emptyExpenseListMessage() {
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "You have not spent anything!" + newLine
+ + SEPARATOR_LINE;
+
+ testUI.listExpense(financialTracker.getExpenses());
+
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void listIncome_emptyFinancialTracker_emptyIncomeListMessage() {
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "You have not entered any income!" + newLine
+ + SEPARATOR_LINE;
+
+ testUI.listIncome(financialTracker.getIncomes());
+
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void listFind_emptyFilteredList_notFoundMessage() {
+ final String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your search did not match any of the entries!" + newLine
+ + SEPARATOR_LINE;
+ ArrayList entries = new ArrayList<>();
+ testUI.listFind(entries);
+
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+
+ @Test
+ public void printTotalExpense_doubleExpense_totalExpenseMessage() {
+ double totalExpense = 98.72923;
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your total expense is: $98.73" + newLine
+ + SEPARATOR_LINE;
+ testUI.printTotalExpense(totalExpense);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printTotalIncome_doubleIncome_totalIncomeMessage() {
+ double totalIncome = 98.72923;
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your total income is: $98.73" + newLine
+ + SEPARATOR_LINE;
+ testUI.printTotalIncome(totalIncome);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void expenseDeleted_oneExpenseDeleted_deletedExpenseMessage()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ initialiseFinancialTracker();
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "You removed this: " + newLine
+ + "[E] chocolate - $56.12 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ Expense toBeDeletedExpense = new Expense("chocolate", 56.12, ExpenseCategory.FOOD);
+ testUI.printExpenseDeleted(toBeDeletedExpense);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void incomeDeleted_oneIncomeDeleted_deletedIncomeMessage()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ initialiseFinancialTracker();
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "You removed this: " + newLine
+ + "[I] august paycheck - $567.12 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ Income toBeDeletedIncome = new Income("august paycheck", 567.12, IncomeCategory.SALARY);
+ testUI.printIncomeDeleted(toBeDeletedIncome);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printTotalExpenseBetween_noExpenseBetween_printNoExpenseBetweenMessage() {
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "You do not have any expense between 30/08/2090 and 30/08/2092" + newLine
+ + SEPARATOR_LINE;
+ LocalDate testDate1 = LocalDate.of(2090,8,30);
+ LocalDate testDate2 = LocalDate.of(2092,8,30);
+
+ double totalExpense = 0;
+ testUI.printTotalExpenseBetween(totalExpense, testDate1, testDate2);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printTotalExpenseBetween_gotExpenseBetween_printTotalExpenseBetweenMessage() {
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your total expense between 30/08/2090 and 30/08/2092 is $7512.00" + newLine
+ + SEPARATOR_LINE;
+ LocalDate testDate1 = LocalDate.of(2090,8,30);
+ LocalDate testDate2 = LocalDate.of(2092,8,30);
+
+ double totalExpense = 7512;
+ testUI.printTotalExpenseBetween(totalExpense, testDate1, testDate2);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printTotalIncomeBetween_noIncomeBetween_printNoIncomeBetweenMessage() {
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "You do not have any income between 30/08/2090 and 30/08/2092" + newLine
+ + SEPARATOR_LINE;
+ LocalDate testDate1 = LocalDate.of(2090,8,30);
+ LocalDate testDate2 = LocalDate.of(2092,8,30);
+
+ double totalIncome = 0;
+ testUI.printTotalIncomeBetween(totalIncome, testDate1, testDate2);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printTotalIncomeBetween_gotIncomeBetween_printTotalIncomeBetweenMessage() {
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your total income between 30/08/2090 and 30/08/2092 is $988.10" + newLine
+ + SEPARATOR_LINE;
+ LocalDate testDate1 = LocalDate.of(2090,8,30);
+ LocalDate testDate2 = LocalDate.of(2092,8,30);
+
+ double totalIncome = 988.1;
+ testUI.printTotalIncomeBetween(totalIncome, testDate1, testDate2);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printGraph_validStonksGraph_printCorrectGraph() {
+ int currentYear = LocalDate.now().getYear();
+ //empty financialtracker
+ StonksGraph stonksGraph = new StonksGraph(financialTracker,currentYear);
+ String expectedOutput = SEPARATOR_LINE
+ + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ + "x x"
+ + "x Account Balance: $0.00 Legend: x"
+ + "x Current month total expense: $0.00 # is Expense x"
+ + "x Current month total income: $0.00 o is Income x"
+ + "x Year Report Unit: 0.01 x"
+ + "x ------------------------------------------------------------------------------------------------ x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x x"
+ + "x ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x"
+ + "x Jan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec x"
+ + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ + SEPARATOR_LINE;
+ testUI.printGraph(stonksGraph);
+
+ String fullOutput = outputStreamCaptor.toString().trim();
+ String fullOutputWithoutNewLine = fullOutput.replace(System.lineSeparator(),"");
+ String afterRemoveCurrentMonth = fullOutputWithoutNewLine.replaceAll("month.*?t","month t");
+ String afterRemoveCurrentYear = afterRemoveCurrentMonth.replaceAll("Year.*?R","Year R");
+
+ assertEquals(expectedOutput, afterRemoveCurrentYear);
+ }
+
+ @Test
+ public void printBudget_givenBudget_printBudgetMsg() {
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Current FOOD limit is $58.71" + newLine
+ + SEPARATOR_LINE;
+ testUI.printBudget(ExpenseCategory.FOOD, 58.71);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void printThresholdConfirmation_validThreshold_printThresholdMsg() {
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Threshold for budget reminders set to 0.58" + newLine
+ + "We'll warn you when you spend 58.0% of your budget!" + newLine
+ + SEPARATOR_LINE;
+ testUI.printThresholdConfirmation(0.58);
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void filterByKeyword_testFood_printOnlyFoodEntries()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ FindCommand testFindCommand = new FindCommand("food");
+ initialiseFinancialTracker();
+ testFindCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all your findings!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + "2: [E] Bought cookies - $5.00 " + currentDate + newLine
+ + "3: [E] Bought cakes - $7.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void filterByKeyword_testWordCasing_printFoodEntries()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ FindCommand testFindCommand = new FindCommand("FOod");
+ initialiseFinancialTracker();
+ testFindCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all your findings!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + "2: [E] Bought cookies - $5.00 " + currentDate + newLine
+ + "3: [E] Bought cakes - $7.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void filterByDate_dateGotMatch_printOnlyEntriesOfThatDate()
+ throws IncomeOverflowException, ExpenseOverflowException {
+ String currDate = LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ FindCommand testFindCommand = new FindCommand(currDate);
+ initialiseFinancialTracker();
+ LocalDate date = LocalDate.parse("11/11/2121", DateTimeFormatter.ofPattern(DATE_FORMAT));
+ Income incomeWithDiffDate = new Income("Paycheck August", 25.0, IncomeCategory.SALARY, date);
+ financialTracker.addIncome(incomeWithDiffDate);
+ testFindCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all your findings!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + "2: [E] Bought cookies - $5.00 " + currentDate + newLine
+ + "3: [E] Bought cakes - $7.00 " + currentDate + newLine
+ + "4: [I] Paycheck August - $25.00 " + currentDate + newLine
+ + "5: [I] Rob a bank - $2000.00 " + currentDate + newLine
+ + "6: [I] Paycheck July - $25.00 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void filterByDate_dateNoMatch_printNoEntryFound()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ FindCommand testFindCommand = new FindCommand("25/10/2099");
+ initialiseFinancialTracker();
+ testFindCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your search did not match any of the entries!" + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void filterByKeyword_matchInDescription_printEntriesFound()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ FindCommand testFindCommand = new FindCommand("game");
+ initialiseFinancialTracker();
+ testFindCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all your findings!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void filterByKeyword_matchInAmount_printEntriesFound()
+ throws ExpenseOverflowException, IncomeOverflowException {
+ FindCommand testFindCommand = new FindCommand("19.73");
+ initialiseFinancialTracker();
+ testFindCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Below is a list of all your findings!" + newLine
+ + SEPARATOR_LINE + newLine
+ + "1: [E] Bought a game - $19.73 " + currentDate + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void listCurrencyTypes_matchInCurrencyTypes_printCorrectList() {
+ ListCurrencyTypesCommand testListCurrencyTypesCommand = new ListCurrencyTypesCommand();
+ testListCurrencyTypesCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Here is a list of available currencies you can convert to!" + newLine
+ + "1. HKD" + newLine
+ + "2. SGD" + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void checkCurrencyTypes_matchInCurrentCurrencyTypes_printCorrectType() {
+ CheckCurrentCurrencyCommand testCurrentCurrencyTypeCommand = new CheckCurrentCurrencyCommand();
+ testCurrentCurrencyTypeCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your currency setting currently: SGD" + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void setCurrencyTypeHdk_matchesCurrencyTypeHdk_printConfirmationMessage() {
+ CurrencyConversionCommand testCurrencyConversionCommand = new CurrencyConversionCommand(CurrencyType.HKD);
+ testCurrencyConversionCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "All entries have been converted to HKD!" + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+
+ @Test
+ public void setCurrencyTypeSgd_matchesCurrencyTypeSgd_printSameTypeExceptionMessage() {
+ CurrencyConversionCommand testCurrencyConversionCommand = new CurrencyConversionCommand(CurrencyType.SGD);
+ testCurrencyConversionCommand.execute(financialTracker, testUI, budgetManager, currencyManager);
+ String expectedOutput = SEPARATOR_LINE + newLine
+ + "Your lists are already in the requested currency type!- SGD" + newLine
+ + SEPARATOR_LINE;
+ assertEquals(expectedOutput, outputStreamCaptor.toString().trim());
+ }
+}
+
diff --git a/text-ui-test/.gradle/6.8/executionHistory/executionHistory.lock b/text-ui-test/.gradle/6.8/executionHistory/executionHistory.lock
new file mode 100644
index 0000000000..315b48556a
Binary files /dev/null and b/text-ui-test/.gradle/6.8/executionHistory/executionHistory.lock differ
diff --git a/text-ui-test/.gradle/6.8/fileChanges/last-build.bin b/text-ui-test/.gradle/6.8/fileChanges/last-build.bin
new file mode 100644
index 0000000000..f76dd238ad
Binary files /dev/null and b/text-ui-test/.gradle/6.8/fileChanges/last-build.bin differ
diff --git a/text-ui-test/.gradle/6.8/fileHashes/fileHashes.lock b/text-ui-test/.gradle/6.8/fileHashes/fileHashes.lock
new file mode 100644
index 0000000000..a125afe7d5
Binary files /dev/null and b/text-ui-test/.gradle/6.8/fileHashes/fileHashes.lock differ
diff --git a/text-ui-test/.gradle/6.8/gc.properties b/text-ui-test/.gradle/6.8/gc.properties
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/text-ui-test/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/text-ui-test/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000000..6cf65a3bac
Binary files /dev/null and b/text-ui-test/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/text-ui-test/.gradle/buildOutputCleanup/cache.properties b/text-ui-test/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 0000000000..46983625b1
--- /dev/null
+++ b/text-ui-test/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Mon Oct 04 19:05:10 SGT 2021
+gradle.version=6.8
diff --git a/text-ui-test/.gradle/checksums/checksums.lock b/text-ui-test/.gradle/checksums/checksums.lock
new file mode 100644
index 0000000000..9cffebe184
Binary files /dev/null and b/text-ui-test/.gradle/checksums/checksums.lock differ
diff --git a/text-ui-test/.gradle/configuration-cache/gc.properties b/text-ui-test/.gradle/configuration-cache/gc.properties
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/text-ui-test/.gradle/vcs-1/gc.properties b/text-ui-test/.gradle/vcs-1/gc.properties
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..0e87fc6ce9 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,14 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
-What is your name?
-Hello James Gosling
+-----------------------------------------------------------------------------------------------------
+Unable to find StonksXD_Settings.csv, a new one has been made.
+-----------------------------------------------------------------------------------------------------
+Unable to find StonksXD_Entries.csv, a new one has been made.
+-----------------------------------------------------------------------------------------------------
+███████ ████████ ██████ ███ ██ ██ ██ ███████ ██ ██ ██████
+██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██ ██ ██ ██ ██ ██ █████ ███████ ███ ██ ██
+ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+███████ ██ ██████ ██ ████ ██ ██ ███████ ██ ██ ██████
+-----------------------------------------------------------------------------------------------------
+Would you like to set your budget before you begin?
+You can use the set budget commands shown in the help command!
+-----------------------------------------------------------------------------------------------------
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..e32b0df9c6 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1 @@
-James Gosling
\ No newline at end of file
+end
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 25ac7a2989..23053e68dc 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -16,4 +16,4 @@ java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TX
cd ..\..\text-ui-test
-FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed!
+FC EXPECTED.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed!
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
index 1dcbd12021..0ec9903ff7 100755
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -8,6 +8,16 @@ cd ..
cd text-ui-test
+if [ -e "./StonksXD_Entries.csv" ]
+then
+ rm StonksXD_Entries.csv
+fi
+
+if [ -e "./StonksXD_Settings.csv" ]
+then
+ rm StonksXD_Budget.csv
+fi
+
java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT
cp EXPECTED.TXT EXPECTED-UNIX.TXT