diff --git a/.gitignore b/.gitignore index 2873e189e1..399094d68f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,9 @@ src/main/resources/docs/ *.iml bin/ -/text-ui-test/ACTUAL.TXT -text-ui-test/EXPECTED-UNIX.TXT +ACTUAL*.TXT +EXPECTED*-UNIX.TXT + +.vscode/ +data/ +log/ \ No newline at end of file diff --git a/README.md b/README.md index f82e2494b7..5f0db2e280 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,22 @@ -# Duke project template +# Long Ah! -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. +![LongAh logo.jpg](docs%2Fdiagrams%2FLongAh%20logo.jpg) -## Setting up in Intellij +## Introduction -Prerequisites: JDK 11 (use the exact version), update Intellij to the most recent version. +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the +least transaction method of settling these debts. It is optimized for busy people with large transaction quantities +among friends. -1. **Ensure Intellij JDK 11 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 11 in a previous Intellij project. -1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -1. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: - ``` - > Task :compileJava - > Task :processResources NO-SOURCE - > Task :classes - - > Task :Duke.main() - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - - What is your name? - ``` - Type some word and press enter to let the execution proceed to the end. +## Quick Start -## Build automation using Gradle - -* This project uses Gradle for build automation and dependency management. It includes a basic build script as well (i.e. the `build.gradle` file). -* If you are new to Gradle, refer to the [Gradle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/gradle.html). - -## Testing - -### I/O redirection tests - -* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script. - -### JUnit tests - -* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template. -* If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html). - -## Checkstyle - -* A sample CheckStyle rule configuration is provided in this project. -* If you are new to Checkstyle, refer to the [Checkstyle Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/checkstyle.html). - -## CI using GitHub Actions - -The project uses [GitHub actions](https://github.com/features/actions) for CI. When you push a commit to this repo or PR against it, GitHub actions will run automatically to build and verify the code as updated by the commit/PR. - -## Documentation - -`/docs` folder contains a skeleton version of the project documentation. - -Steps for publishing documentation to the public: -1. If you are using this project template for an individual project, go your fork on GitHub.
- If you are using this project template for a team project, go to the team fork on GitHub. -1. Click on the `settings` tab. -1. Scroll down to the `GitHub Pages` section. -1. Set the `source` as `master branch /docs folder`. -1. Optionally, use the `choose a theme` button to choose a theme for your documentation. +1. Ensure that you have Java 11 or above installed. +2. Download the latest version of `LongAh!` from [here](https://github.com/AY2324S2-CS2113-T15-1/tp/releases). +3. Copy the JAR file to the folder you want to use as the home folder for your LongAh! application. +4. Open a command terminal, navigate to the folder containing the JAR file and run the command: +``` +java -jar tp.jar +``` +5. Upon starting the application, you will be prompted to enter your PIN. The user PIN is required to access the application. + The app will prompt you to create your own PIN if it is your first time using the application. +6. You can now start using LongAh! by entering commands into the command terminal. \ No newline at end of file diff --git a/build.gradle b/build.gradle index ea82051fab..aacffff4ba 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + implementation 'org.knowm.xchart:xchart:3.8.1' } test { @@ -29,11 +30,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("longah.LongAh") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("longah") archiveClassifier.set("") } @@ -43,4 +44,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..d8371b4ca0 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -2,8 +2,8 @@ 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) +![](https://via.placeholder.com/100.png?text=Photo) | Leong Deng Jun | [Github](https://github.com/djleong01) | [Portfolio](team/djleong01.md) +![](https://via.placeholder.com/100.png?text=Photo) | Sim Justin | [Github](https://github.com/1simjustin) | [Portfolio](team/1simjustin.md) +![](https://via.placeholder.com/100.png?text=Photo) | Chew Jing Xiang | [Github](https://github.com/jing-xiang) | [Portfolio](team/jing-xiang.md) +![](https://via.placeholder.com/100.png?text=Photo) | Liao Jingyu | [Github](https://github.com/FeathersRe) | [Portfolio](team/feathersre.md) +![](https://via.placeholder.com/100.png?text=Photo) | Wu Hao Wern | [Github](https://github.com/haowern98) | [Portfolio](team/haowern98.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..f019c6f6a7 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,1105 @@ # Developer Guide +## Table of Contents +- [Developer Guide](#developer-guide) + - [Table of Contents](#table-of-contents) + - [Acknowledgements](#acknowledgements) + - [Design \& Implementation](#design--implementation) + - [UI and I/O](#ui-and-io) + - [Commands](#commands) + - [Storage](#storage) + - [Group and GroupList](#group-and-grouplist) + - [Member and MemberList](#member-and-memberlist) + - [Transaction and TransactionList](#transaction-and-transactionlist) + - [DateTime](#datetime) + - [PIN](#pin) + - [Chart](#chart) + - [Exceptions and Logging](#exceptions-and-logging) + - [User Stories](#user-stories) + - [Product scope](#product-scope) + - [Target user profile](#target-user-profile) + - [Value proposition](#value-proposition) + - [Non-Functional Requirements](#non-functional-requirements) + - [Glossary](#glossary) + - [Instructions for Testing](#instructions-for-testing) + - [Manual Testing](#manual-testing) + - [JUnit Testing](#junit-testing) + - [Text UI Testing](#text-ui-testing) + - [Future Enhancements](#future-enhancements) + +
+ ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +LongAh uses the following libraries: + +1. [XChart](https://knowm.org/open-source/xchart/) - Used for generating charts to visualize data. + +LongAh uses the following tools for development: + +1. [JUnit 5](https://junit.org/junit5/) - Used for testing. +2. [Gradle](https://gradle.org/) - Used for build automation. + +## Design & Implementation + +The UML diagram below provides an overview of the classes and their interactions within the LongAh application. + +![Main UML](diagrams/main.png) + +
+ +The high-level overview of the application is provided in the flowchart below as well. + +![Flowchart](diagrams/Flowchart.png) + +
+ +Design and Implementation has been broken down into the subsequent sections, each tagged for ease of reference: + +1. [UI and I/O](#ui-and-io) +2. [Commands](#commands) +3. [Storage](#storage) +4. [Group and GroupList](#group-and-grouplist) +5. [Member and MemberList](#member-and-memberlist) +6. [Transaction and TransactionList](#transaction-and-transactionlist) +7. [DateTime](#datetime) +8. [PIN](#pin) +9. [Chart](#chart) +10. [Exceptions and Logging](#exceptions-and-logging) + +### UI and I/O + +Overview + +The UI and I/O functionalities act as the interface between the user and the application. They are managed by the `UI` and `InputHandler` classes, respectively, with `UI` handling displaying messages to the user and reading user input, while `InputHandler` is responsible for parsing user input and returning the corresponding `Command` object. + +Class Structure + +The `UI` class has the following static attributes: + +* *SEPARATOR*: A constant string representing a straight line to be printed to the console. +* *scanner*: A `Scanner` object used for reading from `System.in` I/O. + +The `InputHandler` class itself does not have any attributes. + +Methods + +The `UI` class has the following key methods: + +* *getUserInput*: Reads the user input from the console and returns it as a String. +* *showMessage*: Displays the provided message to the user. This is overloaded to take either a String or a String and a boolean. The latter is used to define whether a newline should be printed at the end of the String. Newline is printed by default. + +The `InputHandler` class has the following key method: + +* *parseInput*: Parses the user input and returns the corresponding `Command` object. + +
+ +Design Considerations + +* `UI` class is used as part of exception handling for displaying of error messages to the user for feedback. + +### Commands + +Overview + +The abstract `Command` class has been implemented to introduce an additional layer of abstraction between I/O and command execution, allowing for separation of handling command keywords and executing commands. + +The `Command` class has been subdivided into further packages for similar commands, such as `AddCommand` and `EditCommand`. There are other niche children classes that have not been aggregated into a package as well. + +Implementation Details + +The following diagram is an inheritance diagram for `Command` and its children classes. This has been heavily simplified and only shows the key commands. + +![Command Inheritance Diagram](diagrams/CommandInheritance.png) + +
+ +The following diagram is a sequence diagram for execution of `Command`. + +![Command Execution Sequence Diagram](diagrams/CommandExecutionSequenceDiagram.png) + +Class Structure + +The abstract `Command` class and its related children classes have the following attributes: + +* *CommandString*: String indicating the command being parsed. +* *TaskExpression*: String containing details for the command to effect. + +Constructor + +The `Command` constructor updates the attributes based on the input arguments. + +Methods + +The abstract `Command` class and its related children classes have the following method: + +* *execute*: Effect the command based on the `CommandString` and the `TaskExpression`. + +
+ +### Storage + +Overview + +The `StorageHandler` class is responsible for managing the loading and saving of data regarding members and transactions from and onto the local machine. Each `Group` calls its own `StorageHandler` object such that they maintain distinct storage directories. + +Implementation Details + +Each `StorageHandler` instance creates `members.txt` and `transactions.txt` in their respective subdirectories based on the name of the `Group`. The file formats are as follows, with samples provided. + +* `members.txt` + +``` +NAME | BALANCE +``` + +![Sample Members File](diagrams/MembersFileSample.png) + +* `transactions.txt` + +``` +LENDER NAME | TRANSACTION TIME(if applicable) | BORROWER1 NAME | AMOUNT1 | ... +``` + +![Sample Transactions File](diagrams/TransactionsFileSample.png) + +
+ +The following diagram is a sequence diagram of the initialisation of `StorageHandler`. Here, it reads data from the 2 data storage files and creates `Member` and `Transaction` objects in the associated utility list objects. + +![StorageHandler Init Sequence Diagram](diagrams/StorageHandlerInitSequenceDiagram.png) + +Class Structure + +The `StorageHandler` has the following attributes: + +* *storageFolderPath*: A string containing the path to the storage directory specific to the group. +* *storageMembersFilePath*: A string containing the path to the `members.txt` directory associated with the group. +* *storageTransactionsFilePath*: A string containing the path to the `transactions.txt` directory associated with the group. +* *membersFile*: A File object representing the `members.txt` file associated with the group. +* *transactionsFile*: A File object representing the `transactions.txt` file associated with the group. +* *members*: A MemberList object representing the list of Members in the group. +* *transactions*: A TransactionList object representing the list of Transactions in the group. +* *scanners*: A size 2 array of Scanners to be used for loading data from the data storage files. The first Scanner in the array is used for reading from `members.txt` while the second is used for reading from `transactions.txt`. + +Constructor + +The `StorageHandler` constructor creates the relevant data storage directories if they do not current exist while initializing the attributes of the object. + +Key arguments for the constructor are a `MemberList` object, a `TransactionList` object and a string `groupName`. The first two are used to represent the list of `Member` objects and the list of `Transaction` objects associated with the group for reference when loading or saving data. The last represents the directory to be written to ensure that data across groups are kept discrete. + +Methods + +* *loadMembersData*: Reads data from `membersFile` and unpacks it before inserting `Member` objects into `MemberList`. +* *loadTransactionsData*: Reads data from `transactionsFile` and unpacks it, checking if each member exists in `MemberList` before inserting `Transaction` objects into `TransactionList`. +* *saveMembersData*: Writes packaged data from each `Member` and saves it as a record in `membersFile`. +* *saveTransactionsData*: Writes packaged data from each `Transaction` and saves it as a record in `transactionsFile`. + +Data loading methods are merged in the *loadAllData* method while data saving methods are merged in the *saveAllData* method. + +Usage Example + +The following code segment outlines the use of `StorageHandler`. + +``` +import longah.util.MemberList; +import longah.util.TransactionList; + +// Initialization and loading from storage +MemberList members = new MemberList(); +TransactionList transactions = new TransactionList(); +String name = "foo"; +StorageHandler storage = new StorageHandler(members, transactions, name); + +/* +At this point a subdirectory "foo" will be created if it did not previously exist +Data is loaded from "foo" to members and transactions +Assume functions modifying members and transactions are called following that. +*/ + +// Writing to storage +storage.saveAllData(); +``` + +
+ +Design Considerations + +* Update upon change, not upon exit - This allows for data to be saved even if the application exits ungracefully. +* *checkTransactions* - Methods are provided to have a quick check to ensure that data from data storage is not corrupted. + +### Group and GroupList + +Overview + +The `Group` class is used to represent a group of people who have transactions among themselves. The `GroupList` class is used to represent a list of groups +stored in the application. + +Class Structure + +The `Group` class has the following attributes. +* *memberList*: A MemberList object representing the list of Members in the group. +* *transactionList*: A TransactionList object representing the list of Transactions in the group. +* *storage*: A StorageHandler object representing the storage handler for the group. +* *groupName*: A string representing the name of the group. +* *transactionSolution*: An array list collection of Subtransaction objects representing the least transactions solution to solving all debts in the group. + +The `GroupList` class has the following static fields. +* *GROUP_LIST_FILE_PATH*: The path to the file where the group list is stored. +* *activeGroup*: A Group object representing the currently active group. +* *groupList*: An array list collection of Group objects representing the list of groups stored in the application. + +
+ +Implementation Details + +The detailed class diagram for `Group` and `GroupList` can be found below. + +![Group Class Diagram](diagrams/GroupClass.png) + +Constructor + +The `Group` constructor creates a group object with the given group name and initializes a new member list, transaction list, storage handler. The latter is used to ensure that data across groups are kept discrete. + +Key arguments of the Group constructor is a string `groupName`. + +The `GroupList` constructor initializes an empty array list of groups for newly created groups to be added and stored to. + +
+ +Methods + +The `Group` class has the following key methods. +* *updateTransactionSolution*: Updates the current transaction solution of the group based on the current list of transactions. +* *settleUp*: Settles the debt of the specified member by creating a transaction with the lender(s) based on the transaction solution of the group. +* *saveAllData*: Saves the current member and transaction data of the group to the storage handler. +* *listDebts*: Returns a string representation of the current transaction solution to all debts in the group. +* *listIndivDebt*: Returns a string representation of the current transaction solution to the debt of a specified member in the group. + +The `GroupList` class has the following key methods. +* *switchActiveGroup*: Switches the active group to the group with the specified name. This method is used when the user wants to switch to manage a different group. +* *createGroup*: Prompts user to enter a new group name and creates a new group with the specified name. Automatically sets it as the active group. +* *loadGroupList*: Loads the list of groups stored in the application from the storage handler. +* *addGroup*: Adds a group to the group list. This method is used when a new group is created. +* *deleteGroup*: Deletes a group from the group list based on the specified group name. The member and transaction files associated with the group are also deleted from storage. +* *saveGroupList*: Saves the list of groups stored in the groupList to the storage handler. + +
+ +Usage Example + +The following code segment outlines a sample use of `Group`. + +``` +import longah.util.TransactionList; +import longah.handler.UI; +import longah.exception.LongAhException; +import longah.node.Group; + +// Creating a new group +Group myGroup = new Group("MyGroup"); + +// Adding members to the group +myGroup.getMemberList().addMember("Alice"); +myGroup.getMemberList().addMember("Bob"); +myGroup.getMemberList().addMember("Charlie"); + +// Adding transactions to the group +TransactionList transactions = new TransactionList(); +transactions.addTransaction("Alice p/Bob a/20", myGroup.getMemberList()); +transactions.addTransaction("Charlie p/Alice a/15", myGroup.getMemberList()); +transactions.addTransaction("Bob p/Charlie a/10", myGroup.getMemberList()); + +// Setting the transactions for the group +myGroup.setTransactionList(transactions); + +// Updating transaction solutions for the group +myGroup.updateTransactionSolution(); + +// Listing debts in the group +String debts = myGroup.listDebts(); +UI.showMessage("Debts in the group:\n" + debts); + +// Settling up debts for a specific member +myGroup.settleUp("Alice"); + +// Saving all data related to the group +myGroup.saveAllData(); +``` + +
+ +The following code segment outlines a sample use of `GroupList`. + +``` +import longah.exception.LongAhException; +import longah.handler.UI; +import longah.node.GroupList; + +// Creating a new GroupList instance +GroupList groupList = new GroupList(); + +// Creating a new group and adding it to the group list +groupList.createGroup(); + +// Init and add more groups to the list +Group group2 = new Group("Group1"); +Group group3 = new Group("Group2"); +groupList.addGroup(group2); +groupList.addGroup(group3); + +// Getting the active group +String currGroup = GroupList.getActiveGroup().getGroupName()); + +// Listing all groups +String allGroups = groupList.getGroupList(); + +// Switching to a different active group +GroupList.switchActiveGroup(groupList.getGroup("Group2")); + +// Deleting a group from the list +groupList.deleteGroup("Group3"); + +// Saving the updated group list +groupList.saveGroupList(); +``` + +Design Considerations + +The `Group` class takes the following into consideration. +* The class ensures that group names are alphanumeric and does not allow for special characters including blank space. +* `settleUp` minimizes the number of transactions needed to settle the debt of a member by creating a single transaction with all lender(s) as borrower(s) based on the transaction solution of the group. + +The `GroupList` class takes the following into consideration. +* `createGroup` checks if the groupList is empty and automatically prompts the user to create a new group if it is and sets it as the active group. +* `loadGroupList` is called at the start of the application to ensure that all groups are loaded from storage into the groupList. + +### Member and MemberList + +Overview + +The `Member` class is used to represent a discrete person object, while the `MemberList` class is used to represent the aggregation members within a group. + +Class Structure + +The `Member` class has the following attributes. + +* *name*: A string representing the name of a person within the group. Used for visual identification of each member. Name of the member is strictly alphanumeric and cannot include special characters including the blank character. +* *balance*: A double representing the amount loaned/owed by the member. A positive value indicates that the member is owed money while a negative value indicates that the member owes money. + +The `MemberList` class has the following attribute. + +* *members*: An array list collection of Member objects. + +Implementation Details + +The detailed class diagram for `Member` and `MemberList` can be found below. + +![Member Class Diagram](diagrams/Member.png) + +
+ +Constructor + +The `Member` constructor creates a member object and initialises the current balance of the member, either to 0 or to a specified value. The latter is largely only used as part of storage methods. Checking for validity of the name is performed here. + +Key arguments of the `Member` constructor are a string `name` and optionally a double `balance`. + +The MemberList constructor initializes an empty array list of members for newly created members to be added to. + +Methods + +The `Member` class has the following key methods. + +* *setName*: Updates the name of a member. Used when edit member command is invoked. +* *addToBalance*: Adds the value of a transaction to a member. Absolute values are used to reduce complexity of balance update method calls for both the loaner and the borrower. +* *subtractFromBalance*: Subtracts the value of a transaction from a member. Absolute values are used to reduce complexity of balance update method calls for both the loaner and the borrower. +* *clearBalance*: Resets the current balance of a member to zero. Used then the clear command is invoked. + +The `MemberList` class has the following key methods. + +* *addMember*: Adds a member object to the current array list of members. This method is overloaded to allow for appending an existing member object or appending a newly created member object. +* *isMember*: Checks if a member object is already a part of the current array list of members. +* *getMember*: Returns the member object representation given the name of a member. +* *editMemberName*: Updates the name of an existing member based on their current name. +* *listMembers*: Returns a string representation of the current array list of members. +* *updateMembersBalance*: Updates the current balance of all members in the group based on a passed in `TransactionList` object. +* *solveTransactions*: Returns an array list of `Subtransaction` representing the least transactions solution to solving all debts in the group. +* *deleteMember*: Removes a member from the current array list of members. + +
+ +Usage Example + +The following code segment outlines a sample use of `Member`. + +``` +import longah.node.Member; +import longah.handler.UI; + +// Init member with balance of 0 +String name = "foo"; +Member member1 = new Member(name); + +// Init member with balance of 5 +String name = "bar"; +double balance = 5; +Member member2 = new Member(name, balance); + +// Updating member balance +member1.addToBalance(5); +member2.subtractFromBalance(1.30); +member1.clearBalance(); + +// Print string representation of member +UI.showMessage(member1); // Using our UI methods is preferred over System.out calls +``` + +The following code segment outlines a sample use of `MemberList`. + +``` +import longah.util.MemberList + +MemberList members = new MemberList(); +members.addMember("Alice"); +members.editMemberName("Alice", "Bob"); + +// Assuming we have a pre-defined TransactionList object +members.updateMembersBalance(transactions); +ArrayList solution = members.solveTransactions(); +members.clearBalances(); +members.delete("Bob"); +``` + +
+ +Design Considerations + +The `Member` class takes the following into consideration. + +* The class ensures that member names are alphanumeric and does not allow for special characters including blank space. +* This method is used in conjunction with a `TransactionList` object as part of a `Group`. + +The `MemberList` class takes the following into consideration. + +* `updateMembersBalance` clears current balances at the start of invocation. This removes any transactions that are not captured within the `TransactionList` object passed into the method. + +### Transaction and TransactionList +Transaction Overview + +The `Transaction` class is responsible for representing a single transaction in the LongAh application between 2 members. +It contains information about the lender, borrowers, and the amount involved in the transaction. + +The `TransactionList` class manages a list of transactions in the LongAh application, providing methods to add, delete, and retrieve transactions from the list. + +Class Structure + +The `Transaction` class has the following attributes. + +* *lender*: A member object representing the lender in the transaction. +* *transactionTime*: A DateTime object representing the time of the transaction. (optional) +* *subtransactions*: An ArrayList of Subtransaction objects, representing individual borrowings within the transaction. + +The `TransactionList` class has the following attribute. + +* *transactions*: An ArrayList of Transaction objects representing the list of transactions in a group. + +
-## Design & implementation +Implementation Details -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +The detailed class diagram for `Transaction` and `TransactionList` can be found below. + +![Transaction Class Diagram](diagrams/TransactionClass.png) + +Constructor + +The `Transaction` constructor creates a transaction object with the specified lender and transaction time (if applicable). The subtransactions are initialized as an empty ArrayList. + +Key arguments of the `Transaction` constructor are a `Member` object `lender`, an ArrayList of `subtransactions`, and optionally a `DateTime` object `transactionTime`. + +
+ +Methods + +The `Transaction class` has the following key methods. + +- *parseTransaction*: Parses the user input to extract lender and borrowers, then adds them to the transaction. +- *editTransaction*: Edits the transaction based on new user input. +- *deleteMember*: Deletes a member from the transaction and returns true if transaction needs to be removed. + +The `TransactionList` class has the following key methods. + +- *addTransaction*: Adds a new transaction to the list based on user input. +- *remove*: Removes a transaction from the list based on the index. +- *clear*: Clears all transactions from the list. +- *findLender*: Finds all transactions where a specified member is the lender. +- *findBorrower*: Finds all transactions where a specified member is a borrower. +- *findTransactions*: Finds all transactions where a specified member is involved. +- *filterTransactionsEqualToDateTime*: Filters transactions based on the specified date and time. +- *filterTransactionsBeforeDateTime*: Filters transactions before the specified date and time. +- *filterTransactionsAfterDateTime*: Filters transactions after the specified date and time. +- *filterTransactionsBetweenDateTime*: Filters transactions between the specified start and end date and time. +- *editTransactionList*: Edits a transaction in the list based on user input. +- *findDebts*: Finds all debts owed by a specified member. +- *deleteMember*: Deletes a member from all transactions in the list. + +
+ +Usage Example + +The diagram below illustrates a sample usage scenario of adding a transaction: + +![addTransaction.png](diagrams/addTransaction.png) + +
+ +The following code segment outlines a few sample usage of `TransactionList`. + +``` +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.util.DateTime; + +// Adding a new transaction +String input = "Alice p/Bob a/20"; +TransactionList transactions = new TransactionList(); +transactions.addTransaction(input, members); + +// Editing a transaction +String editInput = "1 Alice p/Bob a/30"; +transactions.editTransactionList(editInput, members); + +// Deleting a transaction +transactions.remove(1); + +// Finding all transactions where Alice is the lender +ArrayList aliceTransactions = transactions.findLender("Alice"); + +// Finding all transactions where Bob is a borrower +ArrayList bobTransactions = transactions.findBorrower("Bob"); + +// Finding a transaction based on member name +Transaction transaction = transactions.findTransactions("Alice", "Bob"); + +// Filtering transactions based on date and time +DateTime dateTime = new DateTime("01-01-2022 1200"); +ArrayList filteredTransactions; +filteredTransactions = transactions.filterTransactionsEqualToDateTime(dateTime); + +// Deleting a member from all transactions +transactions.deleteMember("Alice"); +``` + +Design Considerations + +The `Transaction` class takes the following into consideration. + +- Separate constructors and `parseTransaction` methods for storage purposes and user input parsing respectively. +- `toStorageString` method takes in a String `delimiter` for the purpose of splitting the transaction string into its constituent parts for storage. +- `subtransactions` are used to represent individual borrowings within a transaction. + +The `TransactionList` class takes the following into consideration. +- Transactions are indexed starting from 1 for user reference and ease of use by other methods such as edit and delete. + +
+ +### DateTime + + Overview + +The `DateTime` class handles all operations in LongAh involving the tracking of time. This includes storing and printing +the datetime elements in dated transactions, parsing user's date & time related inputs as well as filtering transactions +according to their stored date & time. Implementation of the class is made possible with the help of the *java.time* +system class. + + Class Structure + +Storing requirements only occurs for the specific datetime component of the class. Hence, the class field structure is +as follows: + +- *dateTime*: A dateTime object from *java.time* representing date & time associated with the current +instance. + + Constructor + +The `DateTime` constructor takes in a string representation of date & time in the `DD-MM-YYYY HHMM` form and parse it into +a LocalDateTime instance from *java.time* and stores it under the *dateTime* field. + +Invalid string date & time inputs to the constructor will trigger exceptions. The exceptions and triggering conditions +are as follows: + +- `INVALID_TIME_FORMAT`: String input representing date & time is not in the `DD-MM-YYYY HHMM` format. +- `INVALID_TIME_INPUT`: String input is representing a future date & time. This is not permitted in the LongAh system + considering real-life practicability. + + Methods + +The `DateTime` class has the following key methods: + +- *isBefore*: Determines whether an input DateTime object has a dateTime field that is before that of the current +instance. +- *isAfter*: Determines whether an input DateTime object has a dateTime field that is after that of the current +instance. +- *isEqual*: Determines whether an input DateTime object has a dateTime field that is equal to that of the current +instance. +- *isFuture*: Determines whether the dateTime field of the current instance represent a future date & time (e.g. is +after the preset system time). Currently used within the constructor only. +- *toStorageString*: Formats the dateTime field of the current instance into a String output suitable for loading and +storing. + +
+ +Usage Example + +The following UML diagram displays how the dateTime component is handled when the user is adding a dated transaction. + +![addDateTimeforDatedTransaction.png](diagrams/addDateTimeforDatedTransaction.png) + +Given below is an example usage scenario of how the `DateTime` class behaves at each step in adding dated transactions: + +1. Following steps 1-3 of the scenario of adding a new transaction, the `Transaction` class now identifies a potential +presence of a dateTime component in the userExpression through the specified prefix. +2. It initiates the constructor method of the `DateTime` class and attempts to create a new object to store the user input +date & time. Validation of the dateTimeExpression will occur in the DateTime class at this stage. +3. If the dateTimeExpression from the user is valid, the corresponding `DateTime` object will be returned to the +Transaction class as a result and stored as the dateTime of the transaction. +4. If the dateTimeExpression from the user is in the wrong format, exception occurs and the DateTime class will output +the "Invalid dateTime format" warning through the logger. +5. If the dateTimeExpression from the user is an unrealistic future date & time, exception occurs and the `DateTime` class +will output the "Invalid dateTime input" warning through the logger. +6. If the `dateTime` component is appended successfully, the `Transaction` class will proceed to handle other details of the +transaction input, as per adding a normal transaction. + +
+ +The following code segment outlines the above usage: +``` +import longah.util.DateTime; +import longah.util.Transaction; + +// In pareTransaction() method of the Transaction Class +// Check for prefix of date & time component while adding parsing user expression +if (splitInput[0].contains("t/")) { + String[] splitLenderTime = splitInput[0].split("t/", 2); + ... + this.transactionTime = new DateTime(splitLenderTime[1]); +} +``` + +The following UML diagram displays how the dateTime component is handled when printout requests are initiated for dated +transactions. + +![printingDateTime](diagrams/printingDateTime.png) + +Given below is an example usage scenario of how the `DateTime` class behaves at each step when printouts are required: +1. A String printout request is sent to the `Transaction` Class. +2. If the transaction has a dateTime component, proceed with sending a String printout request further to the DateTime +class. +3. The `DateTime` class formats the dateTime object of the current transaction into a String representation suitable for +printout and returns this result back to Transaction class. +4. The transaction class appends the returned String representation to the existing printout String. + +
+ +The following code segment outlines the above usage: +``` +import longah.util.DateTime; +import longah.util.Transaction; + +// In toString() method of the Transaction Class +// Checks whether the current transaction has a dateTime component +if (this.haveTime()) { + // Initiate a toString() call to the DateTime class + time = "Transaction time: " + this.transactionTime + "\n"; +} +``` + +The following UML diagram displays how the dateTime component is compared with user inputs in time filtering methods +(e.g. in *filterTransactionsEqualToDateTime*). + +![comparingDateTime](diagrams/comparingDateTime.png) + +Given below is an example usage scenario of how the `DateTime` class behaves at each step when comparison is initiated by +filter methods. +1. Upon receiving a filtering command, the `TransactionList` class initiates the `DateTime` constructor to store the user's dateTime Expression into a `DateTime` object. +2. For every transaction, the `TransactionList` first gets the dateTime object of the transaction by calling the getTransactionTime() method of the `Transaction` class. +3. A comparison method (in this case .isEqual()) of the `DateTime` class is initiated, comparing the transactionDateTime as well as userDateTime objects of the class. +4. Depending on the Boolean value determining the result of comparison, the filtering method will then proceed to decide if the current transaction is to be added to the printout. + +
+ +The following code segment outlines the above usage: +``` +import longah.util.DateTime; +import longah.util.Transaction; +import longah.util.TransactionList; + +// In filterTransactionsEqualToDateTime() method of the TransactionList Class +// Store user expression into a DateTime object +DateTime dateTimeToCompare = new DateTime(dateTime); +... +for (Transaction transaction : this.transactions) { + ... + // Compares the two DateTime objects using .isEqual() comparison method + if (transaction.getTransactionTime().isEqual(dateTimeToCompare)) { + ... +} +``` + + Design Considerations + +To reduce the coupling of time-related operations with other classes as much as possible, the following precautions was +put in-placed during the development of the `DateTime` class. + +- Isolation of `dateTime` checks: The validity of all dateTime inputs of LongAh is only checked and handled within the +constructor of the `DateTime` class. +- Isolation of comparison methods: `dateTime` fields are only accessed and compared through defined methods of the +DateTime class. +- Isolation of printouts: All `dateTime` fields are formatted and output only through the toString() method. + +The above methods effectively contains all time handling under the single `DateTime` class. This allows developer to +change the input and output structure of time-related behaviors(e.g. formatting of time in printouts) easily without +compromising compatibility with other parts of the LongAh system. + +### PIN + + Overview + +The `PINHandler` class is responsible for managing the creation, loading, authentication, and resetting of a +Personal Identification Number (PIN) used for authentication in the LongAh application. It uses SHA-256 hashing to +securely store and compare PINs. The PINHandler class interacts with the StorageHandler class to save and load the PIN +and authentication status. + +Note: PIN is disabled by default and needs to be set upon first startup. + +
+ +Implementation Details + +*Data Storage:* + +The PIN and authentication enabled status are saved in a file located at ./data/pin.txt. +The file format is as follows: + +`hashed PIN`
+`authenticationEnabled`
+ + Class Structure + +The `PINHandler` class has the following static fields: + +- *PIN_FILE_PATH*: The path to the file where the PIN and authentication status are saved. +- *savedPin*: The hashed PIN saved in the file. +- *authenticationEnabled*: A boolean flag indicating whether authentication is enabled. + + Constructor + +The `PINHandler` constructor initializes the savedPin and authenticationEnabled fields by loading them from the file using +the loadPinAndAuthenticationEnabled method. + +If the file does not exist or the savedPin is empty, it calls the createPin method to create a new PIN. + + Methods + +- *loadPinAndAuthenticationEnabled*: Loads the saved PIN and authentication enabled status from the file. +- *savePinAndAuthenticationEnabled*: Saves the PIN and authentication enabled status to the file. +- *getPinFilePath*: Returns the file path of the PIN file. +- *createPin*: Prompts the user to create a new 6-digit PIN and hashes it before saving. +- *authenticate*: Authenticates the user by comparing the entered PIN with the saved PIN. +- *resetPin*: Resets the PIN for the user by prompting for the current PIN and creating a new PIN if the current PIN is correct. +- *enablePin*: Enables authentication upon startup. +- *disablePin*: Disables authentication upon startup. +- *getSavedPin*: Returns the saved PIN. +- *getAuthenticationStatus*: Returns the authentication status. + +
+ + Usage Example + +The following diagram illustrates the sequence during PIN authentication. + +![pinhandler longah.png](diagrams/pinhandler%20longah.png) + +
+ +This diagram shows the sequence when the user resets their PIN. + +![pinreset.png](diagrams/pinreset.png) + +
+ +Given below is an example usage scenario and how the PIN creation and authentication mechanism behaves at each step: + +1. PINHandler initialization loads the saved PIN and authentication enabled status from the file. If no PIN exists, prompt the user to create a new PIN. +2. User creates a new 6-digit PIN using the createPin method. The entered PIN is hashed using SHA-256 before saving it to the file. +3. User enables authentication upon startup using the 'pin enable' command. The authenticationEnabled flag is set to True and saved to the file. +4. The user closes the application and relaunches it. The PINHandler loads the saved PIN and authentication enabled status from the file again. +5. The user attempts to log in by entering their PIN. The authenticate method hashes the entered PIN and compares it with the saved hashed PIN.If they match, the user is successfully authenticated. Otherwise, the user is denied access. +6. The user decides to reset their PIN by entering their current PIN and creating a new one using the resetPin method. +7. The user disables authentication upon startup using the 'pin disable' command. The authenticationEnabled flag is set to false and saved to the file. +8. On relaunch, authentication is not required since it was disabled. The user can proceed with the application and do any actions without entering a PIN. + +Code Segment: +``` +// Initialize PINHandler +PINHandler pinHandler = new PINHandler(); + +// Check if authentication is enabled +if (PINHandler.getAuthenticationStatus()) { +// Authenticate user +PINHandler.authenticate(); +} else { +// Authentication is disabled, proceed with application logic +} +``` + + Design Considerations + +Resetting PIN: The resetPin() method allows users to change their PIN by first verifying their current PIN. This adds +an extra layer of security to prevent unauthorized PIN changes. + +Authentication Management: Users have the option to enable or disable authentication upon startup using the 'pin enable' +and 'pin disable' commands. This flexibility allows users to customize their authentication preferences based on their +security needs and convenience. + +
+ +### Chart + +Overview + +The `Chart` class is responsible for generating a visual representation of the transaction solution in the form of a chart. + +The chart is displayed in a separate window and shows the individual balances among the group members. + +This is handled using a package called 'XChart'. + +It provides a convenient way to visualize data, particularly for member balances in the LongAh application. The bar chart generated by this class showcases positive and negative balances of members, offering insights into financial statuses. + +Implementation Details + +Data Representation: + +The `Chart` class utilizes the XChart library to represent data in the form of bar charts. It distinguishes positive and negative balances by differentiating them with green and red colors, respectively. + +Class Structure + +The `Chart` class consists of the following components: + +- *Constructor*: Instantiates a new Chart object by displaying the provided category chart. + +Methods + +- *viewBalancesBarChart*(List members, List balances): Generates a bar chart displaying member balances. It + distinguishes positive and negative balances and adds tooltips for enhanced user interaction. Additionally, it includes + an annotation recommending a command for managing debts effectively. + +Usage Example + +Given below is an example usage scenario and how the Chart class behaves at each step: + +1. The user adds a few members to the group and performs transactions among them. +2. The user enters the 'chart' command to view the current balances of all members. + +
+ +Code Segment: +``` +// Prepare data +List members = Arrays.asList("Member1", "Member2", "Member3"); +List balances = Arrays.asList(100.0, -50.0, 75.0); + +// Display bar chart +Chart.viewBalancesBarChart(members, balances); +``` +Design Considerations + +Data Visualization - The class focuses on providing clear and concise visualization of member balances through bar +charts, aiding users in understanding financial distributions. + +Interactivity - Tooltips are enabled to enhance user interaction, providing insights into specific data points when +hovered over. + +Annotation - An annotation is included to suggest a command for managing debts efficiently, ensuring users +are aware of available features within the application. + +### Exceptions and Logging + +Overview + +This project makes used of centralised exception handling and logging means, allowing for greater standardisation throughout the codebase. This is done through the `LongAhException` class and `Logging` class respectively. + +Implementation Details + +The `LongAhException` class makes use of enumerations `ExceptionMessage` and `ExceptionType` to dictate its behaviour. `ExceptionMessage` stores the desired output message for each kind of potential error along with its associated `ExceptionType`. `ExceptionType` is used to define the manner in which the exception is logged. + +Note: All exception calls are logged by default, either as WARNING or INFO depending on the `ExceptionType` classification tagged to the `ExceptionMessage`. + +Class Structure + +The `LongAhException` class has the following static field: +* *type*: A ExceptionType enumeration denoting how the exception should be logged. + +The `Logging` class has the following static field: +* *longAhLogger*: A Logger type object to perform the logging. + +
+ +Constructor + +The `LongAhException` class calls the Exception constructor using the message associated with the received ExceptionMessage and stores the type of exception. + +The `Logging` class initializes a file directory to store logging data. + +Methods + +The `LongAhException` class has the following key methods: + +* *printException*: Prints the desired output message when an exception is thrown. + +The `Logging` class has the following key methods: + +* *logInfo*: Takes a string `message` as an argument. Create a log at the INFO level. +* *logWarning*: Takes a string `message` as an argument. Create a log at the WARNING level. + +Usage Example + +Use of the `LongAhException` class is demonstrated below, including throwing of an exception and printing the desired output message. This example covers the throwing exception due to invalid index. +``` +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +// Throw an exception +throw new LongAhException(ExceptionMessage.INVALID_INDEX); + +// Catch exception and output desired message +catch (LongAhException e) { + LongAhException.printException(e); +} +``` + +`Logging` can be performed using the following lines of code: +``` +import longah.handler.Logging; + +// Create log of INFO Level +Logging.logInfo(message); + +// Create log of WARNING Level +Logging.logWarning(message); +``` + +
+ +## User Stories + +| Version | As a ... | I want to ... | So that I can ... | +| ------- | -------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- | +| v1.0 | user | find the least transactions to resolve amounts owed by people in a group | - | +| v1.0 | user | add transactions involving multiple people in a group | keep track of people involved and value of the transaction | +| v1.0 | user | edit transactions | fix mistakes made when entering transactions | +| v1.0 | user | delete transactions | clear erroneous transactions which I do not intend to keep | +| v1.0 | user | keep a log of my data | retain memory of past transactions in past runs of the platform | +| v1.0 | user | have easy access command to clear my pending debts | - | +| v1.0 | user | be able to organise people into groups | minimise the occurence of being affected by typos | +| v1.0 | user | add members to a group | add them to future transactions | +| v1.0 | user | restart data for a group | reduce clutter of the application | +| v1.0 | user | find transactions related to a certain member | better keep track of my pending transactions or payments | +| v2.0 | new user | view help commands | have an easy reference for commands within the application | +| v2.0 | user | enable the use of passwords for my application | prevent wrongful access to my records | +| v2.0 | user | disable the password | have an easier time allowing people to view my records | +| v2.0 | user | edit my password | change my password in case it has been compromised | +| v2.0 | user | have my password be encrypted | ensure my password cannot be easily found out | +| v2.0 | user | edit members in my group | change their nicknames which I store within the application | +| v2.0 | user | delete current members | keep my groups neat and free of people who are no longer part of them | +| v2.0 | user | create more groups | use the application for multiple groups of friends without data overlapping | +| v2.0 | forgetful user | time of transactions to be saved | reference when each transaction were made | +| v2.0 | user | search for specific transactions | find information relating to the transaction | +| v2.1 | user | filter transactions based on transaction time | reference a transaction made during an interested time period | +| v2.1 | advanced user | have command shortcuts | input commands faster | ## Product scope + ### Target user profile -{Describe the target user profile} +Busy people with large transaction quantities among friends ### Value proposition -{Describe the value proposition: what problem does it solve?} +- Help users to find the least transactions solution to a large quantity of transactions +- Allow users to view past expenses of a group -## User Stories - -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +
## Non-Functional Requirements -{Give non-functional requirements} +* Technical Requirements: Any mainstream OS, i.e. Windows, macOS or Linux, with Java 11 installed. Instructions for downloading Java 11 can be found [here](https://www.oracle.com/sg/java/technologies/javase/jdk11-archive-downloads.html). +* Project Scope Constraints: The application should only be used for tracking. It is not meant to be involved in any form of monetary transaction. +* Project Scope Constraints: Data storage is only to be performed locally. +* Quality Requirements: The application should be able to be used effectively by a novice with little experience with CLIs. ## Glossary -* *glossary item* - Definition +* Lender - Member making payments on behalf of other members. +* Borrower - Members being paid for by the lender. +* Transaction - Payment made by ONE Lender on behalf of MULTIPLE Borrower, represented as a list of Subtransaction. +* Subtransaction - Subset of Transaction, consists of ONE Lender and ONE Borrower. +* Group - Discrete units each containing their respective lists of Member and Transaction. +* Separator - "\|" has been used to denote separator within this document but within the Storage related classes, the ASCII Unit Separator as denoted by ASCII 31 is used instead. This is defined within `StorageHandler`. + +
+ +## Instructions for Testing + +### Manual Testing + +View the [User Guide](UserGuide.md) for the full list of UI commands and their related use case and expected outcomes. + +### JUnit Testing + +JUnit tests are written in the subdirectory `test` and serve to test key methods part of the application. + +### Text UI Testing + +Files relating to Text UI Testing can be found in the directory `text-ui-test`. + +Text UI testing has been configured to simulate multiple sessions run by the same user with a total of 10 tests being run. Details of each set of tests can be found in the README in the above directory. Tests can be modified by changing command calls in the `input` subdirectory, but this is not recommended since the differing expected output may cause tests to fail. + +When running tests on a Windows system, run the following command from the specified directory: + +``` +./runtest.bat +``` + +When running tests on a UNIX-based system, run the following command from the specified directory: +``` +./runtest.sh +``` + +Outcomes of these tests are listed in the below code segment. +``` +// Successfully passed all tests +All tests passed! + +// Tests failed: Differing output in test group 2 and member data files +2 tests failed: MEMBER 2 +``` + +
+ +## Future Enhancements -## Instructions for manual testing +The following are features we intend to include in future iterations of this application. -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +1. Allow methods for undo-ing previous commands. +2. Allow users to set expenditure limits. +3. Increase the number of group operations available (i.e. edit, merge). +4. Add functionality for splitting a transaction by percentage share instead of raw value. +5. Add page-scrolling for `list`, `find` and `filter` commands to reduce screen clogging. +6. Inclusion of anomaly detection algorithms to flag out potentially erroneous transactions. +7. Adding of further details tagged to each transaction and allow for searching of transactions based on these details. +8. Create a reminder system to inform users of upcoming events or to warn them to settle payments. +9. Allow the setting up of recurring transactions such as credit is deducted periodically. diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..08476dffc9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,18 @@ -# Duke +# LongAh -{Give product intro here} +![LongAh logo.jpg](diagrams%2FLongAh%20logo.jpg) -Useful links: +Never owe people money over Chinese New Year! In the Year of the Dragon, LongAh! seeks to help students track debts within friend groups and determine the least transactions method of settling these debts. + +Useful links * [User Guide](UserGuide.md) * [Developer Guide](DeveloperGuide.md) * [About Us](AboutUs.md) +* [Project Website](https://ay2324s2-cs2113-t15-1.github.io/tp/) + +PPP Links +* Leong Deng Jun: [djleong01](team/djleong01.md) +* Sim Justin: [1simjustin](team/1simjustin.md) +* Chew Jing Xiang: [jing-xiang](team/jing-xiang.md) +* Liao Jingyu: [FeathersRe](team/feathersre.md) +* Wu Hao Wern: [haowern98](team/haowern98.md) \ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..9b300fbca7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,832 @@ -# User Guide +# LongAh! User Guide ## Introduction -{Give a product intro} +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the +least transaction method of settling these debts. It is optimized for busy people with large transaction quantities +among friends. ## Quick Start -{Give steps to get started quickly} +1. Ensure that you have Java 11 or above installed. +2. Download the latest version of `LongAh!` from [here](https://github.com/AY2324S2-CS2113-T15-1/tp/releases). +3. Copy the JAR file to the folder you want to use as the home folder for your LongAh! application. +4. Open a command terminal, navigate to the folder containing the JAR file and run the command: +``` +java -jar CS2113-T15-1.LongAh.jar +``` +5. Upon starting the application, you will be prompted to enter your PIN. The user PIN is required to access the application. +The app will prompt you to create your own PIN if it is your first time using the application. +6. You can now start using LongAh! by entering commands into the command terminal. -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +
-## Features +## Command Reference -{Give detailed description of each feature} +A quick reference table for all commands is presented below. Certain commands have shortcuts which can be used in place of the provided long form commands as well. -### Adding a todo: `todo` -Adds a new item to the list of todo items. +| Task | Command Expression | Command Shortcut | +|------------------------|-------------------------------------------------------------------------------------------------------|-------------------| +| Help menu | `help` | `?` | +| Add member | `add member [name]` | `addm` or `am` | +| Add transaction | `add transaction [lender] p/[borrower1] a/[amount] p/[borrower2] a/[amount] ...` | `addt` or `at` | +| Add dated transaction | `add transaction lender t/[DD-MM-YYYY HHMM] p/[borrower1] a/[amount] p/[borrower2] a/[amount] ...` | `addt` or `at` | +| Add group | `add group [name]` | `addg` or `ag` | +| List members | `list members` | `listm` or `lm` | +| List transactions | `list transactions` | `listt` or `lt` | +| List debts | `list debts` | `listd` or `ld` | +| List groups | `list groups` | `listg` or `lg` | +| Find transactions | `find transactions [member]` | `findt` or `ft` | +| Find lender | `find lender [member]` | `findl` or `fl` | +| Find borrower | `find borrower [member]` | `findb` or `fb` | +| Find debts | `find debts [member]` | `findd` or `fd` | +| Delete member | `delete member [member]` | `deletem` or `dm` | +| Delete transaction | `delete transaction [transaction_index]` | `deletet` or `dt` | +| Delete group | `delete group [name]` | `deleteg` or `dg` | +| Edit member | `edit member [old_name] p/[new_name]` | `editm` or `em` | +| Edit transaction | `edit transaction [transaction_index] [lender] p/[borrower1] a/[amount] p/[borrower2] a/[amount] ...` | `editt` or `et` | +| Enable PIN | `pin enable` | N/A | +| Disable PIN | `pin disable` | N/A | +| Reset PIN | `pin reset` | N/A | +| Clear all transactions | `clear` | N/A | +| Settle up debts | `settleup [member]` | `settle` | +| Switch groups | `group [group_name]` | N/A | +| Filter transactions | `filter a/[TIME] b/[TIME]` | N/A | +| View chart | `chart` | N/A | +| Exit | `exit` | N/A | -Format: `todo n/TODO_NAME d/DEADLINE` +
-* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +## Table of Contents +- [LongAh! User Guide](#longah-user-guide) + - [Introduction](#introduction) + - [Quick Start](#quick-start) + - [Command Reference](#command-reference) + - [Table of Contents](#table-of-contents) + - [Features](#features) + - [Group Management](#group-management) + - [Member and Transaction Management](#member-and-transaction-management) + - [Group Balances \& Expense Tracking](#group-balances--expense-tracking) + - [Easily Finding Transactions](#easily-finding-transactions) + - [Debt Simplification](#debt-simplification) + - [Security](#security) + - [Data Storage](#data-storage) + - [Data Editing](#data-editing) + - [Chart Visualization](#chart-visualization) + - [Command Format](#command-format) + - [Viewing help: `help`](#viewing-help-help) + - [Adding a member: `add member`](#adding-a-member-add-member) + - [Adding a transaction: `add transaction`](#adding-a-transaction-add-transaction) + - [Adding a new group `add group`](#adding-a-new-group-add-group) + - [Listing all members: `list members`](#listing-all-members-list-members) + - [Listing all transactions: `list transactions`](#listing-all-transactions-list-transactions) + - [Listing all debts: `list debts`](#listing-all-debts-list-debts) + - [Listing all groups: `list groups`](#listing-all-groups-list-groups) + - [Find Transactions: `find transactions`](#find-transactions-find-transactions) + - [Find Lender `find lender`](#find-lender-find-lender) + - [Find Borrower `find borrower`](#find-borrower-find-borrower) + - [Find Debts `find debts`](#find-debts-find-debts) + - [Deleting a member: `delete member`](#deleting-a-member-delete-member) + - [Deleting a transaction: `delete transaction`](#deleting-a-transaction-delete-transaction) + - [Deleting a group `delete group`](#deleting-a-group-delete-group) + - [Editing a member: `edit member`](#editing-a-member-edit-member) + - [Editing a transaction: `edit transaction`](#editing-a-transaction-edit-transaction) + - [Enabling the user PIN: `pin enable`](#enabling-the-user-pin-pin-enable) + - [Disabling the user PIN: `pin disable`](#disabling-the-user-pin-pin-disable) + - [Resetting user PIN: `pin reset`](#resetting-user-pin-pin-reset) + - [Clearing all transactions `clear`](#clearing-all-transactions-clear) + - [Switching groups: `group`](#switching-groups-group) + - [Settle a user's debts: `settleup`](#settle-a-users-debts-settleup) + - [Filter transactions: `filter`](#filter-transactions-filter) + - [Views the balances of all members on a chart: `chart`](#views-the-balances-of-all-members-on-a-chart-chart) + - [Exiting the application: `exit`](#exiting-the-application-exit) + - [FAQ](#faq) + - [Common Errors](#common-errors) + - [Failure to adhere to command format](#failure-to-adhere-to-command-format) + - [Invalid Requests](#invalid-requests) + - [Future Improvements](#future-improvements) + +
+ +## Features + +LongAh! offers robust group management capabilities, allowing users to effortlessly create and delete groups to categorize and organize their expenses. With the ability to seamlessly switch between groups, users can conveniently monitor the financial activities of various social circles or projects. + +### Group Management + +Within each group, LongAh! provides comprehensive member and transaction management functionalities. Users can easily add, remove, or modify group members and transactions, ensuring accurate and up-to-date records of all financial engagements. + +### Member and Transaction Management + +Within each group, LongAh! provides comprehensive member and transaction management functionalities. Users can easily add, remove, or modify group members and transactions, ensuring accurate and up-to-date records of all financial engagements. + +### Group Balances & Expense Tracking + +Tracking group balances and expenses has never been easier with LongAh! Users can log transactions between members, facilitating transparent and equitable expense distribution. LongAh! also offers intuitive visualizations, allowing users to quickly assess group financial dynamics at a glance. + +### Easily Finding Transactions + +Apart from neatly organising users' pending transactions, LongAh! offers a variety of ways for users to conveniently locate the transactions they are interested in by simply providing the related details. As of now, LongAh! has embedded support towards user searches based on both member names and transaction time, effectively lowering the cost of navigating through the transaction list when entries increase. + +### Debt Simplification + +LongAh! streamlines debt settlement processes by automatically computing the optimal repayment strategy. By presenting users with a simplified list of debts and transactions, LongAh! minimizes the effort required to settle financial obligations within the group, fostering smoother financial interactions. + +
+ +### Security + +Protecting sensitive financial data is paramount, which is why LongAh! prioritizes security. With the option to set a personalized PIN, users can safeguard their LongAh! accounts against unauthorized access. This additional layer of security ensures peace of mind, knowing that financial information remains confidential and secure. Additionally, users have the flexibility to enable, disable, or modify their PIN settings according to their preferences and needs. + +### Data Storage +LongAh! data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +The file is also created automatically if it does not exist. + +If all is well, LongAh will save the files in the following data structure during execution. +``` + +│ +├─data +│ │ groupList.txt +│ │ pin.txt +│ ├─ +│ │ members.txt +│ │ transactions.txt +│ ├─ +│ │ members.txt +│ │ transactions.txt +│ . +│ . +├─log +│ LongAh.log +└─tp.jar +``` + +Note: It is not recommended to edit any data files manually. Corrupt lines of data will be ignored and overwritten over the course of the use of the application. + +### Data Editing + +LongAh! data is saved as a TXT file in the hard disk. Advanced users are welcome to edit the data file directly, but please ensure that the data is in the correct format. +The PIN TXT file contains the pin hash of each user's PIN for security purposes. It is not recommended to edit this file directly. + +### Chart Visualization +The XChart feature in LongAh! provides a visual representation of the balances of all members in the current group. + +
+ +## Command Format + +A command has the general structure: +``` +[COMMAND] [SUBCOMMAND] [EXPRESSION] +``` + +There are 5 main group of commands: 'add', 'delete', 'edit', 'find', 'list', along with other commands. +Command shortcuts are available for certain commands and are detailed below in the `Format` section for relevant commands. + +### Viewing help: `help` + +Shows a help message containing all the commands available in LongAh!. + +Format: `help` OR `?` + +Example of usage: +``` +help +? + Here are the full list of commands available: + + ADD commands: + ____________________________________________________________ + 1. `add member [NAME]` - Add a new member to the group. + ... + ... +``` + +
+ +### Adding a member: `add member` + +Adds a new member to the list of members in LongAh!. + +Format: `add member [NAME]` OR `addm` OR `am` + +* Name of new member should not be a duplicate of an existing member. +* The entered name should only contain alphanumeric characters, no spaces or special characters are allowed. +* We suggest using pascal case for names with spaces or special characters, i.e. Tan Xiao Hong, Alicia = `TanXiaoHongAlicia`. +* The name of the member is case-sensitive. i.e. 'Alice' and 'alice' are not considered the same member. +* Names are limited to 50 characters. + +Example of usage: +``` +add member Alice +addm Bob +am Charlie + Added member: Charlie +``` + +
+ +### Adding a transaction: `add transaction` + +Adds a new transaction to the list of transactions in LongAh!. + +Format: `add transaction [LENDER] t/[DATE IN DD-MM-YYYY HHMM] p/[BORROWER1] a/[AMOUNT] p/[BORROWER2] a/[AMOUNT] ...` OR `addt` OR `at` +* The transaction supports 1 or more borrower(s), each with custom borrowed amounts. +* `t/` is the prefix for the transaction time, and should be followed after the transaction lender and before the name of the first borrower. +* The transaction time is optional and can be omitted. +* `p/` is the prefix for the borrower's name, and should be followed by the name of the borrower. +* `a/` is the prefix for the amount borrowed, and should be followed by the amount borrowed by that borrower from the lender. +* The `LENDER` and `BORROWER(s)` should be an existing member. +* The `LENDER` AND `BORROWER` should not be the same person. + +Example of usage: +``` +add transaction Alice p/Bob a/10 +addt Bob p/Alice a/5 +at Alice p/Bob a/7 + +// Multiple Borrowers +add transaction Alice p/Bob a/10 p/Charlie a/5 + +// Dated Transaction +add transaction Alice t/11-11-2000 2359 p/Bob a/10 + Transaction added successfully! + Lender: alice + Transaction time: 11-11-2000 2359 + Borrower 1: bob Owed amount: $10.00 +``` + +
+ +### Adding a new group `add group` + +Adds a new group to LongAh! for you to manage expenses and debts separately for different groups of people. + +Format: `add group [GROUP_NAME]` OR `addg` OR `ag` + +* The Application will automatically prompt you to create a new group if this is your first time using LongAh!. +* The Application will not automatically switch to the new group after adding. You can switch to the new group using the `group` command. +* The entered group name should not be a duplicate of an existing group. +* The entered group name should only contain alphanumeric characters, and no spaces are allowed. +* The name of the group is case-sensitive. i.e. 'Tiktok' and 'tiktok' are not considered the same group. +* Group names are limited to 50 characters. + +Example of usage: +``` +add group Tiktok +addg Tiktok +ag Tiktok + Added group: Tiktok +``` + +
+ +### Listing all members: `list members` + +Shows a list of all current members in the group along with their current balances. + +Format: `list members` OR `listm` OR `lm` + +* Positive balance indicate that the member is owed money by other member(s) in the group. +* Negative balance indicate that the member owes money to other member(s). +* A balance of 0 indicates that the member neither owes nor is owed money. + +Example of usage: +``` +add member alice +add member bob +add transaction alice p/bob a/5 + +list members +listm +lm + alice: $5.0 + bob: -$5.0 +``` + +### Listing all transactions: `list transactions` + +Shows a list of all transactions in LongAh!. + +* Each transaction is indexed for easy reference and use in other commands. + +Format: `list transactions` OR `listt` OR `lt` + +Example of usage: +``` +add member alice +add member bob +add transaction alice p/bob a/5 + +list transactions +listt +lt + 1. + Lender: alice + Borrower 1: bob Owed amount: 5.00 +``` + +
+ +### Listing all debts: `list debts` + +Calculates the simplest way to repay all debts between all members and shows a list of all debts in LongAh!. + +Format: `list debts` OR `listd` OR `ld` + +Example of usage: +``` +add member alice +add member bob +add member charlie +add transaction alice p/bob a/3 p/charlie a/4 +add transaction charlie p/alice a/5 p/bob a/1 + +list debts +listd +ld + Best Way to Solve Debts: + bob owes charlie $2.0 + bob owes alice $2.0 +``` + +### Listing all groups: `list groups` + +Shows a list of all groups in LongAh!. + +Format: `list groups` OR `listg` OR `lg` + +Example of usage: +``` +// assume that the group 'Tiktok' already exists + +add group Friends +add group Family + +list groups +listg +lg + 1. Tiktok + 2. Friends + 3. Family +``` + +
+ +### Find Transactions: `find transactions` + +Finds all transactions that involves the specified member. The member can be involved as a lender or a borrower in the transaction(s). + +Format: `find transactions [MEMBER]` OR `findt` OR `ft` +* The `MEMBER` should be an existing member. Example of usage: +``` +add member Alice +add member Bob +add transaction Alice p/Bob a/5 +add transaction Bob p/Alice a/3 + +find transactions Alice +findt Alice +ft Alice + Alice is a part of the following list of transaction(s). + 1. + Lender: Alice + Borrower 1: Bob Owed amount: $5.00 + + 2. + Lender: Bob + Borrower 1: Alice Owed amount: $3.00 +``` + +
+ +### Find Lender `find lender` + +Finds all transactions where the specified member is the lender. + +Format: `find lender [MEMBER]` OR `findl` OR `fl` +* The `MEMBER` should be an existing member. + +Example of usage: +``` +// Continuing from above example +find lender Alice +findl Alice +fl Alice + Alice is a lender in the following list of transaction(s). + 1. + Lender: Alice + Borrower 1: Bob Owed amount: $5.00 +``` + +### Find Borrower `find borrower` + +Finds all transactions where the specified member is a borrower. + +Format: `find borrower [MEMBER]` OR `findb` OR `fb` +* The `MEMBER` should be an existing member. + +Example of usage: +``` +// Continuing from above example + +find borrower Alice +findb Alice +fb Alice + Alice is a borrower in the following list of transaction(s). + 1. + Lender: Bob + Borrower 1: Alice Owed amount: $3.00 +``` + +
-`todo n/Write the rest of the User Guide d/next week` +### Find Debts `find debts` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +Finds all debts that the specified member has with other members. + +Format: `find debts [MEMBER]` OR `findd` OR `fd` +* The `MEMBER` should be an existing member. + +Example of usage: +``` +// Continuing from above example +add member Charlie +add transaction Alice p/Charlie a/3 + +find debts Alice + Bob owes Alice $2.0 + Charlie owes Alice $3.0 + +add transaction Charlie p/Alice a/10 + +findd Alice +fd Alice + Alice owes Charlie $5.0 +``` + +
+ +### Deleting a member: `delete member` + +Deletes a member from the list of members in the group. + +Format: `delete member [MEMBER]` OR `deletem` OR `dm` +* The `MEMBER` should be an existing member. +* All transactions involving the member will be deleted. +* All debts involving the member will be recalculated. + +Example of usage: +``` +delete member Alice +deletem Alice +dm Alice + Deleted member: Alice +``` + +### Deleting a transaction: `delete transaction` + +Deletes a transaction from the list of transactions in the group. + +Format: `delete transaction [TRANSACTION_INDEX]` OR `deletet` OR `dt` +* The `TRANSACTION_INDEX` should be an existing transaction number. +* All debts involving the transaction will be recalculated. +* The transaction number can be found by using the `list transactions` command and taking the corresponding index of +the transaction that you want to delete. + +Example of usage: +``` +delete transaction 3 +deletet 3 +dt 3 + Transaction #3 removed successfully. + Lender: Alice + Transaction time: 11-11-2000 2359 + Borrower 1: Bob Owed amount: $10.00 +``` + +
+ +### Deleting a group `delete group` + +Deletes a group from LongAh!. + +Format: `delete group [GROUP_NAME]` OR `deleteg` OR `dg` +* The `GROUP_NAME` should be an existing group. +* All transactions and members in the group will be deleted, and the group will be removed from the list of groups. +* The Application will automatically switch to the first group in the list if the current group that you are managing is deleted. +* If all groups are deleted, the Application will automatically prompt you to create a new group. + +Example of usage: +``` +// assume that the group 'Tiktok' already exits +add group friends +delete group friends +deleteg friends +dg friends + Remaining groups: + 1. Tiktok + +Deleted group: friends +``` + +### Editing a member: `edit member` + +Edits the name of a member in the list of members in LongAh!. + +Format: `edit member [OLD_NAME] p/[NEW_NAME]` OR `editm` OR `em` +* `p/` is the prefix for the new name of the member. +* The `OLD_NAME` should be an existing member. +* The `NEW_NAME` should not be a duplicate of an existing member. +* All transactions involving the member will be updated to reflect the new name. + +Example of usage: +``` +edit member Alice p/Bob + Member name edited successfully! Alice is renamed to: Bob +``` + +
+ +### Editing a transaction: `edit transaction` + +Edits the details of a transaction in the list of transactions in LongAh!. + +Format: `edit transaction [TRANSACTION_INDEX] [LENDER] p/[BORROWER1] a/[AMOUNT] p/[BORROWER2] a/[AMOUNT] ...` OR `editt` OR `et` +* The `TRANSACTION_INDEX` should be an existing transaction number. +* The `LENDER` and `BORROWER(s)` should be an existing member. +* Transaction date and time can similarly be edited or added through the same format as per [add transaction](#adding-a-transaction-add-transaction) +* Allows for edits to the lender and the borrowers involved in the transaction, as well as the amount. +* The transaction number can be found by using the `list transactions` command and taking the corresponding index. +* All debts involving the transaction will be recalculated. + +Example of usage: +``` +add member Alice +add member Bob +add member Charlie +add transaction Alice p/Bob a/1 + +edit transaction 1 Bob p/Alice a/3 +editt 1 Bob p/Alice a/3 +et 1 Bob p/Alice a/3 + Transaction #1 edited successfully. + Lender: Bob + Borrower 1: Alice Owed amount: $3.00 +``` + +
+ +### Enabling the user PIN: `pin enable` + +Enables user PIN authentication for the application. (disabled by default) + +Format: `pin enable` + +Example of usage: +``` +pin enable + Authentication enabled upon startup. +``` + +### Disabling the user PIN: `pin disable` + +Disables user PIN authentication for the application. + +Format: `pin disable` + +Example of usage: +``` +pin disable + Authentication disabled upon startup. +``` + +### Resetting user PIN: `pin reset` + +Resets the user's PIN for the application. Follow the instructions as prompted to reset the PIN. + +Format: `pin reset` +* The new PIN should only contain numbers (0-9). + +Example of usage: +``` +pin reset + Enter your current PIN: 654321 + Create your 6-digit PIN: 123456 + PIN saved successfully! You can enter 'pin enable' to enable ... +``` + +
+ +### Clearing all transactions `clear` + +Clear all previous transactions logged in the group. Members balances will be reset to 0. + +* The application will prompt you to confirm the action before clearing all transactions. +* This action cannot be undone. + +Format: `clear` + +Example of usage: +``` +add member Alice +add member Bob +add transaction Bob p/Alice a/3 +clear + Are you sure you want to clear all transactions? (Y/N) + This action cannot be undone. All transaction data will be lost. + Enter 'N' or any other key to cancel. + +y + All transaction records have been cleared. + All transactions have been cleared for this account. +``` + +### Switching groups: `group` + +Switches to the specified group in LongAh!. + +Format: `group [GROUP_NAME]` +* The `GROUP_NAME` should be an existing group that has been added to LongAh!. + +Example of usage: +``` +// assume that the user is currently managing group 'Tiktok' +add group friends + Added group: friends + +group friends + Switching groups... + You are now managing: friends +``` + +
+ +### Settle a user's debts: `settleup` + +Settles all debts of the specified member with all other members. A transaction will be created to settle the debts and reset +the debt balance of the specified member to 0, while updating the balance(s) of all relevant lender(s). + +Format: `settle [MEMBER]` OR `settleup [MEMBER]` +* The `MEMBER` should be an existing member. +* The `MEMBER` should be a valid debtor in the group (i.e. the member should owe money to other members). + +Example of usage: +``` +add member alice +add member bob +add member charlie +add transaction alice p/bob a/3 p/charlie a/4 +add transaction charlie p/alice a/6 p/bob a/1 + +list debts + Best Way to Solve Debts: + bob owes alice $1.0 + bob owes charlie $3.0 + +settleup bob +settle bob + bob has repaid alice $1.0 + Transaction added successfully! + bob has repaid charlie $3.0 + + Transaction added successfully! + Lender: bob + Borrower 1: alice Owed amount: $1.00 + Borrower 2: charlie Owed amount: $3.00 + + bob has no more debts! +``` + +
+ +### Filter transactions: `filter` + +Filters transactions based on the date & time of dated transactions. + +Format: `filter a/[TIME] b/[TIME]` OR `filter a/[TIME]` OR `filter b/[TIME]` OR `filter [TIME]` + +* `TIME` should be in the format of `DD-MM-YYYY HHMM`. +* `a/` prefix specifies the earlier time bound of the search. It should be before the `b/` prefix. +* `b/` prefix specifies the later time bound of the search. +* `filter a/[TIME] b/[TIME]` for searching transactions occurring between a specified time period. +* `filter a/[TIME]` for searching transactions after a specified datetime. +* `filter b/[TIME]` for searching transactions before a specified datetime. +* `filter [TIME]` for searching transactions matching a specified datetime. + +Example of usage: +``` +// Assuming 3 transactions on 1st Jan 2022, 2023 and 2024 + +// Filter transactions after a specified date & time +filter a/02-01-2023 2359 + The following list of transactions is after the time 01-01-2020 2359. + 3 . + Lender: alice + Transaction time: 01-01-2024 2359 + Borrower 1: bob Owed amount: $3.00 + +// Filter transcations before a specified date & time +filter b/31-12-2022 2359 + The following list of transactions is before the time 31-12-2022 2359. + 1. + Lender: alice + Transaction time: 01-01-2022 2359 + Borrower 1: bob Owed amount: $3.00 + +// Filter transactions matching a specified date & time +filter 01-01-2024 2359 + The following list of transactions matches with the time 01-01-2024 2359. + 3. + Lender: alice + Transaction time: 01-01-2024 2359 + Borrower 1: bob Owed amount: $3.00 +``` + +
+ +### Views the balances of all members on a chart: `chart` + +Shows a chart of the balances of all members in the group. + +Format: `chart` + +Example of usage: +``` +// Assume members are in the list +add transaction alice p/bob a/100 +add transaction charlie p/alice a/6 p/bob a/1 + +chart + Loading balances chart... +``` + +A separate window will pop up displaying the balances of all members in the group in the form of a category chart. + +They are color-coded to show the balance status of each member. A separate tooltip will show the exact balance of each member. + +![viewChart.png](diagrams/viewChart.png) + +
+ +### Exiting the application: `exit` + +Exits the application. + +Format: `exit` or `close` + +Example of usage: +``` +exit +close + Goodbye! Hope to see you again soon! +``` ## FAQ **Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: Install LongAh! on the other computer and replace the empty members, pin, and transaction TXT files it creates with the files containing your data. + +## Common Errors + +### Failure to adhere to command format +Error messages will be output by LongAh! in the event if the user input does not match the corresponding formatting for +the desired operation. For e.g. +``` +lsit transactions + Invalid command. Use 'help' to see the list of commands. +``` +This could be potentially caused by +* Misspelled Action keywords (e.g. lsit transactions instead of list transactions) +* Absence of required user parameters +* Absence of important formatting prefixes (e.g. t/) +* User parameters does not follow intended standards (e.g. Wrong formatting of date & time input) + +
+ +### Invalid Requests +As LongAh! is designed to specifically target transactions taking place in real-life, illogical requests going against +real-life/system standards may also trigger error messages. For e.g., +``` +add transaction alice t/20-07-2077 2359 p/bob a/200 + Invalid DateTime input. Dates of the future are not allowed. +``` +Or +``` +add transaction alice p/bob a/200.0005 + Invalid transaction value. +``` +This could be potentially caused by +* Invalid parameters (e.g. Dates of the future, Transaction Values more than 2 decimal places) +* Illogical parameters (e.g. A member being both a lender and a borrower within a transaction) -## Command Summary +## Future Improvements -{Give a 'cheat sheet' of commands here} +The following quality of life improvements have been taken into consideration and will be implemented in future versions of LongAh! -* Add todo `todo n/TODO_NAME d/DEADLINE` +1. Edit Group Names +2. Settle with Reference to Specific Lender Only \ No newline at end of file diff --git a/docs/diagrams/CommandExecutionSequence.puml b/docs/diagrams/CommandExecutionSequence.puml new file mode 100644 index 0000000000..77a9204da2 --- /dev/null +++ b/docs/diagrams/CommandExecutionSequence.puml @@ -0,0 +1,39 @@ +@startuml +actor User +participant ":LongAh" +participant ":Group" +participant ":Command" +participant ":MemberList" +participant ":TransactionList" +participant ":StorageHandler" + +User -> ":LongAh": Sample Command +":LongAh" -> ":Group": Get group +":Group"--> ":LongAh": group +":LongAh" -> ":Command": command, group +":Command" -> ":Group": Execute command + +opt update members + ":Group" -> ":MemberList": Get members + ":MemberList" --> ":Group": members + ":Group"-> ":MemberList": Execute members-related command + ":MemberList" --> ":Group" +end + +opt update transactions + ":Group"-> ":TransactionList": Get transactions + ":TransactionList"--> ":Group": transactions + ":Group"-> ":TransactionList": Execute transaction-related command + ":TransactionList"--> ":Group" +end + +opt data updated + ":Group"-> ":MemberList": Update transactions solution + MemberList --> ":Group" + ":Group"-> ":StorageHandler": Save new data + ":StorageHandler" --> ":Group" +end + +":Group"--> ":LongAh" +":LongAh" --> User +@enduml \ No newline at end of file diff --git a/docs/diagrams/CommandExecutionSequenceDiagram.png b/docs/diagrams/CommandExecutionSequenceDiagram.png new file mode 100644 index 0000000000..addc5b7937 Binary files /dev/null and b/docs/diagrams/CommandExecutionSequenceDiagram.png differ diff --git a/docs/diagrams/CommandInheritance.png b/docs/diagrams/CommandInheritance.png new file mode 100644 index 0000000000..bc75776ecc Binary files /dev/null and b/docs/diagrams/CommandInheritance.png differ diff --git a/docs/diagrams/CommandInheritance.puml b/docs/diagrams/CommandInheritance.puml new file mode 100644 index 0000000000..40c35034e9 --- /dev/null +++ b/docs/diagrams/CommandInheritance.puml @@ -0,0 +1,29 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle + +class "{abstract}\nCommand" { + -commandString: String + -taskExpression: String + +execute(Group) +} + +class Group + +together { + class AddCommand + class EditCommand + class ListCommand + class DeleteCommand + class FindCommand +} + +AddCommand -up-|> "{abstract}\nCommand" +EditCommand-up-|> "{abstract}\nCommand" +ListCommand -up-|> "{abstract}\nCommand" +DeleteCommand -up-|> "{abstract}\nCommand" +FindCommand -up-|> "{abstract}\nCommand" + +"{abstract}\nCommand" -> Group + +@enduml \ No newline at end of file diff --git a/docs/diagrams/Flowchart.png b/docs/diagrams/Flowchart.png new file mode 100644 index 0000000000..198fcddbad Binary files /dev/null and b/docs/diagrams/Flowchart.png differ diff --git a/docs/diagrams/GroupClass.png b/docs/diagrams/GroupClass.png new file mode 100644 index 0000000000..84b841eccc Binary files /dev/null and b/docs/diagrams/GroupClass.png differ diff --git a/docs/diagrams/GroupClass.puml b/docs/diagrams/GroupClass.puml new file mode 100644 index 0000000000..558307a287 --- /dev/null +++ b/docs/diagrams/GroupClass.puml @@ -0,0 +1,31 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle +class Group { + - members: MemberList + - transactions: TransactionList + - storage: StorageHandler + - groupName: String + - transactionSolution: ArrayList + + Constructor(String) + + updateTransactionSolution() + + settleUp(String) + + saveAllData() + + listDebts(): String + + listIndivDebt(String): String +} + +class GroupList { + - activeGroup: Group + - groupList: ArrayList + + Constructor() + + switchActiveGroup(Group) + + createGroup() + + loadGroupList() + + addGroup(Group) + + deleteGroup(String) + + saveGroupList() +} + +GroupList "1" --> "*" Group +@enduml \ No newline at end of file diff --git a/docs/diagrams/LongAh logo.jpg b/docs/diagrams/LongAh logo.jpg new file mode 100644 index 0000000000..6e9b3d2134 Binary files /dev/null and b/docs/diagrams/LongAh logo.jpg differ diff --git a/docs/diagrams/LongAh.puml b/docs/diagrams/LongAh.puml new file mode 100644 index 0000000000..7eb9db0868 --- /dev/null +++ b/docs/diagrams/LongAh.puml @@ -0,0 +1,362 @@ +@startuml +class longah.commands.delete.DeleteTransactionCommand { ++ <> DeleteTransactionCommand(String,String) ++ void execute(Group) +} + +class longah.handler.Logging { +- {static} Logger longAhLogger ++ <> Logging() ++ {static} void log(Level,String) ++ {static} void logInfo(String) ++ {static} void logWarning(String) +} + + +class longah.commands.list.ListMemberCommand { ++ <> ListMemberCommand(String,String) ++ void execute(Group) +} + +class longah.commands.find.FindBorrowerCommand { ++ <> FindBorrowerCommand(String,String) ++ void execute(Group) +} + +class longah.node.Transaction { +- Member lender +- LocalDateTime transactionTime +- ArrayList subtransactions ++ <> Transaction(String,MemberList) ++ <> Transaction(Member,ArrayList,MemberList) ++ <> Transaction(Member,ArrayList,MemberList,String) ++ void parseTransaction(String,MemberList) ++ void parseTransaction(Member,ArrayList,MemberList) ++ void addBorrower(String,MemberList,Member) ++ Member getLender() ++ boolean isLender(String) ++ boolean isBorrower(String) ++ boolean isInvolved(String) ++ String toString() ++ String toStorageString(String) ++ ArrayList getSubtransactions() ++ void editTransaction(String,MemberList) ++ boolean deleteMember(Member) ++ boolean haveTime() +} + + +class longah.util.TransactionList { +- ArrayList transactions ++ void addTransaction(Transaction) ++ void addTransaction(String,MemberList) ++ int getTransactionListSize() ++ void remove(String) ++ void clear(MemberList) ++ ArrayList getTransactions() ++ String listTransactions() ++ String findLender(String) ++ String findBorrower(String) ++ String findTransactions(String) ++ void editTransactionList(String,MemberList) ++ String findDebts(String) ++ void deleteMember(String,MemberList) +} + + +class longah.commands.delete.DeleteCommand { +- String subCommand ++ <> DeleteCommand(String,String) ++ void execute(Group) +} + + +class longah.commands.add.AddCommand { +- String subCommand ++ <> AddCommand(String,String) ++ void execute(Group) +} + + +class longah.util.MemberList { +- ArrayList members ++ <> MemberList() ++ void addMember(Member) ++ void addMember(String) ++ void addMember(String,double) ++ boolean isMember(String) ++ boolean isMember(Member) ++ Member getMember(String) ++ void editMemberName(String) ++ String listMembers() ++ void updateMembersBalance(TransactionList) ++ ArrayList> classifyMembers() ++ ArrayList solveTransactions() ++ int getMemberListSize() ++ ArrayList getMembers() ++ double getMemberBalance(String) ++ void clearBalances() ++ void deleteMember(String) +} + + +class longah.commands.add.AddMemberCommand { ++ <> AddMemberCommand(String,String) ++ void execute(Group) +} + +class longah.util.Subtransaction { +- Member lender +- Member borrower +- double amount ++ <> Subtransaction(Member,Member,double) ++ Member getLender() ++ Member getBorrower() ++ double getAmount() ++ boolean isInvolved(String) ++ String toString() +} + + +class longah.commands.HelpCommand { ++ <> HelpCommand(String,String) ++ void execute(Group) ++ {static} void listAllCommands() +} + +class longah.commands.delete.DeleteMemberCommand { ++ <> DeleteMemberCommand(String,String) ++ void execute(Group) +} + +class longah.commands.edit.EditMemberCommand { ++ <> EditMemberCommand(String,String) ++ void execute(Group) +} + +class longah.commands.edit.EditCommand { +- String subCommand ++ <> EditCommand(String,String) ++ void execute(Group) +} + + +class longah.commands.find.FindLenderCommand { ++ <> FindLenderCommand(String,String) ++ void execute(Group) +} + +class longah.commands.add.AddTransactionCommand { ++ <> AddTransactionCommand(String,String) ++ void execute(Group) +} + +class longah.commands.list.ListDebtCommand { ++ <> ListDebtCommand(String,String) ++ void execute(Group) +} + + +class longah.handler.UI { +- {static} Scanner scanner +- {static} String SEPARATOR ++ {static} void showWelcomeMessage() ++ {static} void showCommandPrompt() ++ {static} String getUserInput() ++ {static} void showMessage(String) ++ {static} void showMessage(String,boolean) ++ {static} boolean hasNextLine() ++ {static} void printSeparator() +} + + +class longah.commands.find.FindTransactionCommand { ++ <> FindTransactionCommand(String,String) ++ void execute(Group) +} + + +class longah.commands.list.ListCommand { +- String subCommand ++ <> ListCommand(String,String) ++ void execute(Group) +} + + +class longah.commands.edit.EditTransactionCommand { ++ <> EditTransactionCommand(String,String) ++ void execute(Group) +} + +class longah.commands.ExitCommand { ++ <> ExitCommand(String,String) ++ void execute(Group) +} + +class longah.commands.ClearCommand { ++ <> ClearCommand(String,String) ++ void execute(Group) +} + +class longah.node.Member { +- String name +- double balance ++ <> Member(String) ++ <> Member(String,double) ++ void setName(String) ++ void addToBalance(double) ++ void subtractFromBalance(double) ++ double getBalance() ++ String toString() ++ String toStorageString(String) ++ String getName() ++ boolean isName(String) ++ void clearBalance() ++ void resetBalance() +} + + +class longah.commands.list.ListTransactionCommand { ++ <> ListTransactionCommand(String,String) ++ void execute(Group) +} + +class longah.commands.PINCommand { ++ <> PINCommand(String,String) ++ void execute(Group) ++ void execute() +} + +class longah.handler.PINHandler { +- {static} String PIN_FILE_PATH +- {static} String savedPin +- {static} boolean authenticationEnabled ++ <> PINHandler() ++ {static} void loadPinAndAuthenticationEnabled() ++ {static} void savePinAndAuthenticationEnabled() ++ {static} String getPinFilePath() ++ {static} void createPin() ++ {static} void authenticate() ++ {static} void resetPin() ++ {static} void enablePin() ++ {static} void disablePin() ++ {static} String getSavedPin() ++ {static} boolean getAuthenticationStatus() +} + + +class longah.LongAh { +- {static} Group group ++ {static} void init() ++ {static} void main(String[]) +} + + +abstract class longah.commands.Command { +# String commandString +# String taskExpression ++ <> Command(String,String) ++ {abstract}void execute(Group) ++ String getCommandString() ++ String getTaskExpression() +} + + +class longah.node.Group { +- {static} Logger logger +- MemberList members +- TransactionList transactions +- StorageHandler storage +- String groupName +- ArrayList transactionSolution ++ <> Group(String) ++ void setGroupName(String) ++ String getGroupName() ++ void setMemberList(MemberList) ++ MemberList getMemberList() ++ void setTransactionList(TransactionList) ++ TransactionList getTransactionList() ++ void updateTransactionSolution() ++ void settleUp(String) ++ void saveMembersData() ++ void saveTransactionsData() ++ void saveAllData() ++ String listDebts() ++ String listIndivDebt(String) +} + + +class longah.handler.InputHandler { ++ {static} Command parseInput(String) ++ {static} Command parseCommand(String,String) +} + +class longah.handler.StorageHandler { +- {static} String SEPARATOR +- {static} String MEMBERS_FILE_STRING +- {static} String TRANSACTIONS_FILE_STRING +- String storageFolderPath +- String storageMembersFilePath +- String storageTransactionsFilePath +- File membersFile +- File transactionsFile +- MemberList members +- TransactionList transactions +- Scanner[] scanners ++ <> StorageHandler(MemberList,TransactionList,String) ++ void initStorageScanners() ++ {static} void initDir() ++ void loadMembersData() ++ void loadTransactionsData() ++ {static} Subtransaction parseSubtransaction(String,String,Member,MemberList) ++ boolean checkTransactions(MemberList) ++ void loadAllData() ++ void saveMembersData() ++ void saveTransactionsData() ++ void saveAllData() +} + + +class longah.commands.SettleCommand { ++ <> SettleCommand(String,String) ++ void execute(Group) +} + +class longah.commands.find.FindCommand { +- String subCommand ++ <> FindCommand(String,String) ++ void execute(Group) +} + + +class longah.commands.find.FindDebtCommand { ++ <> FindDebtCommand(String,String) ++ void execute(Group) +} + + + +longah.commands.Command <|-- longah.commands.delete.DeleteTransactionCommand +longah.commands.Command <|-- longah.commands.list.ListMemberCommand +longah.commands.Command <|-- longah.commands.find.FindBorrowerCommand +longah.commands.Command <|-- longah.commands.delete.DeleteCommand +longah.commands.Command <|-- longah.commands.add.AddCommand +longah.commands.Command <|-- longah.commands.add.AddMemberCommand +longah.commands.Command <|-- longah.commands.HelpCommand +longah.commands.Command <|-- longah.commands.delete.DeleteMemberCommand +longah.commands.Command <|-- longah.commands.edit.EditMemberCommand +longah.commands.Command <|-- longah.commands.edit.EditCommand +longah.commands.Command <|-- longah.commands.find.FindLenderCommand +longah.commands.Command <|-- longah.commands.add.AddTransactionCommand +longah.commands.Command <|-- longah.commands.list.ListDebtCommand +longah.commands.Command <|-- longah.commands.find.FindTransactionCommand +longah.commands.Command <|-- longah.commands.list.ListCommand +longah.commands.Command <|-- longah.commands.edit.EditTransactionCommand +longah.commands.Command <|-- longah.commands.ExitCommand +longah.commands.Command <|-- longah.commands.ClearCommand +longah.commands.Command <|-- longah.commands.list.ListTransactionCommand +longah.commands.Command <|-- longah.commands.PINCommand +longah.commands.Command <|-- longah.commands.SettleCommand +longah.commands.Command <|-- longah.commands.find.FindCommand +longah.commands.Command <|-- longah.commands.find.FindDebtCommand +@enduml \ No newline at end of file diff --git a/docs/diagrams/Member.png b/docs/diagrams/Member.png new file mode 100644 index 0000000000..f362cef589 Binary files /dev/null and b/docs/diagrams/Member.png differ diff --git a/docs/diagrams/Member.puml b/docs/diagrams/Member.puml new file mode 100644 index 0000000000..fa099f8fd2 --- /dev/null +++ b/docs/diagrams/Member.puml @@ -0,0 +1,34 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle + +class MemberList { + -members: Arraylist + +addMember(Member) + +addMember(String) + +addMember(String, double) + +editMemberName(String, String) + +listMembers(): String + +updateMembersBalance(TransactionList) + +solveTransactions(): ArrayList + +clearBalances() + +deleteMember(String) +} + +class Member { + -name: String + -balance: double + +Constructor(String) + +Constructor(String, double) + +addToBalance(double) + +subtractFromBalance(double) + +clearBalance() +} + +class TransactionList +class Subtransaction + +MemberList -> Member +MemberList -[dashed]-> TransactionList +MemberList -[dashed]-> Subtransaction +@enduml \ No newline at end of file diff --git a/docs/diagrams/MembersFileSample.png b/docs/diagrams/MembersFileSample.png new file mode 100644 index 0000000000..af201d773f Binary files /dev/null and b/docs/diagrams/MembersFileSample.png differ diff --git a/docs/diagrams/StorageHandlerInitSequenceDiagram.png b/docs/diagrams/StorageHandlerInitSequenceDiagram.png new file mode 100644 index 0000000000..d16f9972c7 Binary files /dev/null and b/docs/diagrams/StorageHandlerInitSequenceDiagram.png differ diff --git a/docs/diagrams/StorageHandlerInitSequenceDiagram.puml b/docs/diagrams/StorageHandlerInitSequenceDiagram.puml new file mode 100644 index 0000000000..cc681268e1 --- /dev/null +++ b/docs/diagrams/StorageHandlerInitSequenceDiagram.puml @@ -0,0 +1,23 @@ +@startuml +participant ":LongAh" +participant ":Group" +participant ":MemberList" +participant ":TransactionList" +participant ":StorageHandler" + +":LongAh" -> ":Group": Get group +":Group" -> ":MemberList": Create members +":MemberList" --> ":Group": Members +":Group" -> ":TransactionList": Create transactions +":TransactionList" --> ":Group": Transactions +":Group" --> ":LongAh": group +":LongAh" -> ":StorageHandler": Members, Transactions, Name +loop until file is fully read +":StorageHandler" -> ":StorageHandler": Read Data from Files +":StorageHandler" -> ":MemberList": Get Member Data +":MemberList" --> ":StorageHandler" : Member Data +":StorageHandler" -> ":TransactionList": Get Transaction Data +":TransactionList" --> ":StorageHandler" : Transaction Data +end +":StorageHandler" --> ":LongAh" +@enduml \ No newline at end of file diff --git a/docs/diagrams/TransactionClass.png b/docs/diagrams/TransactionClass.png new file mode 100644 index 0000000000..89d27dcd67 Binary files /dev/null and b/docs/diagrams/TransactionClass.png differ diff --git a/docs/diagrams/TransactionClass.puml b/docs/diagrams/TransactionClass.puml new file mode 100644 index 0000000000..44a7c311a7 --- /dev/null +++ b/docs/diagrams/TransactionClass.puml @@ -0,0 +1,36 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle +class Transaction { + - lender : Member + - transactionTime : dateTime + - subtransactions : ArrayList + + Constructor(String, memberList) + + Constructor(Member, ArrayList, MemberList) + + Constructor(Member, ArrayList, MemberList, String) + + parseTransaction(String, MemberList) + + parseTransaction(Member, ArrayList, MemberList) + + editTransaction(Member, ArrayList, MemberList) + + deleteMember(Member) +} + +class TransactionList { + - transactions : ArrayList + + addTransaction(Transaction) + + addTransaction(String, MemberList) + + remove(Transaction) + + clear(MemberList) + + findLender(Member, MemberList) + + findBorrower(Member, MemberList) + + findTransactions(Member, MemberList) + + filterTransactionsEqualToDateTime(String) + + filterTransactionsBeforeDateTime(String) + + filterTransactionsAfterDateTime(String) + + filterTransactionsBetweenDateTime(String, String) + + editTransaction(String, MemberList) + + findDebts(String) + + deleteMember(String, MemberList) +} + +TransactionList "1" --> "*" Transaction +@enduml \ No newline at end of file diff --git a/docs/diagrams/TransactionsFileSample.png b/docs/diagrams/TransactionsFileSample.png new file mode 100644 index 0000000000..f798fdc50a Binary files /dev/null and b/docs/diagrams/TransactionsFileSample.png differ diff --git a/docs/diagrams/addDateTimeforDatedTransaction.png b/docs/diagrams/addDateTimeforDatedTransaction.png new file mode 100644 index 0000000000..9c54ed6f6d Binary files /dev/null and b/docs/diagrams/addDateTimeforDatedTransaction.png differ diff --git a/docs/diagrams/addDateTimeforDatedTransaction.puml b/docs/diagrams/addDateTimeforDatedTransaction.puml new file mode 100644 index 0000000000..32a829335c --- /dev/null +++ b/docs/diagrams/addDateTimeforDatedTransaction.puml @@ -0,0 +1,27 @@ +@startuml +participant ":TransactionList" as Foo1 +participant ":Transaction" as Foo2 +participant ":DateTime" as Foo3 +participant ":LongAhException" as Foo4 +[-> Foo1 : ""addTransaction(taskExpression, members)"" +Foo1 -> Foo1:addTransaction(expression,memberList) +Foo1 -> Foo2: new Transaction(expression, memberList) +Foo2 -> Foo2:parseTransaction(expression, memberList) +alt expression contains dateTimeExpression + Foo2 -> Foo3:new DateTime(dateTimeExpression) + alt dateTimeExpression is valid + Foo3 --> Foo2: DateTime object created + Foo2 -> Foo2: Transaction DateTime added + ref over Foo2: Adding other transaction details + Foo2 --> Foo1: Transaction object created + Foo1 --> Foo1: Transaction added + Foo1 -->[ : + else dateTimeExpression is not in format + Foo3 -> Foo4:LongAhException(invalid dateTime format) + ref over Foo4: Handling Invalid DateTime Format + else dateTimeExpression is of future + Foo3 -> Foo4: LongAhException(invalid dateTime input) + ref over Foo4: Handling Invalid DateTime Input + end +end +@enduml \ No newline at end of file diff --git a/docs/diagrams/addTransaction.png b/docs/diagrams/addTransaction.png new file mode 100644 index 0000000000..688222493c Binary files /dev/null and b/docs/diagrams/addTransaction.png differ diff --git a/docs/diagrams/addTransaction.puml b/docs/diagrams/addTransaction.puml new file mode 100644 index 0000000000..a57c0be0f5 --- /dev/null +++ b/docs/diagrams/addTransaction.puml @@ -0,0 +1,33 @@ +@startuml +actor User + +participant ":LongAh" +participant ":Group" +participant ":Command" +participant ":MemberList" +participant ":TransactionList" +participant ":Transaction" + + +ref over "User" , ":Command" +Command Execution Sequence +(addTransaction) +end ref +":Group" -> ":MemberList" : Get members +":MemberList" --> ":Group" : members +":Group" -> ":TransactionList" : Get transactions +":TransactionList" --> ":Group" : transactions +":Group" -> ":TransactionList": Add transaction +alt Valid transaction format + ":TransactionList" -> ":Transaction": Add transaction + ":Transaction" --> ":TransactionList": Transaction added + ":TransactionList" --> ":Group": Update transaction list + ":Group" --> "User": Transaction details + ":Group" -> ":Group": Update members balance + ":Group" -> ":Group": Update transaction solution +else Invalid transaction format + ":TransactionList" -> ":Group": Invalid transaction format + ":Group" --> "User" : "Invalid transaction format" +end + +@enduml \ No newline at end of file diff --git a/docs/diagrams/comparingDateTime.png b/docs/diagrams/comparingDateTime.png new file mode 100644 index 0000000000..0dfa397042 Binary files /dev/null and b/docs/diagrams/comparingDateTime.png differ diff --git a/docs/diagrams/comparingDateTime.puml b/docs/diagrams/comparingDateTime.puml new file mode 100644 index 0000000000..0fa23b414a --- /dev/null +++ b/docs/diagrams/comparingDateTime.puml @@ -0,0 +1,19 @@ +@startuml +participant ":TransactionList" as Foo +participant ":Transaction" as Foo1 +participant ":DateTime" as Foo2 +[-> Foo:""filterTransactionsEqualToDateTime(dateTimeExpression)"" +Foo -> Foo2:new DateTime(dateTimeExpression) +Foo2 --> Foo: DateTime object of user input created +loop for Transaction in Transactionlist + Foo -> Foo1: transaction.getTransactionTime() + Foo1 --> Foo: DateTime object of transaction + Foo -> Foo2: transactionDateTime.isEqual(userDateTime) + Foo2 --> Foo: Boolean determining whether the two DateTimes are equal + alt two DateTimes are equal + ref over Foo1:Generate transaction printout + Foo1 --> Foo:Added Transaction to Transactionlist printout + end +end +Foo -->[ : ""Transactionlist printout"" +@enduml \ No newline at end of file diff --git a/docs/diagrams/main.png b/docs/diagrams/main.png new file mode 100644 index 0000000000..59b6ebbbe8 Binary files /dev/null and b/docs/diagrams/main.png differ diff --git a/docs/diagrams/main.puml b/docs/diagrams/main.puml new file mode 100644 index 0000000000..9748994237 --- /dev/null +++ b/docs/diagrams/main.puml @@ -0,0 +1,30 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle + +class LongAh +class PINHandler +class "{abstract}\nCommand" +class GroupList +class Group +class MemberList +class Member +class TransactionList +class Transaction +class Subtransaction +class StorageHandler + +LongAh "1" -> "1" PINHandler +LongAh -> "{abstract}\nCommand" +LongAh "1" -d-> "1" GroupList +"{abstract}\nCommand" -[dashed]-> Group +GroupList "1" -> "*" Group +Group "1" -d-> "1" StorageHandler +Group "1" -d-> "1" MemberList +Group "1" -d-> "1" TransactionList +MemberList "1" -d-> "*" Member +TransactionList "1" -d-> "*" Transaction +Transaction "1" -d-> "*" Subtransaction +Group "1" -> "*" Subtransaction + +@enduml \ No newline at end of file diff --git a/docs/diagrams/pin.puml b/docs/diagrams/pin.puml new file mode 100644 index 0000000000..53c8e11082 --- /dev/null +++ b/docs/diagrams/pin.puml @@ -0,0 +1,16 @@ +@startuml +actor User +participant ":PINHandler" as PINHandler + +User -> PINHandler: start up +loop until valid PIN entered + PINHandler --> User: Enter your PIN + User -> PINHandler: [enters PIN] + PINHandler -> PINHandler: Compare entered PIN + alt PIN is correct + PINHandler --> User: Login successful! + else PIN is incorrect + PINHandler --> User: Invalid PIN. Please try again. + end +end +@enduml \ No newline at end of file diff --git a/docs/diagrams/pinhandler longah.png b/docs/diagrams/pinhandler longah.png new file mode 100644 index 0000000000..96dfbdb408 Binary files /dev/null and b/docs/diagrams/pinhandler longah.png differ diff --git a/docs/diagrams/pinreset.png b/docs/diagrams/pinreset.png new file mode 100644 index 0000000000..d1e2a03a06 Binary files /dev/null and b/docs/diagrams/pinreset.png differ diff --git a/docs/diagrams/pinreset.puml b/docs/diagrams/pinreset.puml new file mode 100644 index 0000000000..ea0bd37d24 --- /dev/null +++ b/docs/diagrams/pinreset.puml @@ -0,0 +1,20 @@ +@startuml +actor User +participant ":PINHandler" as PINHandler + +User -> PINHandler: reset pin +loop until valid current PIN entered + PINHandler --> User: Enter your current PIN + User -> PINHandler: [enters current PIN] + PINHandler -> PINHandler: Compare entered current PIN + alt current PIN is correct + PINHandler --> User: Create your 6-digit PIN + User -> PINHandler: [enters new PIN] + PINHandler -> PINHandler: Encrypt new PIN + PINHandler -> PINHandler: Save new PIN + PINHandler --> User: New PIN saved + else current PIN is incorrect + PINHandler --> User: Invalid PIN + end +end +@enduml \ No newline at end of file diff --git a/docs/diagrams/printingDateTime.png b/docs/diagrams/printingDateTime.png new file mode 100644 index 0000000000..e2e44dd35e Binary files /dev/null and b/docs/diagrams/printingDateTime.png differ diff --git a/docs/diagrams/printingDateTime.puml b/docs/diagrams/printingDateTime.puml new file mode 100644 index 0000000000..6d6bb25fca --- /dev/null +++ b/docs/diagrams/printingDateTime.puml @@ -0,0 +1,12 @@ +@startuml +participant ":Transaction" as Foo +participant ":DateTime" as Foo1 +[-> Foo:""toString()"" +alt Transaction has dateTime + Foo -> Foo1:toString() + Foo1 --> Foo: String representing dateTime + Foo -> Foo: Added String dateTime to printout +end +ref over Foo:Adding other transaction details to printout +Foo -->[ : ""Transaction printout"" +@enduml \ No newline at end of file diff --git a/docs/diagrams/viewChart.png b/docs/diagrams/viewChart.png new file mode 100644 index 0000000000..58433b81be Binary files /dev/null and b/docs/diagrams/viewChart.png differ diff --git a/docs/team/1simjustin.md b/docs/team/1simjustin.md new file mode 100644 index 0000000000..20ebf1d044 --- /dev/null +++ b/docs/team/1simjustin.md @@ -0,0 +1,66 @@ +# Sim Justin - Project Portfolio Page + +## Overview + +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the least transaction method of settling these debts. It is optimized for busy people with large transaction quantities among friends. It is written in Java. + +### Summary of Contributions + +Given below are my contributions to the project. + +* **New Feature**: Member Handling + * What it does: Representation of each member object, with name and balance as attributes and methods to get, set or modify each of the attributes. + * Justification: Encapsulation of member-related data and behavior to promote code organization and modularity. + * Highlights: Incorporates validation checks to ensure the integrity of member data, enhancing reliability and consistency. + +* **New Feature**: Transaction Solving Algorithm + * What it does: This feature implements an algorithm to balance transactions between members by creating subtransactions. + * Justification: This algorithm efficiently distributes debts and credits among members, minimizing discrepancies and simplifying financial reconciliation. + * Highlights: Handles cases where one member owes multiple others or is owed by multiple others, ensuring all debts and credits are properly accounted for. Optimizes transaction processing by minimizing the number of subtransactions required while maintaining accuracy. + +* **New Feature**: Added Data Storage Capability. + * What it does: This feature introduces the ability to store member and transaction data for a group in persistent files. It includes methods for loading data from files into memory and saving data from memory to files. + * Justification: Storage capability is essential for preserving data between program executions. By storing data in files, users can resume their work and maintain a record of their transactions even after closing the application. This feature enhances the usability and practicality of the application. + * Highlights: Creates subdirectories for each discrete group unit. + +
+ +* **General Contributions**: Abstraction of Commands, Exceptions and Logging + * What it does + * *Commands Abstraction*: Encapsulates command execution logic into a base class `Command`, providing a standardized interface for all commands in the system. + * *Exception Abstraction*: Centralizes exception handling logic, allowing for consistent error reporting and graceful error recovery across the application. It includes methods for printing exception messages to the user interface and logging them based on severity level. + * *Logging Abstraction*: Encapsulation of logging logic for consistent logging across the application. + * Justification: Abstraction of commands, exceptions, and logging enhances the overall structure and maintainability of the codebase. It promotes separation of concerns, making it easier to manage and extend different aspects of the application independently. + +* **General Contributions**: Improve text UI testing + * What it does + * Carry out automated testing as if under use by a normal user. Simulates multiple use sessions. + * Justification: Helps to catch bugs that may show up over the course of a normal use case. + * Impact + * Identified and rectified bugs relating to double representation in storage and list commands. [#160](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/160) + +* **Code Contributed**: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=1simjustin&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Documentation** + * User Guide + * Updated command reference. [#98](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/98) + * Paginated v2.0 UG. [#101](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/101), [#102](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/102) + * Updated application expected behaviour as of v2.0. [#153](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/153) + * Developer Guide + * Added user stories and glossary. [#70](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/70) + * Added implementation details for StorageHandler, Exception and Logging. Added instructions for testing. [#71](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/71) + * Added inheritance diagram for `Command`. [#92](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/92) + * Updated overall class diagram. [#154](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/154) + * Added high-level flowchart. [#155](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/155) + * Added section on `UI and I/O` and `Member and MemberList`. [#155](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/155) + +
+ +* **Project Management** + * Maintained issues and managed milestones. + * In charge of issues assignee allocation. + * Conducted triaging of bugs during PE-D. + +* **Community** + * PRs reviewed (with non-trivial review comments): [#31](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/31), [#32](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/32), [#40](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/40), [#49](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/49), [#53](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/53), [#55](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/55), [#61](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/61), [#77](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/77), [#86](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/86), [#89](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/89), [#174](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/174), [#175](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/175) + * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/1simjustin/ped/issues/1), [2](https://github.com/1simjustin/ped/issues/2), [3](https://github.com/1simjustin/ped/issues/6)) diff --git a/docs/team/djleong01.md b/docs/team/djleong01.md new file mode 100644 index 0000000000..fe0d04d8d4 --- /dev/null +++ b/docs/team/djleong01.md @@ -0,0 +1,48 @@ +# Leong Deng Jun - Project Portfolio Page + +## Overview + +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the least transaction method of settling these debts. It is optimized for busy people with large transaction quantities among friends. It is written in Java. + +### Summary of Contributions + +Given below are my contributions to the project. + +* **New Feature**: Group List Handling and Storage + * What it does: Manages and stores a list of groups in the application. It allows users to create, load, switch between, add, and delete groups to manage transactions between different groups of people separately. + * Justification: Encapsulation of group-related data and behavior to promote code organization and modularity. + * Highlights: Allows for user to create and handle multiple groups of people, each with their own set of transactions and members. Storage also allows for users to save and load groups for repeated future use. + +* **New Feature**: Transaction Settlement + * What it does: This feature allows for users to settle debts among members within a group. + * Justification: This simplifies the process of settling debts among members, ensuring that all debts are cleared with a single command. + * Highlights: Lumps all debts into a single transaction based on the least transactions solution, ensuring that all debts are cleared with a single transaction. This reduces the complexity of settling debts among members. + +* **General Contributions**: Added Help Menu + * What it does: Displays a list of all available commands and their descriptions to the user. + * Justification: Provides users with a quick reference to all available commands and their usage. + + +* **Code Contributed**: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=djleong&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +
+ +* **Documentation** + * User Guide + * Initial draft and overall structure of UG for v2.0. [#69](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/69) + * Fix UG typos. [#100](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/100) + * Updated UG with PED feedback, newly added features, and format fixes. [#171](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/171) + + * Developer Guide + * Added implementation details for Transaction class. [#76](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/76) + * Added implementation details for Group class and updated Transaction class diagrams. [#174](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/174) + +
+ +* **Project Management** + * Managed internal milestones and deadlines for the team. + * Called for and conducted team meetings to discuss project progress and issues. + +* **Community** + * PRs reviewed (with non-trivial review comments): [#41](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/41#discussion_r1530007348), [#43](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/43#discussion_r1531991085) + * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/djleong01/ped/issues/11), [2](https://github.com/djleong01/ped/issues/9), [3](https://github.com/djleong01/ped/issues/13)) diff --git a/docs/team/feathersre.md b/docs/team/feathersre.md new file mode 100644 index 0000000000..06de53700c --- /dev/null +++ b/docs/team/feathersre.md @@ -0,0 +1,56 @@ +# Liao Jingyu - Project Portfolio Page + +## Overview + +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the least +transaction method of settling these debts. It is optimized for busy people with large transaction quantities among friends. It is written in Java. + +### Summary of Contributions + +My contributions towards the project is as follows. + +* **New Feature**: Finding Transactions (by Member name in general/Lender/Borrower) + * What it does: This feature provides users with the convenience of obtaining a partial list of transactions + consisting only entries that the input member is participating in. + * Justification: Constructed algorithms capable of varying printout by flexibly adding in transactions based on whether + they fulfill the user defined member name criteria. + * Highlights: Based on the input keyword (Lender/Borrower) the list can be further varied to only look for pending + payments (Lender), pending transactions (Borrower) or both. + +* **New Feature**: Transaction Time + * What it does: This feature allows the time component of transactions to be tracked and handled, further + enhancing LongAh!'s ability in reminding users of their pending transactions. + * Justification: Introduction of the DateTime class. Included further polymorphism in methods of the Transaction class + to meet the needs to add, print and store dated transactions. + * Highlights: All time handling operations are throughout encapsulated within the DateTime class. This greatly reduces + code coupling, allowing the formatting of date time variables to be done easily in the system. + +* **New Feature**: Filter Transactions (by Transaction Time) + * What is does: This feature allows users to freely filter the current list of dated transactions based on their + defined time criteria, further enhancing accessibility. + * Justification: Introduction of the filter commands class. Added comparison methods within the DateTime class and new + methods in TransactionList to account for this feature. + * Highlights: Users are given the privilege to choose between 4 modes of filtering. This could be applied to select + transactions after a date, before a date, between two dates and happening exactly at a date. + +* **General Contributions**: String printout of transactions and transaction lists. Comparison methods between member +objects. Initial extraction of logs into external file. + +* **Code Contributed**: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=feathersre&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project Management**: + * Participated actively in the distribution of work and issues in the respective milestones + * Facilitated discussion in weekly meetings to breakdown assigned workload to manageable sub-tasks. + +* **Documentation**: + * User Guide + * Formatted content regarding dated transactions and time filters [#97](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/97) [#175](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/175) + * Added in directory tree to visualise program storage structure [#97](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/97) + * Added in common errors to provide general guide for program mishandling [#175](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/175) + * Developer Guide + * Increment amendments for dated transactions. [#97](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/97) + * Added implementation details for DateTime. [#175](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/175) + +* **Community**: + * Participated in PRs reviews with non-trivial review comments. (e.g.: [#43](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/43) [#121](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/121) [#153](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/153)) + * Actively exchanged implementation ideas with team members in authored PRs. (e.g.: [#38](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/38) [#77](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/77) [#156](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/156)) \ No newline at end of file diff --git a/docs/team/haowern98.md b/docs/team/haowern98.md new file mode 100644 index 0000000000..e5ee0ccf60 --- /dev/null +++ b/docs/team/haowern98.md @@ -0,0 +1,34 @@ +# Wu Hao Wern - Project Portfolio Page + +## Overview + +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the least transaction method of settling these debts. It is optimized for busy people with large transaction quantities among friends. It is written in Java. + +### Summary of Contributions + +Given below are my contributions to the project. + +* **New Feature**: Welcome Message + * What it does: Displays an elaborate welcome message along with ASCII art. + * Justification: Improves user engagement and sets a positive tone for the user experience. + * Highlights: Implemented the showWelcomeMessage() method to showcase the ASCII art and convey a welcoming message. + +* **New Feature**: New Feature: Improved Command Prompt Readability + * What it does: Enhances the command prompt display to include a line after each command input. + * Justification: Improves readability and user interaction by clearly separating each command input and output. + * Highlights: Implemented the printSeparator() method to print the separator line, aiding in visually separating different sections of displayed content. + +* **New Feature**: Exit Message Display + * What it does: Displays a farewell message upon program exit. + * Justification: Provides a polite and friendly closure to the user interaction, enhancing the overall user experience. + * Highlights: Implemented the exit() method to display a farewell message when the program exits, creating a positive final interaction with the user. + +* **Code Contributed**: [RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=haowern98&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other). + + +* **Project Management**: + * Participated in weekly meetings to breakdown assigned workload to manageable sub-tasks. + + +* **Community** + * Gave suggestions and reported bugs for other teams in the class (examples: [1](https://github.com/haowern98/ped/issues/1), [2](https://github.com/haowern98/ped/issues/2), [3](https://github.com/haowern98/ped/issues/3)) diff --git a/docs/team/jing-xiang.md b/docs/team/jing-xiang.md new file mode 100644 index 0000000000..e0283be640 --- /dev/null +++ b/docs/team/jing-xiang.md @@ -0,0 +1,61 @@ +# Jing Xiang - Project Portfolio Page + +## Overview + +LongAh! is a CLI-based application designed to help users track debts within friend groups and determine the +least transaction method of settling these debts. It is optimized for busy people with large transaction quantities +among friends. It is written in Java. + + +### Summary of Contributions + +Given below are my contributions to the project. + +- **New Feature**: Added the `add` command to allow users to add new debts to the application. + - What it does: This feature allows users to add new debts to the application, specifying the debtor, creditor, and amount. + - Justification: This feature is the basis of the application as it allows users to input their debts into the application. + - Highlights: This enhancement impacts both existing and future commands, necessitating a thorough analysis of design alternatives. The implementation was challenging due to the required changes to existing commands and input formats. + +- **New Feature**: Added the `edit` command to allow users to edit existing debts in the application. + - What it does: This feature allows users to edit existing debts in the application, changing the debtor, creditor, or amount. + - Justification: This feature improves the product significantly by enabling users to update information without having to delete and re-enter entries. + - Highlights: This feature extends the functionality of the application, requiring a deep understanding of the existing codebase and the ability to integrate new features seamlessly. + +- **New Feature** Added the `pin` command to implement PIN authentication for the application. + - What it does: This feature allows users to set, reset, enable and disable a PIN for the application, which must be created upon the first start up. + - Justification: This feature enhances the security of the application, ensuring that only authorized users can access the application. + - Highlights: This feature required a thorough understanding of the existing codebase and the ability to integrate new features seamlessly. It also involved implementing a secure and user-friendly authentication system using hashing algorithms. It also required storage of the PIN in a secure manner. + +- **New Feature** Added the `chart` command to allow users to view a graphical representation of the debts in the application. + - What it does: This feature allows users to view a bar chart of the debts in the application, showing the distribution of debts among friends. + - Justification: This feature enhances the user experience by providing a visual representation of the debts, making it easier for users to understand the data. + - Highlights: This feature required integrating a third-party library for chart generation and implementing a parser to convert the data into a format suitable for the library. + - Credits: The implementation of this feature was supported by [XChart](https://knowm.org/open-source/xchart/), an open-source library for chart generation. + + +- **Code contributed**: [RepoSense link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=jing-xiang&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + + +- **Project management**: + - Managed and conducted releases ```v1.0``` - ```v2.1``` (3 releases) on GitHub + - Managed feature increments and frequent bug fixing on GitHub Issues [#78](https://github.com/AY2324S2-CS2113-T15-1/tp/issues/78), [#96](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/96), [#91](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/91), [#117](https://github.com/AY2324S2-CS2113-T15-1/tp/issues/117) [#164](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/164) + +- **Enhancements to existing features**: + - Improved the `list` command to display debts in a more user-friendly format. + - Enhanced the `pin` command to allow for more seamless PIN authentication and management. [#67](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/67) + - Wrote additional tests for the above features. [#86](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/86) + +- **Documentation**: +- User Guide: + - Added documentation for the features `pin`, and `chart`. [#87](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/87) +- Developer Guide: + - Added implementation details and sequence diagram of the features `pin`, and `chart`. [#74](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/74), [#73](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/73) + - Added UML diagram [#90](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/90) [#173](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/173) + +- **Community**: +- PRs reviewed (with non-trivial review comments): examples [#89](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/89), [#80](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/80), [#77](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/77), [#76](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/76), [#43](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/43) +- Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/nus-cs2113-AY2324S2/tp/pull/63), [2](https://github.com/nus-cs2113-AY2324S2/tp/pull/1), [3](https://github.com/nus-cs2113-AY2324S2/tp/pull/13)). +- Contributed to forum posts (examples: [1](https://github.com/nus-cs2113-AY2324S2/forum/issues/14), [2](https://github.com/nus-cs2113-AY2324S2/forum/issues/28)). + +- **Tools**: + - Integrated the `XChart` library for the `chart` feature [#86](https://github.com/AY2324S2-CS2113-T15-1/tp/pull/86). \ 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/src/main/java/longah/LongAh.java b/src/main/java/longah/LongAh.java new file mode 100644 index 0000000000..c5813a79b1 --- /dev/null +++ b/src/main/java/longah/LongAh.java @@ -0,0 +1,68 @@ +package longah; + +import longah.util.GroupList; +import longah.handler.Logging; +import longah.handler.PINHandler; +import longah.handler.UI; +import longah.handler.InputHandler; + +import longah.exception.LongAhException; +import longah.commands.Command; + +/** + * LongAh class manages debts between members. + */ +public class LongAh { + + /** + * Initializes the LongAh application. + */ + public static void init() { + new Logging(); + UI.showMessage("Welcome to LongAh!"); + UI.showWelcomeMessage(); + } + + /** + * The main loop of the LongAh application. + * + * @throws LongAhException If an invalid group name or command is entered + */ + public static void loop() throws LongAhException { + if (GroupList.isEmpty()) { + GroupList.createGroup(); + } else { + UI.showCommandPrompt(); + String command = UI.getUserInput(); + Command c = InputHandler.parseInput(command); + c.execute(GroupList.getActiveGroup()); + } + } + + /** + * The main method to run the LongAh application. + * + * @param args The command-line arguments. + */ + public static void main(String[] args) { + init(); + + Logging.logInfo("Starting Pre-program preparations."); + try { + new PINHandler(); + new GroupList(); + } catch (LongAhException e) { + LongAhException.printException(e); + } + + Logging.logInfo("Entering main program body. Begin accepting user commands."); + while (true) { + UI.printSeparator(); + try { + loop(); + } catch (LongAhException e) { + LongAhException.printException(e); + } + } + } +} diff --git a/src/main/java/longah/commands/ChartCommand.java b/src/main/java/longah/commands/ChartCommand.java new file mode 100644 index 0000000000..1c0987d64a --- /dev/null +++ b/src/main/java/longah/commands/ChartCommand.java @@ -0,0 +1,56 @@ +package longah.commands; + +import longah.util.Chart; +import longah.exception.ExceptionMessage; +import longah.handler.Logging; +import longah.util.MemberList; +import longah.node.Member; +import longah.exception.LongAhException; +import longah.node.Group; + +import java.util.ArrayList; +import java.util.List; + +// @@auther jing-xiang +public class ChartCommand extends Command { + /** + * Constructor for ChartCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ChartCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the chart command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_CHART_COMMAND); + } + + MemberList members = group.getMemberList(); + List memberList = members.getMembers(); + List memberNames = new ArrayList<>(); + List memberBalances = new ArrayList<>(); + if (memberList.size() == 0) { + throw new LongAhException("No members to display"); + } + + for (Member member : memberList) { + memberNames.add(member.getName()); + memberBalances.add(member.getBalance()); + } + + try { + Logging.logInfo("Loading balances chart..."); + Chart.viewBalancesBarChart(memberNames, memberBalances); + } catch (Exception e) { + throw new LongAhException("Unable to generate chart!"); + } + } +} diff --git a/src/main/java/longah/commands/ClearCommand.java b/src/main/java/longah/commands/ClearCommand.java new file mode 100644 index 0000000000..67db484d31 --- /dev/null +++ b/src/main/java/longah/commands/ClearCommand.java @@ -0,0 +1,47 @@ +package longah.commands; + +import longah.handler.UI; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +public class ClearCommand extends Command { + /** + * Constructor for ClearCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ClearCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the clear command. + * + * @param group The group to execute the command on. + * @throws LongAhException If unexpected additional parameters are found. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_CLEAR_COMMAND); + } + // Additional message to ask user for confirmation + UI.showMessage("Are you sure you want to clear all transactions? (Y/N)"); + UI.showMessage("This action cannot be undone. All transaction data will be lost."); + UI.showMessage("Enter 'N' or any other key to cancel."); + String confirmation = UI.getUserInput(); + if (confirmation.equalsIgnoreCase("Y")) { + TransactionList transactions = group.getTransactionList(); + MemberList members = group.getMemberList(); + transactions.clear(members); + group.updateTransactionSolution(); + group.saveAllData(); + UI.showMessage("All transactions have been cleared for this account."); + } else { + UI.showMessage("Clear operation cancelled."); + } + } +} diff --git a/src/main/java/longah/commands/Command.java b/src/main/java/longah/commands/Command.java new file mode 100644 index 0000000000..1ba8ccf740 --- /dev/null +++ b/src/main/java/longah/commands/Command.java @@ -0,0 +1,46 @@ +package longah.commands; + +import longah.node.Group; +import longah.exception.LongAhException; + +public abstract class Command { + protected String commandString; + protected String taskExpression; + + /** + * Constructor for Command. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public Command(String commandString, String taskExpression) { + this.commandString = commandString; + this.taskExpression = taskExpression; + } + + /** + * Executes the command. + * + * @throws LongAhException If an error occurs during the execution of the command. + */ + public abstract void execute(Group group) throws LongAhException; + + /** + * Returns the command string. + * + * @return The command string. + */ + public String getCommandString() { + return commandString; + } + + + /** + * Returns the task expression. + * + * @return The task expression. + */ + public String getTaskExpression() { + return taskExpression; + } +} diff --git a/src/main/java/longah/commands/ExitCommand.java b/src/main/java/longah/commands/ExitCommand.java new file mode 100644 index 0000000000..b38b2d0baa --- /dev/null +++ b/src/main/java/longah/commands/ExitCommand.java @@ -0,0 +1,31 @@ +package longah.commands; + +import longah.node.Group; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class ExitCommand extends Command { + /** + * Constructor for ExitCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ExitCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the exit command. + * + * @param group The group to execute the command on. + * @throws LongAhException If unexpected additional parameters are found. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_EXIT_COMMAND); + } + UI.exit(); + } +} diff --git a/src/main/java/longah/commands/FilterCommand.java b/src/main/java/longah/commands/FilterCommand.java new file mode 100644 index 0000000000..2a8918d35c --- /dev/null +++ b/src/main/java/longah/commands/FilterCommand.java @@ -0,0 +1,51 @@ +package longah.commands; + +import longah.node.Group; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.handler.UI; +import longah.exception.ExceptionMessage; + +public class FilterCommand extends Command { + // @@author FeathersRe + /** + * Constructor for FilterCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + * @throws LongAhException If the filter command is invalid. + */ + public FilterCommand(String commandString, String taskExpression) throws LongAhException { + super(commandString, taskExpression); + } + + /** + * Executes the corresponding filter command based on the subCommand. + * + * @param group The group to execute the command on. + * @throws LongAhException If the taskExpression for the date times to search is in the wrong format + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + String message; + if (taskExpression.contains("b/") && taskExpression.contains("a/")) { + String[] splitExpression = taskExpression.split(" b/"); + if (splitExpression.length < 2 || !splitExpression[0].contains("a/")) { + throw new LongAhException(ExceptionMessage.INVALID_FILTER_DATETIME_COMMAND); + } + String fromDateTimeExpression = splitExpression[0].replaceAll("a/", ""); + String toDateTimeExpression = splitExpression[1].trim(); + message = transactions.filterTransactionsBetweenDateTime(fromDateTimeExpression, toDateTimeExpression); + } else if (taskExpression.contains("a/") && !taskExpression.contains("b/")) { + message = transactions.filterTransactionsAfterDateTime(taskExpression.replaceAll("a/", "")); + } else if (taskExpression.contains("b/") && !taskExpression.contains("a/")) { + message = transactions.filterTransactionsBeforeDateTime(taskExpression.replaceAll("b/", "")); + } else { + assert !(taskExpression.contains("a/") || taskExpression.contains("b/")) : "Invalid request handled" + + "for the filtering single dates"; + message = transactions.filterTransactionsEqualToDateTime(taskExpression); + } + UI.showMessage(message); + } +} + diff --git a/src/main/java/longah/commands/HelpCommand.java b/src/main/java/longah/commands/HelpCommand.java new file mode 100644 index 0000000000..0c9c682b38 --- /dev/null +++ b/src/main/java/longah/commands/HelpCommand.java @@ -0,0 +1,87 @@ +package longah.commands; + +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.node.Group; + +import longah.handler.UI; + +public class HelpCommand extends Command { + /** + * Constructor for HelpCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public HelpCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + + /** + * Executes the help command. + * @param group The group to execute the command on. + * @throws LongAhException if unexpected additional parameters are found. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_HELP_COMMAND); + } + listAllCommands(); + } + + /** + * Lists all available commands. + */ + public static void listAllCommands() { + UI.showMessage("Here are the full list of commands available:\n"); + UI.showMessage("ADD commands: "); + UI.printSeparator(); + UI.showMessage("1. `add member [NAME]` - Add a new member to the group."); + UI.showMessage("2. `add transaction [LENDER] [DD-MM-YYYY HHMM] p/[BORROWER1] a/[AMOUNT OWED]\n" + + "p/[BORROWER2] a/[AMOUNTED OWED] ...` - Add a new transaction. (date/time inputs are optional)"); + UI.showMessage("3. 'add group [GROUP NAME]' - Add a new group.\n"); + UI.showMessage("LIST commands: "); + UI.printSeparator(); + UI.showMessage("4. `list members` - List all current members in the group."); + UI.showMessage("5. `list transactions` - List all transactions in the group."); + UI.showMessage("6. `list debts` - Simplifies and lists all debts in the group."); + UI.showMessage("7. `list groups` - List all groups in the application.\n"); + UI.showMessage("DELETE commands: "); + UI.printSeparator(); + UI.showMessage("8. `delete transaction [TRANSACTION NUMBER]` - Delete a transaction."); + UI.showMessage("9. `delete member [MEMBER NAME]` - Delete a member from the group."); + UI.showMessage("10. `delete group [GROUP NAME]` - Delete a group from the application.\n"); + UI.showMessage("FIND commands: "); + UI.printSeparator(); + UI.showMessage("11. `find borrower [MEMBER NAME]` - Find all transactions where the " + + "member is a borrower."); + UI.showMessage("12. `find lender [MEMBER NAME]` - Find all transactions where the member " + + "is involved as the lender."); + UI.showMessage("13. `find debts [MEMBER NAME]` - Find all debts of the member."); + UI.showMessage("14. `find transactions [MEMBER NAME]` - Find all transactions where " + + "the member is involved.\n"); + UI.showMessage("EDIT commands: "); + UI.printSeparator(); + UI.showMessage("15. `edit member [MEMBER NAME] p/[NEW MEMBER NAME]` " + + "- Edit the name of a member."); + UI.showMessage("16. `edit transaction [TRANSACTION NUMBER] [LENDER] p/[BORROWER1] a/[AMOUNT]\n" + + "p/[BORROWER2] a/[AMOUNT]...` - Edit the details of a transaction.\n"); + UI.showMessage("PIN commands: "); + UI.printSeparator(); + UI.showMessage("17. `PIN enable` - Enable PIN authentication for the application."); + UI.showMessage("18. `PIN disable` - Disable PIN authentication for the application."); + UI.showMessage("19. `PIN reset` - Reset the user PIN.\n"); + UI.showMessage("OTHER commands: "); + UI.printSeparator(); + UI.showMessage("20. `settleup [MEMBER NAME]` - Settle all debts of the member."); + UI.showMessage("21. `clear` - Clear all transaction data in the group."); + UI.showMessage("22. 'group [GROUP NAME]' - Switch to another group with specified name."); + UI.showMessage("23. `filter [TIME PERIOD]` - Filter transactions by time period."); + UI.showMessage("24. `chart` - Display a chart of debts in the group."); + UI.showMessage("25. `exit` - Exit the application."); + UI.showMessage("26. `help` - Display the list of commands.\n"); + UI.showMessage("For more information on a specific command, " + + "or view command shortcuts, do refer to our user guide."); + } +} diff --git a/src/main/java/longah/commands/PINCommand.java b/src/main/java/longah/commands/PINCommand.java new file mode 100644 index 0000000000..dc639d5e75 --- /dev/null +++ b/src/main/java/longah/commands/PINCommand.java @@ -0,0 +1,48 @@ +package longah.commands; + +import longah.exception.LongAhException; +import longah.handler.PINHandler; +import longah.node.Group; +import longah.exception.ExceptionMessage; + + +public class PINCommand extends Command { + /** + * Constructor for PINCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public PINCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + @Override + public void execute(Group group) throws LongAhException { + execute(); + } + + /** + * Executes the reset command. + * + * @throws LongAhException If unexpected additional parameters are found. + */ + public void execute() throws LongAhException { + if (this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_PIN_COMMAND); + } + switch (this.taskExpression) { + case "reset": + PINHandler.resetPin(); + break; + case "enable": + PINHandler.enablePin(); + break; + case "disable": + PINHandler.disablePin(); + break; + default: + throw new LongAhException(ExceptionMessage.INVALID_PIN_COMMAND); + } + } +} diff --git a/src/main/java/longah/commands/SettleCommand.java b/src/main/java/longah/commands/SettleCommand.java new file mode 100644 index 0000000000..f2e56a8531 --- /dev/null +++ b/src/main/java/longah/commands/SettleCommand.java @@ -0,0 +1,31 @@ +package longah.commands; + +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.node.Group; + +public class SettleCommand extends Command { + /** + * Constructor for SettleCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public SettleCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the settle command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_SETTLEUP_COMMAND); + } + group.settleUp(this.taskExpression); + group.updateTransactionSolution(); + group.saveAllData(); + } +} diff --git a/src/main/java/longah/commands/SwitchCommand.java b/src/main/java/longah/commands/SwitchCommand.java new file mode 100644 index 0000000000..8e7c808a3f --- /dev/null +++ b/src/main/java/longah/commands/SwitchCommand.java @@ -0,0 +1,37 @@ +package longah.commands; + +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.UI; +import longah.node.Group; +import longah.util.GroupList; + +public class SwitchCommand extends Command { + /** + * Constructor for SwitchCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public SwitchCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the switch command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_SWITCH_GROUP_COMMAND); + } + if (!GroupList.isGroup(this.taskExpression)) { + throw new LongAhException(ExceptionMessage.GROUP_NOT_FOUND); + } + Group newGroup = GroupList.getGroup(this.taskExpression); + GroupList.switchActiveGroup(newGroup); + UI.showMessage("Switching groups..."); + UI.showMessage("You are now managing: " + newGroup.getGroupName()); + } +} diff --git a/src/main/java/longah/commands/add/AddCommand.java b/src/main/java/longah/commands/add/AddCommand.java new file mode 100644 index 0000000000..b60c1b058a --- /dev/null +++ b/src/main/java/longah/commands/add/AddCommand.java @@ -0,0 +1,57 @@ +package longah.commands.add; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +public class AddCommand extends Command { + private String subCommand; + + /** + * Constructor for AddCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + * @throws LongAhException If the command is invalid. + */ + public AddCommand(String commandString, String taskExpression) throws LongAhException { + super(commandString, taskExpression); + String[] subCommandTaskExpSplit = this.taskExpression.split(" ", 2); + this.subCommand = subCommandTaskExpSplit[0].toLowerCase(); + if (subCommandTaskExpSplit.length > 1) { + this.taskExpression = subCommandTaskExpSplit[1]; + } else { + throw new LongAhException(ExceptionMessage.INVALID_ADD_COMMAND); + } + } + + /** + * Executes the add command. + * Depending on the subCommand, it will execute the add member or add transaction command. + * + * @param group The group to execute the command on. + * @throws LongAhException If the subCommand is invalid. + */ + public void execute(Group group) throws LongAhException { + String fullCommandString = this.commandString + " " + this.subCommand; + switch (this.subCommand) { + case "member": + AddMemberCommand addMemberCommand = + new AddMemberCommand(fullCommandString, this.taskExpression); + addMemberCommand.execute(group); + break; + case "transaction": + AddTransactionCommand addTransactionCommand = + new AddTransactionCommand(fullCommandString, this.taskExpression); + addTransactionCommand.execute(group); + break; + case "group": + AddGroupCommand addGroupCommand = new AddGroupCommand(fullCommandString, this.taskExpression); + addGroupCommand.execute(group); + break; + default: + throw new LongAhException(ExceptionMessage.INVALID_ADD_COMMAND); + } + } +} diff --git a/src/main/java/longah/commands/add/AddGroupCommand.java b/src/main/java/longah/commands/add/AddGroupCommand.java new file mode 100644 index 0000000000..92ffae4f63 --- /dev/null +++ b/src/main/java/longah/commands/add/AddGroupCommand.java @@ -0,0 +1,28 @@ +package longah.commands.add; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.LongAhException; +import longah.util.GroupList; + +public class AddGroupCommand extends Command { + /** + * Constructor for AddGroupCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public AddGroupCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the add group command. + * + * @param group The group current group. + */ + public void execute(Group group) throws LongAhException { + Group newGroup = new Group(this.taskExpression); + GroupList.addGroup(newGroup); + } +} diff --git a/src/main/java/longah/commands/add/AddMemberCommand.java b/src/main/java/longah/commands/add/AddMemberCommand.java new file mode 100644 index 0000000000..da21141c3b --- /dev/null +++ b/src/main/java/longah/commands/add/AddMemberCommand.java @@ -0,0 +1,29 @@ +package longah.commands.add; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.MemberList; +import longah.exception.LongAhException; + +public class AddMemberCommand extends Command { + /** + * Constructor for AddMemberCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public AddMemberCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the add member command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + MemberList members = group.getMemberList(); + members.addMember(taskExpression); + group.saveMembersData(); + } +} diff --git a/src/main/java/longah/commands/add/AddTransactionCommand.java b/src/main/java/longah/commands/add/AddTransactionCommand.java new file mode 100644 index 0000000000..4be99120a9 --- /dev/null +++ b/src/main/java/longah/commands/add/AddTransactionCommand.java @@ -0,0 +1,31 @@ +package longah.commands.add; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; + +public class AddTransactionCommand extends Command { + /** + * Constructor for AddTransactionCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public AddTransactionCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the add transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + MemberList members = group.getMemberList(); + TransactionList transactions = group.getTransactionList(); + transactions.addTransaction(taskExpression, members, group); + group.saveAllData(); + } +} diff --git a/src/main/java/longah/commands/delete/DeleteCommand.java b/src/main/java/longah/commands/delete/DeleteCommand.java new file mode 100644 index 0000000000..aaddb62a50 --- /dev/null +++ b/src/main/java/longah/commands/delete/DeleteCommand.java @@ -0,0 +1,58 @@ +package longah.commands.delete; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; + +public class DeleteCommand extends Command { + private String subCommand; + + /** + * Constructor for DeleteCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + * @throws LongAhException If the command string is invalid. + */ + public DeleteCommand(String commandString, String taskExpression) throws LongAhException { + super(commandString, taskExpression); + String[] subCommandTaskExpSplit = this.taskExpression.split(" ", 2); + this.subCommand = subCommandTaskExpSplit[0].toLowerCase(); + if (subCommandTaskExpSplit.length > 1) { + this.taskExpression = subCommandTaskExpSplit[1]; + } else { + throw new LongAhException(ExceptionMessage.INVALID_DELETE_COMMAND); + } + } + + /** + * Executes the delete command. + * Depending on the subCommand, it will execute the delete member or delete transaction command. + * + * @param group The group to execute the command on. + * @throws LongAhException If the subCommand is invalid. + */ + public void execute(Group group) throws LongAhException { + String fullCommandString = this.commandString + " " + this.subCommand; + switch (this.subCommand) { + case "member": + DeleteMemberCommand deleteMemberCommand = + new DeleteMemberCommand(fullCommandString, this.taskExpression); + deleteMemberCommand.execute(group); + break; + case "transaction": + DeleteTransactionCommand deleteTransactionCommand = + new DeleteTransactionCommand(fullCommandString, this.taskExpression); + deleteTransactionCommand.execute(group); + break; + case "group": + DeleteGroupCommand deleteGroupCommand = + new DeleteGroupCommand(fullCommandString, this.taskExpression); + deleteGroupCommand.execute(group); + break; + default: + throw new LongAhException(ExceptionMessage.INVALID_DELETE_COMMAND); + } + } +} diff --git a/src/main/java/longah/commands/delete/DeleteGroupCommand.java b/src/main/java/longah/commands/delete/DeleteGroupCommand.java new file mode 100644 index 0000000000..4899ada312 --- /dev/null +++ b/src/main/java/longah/commands/delete/DeleteGroupCommand.java @@ -0,0 +1,28 @@ +package longah.commands.delete; + +import longah.commands.Command; +import longah.exception.LongAhException; +import longah.node.Group; +import longah.util.GroupList; + +public class DeleteGroupCommand extends Command { + /** + * Constructor for DeleteGroupCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public DeleteGroupCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the delete group command. + * + * @param group The current group. + * + */ + public void execute(Group group) throws LongAhException { + GroupList.deleteGroup(this.taskExpression); + } +} diff --git a/src/main/java/longah/commands/delete/DeleteMemberCommand.java b/src/main/java/longah/commands/delete/DeleteMemberCommand.java new file mode 100644 index 0000000000..4908d9e994 --- /dev/null +++ b/src/main/java/longah/commands/delete/DeleteMemberCommand.java @@ -0,0 +1,33 @@ +package longah.commands.delete; + +import longah.commands.Command; +import longah.exception.LongAhException; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; + +public class DeleteMemberCommand extends Command { + /** + * Constructor for DeleteMemberCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public DeleteMemberCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the delete transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + MemberList members = group.getMemberList(); + transactions.deleteMember(taskExpression, members); + members.deleteMember(taskExpression); + group.updateTransactionSolution(); + group.saveAllData(); + } +} diff --git a/src/main/java/longah/commands/delete/DeleteTransactionCommand.java b/src/main/java/longah/commands/delete/DeleteTransactionCommand.java new file mode 100644 index 0000000000..5d85c4907b --- /dev/null +++ b/src/main/java/longah/commands/delete/DeleteTransactionCommand.java @@ -0,0 +1,30 @@ +package longah.commands.delete; + +import longah.commands.Command; +import longah.exception.LongAhException; +import longah.node.Group; +import longah.util.TransactionList; + +public class DeleteTransactionCommand extends Command { + /** + * Constructor for DeleteTransactionCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public DeleteTransactionCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the delete transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + transactions.remove(taskExpression); + group.updateTransactionSolution(); + group.saveAllData(); + } +} diff --git a/src/main/java/longah/commands/edit/EditCommand.java b/src/main/java/longah/commands/edit/EditCommand.java new file mode 100644 index 0000000000..c3c6a82d5b --- /dev/null +++ b/src/main/java/longah/commands/edit/EditCommand.java @@ -0,0 +1,53 @@ +package longah.commands.edit; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; + +public class EditCommand extends Command { + private String subCommand; + + /** + * Constructor for EditCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + * @throws LongAhException If the command string is invalid. + */ + public EditCommand(String commandString, String taskExpression) throws LongAhException { + super(commandString, taskExpression); + String[] subCommandTaskExpSplit = this.taskExpression.split(" ", 2); + this.subCommand = subCommandTaskExpSplit[0].toLowerCase(); + if (subCommandTaskExpSplit.length > 1) { + this.taskExpression = subCommandTaskExpSplit[1]; + } else { + throw new LongAhException(ExceptionMessage.INVALID_EDIT_COMMAND); + } + } + + /** + * Executes the edit command. + * Depending on the subCommand, it will execute the edit member or edit transaction command. + * + * @param group The group to execute the command on. + * @throws LongAhException If the subCommand is invalid. + */ + public void execute(Group group) throws LongAhException { + String fullCommandString = this.commandString + " " + this.subCommand; + switch (this.subCommand) { + case "member": + EditMemberCommand editMemberCommand = + new EditMemberCommand(fullCommandString, this.taskExpression); + editMemberCommand.execute(group); + break; + case "transaction": + EditTransactionCommand editTransactionCommand = + new EditTransactionCommand(fullCommandString, this.taskExpression); + editTransactionCommand.execute(group); + break; + default: + throw new LongAhException(ExceptionMessage.INVALID_EDIT_COMMAND); + } + } +} diff --git a/src/main/java/longah/commands/edit/EditMemberCommand.java b/src/main/java/longah/commands/edit/EditMemberCommand.java new file mode 100644 index 0000000000..fd5fa3a914 --- /dev/null +++ b/src/main/java/longah/commands/edit/EditMemberCommand.java @@ -0,0 +1,39 @@ +package longah.commands.edit; + +import longah.commands.Command; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.UI; +import longah.node.Group; +import longah.util.MemberList; + +public class EditMemberCommand extends Command { + /** + * Constructor for EditMemberCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public EditMemberCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the edit member name command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + MemberList members = group.getMemberList(); + String[] namesSplit = taskExpression.split("p/", 2); + if (namesSplit.length != 2) { + throw new LongAhException(ExceptionMessage.INVALID_EDIT_COMMAND); + } + String oldName = namesSplit[0].trim(); + String newName = namesSplit[1].trim(); + members.editMemberName(oldName, newName); + group.updateTransactionSolution(); + group.saveAllData(); + UI.showMessage("Member name edited successfully! " + oldName + " is renamed to: " + newName); + } +} diff --git a/src/main/java/longah/commands/edit/EditTransactionCommand.java b/src/main/java/longah/commands/edit/EditTransactionCommand.java new file mode 100644 index 0000000000..830939b539 --- /dev/null +++ b/src/main/java/longah/commands/edit/EditTransactionCommand.java @@ -0,0 +1,31 @@ +package longah.commands.edit; + +import longah.commands.Command; +import longah.exception.LongAhException; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; + +public class EditTransactionCommand extends Command { + /** + * Constructor for EditTransactionCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public EditTransactionCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the edit transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + MemberList members = group.getMemberList(); + transactions.editTransactionList(taskExpression, members); + group.updateTransactionSolution(); + group.saveAllData();} +} diff --git a/src/main/java/longah/commands/find/FindBorrowerCommand.java b/src/main/java/longah/commands/find/FindBorrowerCommand.java new file mode 100644 index 0000000000..2b6574d79c --- /dev/null +++ b/src/main/java/longah/commands/find/FindBorrowerCommand.java @@ -0,0 +1,31 @@ +package longah.commands.find; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class FindBorrowerCommand extends Command { + /** + * Constructor for FindBorrowerCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public FindBorrowerCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the find transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + MemberList members = group.getMemberList(); + UI.showMessage(transactions.findBorrower(taskExpression, members)); + } +} diff --git a/src/main/java/longah/commands/find/FindCommand.java b/src/main/java/longah/commands/find/FindCommand.java new file mode 100644 index 0000000000..02d8957598 --- /dev/null +++ b/src/main/java/longah/commands/find/FindCommand.java @@ -0,0 +1,56 @@ +package longah.commands.find; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +public class FindCommand extends Command { + private String subCommand; + + /** + * Constructor for FindCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + * @throws LongAhException If the find command is invalid. + */ + public FindCommand(String commandString, String taskExpression) throws LongAhException { + super(commandString, taskExpression); + String[] subCommandTaskExpSplit = this.taskExpression.split(" ", 2); + this.subCommand = subCommandTaskExpSplit[0].toLowerCase(); + if (subCommandTaskExpSplit.length > 1) { + this.taskExpression = subCommandTaskExpSplit[1]; + } else { + throw new LongAhException(ExceptionMessage.INVALID_FIND_COMMAND); + } + } + + public void execute(Group group) throws LongAhException { + String fullCommandString = this.commandString + " " + this.subCommand; + switch (this.subCommand) { + case "transactions": + FindTransactionCommand findTransactionCommand = + new FindTransactionCommand(fullCommandString, this.taskExpression); + findTransactionCommand.execute(group); + break; + case "lender": + FindLenderCommand findLenderCommand = + new FindLenderCommand(fullCommandString, this.taskExpression); + findLenderCommand.execute(group); + break; + case "borrower": + FindBorrowerCommand findBorrowerCommand = + new FindBorrowerCommand(fullCommandString, this.taskExpression); + findBorrowerCommand.execute(group); + break; + case "debts": + FindDebtCommand findDebtCommand = + new FindDebtCommand(fullCommandString, this.taskExpression); + findDebtCommand.execute(group); + break; + default: + throw new LongAhException(ExceptionMessage.INVALID_FIND_COMMAND); + } + } +} diff --git a/src/main/java/longah/commands/find/FindDebtCommand.java b/src/main/java/longah/commands/find/FindDebtCommand.java new file mode 100644 index 0000000000..1be0e4b1ec --- /dev/null +++ b/src/main/java/longah/commands/find/FindDebtCommand.java @@ -0,0 +1,27 @@ +package longah.commands.find; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class FindDebtCommand extends Command { + /** + * Constructor for FindDebtCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public FindDebtCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the find debt command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + UI.showMessage(group.listIndivDebt(taskExpression)); + } +} diff --git a/src/main/java/longah/commands/find/FindLenderCommand.java b/src/main/java/longah/commands/find/FindLenderCommand.java new file mode 100644 index 0000000000..76c8cdc0bf --- /dev/null +++ b/src/main/java/longah/commands/find/FindLenderCommand.java @@ -0,0 +1,31 @@ +package longah.commands.find; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class FindLenderCommand extends Command { + /** + * Constructor for FindLenderCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public FindLenderCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the find transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + MemberList members = group.getMemberList(); + UI.showMessage(transactions.findLender(taskExpression, members)); + } +} diff --git a/src/main/java/longah/commands/find/FindTransactionCommand.java b/src/main/java/longah/commands/find/FindTransactionCommand.java new file mode 100644 index 0000000000..495b706bce --- /dev/null +++ b/src/main/java/longah/commands/find/FindTransactionCommand.java @@ -0,0 +1,31 @@ +package longah.commands.find; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class FindTransactionCommand extends Command { + /** + * Constructor for FindTransactionCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public FindTransactionCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the find transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + TransactionList transactions = group.getTransactionList(); + MemberList members = group.getMemberList(); + UI.showMessage(transactions.findTransactions(taskExpression, members)); + } +} diff --git a/src/main/java/longah/commands/list/ListCommand.java b/src/main/java/longah/commands/list/ListCommand.java new file mode 100644 index 0000000000..740cdfebaf --- /dev/null +++ b/src/main/java/longah/commands/list/ListCommand.java @@ -0,0 +1,63 @@ +package longah.commands.list; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +public class ListCommand extends Command { + private String subCommand; + + /** + * Constructor for ListCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ListCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + String[] subCommandTaskExpSplit = this.taskExpression.split(" ", 2); + this.subCommand = subCommandTaskExpSplit[0].toLowerCase(); + this.taskExpression = subCommandTaskExpSplit.length > 1 ? subCommandTaskExpSplit[1] : ""; + } + + /** + * Executes the list command. + * Depending on the subCommand, it will execute the list member + * or list transaction or list debt command. + * + * @param group The group to execute the command on. + * @throws LongAhException If the subCommand is invalid. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.equals("")) { + throw new LongAhException(ExceptionMessage.INVALID_LIST_COMMAND); + } + + String fullCommandString = this.commandString + " " + this.subCommand; + switch (this.subCommand) { + case "members": + ListMemberCommand listMemberCommand = + new ListMemberCommand(fullCommandString, this.taskExpression); + listMemberCommand.execute(group); + break; + case "transactions": + ListTransactionCommand listTransactionCommand = + new ListTransactionCommand(fullCommandString, this.taskExpression); + listTransactionCommand.execute(group); + break; + case "debts": + ListDebtCommand listDebtCommand = + new ListDebtCommand(fullCommandString, this.taskExpression); + listDebtCommand.execute(group); + break; + case "groups": + ListGroupsCommand listGroupsCommand = + new ListGroupsCommand(fullCommandString, this.taskExpression); + listGroupsCommand.execute(group); + break; + default: + throw new LongAhException(ExceptionMessage.INVALID_LIST_COMMAND); + } + } +} diff --git a/src/main/java/longah/commands/list/ListDebtCommand.java b/src/main/java/longah/commands/list/ListDebtCommand.java new file mode 100644 index 0000000000..a8cb3994c1 --- /dev/null +++ b/src/main/java/longah/commands/list/ListDebtCommand.java @@ -0,0 +1,31 @@ +package longah.commands.list; + +import longah.commands.Command; +import longah.node.Group; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class ListDebtCommand extends Command { + /** + * Constructor for ListDebtCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ListDebtCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the list debt command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_LIST_COMMAND); + } + UI.showMessage(group.listDebts()); + } +} diff --git a/src/main/java/longah/commands/list/ListGroupsCommand.java b/src/main/java/longah/commands/list/ListGroupsCommand.java new file mode 100644 index 0000000000..146f846a68 --- /dev/null +++ b/src/main/java/longah/commands/list/ListGroupsCommand.java @@ -0,0 +1,33 @@ +package longah.commands.list; + +import longah.commands.Command; +import longah.handler.UI; +import longah.node.Group; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.util.GroupList; + +public class ListGroupsCommand extends Command { + /** + * Constructor for ListGroupCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ListGroupsCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the list group command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_LIST_COMMAND); + } + String output = GroupList.getGroupList(); + UI.showMessage(output); + } +} diff --git a/src/main/java/longah/commands/list/ListMemberCommand.java b/src/main/java/longah/commands/list/ListMemberCommand.java new file mode 100644 index 0000000000..c81cfdb7fc --- /dev/null +++ b/src/main/java/longah/commands/list/ListMemberCommand.java @@ -0,0 +1,33 @@ +package longah.commands.list; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.MemberList; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class ListMemberCommand extends Command { + /** + * Constructor for ListMemberCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ListMemberCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the list member command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_LIST_COMMAND); + } + MemberList members = group.getMemberList(); + UI.showMessage(members.listMembers()); + } +} diff --git a/src/main/java/longah/commands/list/ListTransactionCommand.java b/src/main/java/longah/commands/list/ListTransactionCommand.java new file mode 100644 index 0000000000..f7a8d5a1ee --- /dev/null +++ b/src/main/java/longah/commands/list/ListTransactionCommand.java @@ -0,0 +1,33 @@ +package longah.commands.list; + +import longah.commands.Command; +import longah.node.Group; +import longah.util.TransactionList; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.UI; + +public class ListTransactionCommand extends Command { + /** + * Constructor for ListTransactionCommand. + * + * @param commandString The command string. + * @param taskExpression The task expression. + */ + public ListTransactionCommand(String commandString, String taskExpression) { + super(commandString, taskExpression); + } + + /** + * Executes the list transaction command. + * + * @param group The group to execute the command on. + */ + public void execute(Group group) throws LongAhException { + if (!this.taskExpression.isEmpty()) { + throw new LongAhException(ExceptionMessage.INVALID_LIST_COMMAND); + } + TransactionList transactions = group.getTransactionList(); + UI.showMessage(transactions.listTransactions()); + } +} diff --git a/src/main/java/longah/exception/ExceptionMessage.java b/src/main/java/longah/exception/ExceptionMessage.java new file mode 100644 index 0000000000..fc60765cf7 --- /dev/null +++ b/src/main/java/longah/exception/ExceptionMessage.java @@ -0,0 +1,124 @@ +package longah.exception; + +public enum ExceptionMessage { + // [Cause of Exception]([Message to be printed], [Type of Exception]) + // General Exceptions + INVALID_INDEX ("Invalid index.", ExceptionType.WARNING), + + // Member Exceptions + DUPLICATE_MEMBER ("Duplicate member.", ExceptionType.INFO), + INVALID_MEMBER_NAME ("Invalid member name.", ExceptionType.INFO), + MEMBER_NOT_FOUND ("Member not found.", ExceptionType.INFO), + NO_MEMBERS_FOUND ("Member list is empty.", ExceptionType.INFO), + CHAR_LIMIT_EXCEEDED ("Character limit exceeded.", ExceptionType.WARNING), + BALANCE_OVERFLOW ("Balance overflow. Transaction not processed.", ExceptionType.WARNING), + + // Group and Group List Exceptions + INVALID_GROUP_NAME ("Invalid group name.", ExceptionType.INFO), + DUPLICATE_GROUP ("Duplicate group.", ExceptionType.INFO), + EMPTY_GROUP_LIST ("Group list is empty.", ExceptionType.INFO), + GROUP_NOT_FOUND ("Group not found.", ExceptionType.INFO), + + // Transaction Exceptions + INVALID_TRANSACTION_FORMAT ("Invalid transaction format.", ExceptionType.WARNING), + INVALID_TRANSACTION_MEMBER ("Borrower is already the lender.", ExceptionType.WARNING), + INVALID_TRANSACTION_VALUE ("Invalid transaction value.", ExceptionType.WARNING), + + // TransactionList Exceptions + NO_TRANSACTION_FOUND ("No transactions found.", ExceptionType.INFO), + NO_DEBTS_FOUND ("No debts found.", ExceptionType.INFO), + TRANSACTIONS_SUMMED_UP ("No pending payments.", ExceptionType.INFO), + INVALID_DATE_TIME_FILTER ("Invalid datetime filter. The to date your are searching for " + + "is before the from date.", ExceptionType.INFO), + + // Date Time Exceptions + INVALID_TIME_FORMAT ("Invalid DateTime format. Please format " + + "you date and time inputs in the form of DD-MM-YYYY HHmm", ExceptionType.WARNING), + INVALID_TIME_INPUT ("Invalid DateTime input. Dates of the future are not allowed.", ExceptionType.WARNING), + + // Data Storage Exceptions + STORAGE_FILE_NOT_FOUND ("File not found.", ExceptionType.WARNING), + STORAGE_FILE_NOT_CREATED ("File not created.", ExceptionType.WARNING), + STORAGE_FILE_NOT_READ ("File not read.", ExceptionType.WARNING), + STORAGE_FILE_NOT_WRITTEN ("File not written.", ExceptionType.WARNING), + INVALID_STORAGE_CONTENT ("Invalid content in storage file, line ignored.", ExceptionType.WARNING), + STORAGE_FILE_CORRUPTED ("Storage file corrupted, group has been excluded.", ExceptionType.WARNING), + IO_EXCEPTION ("An error occurred while reading/writing to the file.", ExceptionType.WARNING), + // Ui exceptions + INVALID_COMMAND ("Invalid command. Use 'help' to see the list of commands.", + ExceptionType.INFO), + COMMAND_NOT_IMPLEMENTED ("This feature has yet to be implemented.", + ExceptionType.INFO), + INVALID_ADD_COMMAND ("Invalid command format." + + " Use 'add member NAME' or 'add transaction LENDER p/BORRWER1 a/AMOUNT1 ...\n" + + "or 'add group GROUP_NAME'", + ExceptionType.INFO), + INVALID_LIST_COMMAND ("Invalid command format." + + " Use 'list members', 'list transactions', or 'list debts' or 'list groups'", + ExceptionType.INFO), + INVALID_FIND_COMMAND ("Invalid command format." + + " Use 'find transactions', 'find lender', 'find borrower', or 'find debts'", + ExceptionType.INFO), + + INVALID_FILTER_DATETIME_COMMAND("Invalid filter command." + + " Use 'filter b/DateTime' or 'filter a/DateTime' or " + + "'filter a/Datetime b/Datetime' or 'filter Datetime", + ExceptionType.INFO), + INVALID_SETTLEUP_COMMAND ("Invalid command format." + + " Use 'settleup PERSON'", + ExceptionType.INFO), + INVALID_DELETE_COMMAND ("Invalid command format." + + " Use 'delete transaction INDEX' or 'delete member NAME' or 'delete group GROUP_NAME'", + ExceptionType.INFO), + INVALID_CLEAR_COMMAND ("Invalid command format." + + " Use 'clear'", + ExceptionType.INFO), + INVALID_RESET_COMMAND ("Invalid command format." + + " Use 'reset password'", + ExceptionType.INFO), + INVALID_EDIT_COMMAND("Invalid command format." + + " Use 'edit transaction INDEX NEW_TRANSACTION' or 'edit member OLD_NAME p/NEW_NAME'", + ExceptionType.INFO), + INVALID_PIN_COMMAND("Invalid command format." + + " Use 'pin reset' or 'pin enable' or 'pin disable'", + ExceptionType.INFO), + INVALID_EXIT_COMMAND ("Invalid command format." + + " Use 'exit' or 'close'", ExceptionType.INFO), + INVALID_CHART_COMMAND ("Invalid command format." + + " Use 'chart'", ExceptionType.INFO), + INVALID_HELP_COMMAND ("Invalid command format." + + " Use 'help'", ExceptionType.INFO), + INVALID_SWITCH_GROUP_COMMAND ("Invalid command format." + + " Use 'group GROUP_NAME'", ExceptionType.INFO); + + private final String message; + private final ExceptionType type; + + /** + * Constructor for ExceptionMessage. + * + * @param message The message to be printed when the exception is called. + */ + ExceptionMessage(String message, ExceptionType type) { + this.message = message; + this.type = type; + } + + /** + * Returns the message of the exception. + * + * @return The message of the exception. + */ + public String getMessage() { + return this.message; + } + + /** + * Returns the type of the exception. + * + * @return The type of the exception. + */ + public ExceptionType getType() { + return this.type; + } +} diff --git a/src/main/java/longah/exception/ExceptionType.java b/src/main/java/longah/exception/ExceptionType.java new file mode 100644 index 0000000000..bd1fc7184a --- /dev/null +++ b/src/main/java/longah/exception/ExceptionType.java @@ -0,0 +1,6 @@ +package longah.exception; + +public enum ExceptionType { + INFO, + WARNING; +} diff --git a/src/main/java/longah/exception/LongAhException.java b/src/main/java/longah/exception/LongAhException.java new file mode 100644 index 0000000000..01848d9482 --- /dev/null +++ b/src/main/java/longah/exception/LongAhException.java @@ -0,0 +1,52 @@ +package longah.exception; + +import longah.handler.UI; +import longah.handler.Logging; + +public class LongAhException extends Exception { + private static ExceptionType type; + + /** + * Constructor for LongAhExceptions. + * + * @param message The message to be displayed when the exception is thrown. + */ + public LongAhException(String message) { + super(message); + } + + /** + * Constructor for LongAhExceptions. + * + * @param message The cause of the exception using enum {@link ExceptionMessage}. + */ + public LongAhException(ExceptionMessage message) { + super(message.getMessage()); + type = message.getType(); + } + + /** + * Prints the exception message. + * + * @param e The exception to be printed. + */ + public static void printException(LongAhException e) { + UI.showMessage(e.getMessage()); + if (type == ExceptionType.WARNING) { + Logging.logWarning(e.getMessage()); + } else if (type == ExceptionType.INFO) { + Logging.logInfo(e.getMessage()); + } + } + + /** + * Checks if the exception message is equal to the given message. + * + * @param e The exception to be checked. + * @param message The message to be compared, of enum {@link ExceptionMessage}. + * @return True if the exception message is equal to the given message, false otherwise. + */ + public static boolean isMessage(LongAhException e, ExceptionMessage message) { + return e.getMessage().equals(message.getMessage()); + } +} diff --git a/src/main/java/longah/handler/InputHandler.java b/src/main/java/longah/handler/InputHandler.java new file mode 100644 index 0000000000..e7a08617e7 --- /dev/null +++ b/src/main/java/longah/handler/InputHandler.java @@ -0,0 +1,169 @@ +package longah.handler; + +import longah.commands.Command; +import longah.commands.add.AddCommand; +import longah.commands.add.AddGroupCommand; +import longah.commands.add.AddMemberCommand; +import longah.commands.add.AddTransactionCommand; +import longah.commands.delete.DeleteCommand; +import longah.commands.delete.DeleteGroupCommand; +import longah.commands.delete.DeleteMemberCommand; +import longah.commands.delete.DeleteTransactionCommand; +import longah.commands.edit.EditCommand; +import longah.commands.edit.EditMemberCommand; +import longah.commands.edit.EditTransactionCommand; +import longah.commands.find.FindBorrowerCommand; +import longah.commands.find.FindCommand; +import longah.commands.find.FindDebtCommand; +import longah.commands.find.FindLenderCommand; +import longah.commands.find.FindTransactionCommand; +import longah.commands.list.ListCommand; +import longah.commands.list.ListDebtCommand; +import longah.commands.list.ListGroupsCommand; +import longah.commands.list.ListMemberCommand; +import longah.commands.list.ListTransactionCommand; +import longah.commands.ClearCommand; +import longah.commands.SettleCommand; +import longah.commands.ExitCommand; +import longah.commands.FilterCommand; +import longah.commands.PINCommand; +import longah.commands.HelpCommand; +import longah.commands.SwitchCommand; +import longah.commands.ChartCommand; +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; + +public class InputHandler { + /** + * Parses the user input and returns the corresponding command. + * + * @param userInput The user input. + * @return The corresponding command. + */ + public static Command parseInput(String userInput) throws LongAhException { + String[] commandExpressionSplit = userInput.split(" ", 2); + String commandString = commandExpressionSplit[0].toLowerCase(); + String taskExpression = commandExpressionSplit.length > 1 ? commandExpressionSplit[1] : ""; + return parseCommand(commandString, taskExpression); + } + + /** + * Parses the command string and returns the corresponding command. + * + * @param commandString The command string. + * @param taskExpression The task expression. + * @return The corresponding command of type {@link Command}. + */ + public static Command parseCommand(String commandString, String taskExpression) + throws LongAhException { + switch (commandString) { + case "add": + return new AddCommand(commandString, taskExpression); + case "addt": + // Fallthrough + case "at": + return new AddTransactionCommand("add transaction", taskExpression); + case "addm": + // Fallthrough + case "am": + return new AddMemberCommand("add member", taskExpression); + case "addg": + // Fallthrough + case "ag": + return new AddGroupCommand("add group", taskExpression); + + case "list": + return new ListCommand(commandString, taskExpression); + case "listt": + // Fallthrough + case "lt": + return new ListTransactionCommand("list transactions", taskExpression); + case "listm": + // Fallthrough + case "lm": + return new ListMemberCommand("list members", taskExpression); + case "listd": + // Fallthrough + case "ld": + return new ListDebtCommand("list debts", taskExpression); + case "listg": + // Fallthrough + case "lg": + return new ListGroupsCommand("list groups", taskExpression); + + case "find": + return new FindCommand(commandString, taskExpression); + case "findt": + // Fallthrough + case "ft": + return new FindTransactionCommand("find transactions", taskExpression); + case "findd": + // Fallthrough + case "fd": + return new FindDebtCommand("find debts", taskExpression); + case "findl": + // Fallthrough + case "fl": + return new FindLenderCommand("find lender", taskExpression); + case "findb": + // Fallthrough + case "fb": + return new FindBorrowerCommand("find borrower", taskExpression); + + case "filter": + return new FilterCommand(commandString, taskExpression); + case "delete": + return new DeleteCommand(commandString, taskExpression); + case "deleteg": + // Fallthrough + case "dg": + return new DeleteGroupCommand("delete group", taskExpression); + case "deletem": + // Fallthrough + case "dm": + return new DeleteMemberCommand("delete member", taskExpression); + case "deletet": + // Fallthrough + case "dt": + return new DeleteTransactionCommand("delete transaction", taskExpression); + + case "edit": + return new EditCommand(commandString, taskExpression); + case "editm": + // Fallthrough + case "em": + return new EditMemberCommand("edit member", taskExpression); + case "editt": + // Fallthrough + case "et": + return new EditTransactionCommand("edit transaction", taskExpression); + + case "?": + // Fallthrough + case "help": + return new HelpCommand(commandString, taskExpression); + + case "settle": + // Fallthrough + case "settleup": + return new SettleCommand(commandString, taskExpression); + + case "clear": + return new ClearCommand(commandString, taskExpression); + case "pin": + return new PINCommand(commandString, taskExpression); + case "chart": + return new ChartCommand(commandString, taskExpression); + case "group": + return new SwitchCommand(commandString, taskExpression); + + case "close": + // Fallthrough + case "exit": + return new ExitCommand(commandString, taskExpression); + + default: + throw new LongAhException(ExceptionMessage.INVALID_COMMAND); + } + } +} diff --git a/src/main/java/longah/handler/Logging.java b/src/main/java/longah/handler/Logging.java new file mode 100644 index 0000000000..728492871d --- /dev/null +++ b/src/main/java/longah/handler/Logging.java @@ -0,0 +1,80 @@ +package longah.handler; + +import java.util.logging.Logger; +import java.io.File; +import java.io.IOException; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.SimpleFormatter; +import java.util.logging.Level; + +public class Logging { + private static final String loggerDir = "./log"; + private static final String loggerFile = "./log/LongAh.log"; + private static Logger longAhLogger = Logger.getLogger("LongAh"); + + /** + * Constructs a new Logging instance. + * Creates a log file to store log data. + */ + public Logging() { + // @@author FeathersRe + try { + File f = new File(loggerDir); + if (!f.exists()) { + f.mkdir(); + } + f = new File(loggerFile); + if (!f.exists()) { + f.createNewFile(); + } + + FileHandler handler = new FileHandler(loggerFile); + handler.setFormatter(new SimpleFormatter()); + longAhLogger.addHandler(handler); + longAhLogger.setUseParentHandlers(false); + disableConsoleLogging(); + } catch (IOException e) { + longAhLogger.log(Level.WARNING, "Log data may not be saved due to permission."); + } + // @@author + } + + /** + * Logs the message with the specified level. + * + * @param level The level of the log message + * @param message The message to be logged + */ + public static void log(Level level, String message) { + longAhLogger.log(level, message); + } + + /** + * Logs an info message. + * + * @param message The message to be logged + */ + public static void logInfo(String message) { + longAhLogger.log(Level.INFO, message); + } + + /** + * Logs a warning message. + * + * @param message The message to be logged + */ + public static void logWarning(String message) { + longAhLogger.log(Level.WARNING, message); + } + + public static void disableConsoleLogging() { + Handler[] handlers = longAhLogger.getHandlers(); + for (Handler handler : handlers) { + if (handler instanceof ConsoleHandler) { + handler.setLevel(Level.OFF); + } + } + } +} diff --git a/src/main/java/longah/handler/NameHandler.java b/src/main/java/longah/handler/NameHandler.java new file mode 100644 index 0000000000..f44d83db90 --- /dev/null +++ b/src/main/java/longah/handler/NameHandler.java @@ -0,0 +1,66 @@ +package longah.handler; + +import java.util.regex.Pattern; + +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; + +public class NameHandler { + private static final int MAX_NAME_LENGTH = 50; + private static final String NAME_REGEX = "[A-Za-z0-9]+"; + private static final String ERROR = "Error"; + + /** + * Checks the validity of a name. + * Names must be alphanumeric and not exceed the character limit. + * + * @param name The name to be checked. + * @throws LongAhException If the name is invalid. + */ + public static void checkNameConditions(String name) throws LongAhException { + // Check if group name is fully alphanumeric + if (!Pattern.matches(NAME_REGEX, name)) { + throw new LongAhException(ERROR); + } + // Check if name exceeds character limit + if (name.length() > MAX_NAME_LENGTH) { + throw new LongAhException(ExceptionMessage.CHAR_LIMIT_EXCEEDED); + } + } + + /** + * Checks the validity of a member name. + * + * @param name The name of the member. + * @throws LongAhException If the name is invalid. + */ + public static void checkMemberNameValidity(String name) throws LongAhException { + try { + checkNameConditions(name); + } catch (LongAhException e) { + if (e.getMessage().equals(ERROR)) { + throw new LongAhException(ExceptionMessage.INVALID_MEMBER_NAME); + } else { + throw e; + } + } + } + + /** + * Checks the validity of a group name. + * + * @param name The name of the group. + * @throws LongAhException If the name is invalid. + */ + public static void checkGroupNameValidity(String name) throws LongAhException { + try { + checkNameConditions(name); + } catch (LongAhException e) { + if (e.getMessage().equals(ERROR)) { + throw new LongAhException(ExceptionMessage.INVALID_GROUP_NAME); + } else { + throw e; + } + } + } +} diff --git a/src/main/java/longah/handler/PINHandler.java b/src/main/java/longah/handler/PINHandler.java new file mode 100644 index 0000000000..83f69b5516 --- /dev/null +++ b/src/main/java/longah/handler/PINHandler.java @@ -0,0 +1,214 @@ +package longah.handler; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Objects; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.nio.charset.StandardCharsets; +import java.math.BigInteger; + +/** + * Handles the creation, loading, authentication, and resetting of the PIN. + */ +public class PINHandler { + private static final String PIN_FILE_PATH = "./data/pin.txt"; + private static String savedPin; + private static boolean authenticationEnabled; + + // @@author jing-xiang + /** + * Constructs a new PINHandler instance. + */ + public PINHandler() { + StorageHandler.initDir(); + loadPinAndAuthenticationEnabled(); + if (!Files.exists(Paths.get(PINHandler.getPinFilePath())) || savedPin.isEmpty()) { + createPin(); + } + if (authenticationEnabled) { + authenticate(); + } + } + + /** + * Loads the saved PIN and authentication enabled state from the file. + */ + public static void loadPinAndAuthenticationEnabled() { + try { + String[] data = new String(Files.readAllBytes(Paths.get(PIN_FILE_PATH))).split("\n"); + savedPin = data[0]; + if (data.length > 1) { + authenticationEnabled = Boolean.parseBoolean(data[1].trim()); + } + Logging.logInfo("User loaded successfully."); + } catch (IOException | ArrayIndexOutOfBoundsException e) { + UI.showMessage("Error reading saved PIN and authentication enabled state."); + } + } + + /** + * Saves the PIN and authentication enabled state to the file. + */ + public static void savePinAndAuthenticationEnabled() { + try { + String data = savedPin + "\n" + authenticationEnabled; + Files.write(Paths.get(PIN_FILE_PATH), data.getBytes()); + Logging.logInfo("PIN saved successfully."); + } catch (IOException e) { + UI.showMessage("Error saving PIN and authentication enabled state."); + } + } + + /** + * Returns the file path of the PIN file where the PIN is encrypted and saved. + * + * @return The file path of the PIN file where the PIN is encrypted and saved. + */ + public static String getPinFilePath() { + return PIN_FILE_PATH; + } + + /** + * Creates a new PIN for the user. + */ + public static void createPin() { + UI.showMessage("Create your 6-digit PIN:"); + String pin = UI.getUserInput(); + + // check if the input is a 6-digit number + while (pin.length() != 6 || !pin.matches("\\d{6}")) { + if (Objects.equals(pin, "exit")) { + System.exit(0); + } + UI.showMessage("Invalid PIN. Your PIN must be a 6-digit number. " + + "Please try again, or enter 'exit' to exit LongAh."); + UI.showMessage("Enter a 6-digit PIN: ", false); + pin = UI.getUserInput(); + } + + assert pin != null : "PIN should not be null."; + assert pin.length() == 6 : "PIN should be 6 digits long."; + + try { + // Encrypt PIN before saving + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hashedPin = md.digest(pin.getBytes(StandardCharsets.UTF_8)); + String hashedPinHex = new BigInteger(1, hashedPin).toString(16); + savedPin = hashedPinHex; + savePinAndAuthenticationEnabled(); + UI.showMessage("PIN saved successfully! You can enter 'pin enable' to enable authentication upon startup."); + Logging.logInfo("PIN saved successfully!"); + } catch (NoSuchAlgorithmException e) { + UI.showMessage("Error saving PIN. Please try again."); + } + } + + /** + * Authenticates the user by comparing the entered PIN with the saved PIN. + */ + public static void authenticate() { + if (!authenticationEnabled) { + return; + } + assert savedPin != null : "Saved PIN should not be null."; + + UI.showMessage("Enter your PIN: ", false); + String enteredPin = UI.getUserInput(); + assert enteredPin != null : "Entered PIN should not be null."; + + try { + // Hash the entered PIN before comparing + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hashedEnteredPin = md.digest(enteredPin.getBytes(StandardCharsets.UTF_8)); + String hashedEnteredPinHex = new BigInteger(1, hashedEnteredPin).toString(16); + + while (!hashedEnteredPinHex.equals(savedPin)) { + if (Objects.equals(enteredPin, "exit") || Objects.equals(enteredPin, "close")) { + UI.exit(); + } + UI.showMessage("Invalid PIN. Please try again. Alternatively, enter 'exit' or 'close' " + + "to exit LongAh."); + UI.showMessage("Enter your PIN: ", false); + enteredPin = UI.getUserInput(); + hashedEnteredPin = md.digest(enteredPin.getBytes(StandardCharsets.UTF_8)); + hashedEnteredPinHex = new BigInteger(1, hashedEnteredPin).toString(16); + } + Logging.logInfo("Login successful!"); + UI.showMessage("Login successful!"); + } catch (NoSuchAlgorithmException e) { + UI.showMessage("Error authenticating PIN. Please try again."); + } + } + + /** + * Resets the PIN for the user with a new PIN. + */ + public static void resetPin() { + UI.showMessage("Enter your current PIN: ", false); + String enteredPin = UI.getUserInput(); + assert enteredPin != null : "Entered PIN should not be null."; + + try { + // Hash the entered PIN before comparing + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hashedEnteredPin = md.digest(enteredPin.getBytes(StandardCharsets.UTF_8)); + String hashedEnteredPinHex = new BigInteger(1, hashedEnteredPin).toString(16); + + if (hashedEnteredPinHex.equals(savedPin)) { + // If the entered PIN is correct, allow the user to create a new PIN + createPin(); + Logging.logInfo("PIN reset successful!"); + } else { + UI.showMessage("Invalid PIN. Please try again later."); + } + } catch (NoSuchAlgorithmException e) { + UI.showMessage("Error resetting PIN. Please try again."); + } + } + + /** + * Enables authentication upon startup. + */ + public static void enablePin() { + if (!authenticationEnabled) { + authenticationEnabled = true; + savePinAndAuthenticationEnabled(); + UI.showMessage("Authentication enabled upon startup."); + } else { + UI.showMessage("Authentication is already enabled."); + } + } + + /** + * Disables authentication upon startup. + */ + public static void disablePin() { + if (authenticationEnabled) { + authenticationEnabled = false; + savePinAndAuthenticationEnabled(); + UI.showMessage("Authentication disabled upon startup."); + } else { + UI.showMessage("Authentication is already disabled."); + } + } + + /** + * Returns the saved PIN. + * + * @return The saved PIN. + */ + public static String getSavedPin() { + return savedPin; + } + + /** + * Returns the authentication status. + * + * @return The authentication status. + */ + public static boolean getAuthenticationStatus() { + return authenticationEnabled; + } +} diff --git a/src/main/java/longah/handler/StorageHandler.java b/src/main/java/longah/handler/StorageHandler.java new file mode 100644 index 0000000000..eb6a066af1 --- /dev/null +++ b/src/main/java/longah/handler/StorageHandler.java @@ -0,0 +1,325 @@ +package longah.handler; + +import java.util.ArrayList; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; +import java.io.FileWriter; +import java.io.IOException; +import java.math.BigDecimal; + +import longah.node.Member; +import longah.util.MemberList; +import longah.util.Subtransaction; +import longah.node.Transaction; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +/* + * Storage Format + * ----------- + * Members: + * [Name]SEP[Balance] + * + * Transactions: + * [Lender]SEP[Borrower1]SEP[Value]SEP... + */ +public class StorageHandler { + // Constants + private static final double EPSILON = 1e-3; // Double Comparison Epsilon + + // ASCII Defined Separator + private static final String SEPARATOR = String.valueOf(Character.toChars(31)); + private static final String MEMBERS_FILE_STRING = "members.txt"; + private static final String TRANSACTIONS_FILE_STRING = "transactions.txt"; + + // Storage Directory Constants + private String storageFolderPath = "./data"; + private String storageMembersFilePath; + private String storageTransactionsFilePath; + private File membersFile; + private File transactionsFile; + + // Objects for Storate + private MemberList members; + private TransactionList transactions; + private Scanner[] scanners = new Scanner[2]; + + /** + * Initializes a new StorageHandler instance. + * Each instance handles the data storage requirements of each group of members. + * + * @throws LongAhException If the data files are not created + */ + public StorageHandler(MemberList members, TransactionList transactions, String groupName) + throws LongAhException { + // Create data directory if it does not exist + initDir(); + this.storageFolderPath += "/" + groupName; + // Create group directory if it does not exist + if(!new File(this.storageFolderPath).exists()) { + new File(this.storageFolderPath).mkdir(); + } + + // Create data files if they do not exist + this.storageMembersFilePath = this.storageFolderPath + "/" + MEMBERS_FILE_STRING; + this.storageTransactionsFilePath = this.storageFolderPath + "/" + TRANSACTIONS_FILE_STRING; + this.membersFile = new File(this.storageMembersFilePath); + this.transactionsFile = new File(this.storageTransactionsFilePath); + + try { + membersFile.createNewFile(); + transactionsFile.createNewFile(); + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.STORAGE_FILE_NOT_CREATED); + } + + this.members = members; + this.transactions = transactions; + initStorageScanners(); + + // Load data from data files into MemberList and TransactionList objects + loadAllData(); + Logging.logInfo("Data loaded from storage."); + } + + /** + * Initializes the storage scanner to read data files. + * + * @throws LongAhException If the data files are not found + */ + public void initStorageScanners() throws LongAhException { + try { + this.scanners[0] = new Scanner(this.membersFile); + this.scanners[1] = new Scanner(this.transactionsFile); + } catch (FileNotFoundException e) { + throw new LongAhException(ExceptionMessage.STORAGE_FILE_NOT_FOUND); + } + } + + public static void initDir() { + File f = new File("./data"); + if (!f.exists()) { + f.mkdir(); + } + } + + /** + * Loads the members data from the data file into the MemberList object. + * + * @throws LongAhException If the data file is not read or the content is invalid + */ + public void loadMembersData() throws LongAhException { + Scanner sc = this.scanners[0]; + while (sc.hasNextLine()) { + try { + String data = sc.nextLine(); + if (data.equals("")) { + continue; + } + + String[] memberData = data.split(SEPARATOR); + assert memberData.length == 2 : "Member data should have 2 parts."; + + String name = memberData[0]; + double balance = Double.parseDouble(memberData[1]); + this.members.addMember(name, balance); + } catch (LongAhException | NumberFormatException e) { + throw new LongAhException(ExceptionMessage.INVALID_STORAGE_CONTENT); + } + } + } + + /** + * Loads the transactions data from the data file into the TransactionList object. + * + * @throws LongAhException If the data file is not read or the content is invalid + */ + public void loadTransactionsData() throws LongAhException { + Scanner sc = this.scanners[1]; + boolean isError = false; + while (sc.hasNextLine()) { + try { + String data = sc.nextLine(); + if (data.equals("")) { + continue; + } + + String[] transactionData = data.split(SEPARATOR); + String lenderName = transactionData[0]; + String transactionTime = null; + Member lender = members.getMember(lenderName); + Transaction transaction; + ArrayList subtransactions = new ArrayList<>(); + int startOfSubtransactions = 1; + + if (transactionData[1].contains("-")) { + transactionTime = transactionData[1]; + startOfSubtransactions = 2; + } + + for (int i = startOfSubtransactions; i < transactionData.length; i += 2) { + try { + Subtransaction subtransaction = parseSubtransaction(transactionData[i], + transactionData[i + 1], lender, members); + subtransactions.add(subtransaction); + } catch (LongAhException e) { + // Skip the subtransaction if it is invalid + isError = true; + continue; + } + } + + if (startOfSubtransactions == 1) { + transaction = new Transaction(lender, subtransactions, members); + } else { + transactionTime = transactionData[1]; + transaction = new Transaction(lender, subtransactions, members, transactionTime); + } + this.transactions.addTransaction(transaction); + + } catch (LongAhException | NumberFormatException e) { + throw new LongAhException(ExceptionMessage.INVALID_STORAGE_CONTENT); + } + } + + boolean checksum = checkTransactions(members); + if (!checksum) { + throw new LongAhException(ExceptionMessage.STORAGE_FILE_CORRUPTED); + } + if (isError) { + UI.showMessage("Some transactions are invalid and have been skipped."); + } + } + + /** + * Parses the subtransaction data from the data file into a Subtransaction object. + * + * @param borrowerName The name of the borrower in the subtransaction + * @param value The amount borrowed in the subtransaction + * @param lender The lender in the subtransaction + * @param members The MemberList object to reference the members in the subtransaction + * @return The Subtransaction object parsed from the data file + * @throws LongAhException If the data file is not read or the content is invalid + */ + public static Subtransaction parseSubtransaction(String borrowerName, String value, + Member lender, MemberList members) throws LongAhException{ + try { + Member borrower = members.getMember(borrowerName); + double amount = Double.parseDouble(value); + + if (borrower.equals(lender)) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_FORMAT); + } + // Exception is thrown if the amount borrowed has more than 2dp + if (BigDecimal.valueOf(amount).scale() > 2) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + // Exception is thrown if the amount borrowed is not positive + if (amount <= 0) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + + return new Subtransaction(lender, borrower, amount); + + } catch (NumberFormatException | LongAhException e) { + throw new LongAhException(ExceptionMessage.INVALID_STORAGE_CONTENT); + } + } + + /** + * Returns if the total balance of all members in the MemberList object is 0. + * + * @param members The MemberList object to check the total balance from + * @return If the total balance is 0, return true. Otherwise, return false. + */ + public boolean checkTransactions(MemberList members) { + if (members.getMemberListSize() == 0) { + return true; + } + double total = 0.0; + for (Member member : members.getMembers()) { + total += member.getBalance(); + } + if (Math.abs(total) < EPSILON) { + return true; + } + return false; + } + + /** + * Loads all data from the data files into the MemberList and TransactionList objects. + * + * @throws LongAhException If the data files are not read or the content is invalid + */ + public void loadAllData() throws LongAhException { + loadMembersData(); + loadTransactionsData(); + + // Close the scanners after reading the data + this.scanners[0].close(); + this.scanners[1].close(); + } + + /** + * Saves the members data from the MemberList object into the data file. + * + * @throws LongAhException If the data file is not written + */ + public void saveMembersData() throws LongAhException { + try { + FileWriter fw = new FileWriter(this.membersFile); + for (Member member : this.members.getMembers()) { + String data = member.toStorageString(SEPARATOR); + fw.write(data + "\n"); + } + fw.close(); + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.STORAGE_FILE_NOT_WRITTEN); + } + } + + /** + * Saves the transactions data from the TransactionList object into the data file. + * + * @throws LongAhException If the data file is not written + */ + public void saveTransactionsData() throws LongAhException { + try { + FileWriter fw = new FileWriter(this.transactionsFile); + for (Transaction transaction : this.transactions.getTransactions()) { + String data = transaction.toStorageString(SEPARATOR); + fw.write(data + "\n"); + } + fw.close(); + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.STORAGE_FILE_NOT_WRITTEN); + } + } + + /** + * Saves all data from the MemberList and TransactionList objects into the data files. + * + * @throws LongAhException If the data files are not written + */ + public void saveAllData() throws LongAhException { + saveMembersData(); + saveTransactionsData(); + } + + /** + * Helper method to remove a directory and its contents. + * + * @param dir The file to be removed + */ + public static void deleteDir(File dir) { + File[] contents = dir.listFiles(); + if (contents != null) { + for (File file : contents) { + deleteDir(file); + } + } + dir.delete(); + } +} diff --git a/src/main/java/longah/handler/UI.java b/src/main/java/longah/handler/UI.java new file mode 100644 index 0000000000..cde0d9a3a6 --- /dev/null +++ b/src/main/java/longah/handler/UI.java @@ -0,0 +1,104 @@ +package longah.handler; + +import java.util.Scanner; + +/** + * The UI class handles user interaction by displaying messages and reading user input. + */ +public class UI { + private static final String SEPARATOR = "____________________________________________________________"; + private static Scanner scanner = new Scanner(System.in); + + // @@author haowern98 + /** + * Displays the welcome message along with ASCII art. + */ + public static void showWelcomeMessage() { + UI.showMessage(" /$$ /$$$$$$ /$$ /$$"); + UI.showMessage("| $$ /$$__ $$| $$ | $$"); + UI.showMessage("| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \\ $$| $$$$$$$ | $$"); + UI.showMessage("| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$"); + UI.showMessage("| $$ | $$ \\ $$| $$ \\ $$| $$ \\ $$| $$__ $$| $$ \\ $$|__/"); + UI.showMessage("| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$"); + UI.showMessage("| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$"); + UI.showMessage("|________/ \\______/ |__/ |__/ \\____ $$|__/ |__/|__/ |__/|__/"); + UI.showMessage(" /$$ \\ $$"); + UI.showMessage(" | $$$$$$/"); + UI.showMessage(" \\______/"); + UI.showMessage("Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon!"); + } + + /** + * Displays the exit message. + */ + public static void exit() { + showMessage("Goodbye! Hope to see you again soon!"); + System.exit(0); + } + + /** + * Displays the command prompt. + */ + public static void showCommandPrompt() { + showMessage("Enter command: ", false); + } + + /** + * Reads the user input. + * + * @return The user input as a String. + */ + public static String getUserInput() { + if (!scanner.hasNextLine()) { + System.exit(0); + } + return scanner.nextLine().trim(); + } + + /** + * Displays a message. + * + * @param message The message to display. + */ + public static void showMessage(String message) { + System.out.println(message); + } + + /** + * Displays a message. + * + * @param message The message to display. + * @param newLine Whether to print a new line after the message. + */ + public static void showMessage(String message, boolean newLine) { + if (newLine) { + System.out.println(message); + } else { + System.out.print(message); + } + } + + /** + * Checks if there is another line of input. + * Used for text ui testing. + * + * @return true if there is another line of input, false otherwise. + */ + public static boolean hasNextLine() { + return scanner.hasNextLine(); + } + + /** + * Prints a separator. + */ + public static void printSeparator() { + showMessage(SEPARATOR); + } + + /** + * Prints an empty line. + */ + public static void printEmptyLine() { + showMessage(""); + } +} diff --git a/src/main/java/longah/node/Group.java b/src/main/java/longah/node/Group.java new file mode 100644 index 0000000000..9bdc53ee8e --- /dev/null +++ b/src/main/java/longah/node/Group.java @@ -0,0 +1,195 @@ +package longah.node; + +import java.util.ArrayList; + +import longah.util.MemberList; +import longah.util.Subtransaction; +import longah.util.TransactionList; +import longah.handler.Logging; +import longah.handler.NameHandler; +import longah.handler.StorageHandler; +import longah.handler.UI; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +public class Group { + private MemberList members; + private TransactionList transactions; + private StorageHandler storage; + private String groupName; + private ArrayList transactionSolution = new ArrayList<>(); + + /** + * Constructs a new Group instance with an empty member list and transaction list. + * + * @param groupName The name of the group + * @throws LongAhException If the group name is invalid + */ + public Group(String groupName) throws LongAhException { + NameHandler.checkGroupNameValidity(groupName); + this.groupName = groupName; + this.members = new MemberList(); + this.transactions = new TransactionList(); + this.storage = new StorageHandler(this.members, this.transactions, this.groupName); + updateTransactionSolution(); + } + + /** + * Sets the name of the group. + * + * @param groupName The name of the group + */ + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + /** + * Returns the name of the group. + * + * @return The name of the group + */ + public String getGroupName() { + return this.groupName; + } + + /** + * Sets the member list of the group. + * + * @param members The member list to be set + */ + public void setMemberList(MemberList members) { + this.members = members; + } + + /** + * Returns the member list of the group. + * + * @return The member list of the group + */ + public MemberList getMemberList() { + return this.members; + } + + /** + * Sets the transaction list of the group. + * + * @param transactions The transaction list to be set + */ + public void setTransactionList(TransactionList transactions) { + this.transactions = transactions; + } + + /** + * Returns the transaction list of the group. + * + * @return The transaction list of the group + */ + public TransactionList getTransactionList() { + return this.transactions; + } + + /** + * Update the transaction solution of the group based on the debts and credits of the members. + * + * @throws LongAhException If the transaction solution cannot be updated + */ + public void updateTransactionSolution() throws LongAhException { + this.members.updateMembersBalance(this.transactions); + this.transactionSolution = this.members.solveTransactions(); + Logging.logInfo("Transaction solution updated."); + } + + /** + * Settles up the debts of the specified borrower by creating a transaction to repay all debts owed. + * + * @param borrowerName The name of the borrower to settle up. + */ + public void settleUp(String borrowerName) throws LongAhException { + Member borrower = this.members.getMember(borrowerName); + if (borrower.getBalance() == 0) { + throw new LongAhException(ExceptionMessage.NO_DEBTS_FOUND); + } + + String transactionExpression = borrowerName; + for (Subtransaction subtransaction : this.transactionSolution) { + Member subBorrower = subtransaction.getBorrower(); + if (borrower == subBorrower) { + Member lender = subtransaction.getLender(); + double amountRepaid = subtransaction.getAmount(); + transactionExpression += " p/" + lender.getName() + " a/" + amountRepaid; + UI.showMessage(borrowerName + " has repaid " + lender.getName() + " $" + amountRepaid); + } + } + UI.printEmptyLine(); + this.transactions.addTransaction(transactionExpression, this.members); + updateTransactionSolution(); + assert this.members.getMember(borrowerName).getBalance() == 0 : "Borrower should have no more debts."; + UI.showMessage(borrowerName + " has no more debts!"); + } + + /** + * Saves the member data into the storage file. + * + * @throws LongAhException If the data file is not written + */ + public void saveMembersData() throws LongAhException { + this.storage.saveMembersData(); + } + + /** + * Saves the transaction data into the storage file. + * + * @throws LongAhException If the data file is not written + */ + public void saveTransactionsData() throws LongAhException { + this.storage.saveTransactionsData(); + } + + /** + * Saves the data from the member list and transaction list into storage file. + * + * @throws LongAhException If the data file is not written + */ + public void saveAllData() throws LongAhException { + this.storage.saveAllData(); + } + + /** + * Returns a string representation of the solution to all debts in the group. + * + * @return The solution to all debts in the group + * @throws LongAhException If there are no debts to be solved + */ + public String listDebts() throws LongAhException { + if (this.transactionSolution.isEmpty()) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + + String solution = "Best Way to Solve Debts:\n"; + for (Subtransaction subtransaction : this.transactionSolution) { + solution += subtransaction.toString() + "\n"; + } + return solution.trim(); + } + + /** + * Returns the solution to the debt of the specified member in the group. + * + * @return The solution to the debt of the specified member + * @throws LongAhException If there are no members in the group + */ + public String listIndivDebt(String name) throws LongAhException { + double balance = members.getMemberBalance(name); + if (balance == 0) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + + String output = ""; + for (Subtransaction subtransaction : this.transactionSolution) { + if (subtransaction.isInvolved(name)) { + output += subtransaction.toString() + "\n"; + } + } + return output.trim(); + } +} diff --git a/src/main/java/longah/node/Member.java b/src/main/java/longah/node/Member.java new file mode 100644 index 0000000000..a71e2456c0 --- /dev/null +++ b/src/main/java/longah/node/Member.java @@ -0,0 +1,145 @@ +package longah.node; + +import longah.exception.LongAhException; +import longah.handler.NameHandler; +import longah.exception.ExceptionMessage; + +/** + * Represents a member in the LongAh application. + */ +public class Member { + private String name; + private double balance; + + /** + * Constructs a new Member instance with the given name and zero balance. + * + * @param name The name of the member. + * @throws LongAhException If the name is invalid. + */ + public Member(String name) throws LongAhException { + NameHandler.checkMemberNameValidity(name); + this.name = name; + this.balance = 0.0; + } + + /** + * Constructs a new Member instance with the given name and balance. + * Used for storage methods. + * + * @param name The name of the member. + * @param balance The balance of the member. + * @throws LongAhException If the name is invalid. + */ + public Member(String name, double balance) throws LongAhException { + NameHandler.checkMemberNameValidity(name); + this.name = name; + this.balance = balance; + } + + /** + * Sets the name of the member. + * + * @param name The name of the member. + * @throws LongAhException If the name is invalid. + */ + public void setName(String name) throws LongAhException { + NameHandler.checkMemberNameValidity(name); + this.name = name; + } + + /** + * Adds the specified amount to the member's balance. + * + * @param amount The amount to add to the balance. + */ + public synchronized void addToBalance(double amount) throws LongAhException { + if (amount <= 0) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + if (this.balance + amount == Double.POSITIVE_INFINITY) { + throw new LongAhException(ExceptionMessage.BALANCE_OVERFLOW); + } + this.balance += amount; + } + + /** + * Subtracts the specified amount from the member's balance. + * + * @param amount The amount to subtract from the balance. + */ + public synchronized void subtractFromBalance(double amount) throws LongAhException { + if (amount <= 0) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + if (this.balance - amount == Double.NEGATIVE_INFINITY) { + throw new LongAhException(ExceptionMessage.BALANCE_OVERFLOW); + } + this.balance -= amount; + } + + /** + * Gets the current balance of the member. + * + * @return The balance of the member. + */ + public double getBalance() { + return this.balance; + } + + /** + * Returns a string representation of the member, including name and balance. + * + * @return A string representation of the member. + */ + @Override + public String toString() { + double rounded = (double)Math.round(this.balance * 100) / 100; + String roundedString = String.format("%.2f", rounded); + + if (this.balance >= 0) { + return this.name + ": $" + roundedString; + } + // Remove the negative sign + roundedString = roundedString.substring(1); + return (this.name + ": -$" + roundedString).trim(); + } + + /** + * Returns a string representation of the member for storage. + * + * @param delimiter The delimiter to separate the name and balance. + * @return A string representation of the member for storage. + */ + public String toStorageString(String delimiter) { + double rounded = (double)Math.round(this.balance * 100) / 100; + String roundedString = String.format("%.2f", rounded); + return this.name + delimiter + roundedString; + } + + /** + * Gets the name of the member. + * + * @return The name of the member. + */ + public String getName() { + return this.name; + } + + /** + * Used to check whether the input String matches the name of a member. + * + * @param memberName String representation of a member name + * @return A boolean value checking whether the input matches with name. + */ + public boolean isName(String memberName) { + return name.equals(memberName); + } + + /** + * Clears the balance of the member. + */ + public void clearBalance() { + this.balance = 0; + } +} diff --git a/src/main/java/longah/node/Transaction.java b/src/main/java/longah/node/Transaction.java new file mode 100644 index 0000000000..3d0e6116f9 --- /dev/null +++ b/src/main/java/longah/node/Transaction.java @@ -0,0 +1,328 @@ +package longah.node; + +import java.math.BigDecimal; +import java.util.ArrayList; + +import longah.util.DateTime; +import longah.util.MemberList; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; +import longah.util.Subtransaction; + +/** + * Represents a transaction between two members. + */ +public class Transaction { + private Member lender; + private DateTime transactionTime = null; + private ArrayList subtransactions = new ArrayList<>(); + + /** + * Constructs a new Transaction instance with the given user input and member list. + * + * @param userInput The user input for the transaction. + * @param memberList The list of members in the group. + * @throws LongAhException If the user input is in an invalid format or value. + */ + public Transaction(String userInput, MemberList memberList) throws LongAhException { + parseTransaction(userInput, memberList); + } + + /** + * Constructs a new Transaction instance with the given lender, subtransactions and member list. + * Used for storage methods only. + * + * @param lender The member who lent the money in the transaction. + * @param subtransactions The list of subtransactions in the transaction. + * @param members The list of members in the group. + * @throws LongAhException If the lender does not exist in the group. + */ + public Transaction(Member lender, ArrayList subtransactions, + MemberList members) throws LongAhException { + parseTransaction(lender, subtransactions, members); + } + + /** + * Constructs a new Transaction instance with the given lender, subtransactions, member list, and transaction time. + * Used for storage methods and for transaction records with time only. + * + * @param lender The member who lent the money in the transaction. + * @param subtransactions The list of subtransactions in the transaction. + * @param members The list of members in the group. + * @throws LongAhException If the lender does not exist in the group. + */ + public Transaction(Member lender, ArrayList subtransactions, + MemberList members, String transactionTime) throws LongAhException { + parseTransaction(lender, subtransactions, members); + try { + this.transactionTime = new DateTime(transactionTime); + } catch (LongAhException e) { + throw new LongAhException(ExceptionMessage.INVALID_STORAGE_CONTENT); + } + } + + /** + * Parses the user input to create a transaction. + * + * @param expression The user input for the transaction. + * @param members The list of members in the group. + * @throws LongAhException If the user input is in an invalid format or value. + */ + public void parseTransaction(String expression, MemberList members) throws LongAhException { + // User input format: [Lender] t/[transactionTime(opt)] p/[Borrower1] a/[amount1] p/[Borrower2] a/[amount2] ... + + String[] splitInput = expression.split("p/"); + if (splitInput.length < 2 || splitInput[0].isEmpty() || splitInput[1].contains(("t/"))) { + // Minimum of 2 people as part of a transaction + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_FORMAT); + } + assert splitInput.length >= 2 : "Invalid transaction."; + String lenderName; + + if (splitInput[0].contains("t/")) { + // Check presence of time component in expression + String[] splitLenderTime = splitInput[0].split("t/", 2); + lenderName = splitLenderTime[0].trim(); + this.transactionTime = new DateTime(splitLenderTime[1]); + } else { + lenderName = splitInput[0].trim(); + } + this.lender = members.getMember(lenderName); + + // Check for existence of all parties involved in the transaction in the group. + String borrowNameAmount; + for (int i = 1; i < splitInput.length; i++) { + borrowNameAmount = splitInput[i].trim(); + addBorrower(borrowNameAmount, members, this.lender); + } + } + + /** + * Parses the transaction for storage purposes. Used for reading from storage. + * + * @param lender The member who lent the money in the transaction. + * @param subtransactions The list of subtransactions in the transaction. + * @param members The list of members in the group. + * @throws LongAhException If any of the members do not exist in the group. + */ + public void parseTransaction(Member lender, ArrayList subtransactions, + MemberList members) throws LongAhException { + // Exception is thrown if any of the members do not exist in the group + if (!members.isMember(lender)) { + throw new LongAhException(ExceptionMessage.INVALID_STORAGE_CONTENT); + } + for (Subtransaction subtransaction : subtransactions) { + if (!members.isMember(subtransaction.getBorrower())) { + throw new LongAhException(ExceptionMessage.INVALID_STORAGE_CONTENT); + } + } + this.lender = lender; + this.subtransactions = subtransactions; + } + + /** + * Adds a borrower to the subtransaction list. + * + * @param expression The expression containing the borrower and amount borrowed. + * @param memberList The list of members in the group. + * @throws LongAhException If the expression is in an invalid format or value. + */ + public void addBorrower(String expression, MemberList memberList, Member lender) + throws LongAhException { + String[] splitBorrower = expression.split("a/"); + if (splitBorrower.length != 2) { + // Each person owing should have an amount specified + // Feature may be changed in the future + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_FORMAT); + } + + String borrowerName = splitBorrower[0].trim(); + // Exception is thrown if the borrower does not exist in the group + Member borrower = memberList.getMember(borrowerName); + Double amountBorrowed; + + // Exception is thrown if the borrower is the same as the lender + if (borrower.equals(lender)) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_FORMAT); + } + assert !borrower.equals(lender) : "Lender cannot borrow from themselves."; + + try { + amountBorrowed = Double.parseDouble(splitBorrower[1].trim()); + } catch (NumberFormatException e) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + + // Exception is thrown if the amount borrowed has more than 2dp + try { + if (BigDecimal.valueOf(amountBorrowed).scale() > 2) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + } catch (NumberFormatException e) { + throw new LongAhException(ExceptionMessage.BALANCE_OVERFLOW); + } + + // Exception is thrown if the amount borrowed is not positive + if (amountBorrowed <= 0) { + throw new LongAhException(ExceptionMessage.INVALID_TRANSACTION_VALUE); + } + assert amountBorrowed > 0 : "Amount owed should be positive."; + Subtransaction subtransaction = new Subtransaction(this.lender, borrower, amountBorrowed); + this.subtransactions.add(subtransaction); + } + + /** + * Gets the member who is the lender in the transaction. + * + * @return The member who is the lender in the transaction + */ + public Member getLender() { + return this.lender; + } + + /** + * Gets the transaction time of the current transaction. + * + * @return The DateTime object representing the current transaction time + */ + public DateTime getTransactionTime() { + return this.transactionTime; + } + + /** + * Checks whether the input member name is the lender of a transaction. + * + * @param memberName String representation of member name to check + * @return a boolean value determining whether the input name is the owner of the transaction + */ + public boolean checkIsLender(String memberName) { + return lender.isName(memberName); + } + + /** + * Checks whether the input member name is a borrower within the transaction + * + * @param memberName String representation of member name to check + * @return a boolean value determining whether the input name is a borrower in the transaction + */ + public boolean checkIsBorrower(String memberName) { + for (Subtransaction subtransaction : this.subtransactions) { + if (subtransaction.getBorrower().isName(memberName)) { + return true; + } + } + return false; + } + + /** + * Checks whether the input member name is involved in the transaction. + * + * @param memberName String representation of member name to check + * @return a boolean value determining whether the input name is involved in the transaction + */ + public boolean isInvolved(String memberName) { + return checkIsLender(memberName) || checkIsBorrower(memberName); + } + + /** + * Returns a string representation of the transaction for printouts. + * + * @return a string representation of the transaction + */ + @Override + public String toString() { + String lender = "Lender: " + this.lender.getName() + "\n"; + String time = ""; + if (this.haveTime()) { + assert transactionTime != null : "Invalid printouts for transactions without a transaction time"; + time = "Transaction time: " + this.transactionTime + "\n"; + } + String borrower = ""; + int borrowerNo = 1; + for (Subtransaction subtransaction : subtransactions) { + Member member = subtransaction.getBorrower(); + double amount = subtransaction.getAmount(); + borrower += String.format("Borrower %d: %s Owed amount: $%,.2f\n", + borrowerNo, member.getName(), amount); + borrowerNo++; + } + return (lender + time + borrower).trim(); + } + + /** + * Returns a string representation of the transaction for storage. + * + * @param delimiter The delimiter to separate the lender and borrowers. + * @return a string representation of the transaction for storage + */ + public String toStorageString(String delimiter) { + String lender = this.lender.getName(); + String borrower = ""; + String time = ""; + if (this.haveTime()) { + assert transactionTime != null : "Invalid storage for transactions without a transaction time"; + time = delimiter + transactionTime.toStorageString(); + } + for (Subtransaction subtransaction : this.subtransactions) { + String borrowerName = subtransaction.getBorrower().getName(); + double amount = subtransaction.getAmount(); + borrower += delimiter + borrowerName + delimiter + amount; + } + return lender + time + borrower; + } + + /** + * Returns the list of subtransactions in the transaction. + * + * @return The list of subtransactions in the transaction. + */ + public ArrayList getSubtransactions() { + return this.subtransactions; + } + + /** + * Edits the specified transaction based on user input. + * + * @param expression The user input for editing the transaction. + * @param memberList The list of members in the group. + * @throws LongAhException If the transaction index is invalid or if the edit input is in an invalid format. + */ + public void editTransaction(String expression, MemberList memberList) throws LongAhException { + subtransactions.clear(); + parseTransaction(expression, memberList); + } + + /** + * Returns true if the transaction needs to be removed, false otherwise. + * Deletes a member from the transaction. + * + * @param member The member to be deleted from the transaction. + * @return True if the transaction needs to be removed, false otherwise. + */ + public boolean deleteMember(Member member) { + // Delete transaction if member is lender + if (lender.equals(member)) { + return true; + } + + // Delete subtransaction if member is borrower + for (int i = 0; i < subtransactions.size(); i++) { + Subtransaction subtransaction = subtransactions.get(i); + if (subtransaction.getBorrower().equals(member)) { + subtransactions.remove(i); + } + } + + // Delete transaction if no more subtransactions + return subtransactions.isEmpty(); + } + + /** + * Returns true if the transaction has a transaction time + * + * @return True if the transaction has a time, false otherwise + */ + public boolean haveTime() { + return transactionTime != null; + } +} diff --git a/src/main/java/longah/util/Chart.java b/src/main/java/longah/util/Chart.java new file mode 100644 index 0000000000..19766df064 --- /dev/null +++ b/src/main/java/longah/util/Chart.java @@ -0,0 +1,83 @@ +package longah.util; + +import org.knowm.xchart.AnnotationTextPanel; +import org.knowm.xchart.CategoryChart; +import org.knowm.xchart.CategoryChartBuilder; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.style.Styler; + + +import javax.swing.JFrame; +import java.awt.Color; +import java.util.List; +import java.util.ArrayList; +import java.awt.Font; + +public class Chart { + + /** + * Constructor for Chart. + * + * @param chart The chart to be displayed. + */ + public Chart(CategoryChart chart) { + JFrame frame = new SwingWrapper(chart).displayChart(); + javax.swing.SwingUtilities.invokeLater( + ()->frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE) + ); + } + + /** + * Create and display a bar chart. + * + * @param members List of member names or categories. + * @param balances List of member balances corresponding to the labels. + * @return + */ + public static Chart viewBalancesBarChart(List members, List balances) { + CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Member Balances") + .xAxisTitle("Members").yAxisTitle("Balances").theme(Styler.ChartTheme.GGPlot2).build(); + chart.getStyler().setLabelsRotation(1); + + List positiveBalances = new ArrayList<>(); + List negativeBalances = new ArrayList<>(); + for (Double balance : balances) { + if (balance >= 0) { + positiveBalances.add(balance); + negativeBalances.add(null); // Null value to maintain position for negative balances + } else { + positiveBalances.add(null); // Null value to maintain position for positive balances + negativeBalances.add(balance); + } + } + + chart.addSeries("Positive Balances", members, positiveBalances) + .setFillColor(Color.GREEN); + chart.addSeries("Negative Balances", members, negativeBalances) + .setFillColor(Color.RED); + + // set tooltips + chart.getStyler().setOverlapped(true); + chart.getStyler().setToolTipsEnabled(true); + chart.getStyler().setToolTipsAlwaysVisible(true); + chart.getStyler().setToolTipFont( new Font("Verdana", Font.BOLD, 12)); + chart.getStyler().setToolTipHighlightColor(Color.BLUE); + chart.getStyler().setToolTipBorderColor(Color.BLACK); + chart.getStyler().setToolTipBackgroundColor(Color.ORANGE); + chart.getStyler().setToolTipType(Styler.ToolTipType.yLabels); + + // set annotations for recommended command + chart.addAnnotation( + new AnnotationTextPanel( + "Use the command 'list debts' \n to find out the best \n way to solve your debts!", + 800, + 600, + true)); + chart.getStyler().setAnnotationTextPanelPadding(20); + chart.getStyler().setAnnotationTextPanelFont(new Font("Verdana", Font.BOLD, 12)); + chart.getStyler().setAnnotationTextPanelBackgroundColor(Color.DARK_GRAY); + chart.getStyler().setAnnotationTextPanelFontColor(Color.LIGHT_GRAY); + + return new Chart(chart); + } +} diff --git a/src/main/java/longah/util/DateTime.java b/src/main/java/longah/util/DateTime.java new file mode 100644 index 0000000000..0508807814 --- /dev/null +++ b/src/main/java/longah/util/DateTime.java @@ -0,0 +1,104 @@ +package longah.util; + +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Represents objects where the time element is concerned. + */ +public class DateTime { + //@@author FeathersRe + private LocalDateTime dateTime; + + /** + * Constructs a new DateTime object based on a String representation of the date time expression. + * + * @param dateTimeExpression String representation of a date time expression + * @throws LongAhException If the date time expression does not follow the intended date time format + */ + public DateTime(String dateTimeExpression) throws LongAhException { + try { + this.dateTime = LocalDateTime.parse(dateTimeExpression.trim(), + DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm")); + } catch (DateTimeParseException e) { + throw new LongAhException(ExceptionMessage.INVALID_TIME_FORMAT); + } + if (isFuture()) { + throw new LongAhException(ExceptionMessage.INVALID_TIME_INPUT); + } + } + + /** + * Getter method to get the dateTime object associated with the current object instance. Currently used within the + * class only. + * + * @return Returns the dateTime object associated with the current instance. + */ + private LocalDateTime getDateTime() { + return this.dateTime; + } + + /** + * Determines whether the input DateTime object has a date that is before the current object. + * + * @param dateTimeToCompare the reference DateTime object to be compared with + * @return true if the input DateTime object has a date before the current object. false otherwise + */ + public boolean isBefore(DateTime dateTimeToCompare) { + return this.dateTime.isBefore(dateTimeToCompare.getDateTime()); + } + + /** + * Determines whether the input DateTime object has a date that is after the current object. + * + * @param dateTimeToCompare the reference DateTime object to be compared with + * @return true if the input DateTime object has a date after the current object. false otherwise + */ + public boolean isAfter(DateTime dateTimeToCompare) { + return this.dateTime.isAfter(dateTimeToCompare.getDateTime()); + } + + /** + * Determines whether the input DateTime object has a date that is equal to the current object + * + * @param dateTimeToCompare the reference DateTime object to be compared with + * @return true if the input DateTime object has a date equal to the current object. false otherwise + */ + public boolean isEqual(DateTime dateTimeToCompare) { + return this.dateTime.isEqual(dateTimeToCompare.getDateTime()); + } + + /** + * Determines whether the existing object has a future dateTime. This should only be used within the class to + * reject invalid time entries. + * + * @return true if the object has a future dateTime. false otherwise + */ + private boolean isFuture() { + return this.dateTime.isAfter(LocalDateTime.now()); + } + + /** + * Converts the date time object into a String expression suitable for storage + * + * @return A string representation of the date time object suitable for storage + */ + public String toStorageString() { + return this.dateTime.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm")); + } + + /** + * Converts the date time object into a String expression suitable for printing + * + * @return A string representation of the date time object suitable for printing + */ + @Override + public String toString() { + return this.dateTime.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm")); + } + +} diff --git a/src/main/java/longah/util/GroupList.java b/src/main/java/longah/util/GroupList.java new file mode 100644 index 0000000000..fa06551641 --- /dev/null +++ b/src/main/java/longah/util/GroupList.java @@ -0,0 +1,246 @@ +package longah.util; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; + +import longah.exception.ExceptionMessage; +import longah.exception.LongAhException; +import longah.handler.StorageHandler; +import longah.handler.UI; +import longah.node.Group; + +/** + * Represents a list of groups in the LongAh application. + */ +public class GroupList { + private static final String GROUP_LIST_FILE_PATH = "./data/groupList.txt"; + private static Group activeGroup = null; + private static ArrayList groupList = new ArrayList<>(); + + /** + * Constructor for GroupList. + * + * @throws LongAhException If an I/O exception occurs. + */ + public GroupList() throws LongAhException { + StorageHandler.initDir(); + if (!Files.exists(Paths.get(GROUP_LIST_FILE_PATH)) || groupList == null) { + createGroup(); + } else { + loadGroupList(); + getGroupList(); + UI.showMessage("Defaulting to the first group."); + UI.showMessage("You are now managing: " + groupList.get(0).getGroupName()); + activeGroup = groupList.get(0); + } + } + + /** + * Returns the active group. + * + * @return The active group. + */ + public static Group getActiveGroup() { + return activeGroup; + } + + /** + * Switches the active group to the specified group. + * + * @param newGroup The new group to switch to. + */ + public static void switchActiveGroup(Group newGroup) { + activeGroup = newGroup; + } + + /** + * Checks if there is at least 1 valid group in the group list. + * If there is no group, prompt user to create a new group. + */ + public static void createGroup() throws LongAhException { + if (groupList.isEmpty()) { + UI.showMessage("No groups found. Please give a name for your first group or enter " + + "'exit' or 'close' to exit LongAh."); + String groupName = UI.getUserInput(); + if (groupName.equals("exit") || groupName.equals("close")) { + UI.exit(); + } + Group newGroup = new Group(groupName); + groupList.add(newGroup); + try { + Files.write(Paths.get(GROUP_LIST_FILE_PATH), groupName.getBytes()); + } catch (IOException e) { + UI.showMessage(ExceptionMessage.STORAGE_FILE_CORRUPTED.getMessage()); + } + activeGroup = newGroup; + UI.showMessage("Created group: " + groupName); + UI.showMessage("You are now managing: " + activeGroup.getGroupName()); + } + } + + /** + * Loads the group list from the file. + * + * @throws LongAhException If an I/O exception occurs. + */ + public static void loadGroupList() throws LongAhException { + String[] data; + try { + data = new String(Files.readAllBytes(Paths.get(GROUP_LIST_FILE_PATH))).split("\n"); + if (data.length == 0) { + throw new LongAhException(ExceptionMessage.EMPTY_GROUP_LIST); + } + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.IO_EXCEPTION); + } + + for (String groupName : data) { + try { + groupList.add(new Group(groupName)); + } catch (LongAhException e) { + UI.showMessage(ExceptionMessage.STORAGE_FILE_CORRUPTED.getMessage()); + } + } + } + + /** + * Returns the group list. + * + * @return The group list as a string. + * @throws LongAhException If an I/O exception occurs. + */ + public static String getGroupList() throws LongAhException { + // did not use exceptions here as I want to return an empty string + if (groupList.isEmpty()) { + return "Group list is empty."; + } + try { + int index = 1; + String[] data = new String(Files.readAllBytes(Paths.get(GROUP_LIST_FILE_PATH))).split("\n"); + String groupListString = ""; + for (String groupName : data) { + groupListString += index + ". " + groupName + "\n"; + index++; + } + return groupListString; + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.IO_EXCEPTION); + } + } + + /** + * Adds a group to the group list and saves to the txt file. + * + * @param group The group to add. + * @throws LongAhException If the group is already in the list. + */ + public static void addGroup(Group group) throws LongAhException { + if (isGroup(group.getGroupName())) { + throw new LongAhException(ExceptionMessage.DUPLICATE_GROUP); + } + groupList.add(group); + saveGroupList(); + UI.showMessage("Added group: " + group.getGroupName()); + } + + /** + * Deletes a group from the group list and saves to the txt file. + * + * @param groupName The group to delete. + * @throws LongAhException If the group is not found or if error occurs when deleting group folder + */ + public static void deleteGroup(String groupName) throws LongAhException { + if (!GroupList.isGroup(groupName)) { + throw new LongAhException(ExceptionMessage.GROUP_NOT_FOUND); + } + Group group = getGroup(groupName); + groupList.remove(group); + saveGroupList(); + UI.showMessage("Remaining groups:"); + UI.showMessage(getGroupList()); + // delete the group folder + try { + Files.deleteIfExists(Paths.get("./data/" + groupName + "/members.txt")); + Files.deleteIfExists(Paths.get("./data/" + groupName + "/transactions.txt")); + Files.deleteIfExists(Paths.get("./data/" + groupName)); + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.IO_EXCEPTION); + } + // if there is only group that was left is deleted + if (groupList.isEmpty()) { + UI.showMessage("Deleted group: " + groupName); + createGroup(); + } else if (activeGroup.getGroupName().equals(groupName)) { + activeGroup = groupList.get(0); + UI.showMessage("You have deleted the active group that you were managing."); + UI.showMessage("Defaulting back to the first group in the list."); + UI.showMessage("You are now managing: " + activeGroup.getGroupName()); + } else { + UI.showMessage("Deleted group: " + groupName); + } + } + + + /** + * Returns the group with a specified name. + * + * @param name The name of the group. + * @return The group with a specified name. + */ + public static Group getGroup(String name) throws LongAhException { + for (Group group : groupList) { + if (group.getGroupName().equals(name)) { + return group; + } + } + throw new LongAhException(ExceptionMessage.GROUP_NOT_FOUND); + } + + public static boolean isEmpty() { + return groupList.isEmpty(); + } + + /** + * Returns the size of the group list. + * + * @return The size of the group list. + */ + public int getSize() { + return groupList.size(); + } + + /** + * Checks if the group is in the list. + * + * @param groupName The name of the group. + * @return True if the group is in the list, false otherwise. + */ + public static boolean isGroup(String groupName) { + for (Group group : groupList) { + if (group.getGroupName().equals(groupName)) { + return true; + } + } + return false; + } + + /** + * Saves the group list to the txt file. + * + * @throws LongAhException If an I/O exception occurs. + */ + public static void saveGroupList() throws LongAhException { + try { + FileWriter writer = new FileWriter(GROUP_LIST_FILE_PATH); + for (Group group : groupList) { + writer.write(group.getGroupName() + "\n"); + } + writer.close(); + } catch (IOException e) { + throw new LongAhException(ExceptionMessage.IO_EXCEPTION); + } + } +} diff --git a/src/main/java/longah/util/MemberList.java b/src/main/java/longah/util/MemberList.java new file mode 100644 index 0000000000..f2897da726 --- /dev/null +++ b/src/main/java/longah/util/MemberList.java @@ -0,0 +1,319 @@ +package longah.util; + +import java.util.ArrayList; + +import longah.handler.UI; +import longah.node.Member; +import longah.node.Transaction; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +/** + * Represents a list of group members. + */ +public class MemberList { + private ArrayList members; + + /** + * Constructs a new GroupList instance. + */ + public MemberList() { + this.members = new ArrayList<>(); + } + + /** + * Adds a member to the group. + * + * @param member The member to add. + * @throws LongAhException If the member already exists in the group. + */ + public void addMember(Member member) throws LongAhException { + if (isMember(member)) { + throw new LongAhException(ExceptionMessage.DUPLICATE_MEMBER); + } + this.members.add(member); + } + + /** + * Adds a member to the group with the specified name. + * + * @param name The name of the member to add. + * @throws LongAhException If the member already exists in the group. + */ + public void addMember(String name) throws LongAhException { + if (isMember(name)) { + throw new LongAhException(ExceptionMessage.DUPLICATE_MEMBER); + } + this.members.add(new Member(name)); + UI.showMessage("Added member: " + name); + } + + /** + * Adds a member to the group with the specified name and balance. + * For use in storage only. + * + * @param name The name of the member to add. + * @param balance The balance of the member to add. + * @throws LongAhException If the member already exists in the group. + */ + public void addMember(String name, double balance) throws LongAhException { + if (isMember(name)) { + throw new LongAhException(ExceptionMessage.DUPLICATE_MEMBER); + } + this.members.add(new Member(name, balance)); + } + + /** + * Returns true if the member is in the group, false otherwise. + * + * @param name The name of the member to check for. + * @return True if the member is in the group, false otherwise. + */ + public boolean isMember(String name) { + if (this.members.isEmpty()) { + return false; + } + + for (Member member : this.members) { + if (member.getName().equals(name)) { + return true; + } + } + return false; + } + + /** + * Returns true if the member is in the group, false otherwise. + * + * @param member The member to check for. + * @return True if the member is in the group, false otherwise. + */ + public boolean isMember(Member member) { + return this.members.contains(member); + } + + /** + * Returns the member object with the specified name. + * + * @param name The name of the member to get. + * @return The member with the specified name. + * @throws LongAhException If the member does not exist in the group. + */ + public Member getMember(String name) throws LongAhException { + for (Member member : members) { + if (member.getName().equals(name)) { + return member; + } + } + throw new LongAhException(ExceptionMessage.MEMBER_NOT_FOUND); + } + + /** + * Changes the name of the member at the specified index. + * + * @param oldName The old name of the member. + * @param newName The new name of the member. + * @throws LongAhException If the index is invalid. + */ + public void editMemberName(String oldName, String newName) throws LongAhException { + try { + Member member = getMember(oldName); + member.setName(newName); + } catch (IndexOutOfBoundsException | NumberFormatException e) { + throw new LongAhException(ExceptionMessage.INVALID_INDEX); + } + } + + /** + * Prints the list of members in the group. + * + * @throws LongAhException If there are no members in the group. + */ + public String listMembers() throws LongAhException { + if (members.isEmpty()) { + throw new LongAhException(ExceptionMessage.NO_MEMBERS_FOUND); + } + String output = ""; + for (Member member : members) { + output += member + "\n"; + } + return output.trim(); + } + + /** + * Updates the balances of the members in the group based on the transactions. + * + * @param transactions The list of transactions to update the balances with. + * @throws LongAhException If there are no members in the group. + */ + public void updateMembersBalance(TransactionList transactions) throws LongAhException { + clearBalances(); + if (transactions.getTransactions().isEmpty()) { + return; + } + for (Transaction transaction : transactions.getTransactions()) { + for (Subtransaction subtransaction : transaction.getSubtransactions()) { + Member lender = subtransaction.getLender(); + Member borrower = subtransaction.getBorrower(); + double amount = subtransaction.getAmount(); + lender.addToBalance(amount); + borrower.subtractFromBalance(amount); + } + } + } + + /** + * Groups members into two lists: positive balances and negative balances. + * + * @return An array of two lists: members with positive balances and members with negative balances. + * @throws LongAhException If there are no members in the group. + */ + public ArrayList> classifyMembers() throws LongAhException { + if (members.isEmpty()) { + throw new LongAhException(ExceptionMessage.NO_MEMBERS_FOUND); + } + + ArrayList positiveMembers = new ArrayList<>(); + ArrayList negativeMembers = new ArrayList<>(); + + for (Member member : members) { + if (member.getBalance() > 0) { + positiveMembers.add(member); + } else if (member.getBalance() < 0) { + negativeMembers.add(member); + } + } + + ArrayList> classifiedMembers = new ArrayList<>(); + classifiedMembers.add(positiveMembers); + classifiedMembers.add(negativeMembers); + + if (positiveMembers.isEmpty() || negativeMembers.isEmpty()) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + + return classifiedMembers; + } + + /** + * Finds the least transaction needed to solve the balances of the group members. + * This is done by pairing up members with positive balances and negative balances. + * The members are then iterated through and the balances are solved by subtracting the + * negative balance from the positive balance until the transaction has been solved. + * + * @return The list of subtransactions needed to solve the balances of the group members. + */ + public ArrayList solveTransactions() throws LongAhException { + ArrayList> classifiedMembers; + try { + classifiedMembers = classifyMembers(); + } catch (LongAhException e) { + return new ArrayList<>(); + } + + ArrayList positiveMembers = classifiedMembers.get(0); + ArrayList negativeMembers = classifiedMembers.get(1); + assert !positiveMembers.isEmpty() && !negativeMembers.isEmpty() : "Members should be classified."; + + ArrayList subtransactions = new ArrayList<>(); + int positiveIndex = 0; + int negativeIndex = 0; + double positiveBalance = 0; + double negativeBalance = 0; + Member positiveMember = positiveMembers.get(positiveIndex); + Member negativeMember = negativeMembers.get(negativeIndex); + + while (positiveIndex < positiveMembers.size() && + negativeIndex < negativeMembers.size()) { + + // If either balance is 0, move to their respective next member + if (positiveBalance == 0) { + positiveMember = positiveMembers.get(positiveIndex); + positiveBalance = positiveMember.getBalance(); + } + if (negativeBalance == 0) { + negativeMember = negativeMembers.get(negativeIndex); + negativeBalance = Math.abs(negativeMember.getBalance()); + } + + // Check the current pair for which balance is greater or if equal + if (positiveBalance > negativeBalance) { + Subtransaction subtransaction = + new Subtransaction(positiveMember, negativeMember, negativeBalance); + positiveBalance -= negativeBalance; + negativeBalance = 0; + subtransactions.add(subtransaction); + negativeIndex++; + + } else if (positiveBalance < negativeBalance) { + Subtransaction subtransaction = + new Subtransaction(positiveMember, negativeMember, positiveBalance); + negativeBalance -= positiveBalance; + positiveBalance = 0; + subtransactions.add(subtransaction); + positiveIndex++; + + } else { + Subtransaction subtransaction = + new Subtransaction(positiveMember, negativeMember, positiveBalance); + positiveBalance = 0; + negativeBalance = 0; + subtransactions.add(subtransaction); + positiveIndex++; + negativeIndex++; + } + } + + return subtransactions; + } + + /** + * Get the number of members in the group. + * @return The number of members in the group. + */ + public int getMemberListSize() { + return members.size(); + } + + /** + * Returns the list of members, of type in the group. + * For use in storage only. + * + * @return The list of members in the group. + */ + public ArrayList getMembers() { + return members; + } + + /** + * Returns the balance of the member with the specified name. + * + * @param name The name of the member to get the balance of. + * @return The balance of the member with the specified name. + * @throws LongAhException If the member does not exist in the group. + */ + public double getMemberBalance(String name) throws LongAhException { + return getMember(name).getBalance(); + } + + /** + * Iterates through the members list and clears their balances. + */ + public void clearBalances() { + for (Member member : members) { + member.clearBalance(); + } + } + + /** + * Deletes a member from the group. + * + * @param name The name of the member to delete. + * @throws LongAhException If the member does not exist in the group. + */ + public void deleteMember(String name) throws LongAhException { + Member member = getMember(name); + members.remove(member); + UI.showMessage("Deleted member: " + name); + } +} diff --git a/src/main/java/longah/util/Subtransaction.java b/src/main/java/longah/util/Subtransaction.java new file mode 100644 index 0000000000..bd89b26b85 --- /dev/null +++ b/src/main/java/longah/util/Subtransaction.java @@ -0,0 +1,74 @@ +package longah.util; + +import longah.node.Member; + +/** + * Represents a subtransaction within a transaction. + */ +public class Subtransaction { + private Member lender; + private Member borrower; + private double amount; + + /** + * Constructs a new Subtransaction instance with the given lender, person borrower, and amount. + * + * @param lender The lender who lends out money in the subtransaction. + * @param borrower The borrower who borrows money in the subtransaction. + * @param amount The amount borrowed in the subtransaction. + */ + public Subtransaction(Member lender, Member borrower, double amount) { + this.lender = lender; + this.borrower = borrower; + this.amount = amount; + } + + /** + * Returns the lender in the subtransaction. + * + * @return The lender in the subtransaction. + */ + public Member getLender() { + return lender; + } + + /** + * Returns the borrower in the subtransaction. + * + * @return The borrower in the subtransaction. + */ + public Member getBorrower() { + return borrower; + } + + /** + * Returns the amount owed in the subtransaction. + * + * @return The amount owed in the subtransaction. + */ + public double getAmount() { + return amount; + } + + /** + * Returns whether the input name is part of the subtransaction. + * + * @param name The name to check. + * @return A boolean value determining whether the input name is the lender in the subtransaction. + */ + public boolean isInvolved(String name) { + return lender.isName(name) || borrower.isName(name); + } + + /** + * Returns a string representation of the subtransaction. + * + * @return A string representation of the subtransaction. + */ + @Override + public String toString() { + double rounded = (double)Math.round(amount * 100) / 100; + String roundedString = String.format("%.2f", rounded); + return borrower.getName() + " owes " + lender.getName() + " $" + roundedString; + } +} diff --git a/src/main/java/longah/util/TransactionList.java b/src/main/java/longah/util/TransactionList.java new file mode 100644 index 0000000000..0d54e82800 --- /dev/null +++ b/src/main/java/longah/util/TransactionList.java @@ -0,0 +1,408 @@ +package longah.util; + +import java.util.ArrayList; + +import longah.handler.UI; +import longah.node.Group; +import longah.node.Member; +import longah.node.Transaction; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +/** + * Represents a list of transactions. + */ +public class TransactionList { + private ArrayList transactions = new ArrayList<>(); + + /** + * Adds a transaction to the list. + * + * @param transaction The transaction to add. + */ + public void addTransaction(Transaction transaction) { + this.transactions.add(transaction); + } + + /** + * Adds a transaction to the list with the specified expression and member list. + * + * @param expression The expression of the transaction to add. + * @param memberList The member list of the transaction to add. + * @throws LongAhException If the expression is invalid. + */ + public void addTransaction(String expression, MemberList memberList) + throws LongAhException { + Transaction toAddTransaction = new Transaction(expression, memberList); + this.transactions.add(toAddTransaction); + UI.showMessage("Transaction added successfully!"); + UI.showMessage(toAddTransaction.toString()); + } + + /** + * Adds a transaction to the list with the specified expression and member list. + * + * @param expression The expression of the transaction to add. + * @param memberList The member list of the transaction to add. + * @param group The group of the transaction to add. + * @throws LongAhException If the expression is invalid. + */ + public void addTransaction(String expression, MemberList memberList, Group group) + throws LongAhException { + Transaction toAddTransaction = new Transaction(expression, memberList); + this.transactions.add(toAddTransaction); + try { + group.updateTransactionSolution(); + } catch (LongAhException e) { + this.transactions.remove(toAddTransaction); + throw e; + } + UI.showMessage("Transaction added successfully!"); + UI.showMessage(toAddTransaction.toString()); + } + + /** + * Returns the size of the transaction list. + * + * @return The size of the transaction list. + */ + public int getTransactionListSize() { + return this.transactions.size(); + } + + /** + * Removes a transaction from the list by index. + * + * @param indexString The index of the transaction to remove. + * @throws LongAhException If the index is invalid. + */ + public void remove(String indexString) throws LongAhException { + int index = Integer.parseInt(indexString) - 1; + if (index < 0 || index >= this.transactions.size()) { + throw new LongAhException(ExceptionMessage.INVALID_INDEX); + } + Transaction removedTransaction = this.transactions.remove(index); + UI.showMessage("Transaction #" + indexString + " removed successfully."); + UI.showMessage(removedTransaction.toString()); + } + + /** + * Clears all transactions from the list. + * @param memberList The member list to clear balances from. + */ + public void clear(MemberList memberList) { + this.transactions.clear(); + memberList.clearBalances(); + UI.showMessage("All transaction records have been cleared."); + } + + /** + * Gets the list of transactions. + * + * @return The list of transactions. + */ + public ArrayList getTransactions() { + return this.transactions; + } + + /** + * Returns a String printout the list of transactions stored in the system. + */ + public String listTransactions() throws LongAhException { + int transactionListSize = getTransactionListSize(); + if (transactionListSize == 0) { + throw new LongAhException(ExceptionMessage.NO_TRANSACTION_FOUND); + } + int index = 1; + String outString = ""; + for (Transaction transaction : this.transactions) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + index++; + } + return outString.trim(); + } + + /** + * Printout the list of transactions which the member name is involved as the + * transaction lender + * + * @param lenderName User input containing the name of person to search for + * @param members The member list to search for the name in + * @return Returns a String printout of the required list of transactions + */ + public String findLender(String lenderName, MemberList members) throws LongAhException { + if (!members.isMember(lenderName)) { + throw new LongAhException(ExceptionMessage.MEMBER_NOT_FOUND); + } + int index = 1; + int printCount = 0; + String outString = String.format("%s is a lender in the following list of transaction(s).", lenderName) + "\n"; + for (Transaction transaction : this.transactions) { + if (transaction.checkIsLender(lenderName)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + index++; + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + return outString.trim(); + } + + /** + * Printout the list of transactions which the member name is involved as the + * transaction lender + * + * @param borrowerName User input containing the name of person to search for + * @param members The member list to search for the name in + * @return Returns a String printout of the required list of transactions + */ + public String findBorrower(String borrowerName, MemberList members) throws LongAhException { + if (!members.isMember(borrowerName)) { + throw new LongAhException(ExceptionMessage.MEMBER_NOT_FOUND); + } + int index = 1; + int printCount = 0; + String outString = + String.format("%s is a borrower in the following list of transaction(s).", borrowerName) + "\n"; + for (Transaction transaction : this.transactions) { + if (transaction.checkIsBorrower(borrowerName)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + index++; + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + return outString.trim(); + } + + /** + * Printout the list of transactions which the member name is involved as the + * transaction lender + * + * @param name User input containing the name of person to search for + * @param members The member list to search for the name in + * @return Returns a String printout of the required list of transactions + */ + public String findTransactions(String name, MemberList members) throws LongAhException { + if (!members.isMember(name)) { + throw new LongAhException(ExceptionMessage.MEMBER_NOT_FOUND); + } + int index = 1; + int printCount = 0; + String outString = String.format("%s is a part of the following list of transaction(s).", name) + "\n"; + for (Transaction transaction : this.transactions) { + if (transaction.isInvolved(name)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + index++; + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + return outString.trim(); + } + + //@@author FeathersRe + /** + * Filters and return the list of transactions matching the input transaction time + * + * @param dateTime String expression of the date time to filter + * @return String representation of the list of transactions matching the input transaction time + * @throws LongAhException If there are no matching transactions + */ + public String filterTransactionsEqualToDateTime(String dateTime) throws LongAhException { + DateTime dateTimeToCompare = new DateTime(dateTime); + int index = 0; + int printCount = 0; + String outString = "The following list of transactions matches with the time " + dateTimeToCompare + ".\n"; + for (Transaction transaction : this.transactions) { + try { + index++; + if (transaction.getTransactionTime().isEqual(dateTimeToCompare)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + } catch (NullPointerException e) { + // Skip the transaction if the transaction time is not set + continue; + } + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.NO_TRANSACTION_FOUND); + } + return outString.trim(); + } + + /** + * Filters and return the list of transactions before the input transaction time + * + * @param dateTime String expression of the date time to filter + * @return String representation of the list of transactions before the input transaction time + * @throws LongAhException If there are no matching transactions + */ + public String filterTransactionsBeforeDateTime(String dateTime) throws LongAhException { + DateTime dateTimeToCompare = new DateTime(dateTime); + int index = 0; + int printCount = 0; + String outString = "The following list of transactions is before the time " + dateTimeToCompare + ".\n"; + for (Transaction transaction : this.transactions) { + try { + index++; + if (transaction.getTransactionTime().isBefore(dateTimeToCompare)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + } catch (NullPointerException e) { + // Skip the transaction if the transaction time is not set + continue; + } + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.NO_TRANSACTION_FOUND); + } + return outString.trim(); + } + + /** + * Filters and return the list of transactions after the input transaction time + * + * @param dateTime String expression of the date time to filter + * @return String representation of the list of transactions before the input transaction time + * @throws LongAhException If there are no matching transactions + */ + public String filterTransactionsAfterDateTime(String dateTime) throws LongAhException { + DateTime dateTimeToCompare = new DateTime(dateTime); + int index = 0; + int printCount = 0; + String outString = "The following list of transactions is after the time " + dateTimeToCompare + ".\n"; + for (Transaction transaction : this.transactions) { + try { + index++; + if (transaction.getTransactionTime().isAfter(dateTimeToCompare)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + } catch (NullPointerException e) { + // Skip the transaction if the transaction time is not set + continue; + } + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.NO_TRANSACTION_FOUND); + } + return outString.trim(); + } + + /** + * Filters and return the list of transactions between two date times + * + * @param fromDateTime String expression of the earlier bound of the date time period to filter + * @param toDateTime String expression of the later bound of the date time to filter + * @return String representation of the list of transactions before the input transaction time + * @throws LongAhException If the date time filter is invalid or there is no transaction found + */ + public String filterTransactionsBetweenDateTime(String fromDateTime, String toDateTime) throws LongAhException { + DateTime fromDateTimeToCompare = new DateTime(fromDateTime); + DateTime toDateTimeToCompare = new DateTime(toDateTime); + if (toDateTimeToCompare.isBefore(fromDateTimeToCompare)) { + throw new LongAhException(ExceptionMessage.INVALID_DATE_TIME_FILTER); + } + int index = 0; + int printCount = 0; + String outString = "The following list of transactions is between the time " + fromDateTimeToCompare + + " and " + toDateTimeToCompare + ".\n"; + for (Transaction transaction : this.transactions) { + try { + index++; + if (transaction.getTransactionTime().isAfter(fromDateTimeToCompare) && + transaction.getTransactionTime().isBefore(toDateTimeToCompare)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + } catch (NullPointerException e) { + // Skip the transaction if the transaction time is not set + continue; + } + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.NO_TRANSACTION_FOUND); + } + return outString.trim(); + } + //@@author + + /** + * Edits a transaction from the list by index with new expression. + * + * @param expression The new expression to edit the transaction with. + * @param memberList The member list to edit the transaction with. + * @throws LongAhException If the index is invalid or if the edit input is in an invalid format. + */ + public void editTransactionList(String expression, MemberList memberList) throws LongAhException { + String[] indexTransactionSplice = expression.split(" ", 2); + if (indexTransactionSplice.length != 2) { + throw new LongAhException(ExceptionMessage.INVALID_EDIT_COMMAND); + } + + try { + int index = Integer.parseInt(indexTransactionSplice[0]) - 1; + if (index < 0 || index >= transactions.size()) { + throw new LongAhException(ExceptionMessage.INVALID_INDEX); + } + transactions.get(index).editTransaction(indexTransactionSplice[1], memberList); + UI.showMessage("Transaction #" + (index + 1) + " edited successfully."); + UI.showMessage(transactions.get(index).toString()); + } catch (NumberFormatException e) { + throw new LongAhException(ExceptionMessage.INVALID_INDEX); + } + } + + /** + * Printout the list of transactions which a person is involved as a borrower + * + * @param borrowerName containing the String representation of the name of person to search for + * @return Returns a String printout of the required list of transactions + */ + public String findDebts(String borrowerName) throws LongAhException { + String outString = String.format("%s is involved as the payee in the following list of transactions." + , borrowerName) + "\n"; + int index = 1; + int printCount = 0; + for (Transaction transaction : this.transactions) { + if (transaction.checkIsBorrower(borrowerName)) { + outString = outString + String.format("%d.\n%s", index, transaction) + "\n"; + printCount++; + } + index++; + } + if (printCount == 0) { + throw new LongAhException(ExceptionMessage.TRANSACTIONS_SUMMED_UP); + } + return outString.trim(); + } + + /** + * Deletes a member from all transactions in the list. + * + * @param name The name of the member to delete. + * @param members The list of members to delete from. + * @throws LongAhException If the member is not found in the list. + */ + public void deleteMember(String name, MemberList members) throws LongAhException { + Member member = members.getMember(name); + int size = transactions.size(); + for (int i = 0; i < size; i++) { + boolean isDiscard = transactions.get(i).deleteMember(member); + if (isDiscard) { + transactions.remove(i); + size--; + i--; + } + } + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/longah/LongAhTest.java similarity index 82% rename from src/test/java/seedu/duke/DukeTest.java rename to src/test/java/longah/LongAhTest.java index 2dda5fd651..3679c00154 100644 --- a/src/test/java/seedu/duke/DukeTest.java +++ b/src/test/java/longah/LongAhTest.java @@ -1,10 +1,10 @@ -package seedu.duke; +package longah; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -class DukeTest { +class LongAhTest { @Test public void sampleTest() { assertTrue(true); diff --git a/src/test/java/longah/handler/InputHandlerTest.java b/src/test/java/longah/handler/InputHandlerTest.java new file mode 100644 index 0000000000..5d888bbf79 --- /dev/null +++ b/src/test/java/longah/handler/InputHandlerTest.java @@ -0,0 +1,23 @@ +package longah.handler; + +import longah.exception.ExceptionMessage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + +public class InputHandlerTest { + /** + * Tests the parseInput method in InputHandler class with invalid input. + */ + @Test + public void parseInput_invalidCommand_exceptionThrown() { + try { + InputHandler.parseInput(""); + fail(); + } catch (Exception e) { + String expected = ExceptionMessage.INVALID_COMMAND.getMessage(); + assertEquals(expected, e.getMessage()); + } + } +} diff --git a/src/test/java/longah/handler/PINHandlerTest.java b/src/test/java/longah/handler/PINHandlerTest.java new file mode 100644 index 0000000000..d4506e323c --- /dev/null +++ b/src/test/java/longah/handler/PINHandlerTest.java @@ -0,0 +1,131 @@ +package longah.handler; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class PINHandlerTest { + + /** + * Tests the successful file creation when the PINHandler is constructed. + */ + @Test + public void pinHandlerConstructor_fileCreationSuccess() { + try { + File f = new File("./data/pin.txt"); + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + new PINHandler(); + assertTrue(f.exists()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful creation of a PIN with a valid PIN entered. + */ + @Test + public void createPin_validPIN_success() { + try { + File f = new File("./data/pin.txt"); + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + new PINHandler(); + assertTrue(f.exists()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful authentication of a PIN with a valid entered PIN. + */ + @Test + public void authenticate_validPIN_success() { + try { + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + new PINHandler(); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hashedPin = md.digest("123456".getBytes(StandardCharsets.UTF_8)); + String hashedEnteredPinHex = new BigInteger(1, hashedPin).toString(16); + assertEquals((hashedEnteredPinHex), PINHandler.getSavedPin()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful authentication of a PIN with an invalid entered PIN. + */ + @Test + public void authenticate_invalidPIN_authenticateFailure() { + try { + System.setIn(new ByteArrayInputStream("1234567\n".getBytes(StandardCharsets.UTF_8))); + new PINHandler(); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hashedPin = md.digest("1234567\n".getBytes(StandardCharsets.UTF_8)); + String hashedEnteredPinHex = new BigInteger(1, hashedPin).toString(16); + assertNotEquals((hashedEnteredPinHex), PINHandler.getSavedPin()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful creation of a PIN with an invalid entered PIN. + */ + @Test + public void createPin_invalidPIN_failure() { + try { + System.setIn(new ByteArrayInputStream("1234567\n1234567\n".getBytes(StandardCharsets.UTF_8))); + new PINHandler(); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hashedPin = md.digest("1234567\n".getBytes(StandardCharsets.UTF_8)); + String hashedEnteredPinHex = new BigInteger(1, hashedPin).toString(16); + assertNotEquals((hashedEnteredPinHex), PINHandler.getSavedPin()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful enabling of PIN authentication with valid login. + */ + @Test + public void enablePin_validPIN_success() { + try { + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + new PINHandler(); + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + PINHandler.enablePin(); + assertTrue(PINHandler.getAuthenticationStatus()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful disabling of PIN authentication with valid login. + */ + @Test + public void disablePin_validPIN_success() { + try { + PINHandler.enablePin(); + System.setIn(new ByteArrayInputStream("123456\n".getBytes(StandardCharsets.UTF_8))); + PINHandler.disablePin(); + assertTrue(!PINHandler.getAuthenticationStatus()); + } catch (Exception e) { + fail(); + } + } +} diff --git a/src/test/java/longah/handler/StorageHandlerTest.java b/src/test/java/longah/handler/StorageHandlerTest.java new file mode 100644 index 0000000000..04165d8a3f --- /dev/null +++ b/src/test/java/longah/handler/StorageHandlerTest.java @@ -0,0 +1,123 @@ +package longah.handler; + +import org.junit.jupiter.api.Test; + +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import java.io.File; +import java.io.FileWriter; + +public class StorageHandlerTest { + /** + * Tests the successful file creation when the StorageHandler is constructed. + */ + @Test + public void storageHandlerConstructor_fileCreationSuccess() { + try { + File f = new File("./data/test_grp1"); + File g; + StorageHandler.deleteDir(f); + MemberList members = new MemberList(); + TransactionList transactions = new TransactionList(); + new StorageHandler(members, transactions, "test_grp1"); + g = new File("./data/test_grp1/members.txt"); + assertTrue(g.exists()); + g = new File("./data/test_grp1/transactions.txt"); + assertTrue(g.exists()); + // Delete test folders after completion + StorageHandler.deleteDir(f); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful loading of members data from the file. + */ + @Test + public void loadMembersData_dataLoaded_success() { + try { + File f = new File("./data/test_grp2"); + StorageHandler.deleteDir(f); + MemberList members1 = new MemberList(); + TransactionList transactions1 = new TransactionList(); + StorageHandler storage1 = new StorageHandler(members1, transactions1, "test_grp2"); + members1.addMember("Alice", 10); + members1.addMember("Bob", -10); + storage1.saveMembersData(); + MemberList members2 = new MemberList(); + TransactionList transactions2 = new TransactionList(); + new StorageHandler(members2, transactions2, "test_grp2"); + String expected = "Alice: $10.00\nBob: -$10.00"; + assertEquals(expected, members2.listMembers()); + // Delete test folders after completion + StorageHandler.deleteDir(f); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful loading of transactions data from the file. + */ + @Test + public void loadMembersData_invalidMembersData_exceptionThrown() { + File f = new File("./data/test_grp3"); + try { + StorageHandler.deleteDir(f); + MemberList members1 = new MemberList(); + TransactionList transactions1 = new TransactionList(); + StorageHandler storage1 = new StorageHandler(members1, transactions1, "test_grp3"); + members1.addMember("Alice", 10); + storage1.saveMembersData(); + MemberList members2 = new MemberList(); + TransactionList transactions2 = new TransactionList(); + new StorageHandler(members2, transactions2, "test_grp3"); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.STORAGE_FILE_CORRUPTED); + assertTrue(isMessage); + // Delete test folders after completion + f = new File("./data/test_grp3"); + StorageHandler.deleteDir(f); + } + } + + /** + * Tests the unsuccessful loading of transactions data from the file. + */ + @Test + public void loadMembersData_invalidTransactionData_exceptionThrown() { + File f = new File("./data/test_grp4"); + File g; + try { + StorageHandler.deleteDir(f); + MemberList members1 = new MemberList(); + TransactionList transactions1 = new TransactionList(); + new StorageHandler(members1, transactions1, "test_grp4"); + g = new File("./data/test_grp4/transactions.txt"); + FileWriter fw = new FileWriter(g); + fw.write("LOREM IPSUM"); + fw.close(); + MemberList members2 = new MemberList(); + TransactionList transactions2 = new TransactionList(); + new StorageHandler(members2, transactions2, "test_grp4"); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.INVALID_STORAGE_CONTENT); + assertTrue(isMessage); + // Delete test folders after completion + f = new File("./data/test_grp4"); + StorageHandler.deleteDir(f); + } catch (Exception e) { + // Filewriter error + fail(); + } + } +} diff --git a/src/test/java/longah/node/GroupTest.java b/src/test/java/longah/node/GroupTest.java new file mode 100644 index 0000000000..69a5daa33f --- /dev/null +++ b/src/test/java/longah/node/GroupTest.java @@ -0,0 +1,150 @@ +package longah.node; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +import longah.exception.ExceptionMessage; +import longah.handler.StorageHandler; +import longah.util.MemberList; +import longah.util.TransactionList; + +public class GroupTest { + /** + * Tests the successful file creation when the StorageHandler is constructed. + */ + @Test + public void groupConstructor_validName_success() { + try { + Group group = new Group("TestGroup1"); + assertEquals("TestGroup1", group.getGroupName()); + File f = new File("./data/TestGroup1"); + StorageHandler.deleteDir(f); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the exception thrown when an invalid group name is provided. + */ + @Test + public void groupConstructor_invalidName_exceptionThrown() { + try { + new Group("TestGroup1!"); + fail(); + } catch (Exception e) { + assertEquals(ExceptionMessage.INVALID_GROUP_NAME.getMessage(), e.getMessage()); + } + } + + /** + * Tests the exception thrown when a group name exceeds the character limit. + */ + @Test + public void groupConstructor_nameExceedsLimit_exceptionThrown() { + try { + new Group("TestGroup1TestGroup1TestGroup1TestGroup1TestGroup1TestGroup1TestGroup1TestGroup1TestGroup1"); + fail(); + } catch (Exception e) { + assertEquals(ExceptionMessage.CHAR_LIMIT_EXCEEDED.getMessage(), e.getMessage()); + } + } + + /** + * Tests the successful execution of settle up + */ + @Test + public void settleUp_valid_success() { + try { + Group group = new Group("TestGroup1"); + MemberList members = group.getMemberList(); + TransactionList transactions = group.getTransactionList(); + members.addMember("Alice"); + members.addMember("Bob"); + String expression = "Alice p/Bob a/10"; + transactions.addTransaction(expression, members, group); + group.settleUp("Bob"); + assertEquals("Alice: $0.00\nBob: $0.00", members.listMembers()); + File f = new File("./data/TestGroup1"); + StorageHandler.deleteDir(f); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests successful listing of all debts + */ + @Test + public void listDebts_valid_success() { + try { + Group group = new Group("TestGroup1"); + MemberList members = group.getMemberList(); + TransactionList transactions = group.getTransactionList(); + members.addMember("Alice"); + members.addMember("Bob"); + String expression = "Alice p/Bob a/10"; + transactions.addTransaction(expression, members, group); + assertEquals("Best Way to Solve Debts:\nBob owes Alice $10.00", group.listDebts()); + File f = new File("./data/TestGroup1"); + StorageHandler.deleteDir(f); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the exception thrown when there are no debts to be solved + */ + @Test + public void listDebts_noDebts_exceptionThrown() { + try { + Group group = new Group("TestGroup1"); + group.listDebts(); + fail(); + } catch (Exception e) { + assertEquals(ExceptionMessage.TRANSACTIONS_SUMMED_UP.getMessage(), e.getMessage()); + } + } + + /** + * Tests the successful execution of listing an individual's debts + */ + @Test + public void listIndivDebt_valid_success() { + try { + Group group = new Group("TestGroup1"); + MemberList members = group.getMemberList(); + TransactionList transactions = group.getTransactionList(); + members.addMember("Alice"); + members.addMember("Bob"); + String expression = "Alice p/Bob a/10"; + transactions.addTransaction(expression, members, group); + assertEquals("Bob owes Alice $10.00", group.listIndivDebt("Alice")); + File f = new File("./data/TestGroup1"); + StorageHandler.deleteDir(f); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the exception thrown when the individual has no debts + */ + @Test + public void listIndivDebt_noDebts_exceptionThrown() { + try { + Group group = new Group("TestGroup1"); + MemberList members = group.getMemberList(); + members.addMember("Alice"); + group.listIndivDebt("Alice"); + fail(); + } catch (Exception e) { + assertEquals(ExceptionMessage.TRANSACTIONS_SUMMED_UP.getMessage(), e.getMessage()); + } + } +} diff --git a/src/test/java/longah/node/MemberTest.java b/src/test/java/longah/node/MemberTest.java new file mode 100644 index 0000000000..be457f4429 --- /dev/null +++ b/src/test/java/longah/node/MemberTest.java @@ -0,0 +1,208 @@ +package longah.node; + +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + + +public class MemberTest { + /** + * Tests the successful creation of a member with a valid name. + */ + @Test + public void memberConstructor_validName_success() { + try { + Member member = new Member("Alice"); + assertEquals("Alice", member.getName()); + assertEquals(0.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful creation of a member with a valid name and balance. + */ + @Test + public void memberConstructor_validNameAndBalance_success() { + try { + Member member = new Member("Alice", 10); + assertEquals("Alice", member.getName()); + assertEquals(10.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful creation of a member with an invalid name. + */ + @Test + public void memberConstructor_invalidName_exceptionThrown() { + try { + new Member("Alice123-"); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.INVALID_MEMBER_NAME); + assertTrue(isMessage); + } + } + + /** + * Tests the unsuccessful creation of a member with an empty name. + */ + @Test + public void memberConstructor_emptyName_exceptionThrown() { + try { + new Member(""); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.INVALID_MEMBER_NAME); + assertTrue(isMessage); + } + } + + /** + * Tests the unsuccessful creation of a member with a name exceeding the character limit. + */ + @Test + public void memberConstructor_nameExceedsLimit_exceptionThrown() { + try { + new Member("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + fail(); + } catch (Exception e) { + boolean isMessage = LongAhException.isMessage((LongAhException) e, ExceptionMessage.CHAR_LIMIT_EXCEEDED); + assertTrue(isMessage); + } + } + + /** + * Tests the constructor of the Member class with valid name and balance. + */ + @Test + public void addToBalance_validAdd_success() { + try { + Member member = new Member("Alice"); + member.addToBalance(10.0); + assertEquals("Alice", member.getName()); + assertEquals(10.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the addToBalance method of the Member class with invalid added balance. + */ + @Test + public void addToBalance_negativeValue_exceptionThrown() { + try { + Member member = new Member("Bob"); + member.addToBalance(-20.0); + fail(); + } catch (Exception e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the subtractFromBalance method of the Member class with valid subtracted balance. + */ + @Test + public void subtractFromBalance_validSubtract_success() { + try { + Member member = new Member("Alice"); + member.addToBalance(10.0); + member.subtractFromBalance(5.0); + assertEquals(5.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the subtractFromBalance method of the Member class with invalid subtracted balance. + */ + @Test + public void subtractFromBalance_negativeValue_exceptionThrown() { + try { + Member member = new Member("Alice"); + member.subtractFromBalance(-20.0); + fail(); + } catch (Exception e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the subtractFromBalance method of the Member class with boundary values. + */ + @Test + public void subtractFromBalance_boundaryValues_success() { + try { + Member member = new Member("Bob"); + member.addToBalance(Double.MAX_VALUE); + member.subtractFromBalance(Double.MAX_VALUE); + assertEquals(0.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the subtractFromBalance method of the Member class with concurrent transactions. + */ + @Test + public void subtractFromBalance_concurrentTransactions_success() { + try { + Member member = new Member("Alice"); + Thread t1 = new Thread(() -> { + for (int i = 0; i < 1000; i++) { + try { + member.addToBalance(1.0); + } catch (LongAhException e) { + throw new RuntimeException(e); + } + } + }); + Thread t2 = new Thread(() -> { + for (int i = 0; i < 1000; i++) { + try { + member.subtractFromBalance(1.0); + } catch (LongAhException e) { + throw new RuntimeException(e); + } + } + }); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertEquals(0.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful clearing of the balance of a member. + */ + @Test + public void clearBalance_validClear_success() { + try { + Member member = new Member("Alice"); + member.addToBalance(10.0); + member.clearBalance(); + assertEquals(0.0, member.getBalance()); + } catch (Exception e) { + fail(); + } + } +} diff --git a/src/test/java/longah/node/TransactionTest.java b/src/test/java/longah/node/TransactionTest.java new file mode 100644 index 0000000000..41d3bdb165 --- /dev/null +++ b/src/test/java/longah/node/TransactionTest.java @@ -0,0 +1,229 @@ +package longah.node; + +import longah.util.MemberList; +import longah.util.TransactionList; +import longah.exception.LongAhException; +import longah.handler.StorageHandler; +import longah.exception.ExceptionMessage; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; + + +public class TransactionTest { + /** + * Tests the successful creation of a transaction with balances correctly updated. + */ + @Test + public void transactionConstructor_transaction_success() { + try { + Group group = new Group("testGroup"); + MemberList memberList = group.getMemberList(); + TransactionList transactionList = group.getTransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + Transaction transaction = new Transaction("Alice p/Bob a/5", memberList); + transactionList.addTransaction(transaction); + group.updateTransactionSolution(); + Member lender = transaction.getLender(); + assertEquals("Alice", lender.getName()); + assertEquals(5.0, lender.getBalance()); + Member borrower = memberList.getMember("Bob"); + assertEquals(-5.0, borrower.getBalance()); + StorageHandler.deleteDir(new File("./data/testGroup")); + } catch (LongAhException e) { + fail(); + } + } + + /** + * Tests the unsuccessful creation of a transaction with < 2 persons involved. + */ + @Test + public void transactionConstructor_invalidFormat_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + new Transaction("Alice a/5", memberList); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_FORMAT.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the unsuccessful creation of a transaction due to overflow + */ + @Test + public void transactionConstructor_overflowAmount_exceptionThrown() { + try { + Group group = new Group("testGroup"); + MemberList memberList = group.getMemberList(); + TransactionList transactionList = group.getTransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + transactionList.addTransaction("Alice p/Bob a/1e309", memberList, group); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.BALANCE_OVERFLOW.getMessage(); + assertEquals(expectedString, e.getMessage()); + StorageHandler.deleteDir(new File("./data/testGroup")); + } + } + + /** + * Tests the unsuccessful creation of a transaction with invalid arguments. + */ + @Test + public void transactionConstructor_invalidArguments_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + new Transaction("Alice p/Bob a/c", memberList); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the unsuccessful creation of a transaction with an incorrect decimal format. + */ + @Test + public void transactionConstructor_incorrectDecimalFormat_exceptionThrown() { + try { + Group group = new Group("testGroup"); + MemberList memberList = group.getMemberList(); + TransactionList transactionList = group.getTransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + transactionList.addTransaction("Alice p/Bob a/1.1.1", memberList, group); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + StorageHandler.deleteDir(new File("./data/testGroup")); + } + } + + /** + * Tests the unsuccessful creation of a transaction with an incorrect decimal point count. + */ + @Test + public void transactionConstructor_incorrectDecimalPoint_exceptionThrown() { + try { + Group group = new Group("testGroup"); + MemberList memberList = group.getMemberList(); + TransactionList transactionList = group.getTransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + transactionList.addTransaction("Alice p/Bob a/1.1111", memberList, group); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + StorageHandler.deleteDir(new File("./data/testGroup")); + } + } + + /** + * Tests the unsuccessful creation of a transaction with invalid Date Time format. + */ + @Test + public void addTransactionTime_invalidTimeFormat_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + new Transaction("Jack t/2359 p/Jane a/200", memberList); + fail(); + } catch (LongAhException e) { + String expected = ExceptionMessage.INVALID_TIME_FORMAT.getMessage(); + assertEquals(expected, e.getMessage()); + } + } + + /** + * Tests the unsuccessful creation of a transaction with an invalid command to denote amount. + */ + @Test + public void addBorrower_invalidAmountCommand_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + Member lender = memberList.getMember("Alice"); + Transaction transaction = new Transaction("Alice p/Bob a/5", memberList); + transaction.addBorrower("Bob b/5", memberList, lender); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_FORMAT.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the unsuccessful creation of a transaction with an amount value specified in words + * rather than a double value. + */ + @Test + public void addBorrower_invalidAmountValue_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + Member lender = memberList.getMember("Alice"); + Transaction transaction = new Transaction("Alice p/Bob a/5", memberList); + transaction.addBorrower("Bob a/five", memberList, lender); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the unsuccessful creation of a transaction with a negative amount + * value for person that owes. + */ + @Test + public void addBorrower_negativeAmountValue_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + Member lender = memberList.getMember("Alice"); + Transaction transaction = new Transaction("Alice p/Bob a/5", memberList); + transaction.addBorrower("Bob a/-5", memberList, lender); + fail(); + } catch (LongAhException e) { + String expectedString = ExceptionMessage.INVALID_TRANSACTION_VALUE.getMessage(); + assertEquals(expectedString, e.getMessage()); + } + } + + /** + * Tests the successful checking of whether a person is involved in a transaction. + */ + @Test + public void isInvolved_validInput_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + Transaction transaction = new Transaction("Alice p/Bob a/5", memberList); + assertEquals(true, transaction.isInvolved("Alice")); + assertEquals(true, transaction.isInvolved("Bob")); + assertEquals(false, transaction.isInvolved("Charlie")); + } catch (LongAhException e) { + fail(); + } + } +} diff --git a/src/test/java/longah/util/ChartTest.java b/src/test/java/longah/util/ChartTest.java new file mode 100644 index 0000000000..803e3ef60a --- /dev/null +++ b/src/test/java/longah/util/ChartTest.java @@ -0,0 +1,146 @@ +package longah.util; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class ChartTest { + /** + * Tests the successful creation of a bar chart with valid X and Y axis inputs. + */ + @Test + public void viewBalancesBarChart_validInput_success() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(10.00, -5.00, 0.00); + Chart chart = Chart.viewBalancesBarChart(members, balances); + + assert chart != null; + assertEquals(chart.getClass(), Chart.class); + assertEquals(chart instanceof Chart, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Tests the unsuccessful creation of a bar chart with invalid X and Y axis inputs. + */ + @Test + public void viewBalancesBarChart_invalidInput_exceptionThrown() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(10.00, -5.00); + Chart.viewBalancesBarChart(members, balances); + fail(); + } catch (Exception e) { + assertEquals("X and Y-Axis sizes are not the same!!!", e.getMessage()); + } + } + + /** + * Tests the successful creation of a bar chart with MIN_VALUE balances. + */ + @Test + public void viewBalancesBarChart_minValue_success() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); + Chart chart = Chart.viewBalancesBarChart(members, balances); + + assert chart != null; + assertEquals(chart.getClass(), Chart.class); + assertEquals(chart instanceof Chart, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Tests the successful creation of a bar chart with NaN balances. + */ + @Test + public void viewBalancesBarChart_nan_success() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(Double.NaN, Double.NaN, Double.NaN); + Chart chart = Chart.viewBalancesBarChart(members, balances); + + assert chart != null; + assertEquals(chart.getClass(), Chart.class); + assertEquals(chart instanceof Chart, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Tests the unsuccessful creation of a bar chart with no members. + */ + @Test + public void viewBalancesBarChart_noMembers_exceptionThrown() { + try { + List members = Arrays.asList(); + List balances = Arrays.asList(10.00, -5.00, 0.00); + Chart.viewBalancesBarChart(members, balances); + fail(); + } catch (Exception e) { + assertEquals("X-Axis data cannot be empty!!!", e.getMessage()); + } + } + + /** + * Tests the unsuccessful creation of a bar chart with no balances. + */ + @Test + public void viewBalancesBarChart_noBalances_exceptionThrown() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(); + Chart.viewBalancesBarChart(members, balances); + fail(); + } catch (Exception e) { + assertEquals("Y-Axis data cannot be empty!!!", e.getMessage()); + } + } + + /** + * Tests the successful creation of a bar chart with negative balances. + */ + @Test + public void viewBalancesBarChart_negativeBalances_success() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(-10.00, -20.00, -30.00); + Chart chart = Chart.viewBalancesBarChart(members, balances); + + assert chart != null; + assertEquals(chart.getClass(), Chart.class); + assertEquals(chart instanceof Chart, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Tests the successful creation of a bar chart with zero balances. + */ + @Test + public void viewBalancesBarChart_zeroBalances_success() { + try { + List members = Arrays.asList("Alice", "Bob", "Charlie"); + List balances = Arrays.asList(0.00, 0.00, 0.00); + Chart chart = Chart.viewBalancesBarChart(members, balances); + + assert chart != null; + assertEquals(chart.getClass(), Chart.class); + assertEquals(chart instanceof Chart, true); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/longah/util/MemberListTest.java b/src/test/java/longah/util/MemberListTest.java new file mode 100644 index 0000000000..42dc197d97 --- /dev/null +++ b/src/test/java/longah/util/MemberListTest.java @@ -0,0 +1,244 @@ +package longah.util; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +public class MemberListTest { + /** + * Tests the successful addition of a member to the list. + */ + @Test + public void addMember_validName_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + assertEquals(1, memberList.getMemberListSize()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful addition of a member to the list when the name is repeated. + */ + @Test + public void addMember_duplicateName_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Alice"); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.DUPLICATE_MEMBER); + assertTrue(isMessage); + } + } + + /** + * Tests checking of a valid name in the member list. + */ + @Test + public void isMember_validName_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + assertEquals(true, memberList.isMember("Alice")); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests checking of an invalid name in the member list. + */ + @Test + public void isMember_invalidName_failure() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + assertEquals(false, memberList.isMember("Bob")); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful addition of a member to the list. + */ + @Test + public void listMembers_hasMembers_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + String expected = "Alice: $0.00\nBob: $0.00"; + assertEquals(expected,memberList.listMembers()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful listing of members when there are none stored in the system. + */ + @Test + public void listMembers_noMembers_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.listMembers(); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.NO_MEMBERS_FOUND); + assertTrue(isMessage); + } + } + + /** + * Tests the successful addition of a member to the list. + */ + @Test + public void getMemberListSize_validMembers_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + assertEquals(2, memberList.getMemberListSize()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful computation of multiple transactions. + */ + @Test + public void updateMembersBalance_validTransaction_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + TransactionList transactionList = new TransactionList(); + transactionList.addTransaction("Alice p/Bob a/5", memberList); + transactionList.addTransaction("Bob p/Alice a/10", memberList); + memberList.updateMembersBalance(transactionList); + String expected = "Alice: -$5.00\nBob: $5.00"; + assertEquals(expected, memberList.listMembers()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful computation of no transactions. + */ + @Test + public void updateMembersBalance_noTransactions_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + TransactionList transactionList = new TransactionList(); + memberList.updateMembersBalance(transactionList); + String expected = "Alice: $0.00\nBob: $0.00"; + assertEquals(expected, memberList.listMembers()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful computation of multiple members in the group. + */ + @Test + public void updateMembersBalance_multipleMembers_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + memberList.addMember("Charlie"); + TransactionList transactionList = new TransactionList(); + transactionList.addTransaction("Alice p/Bob a/5", memberList); + transactionList.addTransaction("Bob p/Charlie a/10", memberList); + transactionList.addTransaction("Charlie p/Alice a/15", memberList); + memberList.updateMembersBalance(transactionList); + String expected = "Alice: -$10.00\nBob: $5.00\nCharlie: $5.00"; + assertEquals(expected, memberList.listMembers()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the successful edit of name of a member in the group. + */ + @Test + public void editMemberName_validCommand_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice", 5); + String expected = "Alice: $5.00"; + assertEquals(expected, memberList.listMembers()); + memberList.editMemberName("Alice", "Bob"); + expected = "Bob: $5.00"; + assertEquals(expected, memberList.listMembers()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful edit of name of a member in the group when the index is invalid. + */ + @Test + public void editMemberName_invalidMemberName_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice", 5); + memberList.editMemberName("Charlie", "Bob"); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.MEMBER_NOT_FOUND); + assertTrue(isMessage); + } + } + + /** + * Tests the successful deletion of a member in the group. + * Balance should not be updated at this point as updating is performed after commands are invoked. + */ + @Test + public void deleteMember_validName_success() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice", 5); + memberList.addMember("Bob", 10); + memberList.deleteMember("Alice"); + String expected = "Bob: $10.00"; + assertEquals(expected, memberList.listMembers()); + } catch (Exception e) { + fail(); + } + } + + /** + * Tests the unsuccessful deletion of a member in the group when the name is invalid. + */ + @Test + public void deleteMember_invalidName_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + memberList.addMember("Alice", 5); + memberList.addMember("Bob", 10); + memberList.deleteMember("Charlie"); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.MEMBER_NOT_FOUND); + assertTrue(isMessage); + } + } +} diff --git a/src/test/java/longah/util/TransactionListTest.java b/src/test/java/longah/util/TransactionListTest.java new file mode 100644 index 0000000000..d9762a4a31 --- /dev/null +++ b/src/test/java/longah/util/TransactionListTest.java @@ -0,0 +1,410 @@ +package longah.util; + +import org.junit.jupiter.api.Test; + +import longah.exception.LongAhException; +import longah.exception.ExceptionMessage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TransactionListTest { + /** + * Tests the successful removal of a transaction from the list by index. + */ + @Test + public void remove_validIndex_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + + transactionList.addTransaction("Alice p/Bob a/5", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String[] parts = "remove 1".split(" ", 2); + transactionList.remove(parts[1]); + assertEquals(0, transactionList.getTransactionListSize()); + + } catch (LongAhException e) { + fail(); + } + } + + /** + * Tests the unsuccessful removal of a transaction from the list by an invalid index. + */ + @Test + public void remove_invalidIndex_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + + transactionList.addTransaction("Alice p/Bob a/5", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String[] parts = "remove -1".split(" ", 2); + transactionList.remove(parts[1]); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.INVALID_INDEX); + assertTrue(isMessage); + } + } + + /** + * Tests the listing of transactions when there are none stored in the system + */ + @Test + public void list_noTransactions_success() throws LongAhException { + try { + TransactionList transactionList = new TransactionList(); + transactionList.listTransactions(); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.NO_TRANSACTION_FOUND); + assertTrue(isMessage); + } + } + + /** + * Tests the listing of transactions when multiple entries are stored in the system + */ + @Test + public void list_multiTransactions_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + memberList.addMember("James"); + + transactionList.addTransaction("Jack p/Jane a/100 p/James a/200", memberList); + transactionList.addTransaction("Jane p/Jack a/150 p/James a/250", memberList); + String printedOutput = transactionList.listTransactions(); + + assertTrue(printedOutput.contains("Lender: Jack")); + assertTrue(printedOutput.contains("Jane Owed amount: $100.00")); + assertTrue(printedOutput.contains("James Owed amount: $200.00")); + assertTrue(printedOutput.contains("Lender: Jane")); + assertTrue(printedOutput.contains("Jack Owed amount: $150.00")); + assertTrue(printedOutput.contains("James Owed amount: $250.00")); + + } catch (LongAhException e) { + fail(); + } + } + + /** + * Tests the listing of transactions when the input member does not own any + */ + @Test + public void findTransaction_noTransactions_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + memberList.addMember("James"); + + String command = "findtransaction James"; + String[] parts = command.split(" ", 2); + transactionList.findTransactions(parts[1], memberList); + fail(); + + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.TRANSACTIONS_SUMMED_UP); + assertTrue(isMessage); + } + } + + /** + * Tests the listing of payments when the input member owns multiple entries + */ + @Test + public void findTransaction_multiTransactions_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + memberList.addMember("James"); + + transactionList.addTransaction("Jack p/James a/100 p/Jane a/200", memberList); + transactionList.addTransaction("Jack p/Jane a/150 p/James a/250", memberList); + String name = "Jack"; + String printedOutput = transactionList.findTransactions(name, memberList); + + assertTrue(printedOutput.contains("Jack is a part of the following list of transaction(s).")); + assertTrue(printedOutput.contains("Lender: Jack")); + assertTrue(printedOutput.contains("Jane Owed amount: $200.00")); + assertTrue(printedOutput.contains("James Owed amount: $100.00")); + assertTrue(printedOutput.contains("Lender: Jack")); + assertTrue(printedOutput.contains("Jane Owed amount: $150.00")); + assertTrue(printedOutput.contains("James Owed amount: $250.00")); + + } catch (LongAhException e) { + fail(); + } + } + + /** + * Tests the listing of debts when the input member does not owe any + */ + @Test + public void findDebt_noTransactions_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + memberList.addMember("James"); + + transactionList.addTransaction("Jack p/Jane a/200 p/James a/100", memberList); + transactionList.addTransaction("Jack p/Jane a/150 p/James a/200", memberList); + String[] parts = "finddebt Jack".split(" ", 2); + transactionList.findDebts(parts[1]); + fail(); + + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.TRANSACTIONS_SUMMED_UP); + assertTrue(isMessage); + } + } + + + /** + * Tests the listing of debts when the input member owes multiple entries + */ + @Test + public void findDebt_multiTransactions_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + memberList.addMember("James"); + + transactionList.addTransaction("Jack p/Jane a/200 p/James a/100", memberList); + transactionList.addTransaction("Jack p/Jane a/150 p/James a/200", memberList); + String command = "finddebt James"; + String[] parts = command.split(" ", 2); + String printedOutput = transactionList.findDebts(parts[1]); + + assertTrue(printedOutput.contains("Lender: Jack")); + assertTrue(printedOutput.contains("Jane Owed amount: $200.00")); + assertTrue(printedOutput.contains("James Owed amount: $100.00")); + assertTrue(printedOutput.contains("Lender: Jack")); + assertTrue(printedOutput.contains("Jane Owed amount: $150.00")); + assertTrue(printedOutput.contains("James Owed amount: $200.00")); + + } catch (LongAhException e) { + fail(); + } + } + + /** + * Tests the editing of a transaction in the list with a valid index and expression. + */ + @Test + public void editTransactionList_validIndexAndExpression_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + + transactionList.addTransaction("Alice p/Bob a/5", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String command = "1 Alice p/Bob a/10"; + transactionList.editTransactionList(command, memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String expectedString = "1.\nLender: Alice\nBorrower 1: Bob Owed amount: $10.00\n"; + assertEquals(expectedString.trim(), transactionList.listTransactions().trim()); + } catch (LongAhException e) { + fail(); + } + } + + /** + * Tests the editing of a transaction in the list with an invalid index. + */ + @Test + public void editTransactionList_invalidIndex_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + + transactionList.addTransaction("Alice p/Bob a/5", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String command = "-1 Alice p/Bob a/10"; + transactionList.editTransactionList(command, memberList); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.INVALID_INDEX); + assertTrue(isMessage); + } + } + + /** + * Tests the editing of a transaction in the list with an invalid member. + */ + @Test + public void editTransactionList_invalidPerson_exceptionThrown() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + + transactionList.addTransaction("Alice p/Bob a/5", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String command = "1 Alice p/Charlie a/10"; + transactionList.editTransactionList(command, memberList); + fail(); + } catch (LongAhException e) { + boolean isMessage = LongAhException.isMessage(e, ExceptionMessage.MEMBER_NOT_FOUND); + assertTrue(isMessage); + } + } + + /** + * Test the successful deletion of a member from the transaction list. + */ + @Test + public void deleteMember_validMember_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + memberList.addMember("Charlie"); + + transactionList.addTransaction("Alice p/Bob a/5 p/Charlie a/10", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + transactionList.deleteMember("Bob", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + String expected = "1.\nLender: Alice\nBorrower 1: Charlie Owed amount: $10.00\n"; + assertEquals(expected.trim(), transactionList.listTransactions().trim()); + } catch (LongAhException e) { + fail(); + } + } + + /** + * Test the successful deletion of a member from the transaction list. + * The delete command should invoke behvaiour to remove the transaction. + */ + @Test + public void deleteMember_transactionDeleted_exceptionThrown() { + try { + MemberList members = new MemberList(); + TransactionList transactions = new TransactionList(); + members.addMember("Alice"); + members.addMember("Bob"); + + transactions.addTransaction("Alice p/Bob a/5", members); + assertEquals(1, transactions.getTransactionListSize()); + transactions.deleteMember("Bob", members); + assertEquals(0, transactions.getTransactionListSize()); + transactions.listTransactions(); + fail(); + } catch (LongAhException e) { + String expected = ExceptionMessage.NO_TRANSACTION_FOUND.getMessage(); + assertEquals(expected, e.getMessage()); + } + } + + /** + * Test the successful deletion of a member from the transaction list. + * The delete command should invoke behvaiour to remove the transaction. + */ + @Test + public void deleteMember_deleteLender_exceptionThrown() { + try { + MemberList members = new MemberList(); + TransactionList transactions = new TransactionList(); + members.addMember("Alice"); + members.addMember("Bob"); + + transactions.addTransaction("Alice p/Bob a/5", members); + assertEquals(1, transactions.getTransactionListSize()); + transactions.deleteMember("Alice", members); + transactions.listTransactions(); + fail(); + } catch (LongAhException e) { + String expected = ExceptionMessage.NO_TRANSACTION_FOUND.getMessage(); + assertEquals(expected, e.getMessage()); + } + } + + /** + * Test the unsuccessful deletion of a member from the transaction list. + */ + @Test + public void deleteMember_memberNotFound_exceptionThrown() { + try { + MemberList members = new MemberList(); + TransactionList transactions = new TransactionList(); + members.addMember("Alice"); + members.addMember("Bob"); + + transactions.addTransaction("Alice p/Bob a/5", members); + assertEquals(1, transactions.getTransactionListSize()); + transactions.deleteMember("Charlie", members); + fail(); + } catch (LongAhException e) { + String expected = ExceptionMessage.MEMBER_NOT_FOUND.getMessage(); + assertEquals(expected, e.getMessage()); + } + } + + /** + * Test the successful addition and listing of transactions with transaction time + */ + @Test + public void list_transactionsWithTime_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Jack"); + memberList.addMember("Jane"); + transactionList.addTransaction("Jack t/29-11-2023 2359 p/Jane a/200", memberList); + + String printedOutput = transactionList.listTransactions(); + + assertTrue(printedOutput.contains("Lender: Jack")); + assertTrue(printedOutput.contains("Transaction time: 29-11-2023 2359")); + assertTrue(printedOutput.contains("Jane Owed amount: $200.00")); + } catch (LongAhException e) { + fail(); + } + } + + /** + * Test the successful clearing of the transaction list + */ + @Test + public void clear_valid_success() { + try { + MemberList memberList = new MemberList(); + TransactionList transactionList = new TransactionList(); + memberList.addMember("Alice"); + memberList.addMember("Bob"); + + transactionList.addTransaction("Alice p/Bob a/5", memberList); + assertEquals(1, transactionList.getTransactionListSize()); + transactionList.clear(memberList); + assertEquals(0, transactionList.getTransactionListSize()); + + String output = memberList.listMembers(); + assertEquals("Alice: $0.00\nBob: $0.00", output); + } catch (LongAhException e) { + fail(); + } + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT deleted file mode 100644 index 892cb6cae7..0000000000 --- a/text-ui-test/EXPECTED.TXT +++ /dev/null @@ -1,9 +0,0 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling diff --git a/text-ui-test/README.md b/text-ui-test/README.md new file mode 100644 index 0000000000..f9b5270167 --- /dev/null +++ b/text-ui-test/README.md @@ -0,0 +1,59 @@ +# Text UI Testing + +## Overview + +We perform comprehensive testing through text ui testing to ensure that output from all commands are as expected. Multiple files are input to simulate multiple uses of the application. Purpose of each application session can be found below. A series of tests will be run, of which all tests will need to be successful for the Text UI Test to be completed successfully. + +Chart command is not tested here due to its use of a GUI. + +## How To Use + +When running tests on a Windows system, run the following command from the specificied directory: +``` +./runtest.bat +``` + +When running tests on a UNIX-based system, run the following command from the specified directory: +``` +./runtest.sh +``` + +Warning: Text UI Testing has been configured to clear all past data records to simulate a fresh application starting when the above commands are invoked. This WILL result in loss of data from previous runs. + +## Test Files + +### Group 1 Files + +Consists of `input1.txt` and `EXPECTED1.TXT`. + +Testing Purpose: Successful execution of `help`, `list`, `add`, `find` and `edit` commands (cumulatively member-level commands). + +### Group 2 Files + +Consists of `input2.txt` and `EXPECTED2.TXT`. + +Testing Purpose: Unsuccessful execution of `help`, `list`, `add`, `find` and `edit` commands (cumulatively member-level commands). Does not include incorrect commands. Adds group and enables PIN for subsequent tests. + +### Group 3 Files + +Consists of `input3.txt` and `EXPECTED3.txt`. + +Testing Purpose: Successful execution of `filter`, `delete`, `clear`, `settle`, `group` and `pin` commands (cumulatively group-level and account-level commands). + +### Group 4 Files + +Consists of `input4.txt` and `EXPECTED4.txt`. + +Testing Purpose: Unsuccessful execution of `filter`, `delete`, `clear`, `settle`, `group` and `pin` commands (cumulatively group-level and account-level commands). Includes incorrect commands. + +### Group 5 Files + +Consists of `input5A.txt`, `input5B.txt`, `EXPECTED5A.TXT` and `EXPECTED5B.TXT`. + +Testing Purpose: Closure of application during startup sequence. 5A tests for closure during PIN authentication while 5B tests for closure during group creation. + +### Data Files + +Consists of `EXPECTED_GRPLIST.TXT`, `EXPECTED_MEMBER.TXT`, `EXPECTED_PIN.TXT` and `EXPECTED_TRANSACTION.TXT`. + +Testing Purpose: Successful execution of storage-related commands and saving of non-corrupt data to storage. \ No newline at end of file diff --git a/text-ui-test/actual_output/README.md b/text-ui-test/actual_output/README.md new file mode 100644 index 0000000000..b4e9a420e5 --- /dev/null +++ b/text-ui-test/actual_output/README.md @@ -0,0 +1,5 @@ +# Text UI Testing + +Actual outputs from text UI test runs are generated here. + +This file exists to ensure proper generation of the `actual_output` directory. \ No newline at end of file diff --git a/text-ui-test/expected_data/EXPECTED_GRPLIST.TXT b/text-ui-test/expected_data/EXPECTED_GRPLIST.TXT new file mode 100644 index 0000000000..8b66287e48 --- /dev/null +++ b/text-ui-test/expected_data/EXPECTED_GRPLIST.TXT @@ -0,0 +1 @@ +GroupA diff --git a/text-ui-test/expected_data/EXPECTED_MEMBER.TXT b/text-ui-test/expected_data/EXPECTED_MEMBER.TXT new file mode 100644 index 0000000000..babe0300db --- /dev/null +++ b/text-ui-test/expected_data/EXPECTED_MEMBER.TXT @@ -0,0 +1,3 @@ +Esther0.00 +Dane0.00 +Charlie0.00 diff --git a/text-ui-test/expected_data/EXPECTED_PIN.TXT b/text-ui-test/expected_data/EXPECTED_PIN.TXT new file mode 100644 index 0000000000..c3f890ad24 --- /dev/null +++ b/text-ui-test/expected_data/EXPECTED_PIN.TXT @@ -0,0 +1,2 @@ +2dc0269fa54d269a87536810ec453cb095b4b92f45e63826a21dff1c2e76f169 +true \ No newline at end of file diff --git a/text-ui-test/expected_data/EXPECTED_TRANSACTION.TXT b/text-ui-test/expected_data/EXPECTED_TRANSACTION.TXT new file mode 100644 index 0000000000..b75dfdf41b --- /dev/null +++ b/text-ui-test/expected_data/EXPECTED_TRANSACTION.TXT @@ -0,0 +1,6 @@ +EstherDane9.0 +DaneCharlie1.55 +CharlieEsther2.0Dane3.1 +Dane02-02-2000 1000Esther3.15 +Dane01-01-2000 1800Charlie1.0 +DaneEsther3.85Charlie2.55 diff --git a/text-ui-test/expected_output/EXPECTED1.TXT b/text-ui-test/expected_output/EXPECTED1.TXT new file mode 100644 index 0000000000..4e829c9ea6 --- /dev/null +++ b/text-ui-test/expected_output/EXPECTED1.TXT @@ -0,0 +1,227 @@ +Welcome to LongAh! + /$$ /$$$$$$ /$$ /$$ +| $$ /$$__ $$| $$ | $$ +| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \ $$| $$$$$$$ | $$ +| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$ +| $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$__ $$| $$ \ $$|__/ +| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ +| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$ +|________/ \______/ |__/ |__/ \____ $$|__/ |__/|__/ |__/|__/ + /$$ \ $$ + | $$$$$$/ + \______/ +Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon! +No groups found. Please give a name for your first group or enter 'exit' or 'close' to exit LongAh. +Created group: GroupA +You are now managing: GroupA +____________________________________________________________ +Enter command: Here are the full list of commands available: + +ADD commands: +____________________________________________________________ +1. `add member [NAME]` - Add a new member to the group. +2. `add transaction [LENDER] [DD-MM-YYYY HHMM] p/[BORROWER1] a/[AMOUNT OWED] +p/[BORROWER2] a/[AMOUNTED OWED] ...` - Add a new transaction. (date/time inputs are optional) +3. 'add group [GROUP NAME]' - Add a new group. + +LIST commands: +____________________________________________________________ +4. `list members` - List all current members in the group. +5. `list transactions` - List all transactions in the group. +6. `list debts` - Simplifies and lists all debts in the group. +7. `list groups` - List all groups in the application. + +DELETE commands: +____________________________________________________________ +8. `delete transaction [TRANSACTION NUMBER]` - Delete a transaction. +9. `delete member [MEMBER NAME]` - Delete a member from the group. +10. `delete group [GROUP NAME]` - Delete a group from the application. + +FIND commands: +____________________________________________________________ +11. `find borrower [MEMBER NAME]` - Find all transactions where the member is a borrower. +12. `find lender [MEMBER NAME]` - Find all transactions where the member is involved as the lender. +13. `find debts [MEMBER NAME]` - Find all debts of the member. +14. `find transactions [MEMBER NAME]` - Find all transactions where the member is involved. + +EDIT commands: +____________________________________________________________ +15. `edit member [MEMBER NAME] p/[NEW MEMBER NAME]` - Edit the name of a member. +16. `edit transaction [TRANSACTION NUMBER] [LENDER] p/[BORROWER1] a/[AMOUNT] +p/[BORROWER2] a/[AMOUNT]...` - Edit the details of a transaction. + +PIN commands: +____________________________________________________________ +17. `PIN enable` - Enable PIN authentication for the application. +18. `PIN disable` - Disable PIN authentication for the application. +19. `PIN reset` - Reset the user PIN. + +OTHER commands: +____________________________________________________________ +20. `settleup [MEMBER NAME]` - Settle all debts of the member. +21. `clear` - Clear all transaction data in the group. +22. 'group [GROUP NAME]' - Switch to another group with specified name. +23. `filter [TIME PERIOD]` - Filter transactions by time period. +24. `chart` - Display a chart of debts in the group. +25. `exit` - Exit the application. +26. `help` - Display the list of commands. + +For more information on a specific command, or view command shortcuts, do refer to our user guide. +____________________________________________________________ +Enter command: 1. GroupA + +____________________________________________________________ +Enter command: Member list is empty. +____________________________________________________________ +Enter command: No transactions found. +____________________________________________________________ +Enter command: No pending payments. +____________________________________________________________ +Enter command: Added member: Alice +____________________________________________________________ +Enter command: Added member: Bob +____________________________________________________________ +Enter command: Added member: Charlie +____________________________________________________________ +Enter command: Alice: $0.00 +Bob: $0.00 +Charlie: $0.00 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Alice +Borrower 1: Bob Owed amount: $10.00 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Bob +Borrower 1: Charlie Owed amount: $1.55 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Charlie +Borrower 1: Alice Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Alice: $8.00 +Bob: -$11.55 +Charlie: $3.55 +____________________________________________________________ +Enter command: 1. +Lender: Alice +Borrower 1: Bob Owed amount: $10.00 +2. +Lender: Bob +Borrower 1: Charlie Owed amount: $1.55 +3. +Lender: Charlie +Borrower 1: Alice Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Alice: $8.00 +Bob: -$11.55 +Charlie: $3.55 +____________________________________________________________ +Enter command: Alice is a part of the following list of transaction(s). +1. +Lender: Alice +Borrower 1: Bob Owed amount: $10.00 +3. +Lender: Charlie +Borrower 1: Alice Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Bob is a part of the following list of transaction(s). +1. +Lender: Alice +Borrower 1: Bob Owed amount: $10.00 +2. +Lender: Bob +Borrower 1: Charlie Owed amount: $1.55 +3. +Lender: Charlie +Borrower 1: Alice Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Charlie is a part of the following list of transaction(s). +2. +Lender: Bob +Borrower 1: Charlie Owed amount: $1.55 +3. +Lender: Charlie +Borrower 1: Alice Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Member name edited successfully! Alice is renamed to: Dane +____________________________________________________________ +Enter command: Member name edited successfully! Dane is renamed to: Esther +____________________________________________________________ +Enter command: Esther is a borrower in the following list of transaction(s). +3. +Lender: Charlie +Borrower 1: Esther Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Bob is a borrower in the following list of transaction(s). +1. +Lender: Esther +Borrower 1: Bob Owed amount: $10.00 +3. +Lender: Charlie +Borrower 1: Esther Owed amount: $2.00 +Borrower 2: Bob Owed amount: $3.10 +____________________________________________________________ +Enter command: Charlie is a borrower in the following list of transaction(s). +2. +Lender: Bob +Borrower 1: Charlie Owed amount: $1.55 +____________________________________________________________ +Enter command: Member name edited successfully! Bob is renamed to: Dane +____________________________________________________________ +Enter command: Charlie is a lender in the following list of transaction(s). +3. +Lender: Charlie +Borrower 1: Esther Owed amount: $2.00 +Borrower 2: Dane Owed amount: $3.10 +____________________________________________________________ +Enter command: Dane is a lender in the following list of transaction(s). +2. +Lender: Dane +Borrower 1: Charlie Owed amount: $1.55 +____________________________________________________________ +Enter command: Esther is a lender in the following list of transaction(s). +1. +Lender: Esther +Borrower 1: Dane Owed amount: $10.00 +____________________________________________________________ +Enter command: Transaction #1 edited successfully. +Lender: Esther +Borrower 1: Dane Owed amount: $9.00 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Dane +Transaction time: 02-02-2000 1000 +Borrower 1: Esther Owed amount: $3.15 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Dane +Transaction time: 01-01-2000 1800 +Borrower 1: Charlie Owed amount: $1.00 +____________________________________________________________ +Enter command: 1. +Lender: Esther +Borrower 1: Dane Owed amount: $9.00 +2. +Lender: Dane +Borrower 1: Charlie Owed amount: $1.55 +3. +Lender: Charlie +Borrower 1: Esther Owed amount: $2.00 +Borrower 2: Dane Owed amount: $3.10 +4. +Lender: Dane +Transaction time: 02-02-2000 1000 +Borrower 1: Esther Owed amount: $3.15 +5. +Lender: Dane +Transaction time: 01-01-2000 1800 +Borrower 1: Charlie Owed amount: $1.00 +____________________________________________________________ +Enter command: Goodbye! Hope to see you again soon! diff --git a/text-ui-test/expected_output/EXPECTED2.TXT b/text-ui-test/expected_output/EXPECTED2.TXT new file mode 100644 index 0000000000..adb63e6c3d --- /dev/null +++ b/text-ui-test/expected_output/EXPECTED2.TXT @@ -0,0 +1,94 @@ +Welcome to LongAh! + /$$ /$$$$$$ /$$ /$$ +| $$ /$$__ $$| $$ | $$ +| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \ $$| $$$$$$$ | $$ +| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$ +| $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$__ $$| $$ \ $$|__/ +| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ +| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$ +|________/ \______/ |__/ |__/ \____ $$|__/ |__/|__/ |__/|__/ + /$$ \ $$ + | $$$$$$/ + \______/ +Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon! +Defaulting to the first group. +You are now managing: GroupA +____________________________________________________________ +Enter command: Invalid command format. Use 'help' +____________________________________________________________ +Enter command: Invalid command format. Use 'add member NAME' or 'add transaction LENDER p/BORRWER1 a/AMOUNT1 ... +or 'add group GROUP_NAME' +____________________________________________________________ +Enter command: Duplicate member. +____________________________________________________________ +Enter command: Invalid member name. +____________________________________________________________ +Enter command: Invalid member name. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Balance overflow. Transaction not processed. +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: Invalid DateTime input. Dates of the future are not allowed. +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: Invalid command format. Use 'list members', 'list transactions', or 'list debts' or 'list groups' +____________________________________________________________ +Enter command: Invalid command format. Use 'list members', 'list transactions', or 'list debts' or 'list groups' +____________________________________________________________ +Enter command: Invalid command format. Use 'list members', 'list transactions', or 'list debts' or 'list groups' +____________________________________________________________ +Enter command: Invalid command format. Use 'list members', 'list transactions', or 'list debts' or 'list groups' +____________________________________________________________ +Enter command: Invalid command format. Use 'list members', 'list transactions', or 'list debts' or 'list groups' +____________________________________________________________ +Enter command: Invalid command format. Use 'find transactions', 'find lender', 'find borrower', or 'find debts' +____________________________________________________________ +Enter command: Member not found. +____________________________________________________________ +Enter command: Member not found. +____________________________________________________________ +Enter command: Member not found. +____________________________________________________________ +Enter command: Member not found. +____________________________________________________________ +Enter command: Invalid command format. Use 'edit transaction INDEX NEW_TRANSACTION' or 'edit member OLD_NAME p/NEW_NAME' +____________________________________________________________ +Enter command: Invalid command format. Use 'edit transaction INDEX NEW_TRANSACTION' or 'edit member OLD_NAME p/NEW_NAME' +____________________________________________________________ +Enter command: Invalid index. +____________________________________________________________ +Enter command: Invalid transaction format. +____________________________________________________________ +Enter command: Invalid transaction value. +____________________________________________________________ +Enter command: Invalid transaction value. +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: Added group: GroupB +____________________________________________________________ +Enter command: Authentication enabled upon startup. +____________________________________________________________ +Enter command: Goodbye! Hope to see you again soon! diff --git a/text-ui-test/expected_output/EXPECTED3.TXT b/text-ui-test/expected_output/EXPECTED3.TXT new file mode 100644 index 0000000000..9f991f4bbe --- /dev/null +++ b/text-ui-test/expected_output/EXPECTED3.TXT @@ -0,0 +1,178 @@ +Welcome to LongAh! + /$$ /$$$$$$ /$$ /$$ +| $$ /$$__ $$| $$ | $$ +| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \ $$| $$$$$$$ | $$ +| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$ +| $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$__ $$| $$ \ $$|__/ +| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ +| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$ +|________/ \______/ |__/ |__/ \____ $$|__/ |__/|__/ |__/|__/ + /$$ \ $$ + | $$$$$$/ + \______/ +Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon! +Enter your PIN: Login successful! +Defaulting to the first group. +You are now managing: GroupA +____________________________________________________________ +Enter command: Authentication disabled upon startup. +____________________________________________________________ +Enter command: Authentication enabled upon startup. +____________________________________________________________ +Enter command: Enter your current PIN: Create your 6-digit PIN: +PIN saved successfully! You can enter 'pin enable' to enable authentication upon startup. +____________________________________________________________ +Enter command: Switching groups... +You are now managing: GroupB +____________________________________________________________ +Enter command: Added member: Alice +____________________________________________________________ +Enter command: Added member: Bob +____________________________________________________________ +Enter command: Added member: Charlie +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Alice +Borrower 1: Bob Owed amount: $1.00 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Alice +Borrower 1: Bob Owed amount: $2.00 +Borrower 2: Charlie Owed amount: $3.00 +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Bob +Borrower 1: Alice Owed amount: $4.00 +Borrower 2: Charlie Owed amount: $5.00 +____________________________________________________________ +Enter command: Alice: $2.00 +Bob: $6.00 +Charlie: -$8.00 +____________________________________________________________ +Enter command: 1. +Lender: Alice +Borrower 1: Bob Owed amount: $1.00 +2. +Lender: Alice +Borrower 1: Bob Owed amount: $2.00 +Borrower 2: Charlie Owed amount: $3.00 +3. +Lender: Bob +Borrower 1: Alice Owed amount: $4.00 +Borrower 2: Charlie Owed amount: $5.00 +____________________________________________________________ +Enter command: Deleted member: Bob +____________________________________________________________ +Enter command: Alice: $3.00 +Charlie: -$3.00 +____________________________________________________________ +Enter command: 1. +Lender: Alice +Borrower 1: Charlie Owed amount: $3.00 +____________________________________________________________ +Enter command: Transaction #1 removed successfully. +Lender: Alice +Borrower 1: Charlie Owed amount: $3.00 +____________________________________________________________ +Enter command: Alice: $0.00 +Charlie: $0.00 +____________________________________________________________ +Enter command: No transactions found. +____________________________________________________________ +Enter command: Transaction added successfully! +Lender: Alice +Borrower 1: Charlie Owed amount: $3.00 +____________________________________________________________ +Enter command: Are you sure you want to clear all transactions? (Y/N) +This action cannot be undone. All transaction data will be lost. +Enter 'N' or any other key to cancel. +All transaction records have been cleared. +All transactions have been cleared for this account. +____________________________________________________________ +Enter command: Alice: $0.00 +Charlie: $0.00 +____________________________________________________________ +Enter command: No transactions found. +____________________________________________________________ +Enter command: Switching groups... +You are now managing: GroupA +____________________________________________________________ +Enter command: The following list of transactions matches with the time 01-01-2000 1800. +5. +Lender: Dane +Transaction time: 01-01-2000 1800 +Borrower 1: Charlie Owed amount: $1.00 +____________________________________________________________ +Enter command: The following list of transactions is after the time 10-01-2000 1800. +4. +Lender: Dane +Transaction time: 02-02-2000 1000 +Borrower 1: Esther Owed amount: $3.15 +____________________________________________________________ +Enter command: The following list of transactions is before the time 01-01-2005 1800. +4. +Lender: Dane +Transaction time: 02-02-2000 1000 +Borrower 1: Esther Owed amount: $3.15 +5. +Lender: Dane +Transaction time: 01-01-2000 1800 +Borrower 1: Charlie Owed amount: $1.00 +____________________________________________________________ +Enter command: The following list of transactions is between the time 31-12-1999 0000 and 01-01-2005 0000. +4. +Lender: Dane +Transaction time: 02-02-2000 1000 +Borrower 1: Esther Owed amount: $3.15 +5. +Lender: Dane +Transaction time: 01-01-2000 1800 +Borrower 1: Charlie Owed amount: $1.00 +____________________________________________________________ +Enter command: Dane has repaid Esther $3.85 +Dane has repaid Charlie $2.55 + +Transaction added successfully! +Lender: Dane +Borrower 1: Esther Owed amount: $3.85 +Borrower 2: Charlie Owed amount: $2.55 +Dane has no more debts! +____________________________________________________________ +Enter command: Switching groups... +You are now managing: GroupB +____________________________________________________________ +Enter command: Remaining groups: +1. GroupA + +You have deleted the active group that you were managing. +Defaulting back to the first group in the list. +You are now managing: GroupA +____________________________________________________________ +Enter command: Esther: $0.00 +Dane: -$.00 +Charlie: $0.00 +____________________________________________________________ +Enter command: 1. +Lender: Esther +Borrower 1: Dane Owed amount: $9.00 +2. +Lender: Dane +Borrower 1: Charlie Owed amount: $1.55 +3. +Lender: Charlie +Borrower 1: Esther Owed amount: $2.00 +Borrower 2: Dane Owed amount: $3.10 +4. +Lender: Dane +Transaction time: 02-02-2000 1000 +Borrower 1: Esther Owed amount: $3.15 +5. +Lender: Dane +Transaction time: 01-01-2000 1800 +Borrower 1: Charlie Owed amount: $1.00 +6. +Lender: Dane +Borrower 1: Esther Owed amount: $3.85 +Borrower 2: Charlie Owed amount: $2.55 +____________________________________________________________ +Enter command: Goodbye! Hope to see you again soon! diff --git a/text-ui-test/expected_output/EXPECTED4.TXT b/text-ui-test/expected_output/EXPECTED4.TXT new file mode 100644 index 0000000000..66d30e82d3 --- /dev/null +++ b/text-ui-test/expected_output/EXPECTED4.TXT @@ -0,0 +1,73 @@ +Welcome to LongAh! + /$$ /$$$$$$ /$$ /$$ +| $$ /$$__ $$| $$ | $$ +| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \ $$| $$$$$$$ | $$ +| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$ +| $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$__ $$| $$ \ $$|__/ +| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ +| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$ +|________/ \______/ |__/ |__/ \____ $$|__/ |__/|__/ |__/|__/ + /$$ \ $$ + | $$$$$$/ + \______/ +Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon! +Enter your PIN: Invalid PIN. Please try again. Alternatively, enter 'exit' or 'close' to exit LongAh. +Enter your PIN: Login successful! +Defaulting to the first group. +You are now managing: GroupA +____________________________________________________________ +Enter command: No transactions found. +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: No transactions found. +____________________________________________________________ +Enter command: Invalid filter command. Use 'filter b/DateTime' or 'filter a/DateTime' or 'filter a/Datetime b/Datetime' or 'filter Datetime +____________________________________________________________ +Enter command: Invalid DateTime format. Please format you date and time inputs in the form of DD-MM-YYYY HHmm +____________________________________________________________ +Enter command: Invalid command format. Use 'delete transaction INDEX' or 'delete member NAME' or 'delete group GROUP_NAME' +____________________________________________________________ +Enter command: Invalid command format. Use 'delete transaction INDEX' or 'delete member NAME' or 'delete group GROUP_NAME' +____________________________________________________________ +Enter command: Group not found. +____________________________________________________________ +Enter command: Member not found. +____________________________________________________________ +Enter command: Invalid index. +____________________________________________________________ +Enter command: Invalid index. +____________________________________________________________ +Enter command: Invalid command format. Use 'clear' +____________________________________________________________ +Enter command: Are you sure you want to clear all transactions? (Y/N) +This action cannot be undone. All transaction data will be lost. +Enter 'N' or any other key to cancel. +Clear operation cancelled. +____________________________________________________________ +Enter command: Are you sure you want to clear all transactions? (Y/N) +This action cannot be undone. All transaction data will be lost. +Enter 'N' or any other key to cancel. +Clear operation cancelled. +____________________________________________________________ +Enter command: Invalid command format. Use 'settleup PERSON' +____________________________________________________________ +Enter command: Member not found. +____________________________________________________________ +Enter command: Group not found. +____________________________________________________________ +Enter command: Authentication is already enabled. +____________________________________________________________ +Enter command: Authentication disabled upon startup. +____________________________________________________________ +Enter command: Authentication is already disabled. +____________________________________________________________ +Enter command: Enter your current PIN: Invalid PIN. Please try again later. +____________________________________________________________ +Enter command: Invalid command format. Use 'pin reset' or 'pin enable' or 'pin disable' +____________________________________________________________ +Enter command: Invalid command format. Use 'pin reset' or 'pin enable' or 'pin disable' +____________________________________________________________ +Enter command: Authentication enabled upon startup. +____________________________________________________________ +Enter command: Goodbye! Hope to see you again soon! diff --git a/text-ui-test/expected_output/EXPECTED5A.TXT b/text-ui-test/expected_output/EXPECTED5A.TXT new file mode 100644 index 0000000000..dc181f94da --- /dev/null +++ b/text-ui-test/expected_output/EXPECTED5A.TXT @@ -0,0 +1,14 @@ +Welcome to LongAh! + /$$ /$$$$$$ /$$ /$$ +| $$ /$$__ $$| $$ | $$ +| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \ $$| $$$$$$$ | $$ +| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$ +| $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$__ $$| $$ \ $$|__/ +| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ +| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$ +|________/ \______/ |__/ |__/ \____ $$|__/ |__/|__/ |__/|__/ + /$$ \ $$ + | $$$$$$/ + \______/ +Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon! +Enter your PIN: Goodbye! Hope to see you again soon! diff --git a/text-ui-test/expected_output/EXPECTED5B.TXT b/text-ui-test/expected_output/EXPECTED5B.TXT new file mode 100644 index 0000000000..f619e04e07 --- /dev/null +++ b/text-ui-test/expected_output/EXPECTED5B.TXT @@ -0,0 +1,18 @@ +Welcome to LongAh! + /$$ /$$$$$$ /$$ /$$ +| $$ /$$__ $$| $$ | $$ +| $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$ \ $$| $$$$$$$ | $$ +| $$ /$$__ $$| $$__ $$ /$$__ $$| $$$$$$$$| $$__ $$| $$ +| $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$__ $$| $$ \ $$|__/ +| $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ +| $$$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$ | $$| $$ | $$ /$$ +|________/ \______/ |__/ |__/ \____ $$|__/ |__/|__/ |__/|__/ + /$$ \ $$ + | $$$$$$/ + \______/ +Thanks for choosing LongAh! Never worry about owing money during the Year of the Dragon! +Error reading saved PIN and authentication enabled state. +Create your 6-digit PIN: +PIN saved successfully! You can enter 'pin enable' to enable authentication upon startup. +No groups found. Please give a name for your first group or enter 'exit' or 'close' to exit LongAh. +Goodbye! Hope to see you again soon! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt deleted file mode 100644 index f6ec2e9f95..0000000000 --- a/text-ui-test/input.txt +++ /dev/null @@ -1 +0,0 @@ -James Gosling \ No newline at end of file diff --git a/text-ui-test/input/input1.txt b/text-ui-test/input/input1.txt new file mode 100644 index 0000000000..963c3e8125 --- /dev/null +++ b/text-ui-test/input/input1.txt @@ -0,0 +1,33 @@ +GroupA +help +list groups +list members +list transactions +list debts +add member Alice +addm Bob +am Charlie +listm +add transaction Alice p/Bob a/10 +addt Bob p/Charlie a/1.55 +at Charlie p/Alice a/2 p/Bob a/3.1 +listm +listt +lm +find transactions Alice +findt Bob +ft Charlie +edit member Alice p/Dane +editm Dane p/Esther +find borrower Esther +findb Bob +fb Charlie +em Bob p/Dane +find lender Charlie +findl Dane +fl Esther +edit transaction 1 Esther p/Dane a/9 +addt Dane t/02-02-2000 1000 p/Esther a/3.15 +at Dane t/01-01-2000 1800 p/Charlie a/1.00 +lt +exit \ No newline at end of file diff --git a/text-ui-test/input/input2.txt b/text-ui-test/input/input2.txt new file mode 100644 index 0000000000..0515a1f489 --- /dev/null +++ b/text-ui-test/input/input2.txt @@ -0,0 +1,39 @@ +help a +add Charlie p/Dane a/1 +am Charlie +am 123456; +am 234 567 +at Charlie Dane Esther +at Charlie p/Dane p/Ester a/5 +at p/Charlie p/Dane a/1 p/Esther a/2 +at p/Charlie a/1 p/Dane a/2 p/Esther a/3 +at p/Charlie p/Dane a/1.555 +at p/Charlie p/Dane a/-1 +at p/Charlie p/Dane a/0 +at Esther p/Esther a/1 +at Esther p/Dane a/1e309 +at Charlie t/2001-01-01 1800 p/Dane a/1 p/Esther a/2 +at Charlie t/01-01-2099 1800 p/Dane a/1 p/Esther a/2 +at Charlie t/01-01-2000 2500 p/Dane a/1 p/Esther a/2 +at Charlie t/40-01-2000 1800 p/Dane a/1 p/Esther a/2 +at Charlie t/01-15-2000 1800 p/Dane a/1 p/Esther a/2 +list +lm a +lt a +ld a +lg a +find Zeta +findt Zeta +findl Zeta +findb Zeta +findd Zeta +editm Zeta +em Zeta Alpha +editt -1 Charlie p/Dane a/1 +et 1 Esther p/Esther a/1 +et 1 Esther p/Dane a/0 +et 1 Esther p/Dane a/-1 +et 1 Esther t/01-01-2001 2500 p/Charlie a/1 +add group GroupB +pin enable +exit \ No newline at end of file diff --git a/text-ui-test/input/input3.txt b/text-ui-test/input/input3.txt new file mode 100644 index 0000000000..925b3dd07f --- /dev/null +++ b/text-ui-test/input/input3.txt @@ -0,0 +1,37 @@ +123456 +pin disable +pin enable +pin reset +123456 +234567 +group GroupB +am Alice +am Bob +am Charlie +at Alice p/Bob a/1 +at Alice p/Bob a/2 p/Charlie a/3 +at Bob p/Alice a/4 p/Charlie a/5 +lm +lt +delete member Bob +lm +lt +delete transaction 1 +lm +lt +at Alice p/Charlie a/3 +clear +Y +lm +lt +group GroupA +filter 01-01-2000 1800 +filter a/10-01-2000 1800 +filter b/01-01-2005 1800 +filter a/31-12-1999 0000 b/01-01-2005 0000 +settle Dane +group GroupB +delete group GroupB +lm +lt +exit diff --git a/text-ui-test/input/input4.txt b/text-ui-test/input/input4.txt new file mode 100644 index 0000000000..48fdb55c86 --- /dev/null +++ b/text-ui-test/input/input4.txt @@ -0,0 +1,30 @@ +000000 +234567 +filter 01-01-1999 1800 +filter 00-01-2000 1800 +filter a/01-01-2010 1900 +filter b/01-01-2002 0000 a/31-12-1999 0000 +filter +delete +delete a +delete group 123 +delete member Zeta +delete transaction 0 +delete transaction -1 +clear A +clear +N +clear +a +settle +settle A +group 123 +pin enable +pin disable +pin disable +pin reset +123456 +pin +pin a +pin enable +close \ No newline at end of file diff --git a/text-ui-test/input/input5A.txt b/text-ui-test/input/input5A.txt new file mode 100644 index 0000000000..ae3bc0a936 --- /dev/null +++ b/text-ui-test/input/input5A.txt @@ -0,0 +1 @@ +exit \ No newline at end of file diff --git a/text-ui-test/input/input5B.txt b/text-ui-test/input/input5B.txt new file mode 100644 index 0000000000..57f9dc1c2d --- /dev/null +++ b/text-ui-test/input/input5B.txt @@ -0,0 +1,2 @@ +123456 +exit diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 25ac7a2989..2253292cff 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -12,8 +12,32 @@ for /f "tokens=*" %%a in ( set jarloc=%%a ) -java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TXT +set ERROR_COUNT=0 +set FAILED_TESTS= + +java -jar %jarloc% < ..\..\text-ui-test\input\input5B.txt > ..\..\text-ui-test\actual_output\ACTUAL5B.TXT +java -jar %jarloc% < ..\..\text-ui-test\input\input1.txt > ..\..\text-ui-test\actual_output\ACTUAL1.TXT +java -jar %jarloc% < ..\..\text-ui-test\input\input2.txt > ..\..\text-ui-test\actual_output\ACTUAL2.TXT +java -jar %jarloc% < ..\..\text-ui-test\input\input3.txt > ..\..\text-ui-test\actual_output\ACTUAL3.TXT +java -jar %jarloc% < ..\..\text-ui-test\input\input4.txt > ..\..\text-ui-test\actual_output\ACTUAL4.TXT +java -jar %jarloc% < ..\..\text-ui-test\input\input5A.txt > ..\..\text-ui-test\actual_output\ACTUAL5A.TXT + +FC data\GroupA\members.txt ..\..\text-ui-test\expected_data\EXPECTED_MEMBER.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% MEMBER) +FC data\GroupA\transactions.txt ..\..\text-ui-test\expected_data\EXPECTED_TRANSACTION.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% TRANSACTIONS) +FC data\groupList.txt ..\..\text-ui-test\expected_data\EXPECTED_GRPLIST.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% GRPLIST) +FC data\pin.txt ..\..\text-ui-test\expected_data\EXPECTED_PIN.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% PIN) cd ..\..\text-ui-test -FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! +FC actual_output\ACTUAL5B.TXT expected_output\EXPECTED5B.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% 5B) +FC actual_output\ACTUAL1.TXT expected_output\EXPECTED1.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% 1) +FC actual_output\ACTUAL2.TXT expected_output\EXPECTED2.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% 2) +FC actual_output\ACTUAL3.TXT expected_output\EXPECTED3.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% 3) +FC actual_output\ACTUAL4.TXT expected_output\EXPECTED4.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% 4) +FC actual_output\ACTUAL5A.TXT expected_output\EXPECTED5A.TXT >NUL && set /a ERROR_COUNT=ERROR_COUNT || (set /a ERROR_COUNT+=1 && set FAILED_TESTS=%FAILED_TESTS% 5A) + +if %ERROR_COUNT% EQU 0 ( + Echo All tests passed! +) else ( + Echo %ERROR_COUNT% tests failed:%FAILED_TESTS% +) diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 1dcbd12021..902dc916cd 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -1,23 +1,84 @@ #!/usr/bin/env bash -# change to script directory +# Change to script directory cd "${0%/*}" +# Change directory to project root cd .. + +# Build project ./gradlew clean shadowJar +# Change directory to text-ui-test cd text-ui-test +rm -rf ./data/ + +# Function to check test result +check_test() { + local input_file="$1" + local expected_output="$2" + local actual_output="$3" + local test_name="$4" + local error_count_ref="$5" + local failed_tests_ref="$6" + + # Run the test and generate actual output + java -jar "$(find ../build/libs/ -mindepth 1 -print -quit)" < "$input_file" > "$actual_output" + + cp "$expected_output.TXT" "$expected_output-UNIX.TXT" + dos2unix "$expected_output-UNIX.TXT" "$actual_output" + diff "$expected_output-UNIX.TXT" "$actual_output" + + if [ $? -ne 0 ] + then + ((error_count_ref++)) + eval "$failed_tests_ref+=\" $test_name\"" + fi +} + +check_data() { + local expected_output="$1" + local actual_output="$2" + local test_name="$3" + local error_count_ref="$4" + local failed_tests_ref="$5" + + cp "$expected_output.TXT" "$expected_output-UNIX.TXT" + dos2unix "$expected_output-UNIX.TXT" "$actual_output" + diff --strip-trailing-cr "$expected_output-UNIX.TXT" "$actual_output" + + if [ $? -ne 0 ] + then + ((error_count_ref++)) + eval "$failed_tests_ref+=\" $test_name\"" + fi +} -java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT +# Initialize error count variable +ERROR_COUNT=0 -cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix EXPECTED-UNIX.TXT ACTUAL.TXT -diff EXPECTED-UNIX.TXT ACTUAL.TXT -if [ $? -eq 0 ] -then - echo "Test passed!" - exit 0 +# Initialize variable to store names of failed tests +FAILED_TESTS="" + +# Run tests +check_test "input/input5B.txt" "expected_output/EXPECTED5B" "actual_output/ACTUAL5B.TXT" "5B" ERROR_COUNT FAILED_TESTS +check_test "input/input1.txt" "expected_output/EXPECTED1" "actual_output/ACTUAL1.TXT" "1" ERROR_COUNT FAILED_TESTS +check_test "input/input2.txt" "expected_output/EXPECTED2" "actual_output/ACTUAL2.TXT" "2" ERROR_COUNT FAILED_TESTS +check_test "input/input3.txt" "expected_output/EXPECTED3" "actual_output/ACTUAL3.TXT" "3" ERROR_COUNT FAILED_TESTS +check_test "input/input4.txt" "expected_output/EXPECTED4" "actual_output/ACTUAL4.TXT" "4" ERROR_COUNT FAILED_TESTS +check_test "input/input5A.txt" "expected_output/EXPECTED5A" "actual_output/ACTUAL5A.TXT" "5A" ERROR_COUNT FAILED_TESTS + +check_data "expected_data/EXPECTED_MEMBER" "data/GroupA/members.txt" "MEMBER" ERROR_COUNT FAILED_TESTS +check_data "expected_data/EXPECTED_TRANSACTION" "data/GroupA/transactions.txt" "TRANSACTION" ERROR_COUNT FAILED_TESTS +check_data "expected_data/EXPECTED_GRPLIST" "data/groupList.txt" "GRPLIST" ERROR_COUNT FAILED_TESTS +check_data "expected_data/EXPECTED_PIN" "data/pin.txt" "PIN" ERROR_COUNT FAILED_TESTS + +# Output test results +if [ $ERROR_COUNT -eq 0 ]; then + echo "All tests passed!" else - echo "Test failed!" - exit 1 + echo "$ERROR_COUNT tests failed:$FAILED_TESTS" fi + +# Exit with error count +exit $ERROR_COUNT \ No newline at end of file