Wiagi is a simple command line application that helps students who are beginning their financial independence journey by offering a wide range of essential tools and features such as budgeting, saving, and investment analysis.
Sources of all reused/adapted ideas, code, documentation, and third-party libraries:
The Architecture Diagram given above explains the high-level design of the program.
Given below is a quick overview of main components and how they interact with each other.
Wiagi
: The command executor and brain of the program.
WiagiLogger
and Storage
, ensuring that the user data is
loaded securely.Ui
, parse input with Parser
and executes them accordingly
with Command
.Ui
: Takes in user input and prints output of the program.
Parser
: Parse user input to deduce their intended command.
Command
object to Wiagi
based on the user input.Command
: Represents a collection of command classes with different functionalities.Storage
: Reads data from files and writes data to files.WiagiLogger
: Tracks events that happened when the program runs.
On a high level, whenever Wiagi
is started, it will load SpendingList
and IncomeList
from Storage
if they exist,
else, new lists would be created. It will also load the hashed password from Storage
if it exists, else it will
prompt the user to create a new account. The hashed password is checked against the user’s password input for verification.
Wiagi
then takes in user input via the Ui
class, then parse and executes the command through the Parser
class.
The related output is printed through the Ui
class.
Now let’s delve deeper into some of these classes used for the program below
This section introduces the common classes used throughout the program their internal implementations and structure.
The EntryType
class is a class that is used for storing different types of user entry such that the entries
contain the relevant information required by other classes to perform their tasks.
The following are its attributes:
amount
double
description
String
date
LocalDate
tag
String
recurrenceFrequency
RecurrenceFrequency
lastRecurrence
LocalDate
dayOfRecurrence
int
date
, used for internal program checking usageThe methods implemented in this class are a collection of getters and setters that allow other class types to access the information of the entry.
The following are child classes of EntryType
:
Income
Spending
The Income
class inherits from EntryType
class. It is used to store relevant information for entries labelled as
income. This information is used by other classes to perform their tasks.
The Spending
class inherits from EntryType
class. It is used to store relevant information for entries labelled as
spending. This information is used by other classes to perform their tasks.
The IncomeList
class inherits from the ArrayList
class. It is used to store all of the Income
objects used in the
program.
The SpendingList
class inherits from the ArrayList
class. It is used to store all of the Spending
objects used in
the program. Additionally, it stores the budgets that are set by the user.
The purpose of the class are as follows:
Illustrated below is the class diagram for the Recurrence class:
The Recurrence
class is an abstract class that provides the interface for checking Income
and Spending
and adding
recurring entries into the list.
The following are child classes of Recurrence
:
DailyRecurrence
: Handles entries labelled as daily recurring eventsMonthlyRecurrence
: Handles entries labelled as monthly recurring eventsYearlyRecurrence
: Handles entries labelled as yearly recurring eventsRecurrence happens during 2 use cases:
Method implementations will be explained in later parts, in their respective use cases
The RecurrenceFrequency
enumeration is used to determine the type of recurring entry of EntryType
and its child
classes, stored in the recurrenceFrequency
attribute
Enumeration constants:
NONE
: Represents not recurring entryDAILY
: Represents a daily recurring entryMONTHLY
: Represents a monthly recurring entryYEARLY
: Represents a yearly recurring entryThe Storage
class is a class that stores incomes
, spendings
and password
.
Upon instantiation, it will call IncomeListStorage.load()
, SpendingListStorage.load()
and LoginStorage.load()
, which will initialise the variables in Storage
respectively. It will also call upon IncomeListStorage.save()
and SpendingListStorge.save()
to save the user data into its respective data files.
incomes
→ ./incomes.txt
spendings
→ ./spendings.txt
password
→ ./password.txt
Method implementations will be explained in later parts, in their respective use cases
To load saved lists:
Wiagi
is constructed.Wiagi
constructor, it will create a new instance of Storage
, which will then load the data at the
incomes
and spendings
file paths to an IncomeList
and SpendingList
respectively.Wiagi
will then retrieve the lists in Storage
to initialise its lists.To load password:
IncomeListStorage
SpendingListStorage
load()
, except that SpendingListStorage
also loads budget details.|
’ to access each attributes, converts each attribute to its respective type and adds it to its respective list.LoginStorage
Scanner
to read the file and initialise password
attribute in Storage
.createNewUser()
, which creates a new password file and use getNewUserPassword()
to scan for
the user input. Then, it will be hashed, stored in the file, and be used to initialise password
attribute in Storage
.Below illustrates the reference frame of recurrence updating
For the reference frame of ‘load from storage’, it is as explained previously in load method.
For the reference frame of ‘add recurring entry’, refer to
checkIncomeRecurrence / checkSpendingRecurrence method.
Storage
component will load the IncomeList
and SpendingList
members of
Wiagi
to retrieve past data.updateRecurrence()
is called.SpendingList
and IncomeList
are then iterated through. Each member of the lists is parsed through
Parser#parseRecurrence()
which returns the type of recurrence it is (e.g. DailyRecurrence
, null
)
which is encapsulated as a Recurrence
object.Recurrence
is not null
(i.e. a recurring entry), it checks the entry and adds to the SpendingList
and
IncomeList
if needed via Recurrence#checkIncomeRecurrence()
or Recurrence#checkSpendingRecurrence()
. The following are notable classes and methods used to achieve recurrence updating.
Class: DailyRecurrence
, MonthlyRecurrence
, YearlyRecurrence
Method Signature:
@Override
public void checkIncomeRecurrence(Income recurringIncome, IncomeList incomes,
boolean isAdding)
@Override
public void checkSpendingRecurrence(Spending recurringSpending,
SpendingList spendings, boolean isAdding)
Below illustrates the functionality of the checkIncomeRecurrence method through a sequence diagram
Since checkSpendingRecurrence method follows the same sequence as checkIncomeRecurrence method, the diagram is omitted
for conciseness.
Functionality:
lastRecurrence
attribute of recurringIncome
/recurringSpending
(i.e. entry to check) against the current date
via LocalDate.now()
lastRecurrence
with the frequency incrementallyIncomeList
/SpendingList
if date is still of the past.true
for recurrence updating to allow adding of entries, it may be set to false
for
backlogging as seen later to only update lastRecurrence
attribute to the latest possible recurring date to
be checked against in the future for recurrence updatingClass: Parser
Method Signature:
public static Recurrence parseRecurrence(EntryType entry)
Functionality:
EntryType
(i.e. Spending
, Income
)reccurenceFrequency
attribute with switch case to determine which Recurrence
child to returnDailyRecurrence
, MonthlyRecurrence
, YearlyRecurrence
or null
(If not a recurring entry) accordingly.Class: SpendingList
, IncomeList
Method Signature:
public void updateRecurrence()
Functionality:
Parser#parseRecurrence()
to determine type of Recurrence
Recurrence#checkSpendingRecurrence()
or Recurrence#checkIncomeRecurrence()
to update list if the new
recurring entry is supposed to be addedClass: DailyRecurrence
, MonthlyRecurrence
, YearlyRecurrence
Method Signature:
protected <T extends EntryType> void checkIfDateAltered(T newEntry,
LocalDate checkDate, ArrayList<T> list, boolean isAdding)
Functionality:
dayOfRecurrence
attribute of entryRecurrence
are being set to not recurring events to prevent double recurring entries
added in the futuredayOfRecurrence
is tracked:
lastRecurrence
is stored as
30th SeptemberdayOfRecurrence
is used to track the real date of recurrence since the day will be overwrittencheckIfDateAltered()
is used to validate the date of entryUser input is taken in through the Ui.readCommand()
method that is called from the Wiagi
class. This command is
then passed to the static method Parser#parseUserInput()
. This method determines the command type
based on the command word, and returns a new instance of the respective command, as shown in the
sequence diagram above.
Since there are various list commands that the user can execute, the list commands are split into multiple classes.
If the command word is list
, the parser will call a separate method parseListCommand()
that will return the correct list command.
After the correct command is returned, it is executed by Wiagi by calling the execute()
method of the command.
The referenced sequence diagram for the execution of list commands will be shown in the section for listing entries, while the referenced sequence diagram for the execution of commands will be shown in the sections for adding a new entry and editing entries, which will serve as examples since the execution of most commands will be similar.
The diagram below shows the class diagram for a command.
To save edited lists:
bye
or after keyboard interrupts (i.e.Ctrl-c), which signals the end of the program.IncomeListStorage
SpendingListStorage
Both classes have similar implementation for save()
, except that SpendingListStorage
saves budget details in the
first line of its text file.
daily budget | monthly budget | yearly budget
|
. Hence, each entry will be written line by line to the file.amount | description | date | tag | recurrence frequency | last recurrence date | day of recurrence
add income 10 part time /2024-10-10/ *job* ~monthly~
will be stored as
10.0|part time|2024-10-10|job|MONTHLY|2024-10-10|10
To add new entries, user will have to input the related commands. The sequence diagram above shows the flow of adding a new entry.
handleCommand()
method in AddCommand
class will be called to verify the user input and handle the command.
The entries will be added to the respective list and the user will be informed that the entry has been added.
Illustrated below is the reference frame recurrence backlogging when a recurring entry dated before the current
day is added
Ui#hasRecurrenceBacklog()
method will be called to get
user input on whether to backlogRecurrence#checkIncomeRecurrence()
or Recurrence#checkSpendingRecurrence()
will be
called to update the lastRecurrence
attribute of the entry as well as add recurring entries from the entry date to
current date if user wishes tototal
attribute of SpendingList
or IncomeList
to exceed its limitThe following are notable methods used to achieve recurrence backlogging. MethodsRecurrence#checkIncomeRecurrence()
,
Recurrence#checkSpendingRecurrence()
and
Parser#parseRecurrence()
explained in updating recurrence above are re-used and
thus will be omitted below for conciseness
Class: Recurrence
Method Signature:
public static <T extends EntryType> void checkRecurrenceBackLog(T toAdd,
ArrayList<T> list)
Functionality:
Parser#parseRecurrence()
method to determine the type of recurrencegetNumberOfRecurringEntries()
to obtain total recurring entries to be addedUi#hasRecurrenceBacklog
which returns a boolean, true
if user inputs yes, else false
.throwExceptionIfTotalExceeded()
and throws an error if
total
attribute of SpendingList
or IncomeList
exceeds limit after adding entrieslastRecurrence
attribute of the entry to facilitate future adding of recurrence and if boolean is true,
also adds backlog entries to IncomeList
or SpendingList
via Recurrence#checkIncomeRecurrence
and
Recurrence#checkSpendingRecurrence
Class: Ui
Method Signature:
public static <T extends EntryType> boolean hasRecurrenceBacklog(T toAdd)
Functionality:
Ui#readCommand()
on whether he/she wishes to backlog recurring entriestrue
if users input yes or false
otherwiseEditCommand
validates and parses the given input to determine if it is editing a spending or an income. It then
extracts the entry from its corresponding list (SpendingList or IncomeList). Finally, it uses the parsed input to
determine which attribute to edit and sets this attribute of the extracted entry to the new value.
FindCommand
validates and parses the given input to determine if it is finding entries in a SpendingList
or an
IncomeList
. It then searches through the list based on specified fields (amount
, description
, or date
)
to display matching results.
DeleteCommand
validates and parses the given input to determine if it is deleting a spending or an income. It then
deletes the entry from the respective list (SpendingList or IncomeList) by calling the delete method of that list.
BudgetCommand
first validates and parses the given input. It then determines whether the user wants to add a daily
, monthly, or yearly budget. It then calls the respective method of the SpendingList to set the correct budget.
Since listing requires Wiagi to print items in the spendings and incomes list, the printing will be handled by the Ui class.
When the user requests to list all entries, the program prints all entries in both incomes
and
spendings
by looping through both lists and printing them out with their index.
When users request to list all spendings, they are given the option to choose a time range from the following options:
By selecting options 2, 3, or 4, only the spending entries that are dated within the current week, last week and this week, or current month will be displayed.
If the user chooses to list all spendings, they are then given the option to display all statistics, which consist of:
The sequence diagram below shows what happens when the user executes a list spending
command.
As shown in the diagram, when the command is executed, a handleCommand(...)
method is first called to verify the user
input and handle the command.
Within this method, a static method printListofTimeRange
is called to
allow the user to select a time range. This method returns a boolean value that is true
if the user has selected to list
all spendings and false
otherwise. If this returned value is true
, another static method printStatisticsIfRequired
is
called to allow the user to choose whether to show all spending statistics and print the list accordingly.
The sequence diagram below shows what happens when the user chooses to show their weekly spendings.
As shown in the diagram, the program gets the dates of the Monday and Sunday of the current week. It then loops through the spending list. For every entry in the spending list, it checks whether the date of the entry is between the Monday and the Sunday of the current week (inclusive), and if it is, the entry will be appended to a string along with its index. Finally, the string is printed.
When users request to list incomes, they are also given the option to choose from the same 4 time ranges:
By selecting options 2, 3, or 4, only the spending entries that are dated within the current week, last week and this week, or current month will be displayed.
The implementation of listing incomes is very similar to that of listing spendings, except that users will not be given the option to list statistics if they choose to list all incomes. Hence, the sequence diagram is omitted for this command.
Listing all tags and listing all entries with a specific tag are grouped together into one command called
ListTagsCommand
. When this command is executed, the number of words in the command is checked to determine if the user
wants to list all tags or to list all entries with a specific tag, as shown in the sequence diagram below.
For listing all tags, the static method printAllTags()
from the Ui
class is called. This method simply loops
through all entries and gets an ArrayList
of all the unique tags before printing them out.
For listing entries with a specific tag, the static method printSpecificTag()
from the Ui
class is called. This
method is similar to the printWeekly()
method as it also loops through spendings and incomes while appending
entries with the specified tag to a String
which is then printed out.
When the user types help
, the program will print out a list of commands that the user can use.
When the user types bye
, the program will exit.
An app that help students to manage their financials faster than a typical mouse/GUI driven app.
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can … |
---|---|---|---|
*** | user | start and close the application | use it only when needed |
*** | user | add my financial transactions | track the flow of my money |
*** | user | categorise my entries as income and spendings | better understand my financials |
*** | user | add income and expenditure categories | see my overall net gain or loss |
*** | user | see all my spendings | know what I spent on |
*** | user | delete my entries | correct my mistakes |
*** | user | have a password to my account | protect my account information |
** | user | edit my incomes and spendings | correct my mistakes |
** | user | categorise my expenses | see what I spend on |
** | user | categorise my incomes | see where my savings come from |
** | user | read the amount of money left in my allocated budget | gauge how much to spend for the remaining period |
** | user | set expenses and incomes as recurring | do not need to manually add them each time |
** | user | set budgets for each category of expense | make better financial decisions |
** | user | view my expenses in different time ranges | better analyse my spendings |
** | user subscribed to multiple subscription services | set expenses as recurring | automate the process of adding entries |
** | new user | view usage instructions | refer to them when I forget how to use the application |
* | user | be alerted when I overspend my budget | try to curb my spendings |
* | user | find my entry with keywords | retrieve specific entries easily |
Adding an income entry with optional input for date, tag and recurrence frequency
MSS
Use case ends.
Extensions
Use case restarts at step 1.
Deleting an income or spending from the list
MSS
Use case ends.
Extensions
MSS
Use case ends.
Extensions
Use case of listing all spendings, incomes, tags is similar, omitted for brevity.
Editing an existing income or spending entry
MSS
Use case ends.
Extensions
MSS
Use case ends.
Extensions
Finding an existing income or spending entry with certain information
MSS
Use case ends.
Extensions
Unsure of command specifics and require assistance
MSS
help
into the command terminalUse case ends.
Prerequisites: There should not be a password.txt, spendings.txt, incomes.txt file in the directory where the jar file is located
Prerequisites: Add multiple entries to either incomes or spendings.
find income description a
a
in the description.find spending amount 10
find spending date 2024-11-11 to 2024-12-12
find income amount -1
, find income amount s
, find income date 11-11-2024
Prerequisites: Add 3 or more entries to incomes and spendings.
edit spending 1 amount 100
edit income 3 description Salary
edit spending 3 date 2024-10-20
edit income 1 tag work
edit spending 1 amount not-an-amount
, edit income 2 date invalid-date
Prerequisites: None.
help
Prerequisites: Budget initialised to daily budget of 1, monthly budget of 100, yearly budget of 100000
budget daily 50
budget monthly 1500
budget yearly 20000
budget weekly 500
Prerequisites: Have at least one income and spending in each list
delete spending 1
Prerequisites: None
list
list spending
list tags
list tags food
list income
Prerequisites: None.
add spending 10 macs
add income 1000 job
add spending 10 macs *food*
add income 100 odd job ~daily~
add spending 100 toy /2024-10-10/
add income 10000 salary *SIA* ~monthly~ /2024-05-05/
add spend 10 food
, add spending food 10
, add income 100 job /2024-100-100/
, add income 100 job ~day~
bye