Welcome to cOOPer’s Developer Guide!
cOOPer is a Command Line Interface (CLI) desktop application developed to simplify administrative processes of tech startups such as communication and finance management.
cOOPer was developed in Java 11 following an Object-Oriented Programming (OOP) paradigm, hence the letters OOP in its name.
This developer guide is for software designers, developers, and software testers of cOOPer. It will be your reference manual if you are looking to:
Throughout this developer guide, you will see text formatted differently from normal text, as well as symbols appearing before another line of text. The table below explains the formatting and symbols in this developer guide.
Formatting/Symbol | Meaning |
---|---|
italics | Text in italics represent terminology specific to using / developing cOOPer. |
bold | Text in bold emphasizes the text’s importance and indicates that you should pay more attention to the text. |
code |
Short lines of text highlighted as such indicate a class, method, component or user input. It is also used to represent directories / file paths. |
ℹ️ | The info symbol indicates useful information about diagrams / content in this developer guide. |
💡 | The light bulb symbol indicates a useful tip which eases the development of cOOPer. |
This section includes the sources of code, documentation and third-party libraries reused / adapted in developing cOOPer.
Storage
component was adapted from one of our member’s CS2113T Individual Project (iP). A few of the methods for file reading and file creation were reused.Util.java
was adapted from this website.generate
feature was adapted from this website.💡 These are the software / tools used in developing cOOPer. You are recommended to use them too:
- IDE: IntelliJ IDEA (highly recommended)
- JDK: Java 11
- Version control: Git
- Git GUI: Sourcetree
- Build system: Gradle
cooper.Cooper
src/main/java/cooper/Cooper.java
Cooper.java
and select ‘Run Cooper.main()’. /$$$$$$ /$$$$$$ /$$$$$$$
/$$__ $$ /$$__ $$| $$__ $$
/$$$$$$$| $$ \ $$| $$ \ $$| $$ \ $$ /$$$$$$ /$$$$$$
/$$_____/| $$ | $$| $$ | $$| $$$$$$$//$$__ $$ /$$__ $$
| $$ | $$ | $$| $$ | $$| $$____/| $$$$$$$$| $$ \__/
| $$ | $$ | $$| $$ | $$| $$ | $$_____/| $$
| $$$$$$$| $$$$$$/| $$$$$$/| $$ | $$$$$$$| $$
\_______/ \______/ \______/ |__/ \_______/|__/
=========================================================================
Hello I'm cOOPer! Nice to meet you!
=========================================================================
Log in or register to gain access to my features!
To log in, enter "login [yourUsername] /pw [password] /as [yourRole]".
To register, enter "register [yourUsername] /pw [password] /as [yourRole]".
To exit, enter "exit".
=========================================================================
>> [Logged out]
src/test
.test
and select ‘Run ‘All tests’ ‘.
.github/workflows
folder. CI for cOOPer is automatically run at each push to the ‘master’ branch or whenever a pull request is created.💡 The architecture diagram and UML diagrams in this document were created using draw.io. The
.png
templates used to create the diagrams can be found in thedeveloperGuideDiagrams
folder. To create and edit diagrams, access the draw.io website, select ‘Open Existing Diagram’ and open the desired.png
file. Any changes to the diagram will be saved automatically.
cOOPer consists of two main layers: the verification layer and the features layer as shown in the diagram below. cOOPer recognizes different sets of inputs at each layer.
Upon launching the app, the user starts at the verification layer where they can only log in or register.
Entering valid credentials will then grant the user access to the features layer where they can input commands to use cOOPer’s features.
At this layer, entering the logout
command will bring the user back to the verification layer.
The Architecture Diagram above shows the high-level design of cOOPer and how cOOPer’s components are connected.
Cooper
contains the main method of the program. Cooper
’s responsibilities are as such:
Cooper
initializes the components and loads stored user data into the components.Cooper
reads user input which is then processed by the components to produce a result.Cooper
shuts down the components and performs cleaning up where necessary.Apart from Cooper
, the rest of the app consists of these seven components:
Ui
: Handles the reading of user input and printing of messages to the terminal.Parser
: Interprets and validates user input.Verification
: Verifies that the user is signing in to cOOPer with valid credentials.Command
: Executes commands which are parsed from user input.Resources
: Manages data for cOOPer’s finance, meetings and forum features while the app is running.Storage
: Loads data from, and saves data to storage files in the computer hard disk.Util
: Provides utility which help with some of cOOPer’s features.ℹ️
userInput
represents the credentials input by the user for verification. For example,register John /pw 12345 /as admin
.
ℹ️
userInput
represents a command input by the user. For example,meetings
.
ℹ️XYZCommand
is an object representing a command recognised by cOOPer. For example,MeetingsCommand
.
API: cooper.ui
Ui
component consists of a parent Ui
class and its subclasses as shown by the class diagram above.Ui
class contains general constants and methods used across cOOPer’s components which read user input and print recurring messages.ABCUi
) contain constants and methods specific to the function of that component.
For example, FinanceUi
contains a method printBalanceSheet()
which prints a balance sheet formatted with headers containing the entries input by the user.Ui
component have static methods so Cooper
does not need to contain a reference to Ui
.The Ui
component:
[Logged out]
label indicates that the user is currently logged out of cOOPer.Verification
and Cooper
mainly for reading user input, while it is used by Resources
mainly for printing output messages.API: cooper.parser
Parser
component consists of an abstract ParserBase
class with its subclasses, CommandParser
and SignInDetailsParser
.SignInProtocol
object while user input at the features layer will be parsed to construct a Command
object.SignInProtocol
object executes the signing in of the user with details provided while the Command
object executes the command input by the user.ParserBase
contains a reference to the Parser
interface from the dopsun chatbot-cli library used by cOOPer.
More information about cOOPer’s implementation of the library can be found here.The Parser
component:
SignInProtocol
/ Command
object with the correct parsed attributes.InvalidCommandFormatException
, UnrecognisedCommandException
etc. to signal erroneous input.API: cooper.verification
Verification
component consists of a Verifier
class which contains the list of registered users and methods to verify user credentials.SignInProtocol
class is an abstract class representing one of the two sign in protocols, Login
or Registration
.SignInProtocol
class contains a reference to a SignInDetails
object which as a whole, represents a sign in attempt by the user using one of the two protocols, with the corresponding SignInDetails
.
For example, a Login
object containing SignInDetailsX
represents the user’s login attempt with the details SignInDetailsX
.Login
and Registration
override SignInProtocol
’s abstract method, executeSignIn()
as there are different conditions to check depending on whether the user is trying to log in or register. More on the verification process can be found here.The Verification
component:
API: Command.java
Command
component consists of an abstract Command
class and its subclasses as shown in the diagram above.ABCCommand
) overrides Command
’s abstract method, execute()
and has its own unique implementation of the method based on how the command is to be executed.HelloCommand
which prints out Hello world!
by inheriting from Command
and implementing the execute()
method as such:public class HelloCommand extends Command {
// constructors and attributes for execution of the command are omitted
public void execute() {
System.out.println("Hello world!");
}
}
ScheduleCommand
contains a String
representing the meeting name as well as an ArrayList
representing the users associated with that meeting.execute()
method takes in a SignInDetails
object as a parameter. This object represents the sign in details of a user who has successfully signed in to cOOPer. For some of cOOPer’s finance / meetings features which are only accessible by an admin, the UserRole
attribute of this SignInDetails
object is checked to grant correct access to the feature.The Command
component:
Resources
component depending on the command.Storage
component if there is any change to the data after the command is executed successfully.Ui
component to inform the user of the status of command execution.API: cooper.resources
The Resources
component contains ResourcesManager
which manages access rights to feature managers based on UserRole
.
E.g. the following line will only return a valid reference to FinanceManager
if userRole == UserRole.ADMIN
. Otherwise, null
will be returned indicating the user does not have the access right to that module.
FinanceManager financeManager = resourcesManager.getFinanceManager(userRole);
It also implements a give-receive pattern to pass private members of ResourcesManager
class to StorageManager
safely. This is because StorageManager
has “super privilege” to access internal data structures of feature managers and save to/ load from hard disk.
The Resources
component:
MeetingManager
, FinanceManager
or ForumManager
based on UserRole
of the request body. E.g. Only an admin is able to get FinanceManager
successfully.StorageManager
safely upon request.API: cooper.finance
Finance
component contains the FinanceManager
, BalanceSheet
, CashFlow
, and Projection
classes, as well as the FinanceCommand
enumeration.FinanceManager
constructs the instances of the BalanceSheet
, CashFlow
and Projection
for use, and contains attributes and methods that aid the related functions.FinanceCommamnd
enum helps the Parser
to understand what Finance
function is being used, with three states: CF
, BS
, and IDLE
.Finance
component also contains the PdfGenerator
class (not shown in the diagram above) for the generate
feature. More info on this feature can be found here.The Finance
component:
API: cooper.meetings
The Meetings
component contains the MeetingManager
and Meeting
classes.
MeetingManager
stores two attributes:
TreeMap<LocalTime, ArrayList<String>>
object,ArrayList<Meeting>
object.The MeetingManager
constructs the instances of Meeting
, and stores it as an ArrayList<Meeting>
in itself.
The Meetings
component:
API: cooper.forum
Forum
component contains a ForumManager
, ForumPost
, ForumComment
and ForumPostBase
. Both ForumPost
and ForumComment
are inherited from abstract base class ForumPostBase
as they contain the attributes content
and username
.ForumManager
keeps a list of ForumPost
s and each ForumPost
keeps a list of ForumComment
s.The Forum
component:
username
of the request body must match the username
field of the post or comment.API: cooper.storage
Storage
component consists of a parent Storage
class along with its subclasses as shown in the diagram above.Storage
class contains a filePath
attribute representing the path of the file where the data is to be stored. It also contains methods common to all its subclasses such as getScanner()
and createFileInDirectory()
which aid in the process of writing to and creating the storage file.Cooper
contains a reference to a StorageManager
object. This StorageManager
object in turn contains references to each of the subclasses of Storage
which store cOOPer’s data in the Verification
and Resources
components.ℹ️We do not put the
StorageManager
class underResources
for the following reasons:
Storage
class is cOOPer’s internal construct for bookkeeping various internal data structures and recovering them at startup. This does not categorise under any features the user can interact with and hence should not be kept underResourcesManager
.Storage
has super privileges to access internal data structures of all components inResources
. This contradicts the goal ofResourcesManager
which is to manage access rights to different features depending on user roles, and hence should be kept separate from it.
The Storage
component:
filePath
into the Verifier
, FinanceManager
, MeetingsManager
and ForumManager
objects upon launching the app.filePath
from the Verifier
, FinanceManager
, MeetingsManager
and ForumManager
whenever a change is made to the data in these objects.API: Util.java
Util
component is a component unrelated to cOOPer and serves mainly as a helper component to make some of cOOPer’s features possible.Util
class, namely inputStreamToTmpFile()
and inputStreamToString()
.inputStreamToTmpFile()
is used to recreate the dopsun chatbot-cli’s training files (originally located in src/main/resources/parser
).
In the process of packaging cOOPer into a JAR application, these training files are converted to bytes which are unable to be read in by the chatbot API. Hence, there is a need to recreate these files for the chatbot to work.inputStreamToString()
is used for cOOPer’s generate
feature which allows the user to generate a PDF file from data in cOOPer’s balance sheet or cash flow statement.
This method is used to convert the .tex
template files (located in src/main/resources/pdf
) into a String
object which can then be handled easily in the code. More details of the implementation can be found here.cOOPer uses the dopsun chatbot-cli library as its frontend parser that allows you to define any arbitrary input schema under src/main/resources/parser/command-data.properties
such as
login = login ${username-hint} /pw ${password-hint} /as ${role-hint}
The chatbot’s Parser
library automatically parses the place-holders defined with $
leaders to strings. For example, login Yvonne /pw 12345 /as admin
will be parsed
into the following fields:
{ "username-hint" : Yvonne,
"password-hint" : 12345,
"role-hint" : admin }
This gives great flexibility and extensibility to the Parser
component as you do not need to worry about writing new parsing schemes for every command and adding new commands to cOOPer for new features become trivial.
The Verifier
class facilitates the verification of the credentials of a user registering or logging in to cOOPer.
Different conditions are checked depending on whether a user is trying to log in or register. For example, if a user is trying to register, cOOPer will check if the username is already registered and asks the user to log in if they are not registered yet. On the other hand, if an unregistered user is trying to log in, cOOPer will ask the user to register first.
For a registered user trying to log in, cOOPer will first check if the entered password is correct. This is done with the help of the PasswordHasher
class which hashes the entered password with the user’s salt stored by cOOPer. The hash obtained will then be compared to the user’s stored hash to determine if the entered password is correct.
If the password is correct, the user’s role will then be checked to determine if they are logging in with the role they registered with.
The following sequence diagram shows the detailed process of registering a user.
ℹ️
userInput
isregister John /pw 123 /as admin
.
ℹ️TheexecuteSignIn()
method actually takes in arawPassword
as its parameters but is omitted in this sequence diagram as the registration process does not require the raw password of the user.
The SignInDetailsParser
constructs a SignInDetails
object parsed from the arguments in userInput
. This SignInDetails
object is then used to construct a Registration
object which executes the registration of the user. This process is shown by the sequence diagram below.
Assuming that the above registration has taken place successfully, the following sequence diagram shows the login process of the user.
ℹ️
userInput
islogin John /pw 123 /as admin
.
ℹ️The process of parsinguserInput
takes place similar to when a user is registering. The reference frame is omitted in this sequence diagram for simplicity.
The Password Based Key Derivation Function (PBKDF2) hashing algorithm is used for hashing user passwords. This algorithm is used together with a 64-bit salt text for each password before it is hashed to improve security and decrease susceptibility to rainbow-table attacks, where duplicate user passwords are still stored securely.
This algorithm is recommended by the National Institute of Standards and Technology (NIST) for password storage and our implementation also adheres to NIST specifications:
The Resources
component manages the access rights to other manager components like the FinanceManager
, MeetingManager
and ForumManager
. The following sequence diagram shows the two main operations of ResourcesManager
:
FinanceManager
, the user needs to pass in their UserRole
. ResourcesManager
will check if the user has the right access to the feature and returns the requested object if so, and null
otherwise.StorageManager
class has “super privilege” to access internal data structure of FinanceManager
, MeetingManager
and ForumManager
. Private members are passed safely using the give-receive pattern, instead of universal getters
.
The Finance
component provides features such as adding and listing of financial statements, i.e. the balance sheet and cash flow statement as well as compounded projection of Free Cash Flow growth.
The sequence diagram below illustrates the process of adding to a given financial statement, in this case the balance sheet.
When the user wants to add an entry to a financial statement, FinanceManager
will first determine if the amount should reflect as positive or negative in the financial statement, as well as which section of the financial statement the entry belongs to. FinanceManager
will then add the entry to the respective financial statement and its section’s net amount.
When the user wants to view a financial statement with list
, FinanceManager
will run a check that the net amounts of each section of the financial statement are calculated correctly before the statement is displayed to the output.
When the user wants to project free cash flow, FinanceManager
will first help to calculate free cash flow by subtracting the CapEx (Capital Expenditure: a field of the cash flow statement) from the total cash from Operating Activities. Subsequently FinanceManager
will compare this value to the previous year’s value, and calculate the percentage increase. This percentage increase will then be used in a recursive periodic compound interest formula to calculate the following year’s free cash flow, at the same percentage increase.
The PdfGenerator
abstract class is responsible for the generation of the financial statement as a PDF via the generate
command. It is inherited by the subclasses, BalanceSheetGenerator
and CashFlowStatementGenerator
, with each subclass containing different methods to add different sections to the PDF.
The PDF is generated with the help of an online LaTeX compiler. The LaTeX (.tex
) templates for the PDF can be found under src/main/resources/pdf
. The PdfGenerator
class employs the use of the inputStreamToString()
method of the Util
component to convert the contents of these LaTeX templates into a String
object. The LaTeX template, which is now a String
is then manipulated by calling Java String
methods like replace()
and append()
.
Certain identifiers (in the form of LaTeX comments ‘%
’) in the LaTeX template will be replaced by the actual values of cOOPer’s financial statement.
The example below shows the template of an entry in the financial statement:
\centering
% {Description}
& \centering
& \centering
& \centering
& % {Amount}
\\[3ex]
Calling replace("% {Description}", "Depreciation and Amortisation")
and replace("% {Amount}", 1500)
on the template above will result in the following:
\centering
Depreciation and Amortisation
& \centering
& \centering
& \centering
& 1500
\\[3ex]
When compiled, the LaTeX code above will correspond to an entry ‘Depreciation and Amortisation’ on the PDF with the amount $1500. This technique can be used on the header and summary templates which will format the header and summary of a particular section in the financial statement.
The methods createHeader()
, createEntry()
and createSummary()
in PdfGenerator
perform the text replacement as shown above. The diagram below shows how these methods correspond to the different parts of the ‘Operating Activities’ section in the cash flow statement PDF.
createHeader()
, createEntry()
and createSummary()
also add the template to an ArrayList
after performing the text replacement on the template. Iterating through the ArrayList
, these templates are then appended together using append()
.
This forms a long String
which is then sent to the online LaTeX compiler via a POST request. The reply data obtained from the request is used to construct the PDF via the write()
method of Java’s FileOutputStream
class.
The MeetingManager
class facilitates the storing of availability in cOOPer.
When the user declares an availability, the addAvailability
function in MeetingManager
performs some checks before successfully storing their availability.
addAvailability
looks at the format of the [date]
and [time]
entered, which will be checked using the isValidDateTimeFormat
function.addAvailability
checks if the time entered is at the start of the hour, using the isStartOfHour
function.addAvailability
checks if the user has already entered their availability under the same date and time, by checking all the names under the specified date and time in the availability
TreeMap.The following sequence diagram shows the detailed process of declaring an availability. username
is Sebastian
and userInput
is available 11-08-2021 14:00
.
The MeetingManager
class facilitates the scheduling of meetings.
When the user schedules a meeting ScheduleCommand
checks if the [date]
and [time]
parameter is entered and calls manualScheduleMeeting
in MeetingManager
if it is and autoScheduleMeeting
if it isn’t.
The following sequence diagram shows the process of automatically scheduling a meeting. username
of the user scheduling is Sebastian
and userInput
is schedule Project Meeting /with Eugene
.
The following sequence diagram shows the process of manually scheduling a meeting. username
of the user scheduling is Sebastian
and userInput
is schedule Project Meeting /with Eugene /at 11-08-2021 14:00
.
The following sequence diagram shows three operations with the forum, addPost
, commentPost
and deletePost
.
For adding a post, ForumManager
will create a new ForumPost
object and store its username and content.
For commenting on a post, ForumManager
will first check if the postId
specified is a valid index. If it is not, an exception will be thrown. Otherwise, it will get the ForumPost
by its postId
and add a new ForumComment
to it.
For deleting a post, ForumManager
will again check the postId
and delete the post only if the postId
is valid.
ℹ️ In the actual implementation,
ForumManager
will also ensure the username of the user requesting for thedeletePost
operation matches the owner of the forum post. This checking is omitted in this sequence diagram for simplicity and represented as the method call toremove(postId)
.
ℹ️Due to the way the
Storage
component is implemented, the classes and methods used for storage have names which are quite similar. In order to generalize the explanations in this section for how data is saved and loaded, the termXYZ
will be used as a placeholder whereXYZ
issignInDetails
,balanceSheet
,cashFlowStatement
,availability
,meetings
andforum
.
The StorageManager
class facilitates the saving and loading of cOOPer’s data to and from its storage files. This data includes the sign in details of registered users (from the Verification
component) , entries of the balance sheet and cash flow statement, list of availabilities, scheduled meetings, and forum posts (all from the Resources
component).
cOOPer’s data is stored separately in multiple text files named XYZ.txt
located in the ‘cooperData’ folder in the home folder.
Certain commands, when executed successfully, can change the data in the Verification
and Resources
components. (e.g. register
, add
, available
, etc.) Whenever the data in these components change, the command that made the change will call the saveXYZ()
method of the StorageManager
to update the storage file with the change.
For example, when a new availability is added successfully, the method saveAvailability()
is called by AvailableCommand
and the storage file is updated with the new list of availabilities.
The following sequence diagram shows the general procedure of saving data to the storage file whenever a change is made.
Data is loaded from cOOPer’s storage files into the Verification
and Resources
component upon launching the app. The StorageManager
constructor is first called and each subclass XYZStorage
is initialized with the file paths of their storage files, XYZ.txt
.
The loadAllData()
method of StorageManager
is then called and this method in turn calls the loadXYZ()
methods of the XYZStorage
subclasses. If the storage files are not present upon launching cOOPer, the storage files will be created and any error in file creation will be made known to the user.
Since data in the storage files are of a specific format, any change to the storage format will throw an InvalidFileDataException
and a message will be printed to specify the file containing invalid data.
The following sequence diagram shows the general procedure of loading data from the storage file upon launching cOOPer.
Current choice: Individual subclasses which store the sign in details of registered users, entries of the balance sheet and cash flow statement, list of availabilities, scheduled meetings, and forum posts in separate storage files.
saveXYZ()
, loadXYZ()
, etc.)The target user profile of cOOPer consists of all levels of administration in a tech startup, namely from the employee level of Secretary up to the management level of CEO.
Example Users:
cOOper’s value proposition: Manage company financials faster than typical human accounting means & manage company communication more reliably than a typical 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 … |
---|---|---|---|
*** |
new user | see usage ‘help’ instructions | refer to them when I forget how to use the application |
*** |
new user | register an account | login and return to my saved work at any point later on |
*** |
user | see a list of roles at login | login to the specific role I need to carry out a task |
*** |
user | have a password encrypted login | have my saved work be protected from any external tampering |
*** |
finance admin | create the company’s financial statements | assess the company’s current financial health accurately and quickly |
*** |
secretary employee | see all company personnel’s daily availability | schedule meetings between all available members easily |
** |
finance admin | automatically generate projections on the company’s yearly profitability | assess the company’s potential future growth |
** |
finance admin | generate the company’s financial statements as a PDF document | view and share a neat version of the financial statement with my colleagues |
** |
employee | make posts on a company forum | discuss difficulties or interesting developments in the company |
** |
employee | add my available timings for meetings | ease the scheduling of meetings with my colleagues |
** |
secretary employee | automatically schedule a meeting without having to know other person’s availability | save time on finding an appropriate time to meet |
* |
user in a hurry | customise shortcut keys in the app | save time on retrieving the data I desire |
java -version
. Ensure that you are using Java 11 or above.java -jar cOOPer.jar
. exit
.[Logged out]
.register [username] /pw [password] /as [role]
where [username]
is your username, [password]
is your password and [role]
is one of ‘admin’ or ‘employee’.[Logged out]
.login [username], /pw [password] /as [role]
where [username]
, [password]
and [role]
are the username, password and role you registered with.[Logged out]
label at the command prompt is no longer present.help
.bs
to initiate the balance sheet function.[Balance Sheet]
.add [amount]
to add the first entry. list
upon completion of the balance sheet.bs
to initiate the balance sheet function.[Balance Sheet]
.list
to view the current balance sheet. cf
to initiate the cash flow statement function.[Cash Flow]
.add [amount]
to add the first entry. list
upon completion of the cash flow statement.cf
to initiate the cash flow statement function.[Cash Flow]
.list
to view the current cash flow statement. cf
→ add
.proj [years]
to project up to your specified number of years. The generate
command works regardless of whether the prompt label is showing [Balance Sheet]
, [Cash Flow]
or is not even present.
bs
→ add
.generate bs
.cf
→ add
.generate cf
.available [date in dd-MM-yyyy] [time in HH:mm]
.availability
.schedule [meetingName] /with [username]
. [username]
is a user(not you) who is available in at least one common date and time as you.schedule [meetingName] /with [username] /at [date] [time]
. [username]
and yourself must be both available at this date and time.meetings
.post add hello world
.post comment hello world 2 /on 1
. post delete 1
. post list all
.logout
.
Expected output: A message informing you that you have logged out of cOOPer is shown along with the instructions on how to log in, register or exit. The label at the command prompt now shows [Logged out]
.