By: Team F10-2      Since: Sep 2018      Licence: MIT

1. Setting up

This section describes the necessary dependencies required to work on the application.

1.1. Prerequisites

  1. JDK 9 or later

    JDK 10 on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK 9.
  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

1.2. Setting up the project in your computer

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Make sure the correct JDK version is set up for Gradle

    1. Click Configure > Project Defaults > Project Structure. A window titled 'Project Structure for New Projects' will pop up

    2. Under the "Project SDK" heading, select the correct JDK version (JDK 9 or later).

      1. If the correct version does not appear in the drop-down list, click New…​ > JDK, then select the directory folder of the JDK, then click OK.
        For Windows, the default installation directory would be C:\Program Files\Java\jdk-[version-number]

    3. Click OK to accept the settings and close the window.

  4. Click Import Project

  5. Locate the build.gradle file in this project’s root directory on your computer and select it. Click OK. A window titled 'Import Project from Gradle' will pop up

  6. Click OK to accept the default settings. Gradle will now start building your project. Wait a short while for it to complete (it will say main: sync finished in IntelliJ’s Build tab when it is done)

  7. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

  8. Open XmlAdaptedLoan.java and MainWindow.java and check for any code errors

    1. Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully

    2. To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select Add '--add-modules=…​' to module compiler options for each error

  9. Repeat this for the test folder as well (e.g. check XmlUtilTest.java and HelpWindowTest.java for code errors, and if so, resolve it the same way)

1.3. Verifying the setup

  1. Run the loanbook.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

1.4. Configurations to do before writing code

1.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

1.4.2. Updating documentation to match your fork

After forking the repo, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level4 repo.

If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level4), you should do the following:

  1. Configure the site-wide documentation settings in build.gradle, such as the site-name, to suit your own project.

  2. Replace the URL in the attribute repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

1.4.3. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).

Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

1.4.4. Getting started with coding

When you are ready to start coding, you may move on to Section 2.1, “Architecture” to get some sense of the overall design.

2. Design

This section describes the implementation of the application.

2.1. Architecture

Architecture
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture.

Main has only one class called MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.

  • EventsCenter : This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design)

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

Events-Driven nature of the design

The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete i/1 x/a12345.

SDforDeleteLoan
Figure 3. Component interactions for delete i/1 x/a12345 command (part 1)
Note how the Model simply raises a LoanBookChangedEvent when the Loan Book data are changed, instead of asking the Storage to save the updates to the hard disk.

The diagram below shows how the EventsCenter reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.

SDforDeleteLoanEventHandling
Figure 4. Component interactions for delete i/1 x/a12345 command (part 2)
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 5. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, LoanListPanel, StatusBarFooter, BrowserPanel etc. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model change.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

2.3. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

  1. Logic uses the LoanBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a loan) and/or raise events.

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete i/1 x/a12345") API call.

DeleteLoanSdForLogic
Figure 7. Interactions Inside the Logic Component for the delete i/1 x/a12345 Command

2.4. Model component

The model component:

  • Stores the LoanBook data.

  • Stores the user’s preferences.

  • Exposes an unmodifiable ObservableList<Loan> that can be 'observed' by the UI, so that the UI automatically updates when the data in the model changes.

  • Does not depend on any of the other three components.

It allows the following operations:

  • Viewing, adding, modifying and deleting Bike s and Loan s from the LoanBook.

  • Retrieving lists of Bike s and Loan s that are registered in the LoanBook, filtered by a Predicate.

  • Undo/redo operations.

The component’s class diagram is given below:

ModelClassDiagram
Figure 8. Structure of the Model Component

API file: Model.java

The API is backed by a ModelManager which contains:

  • A VersionedLoanBook object which tracks the main data (i.e. Bike s and Loan s) within the LoanBook.

  • A filtered list each for Bike s and Loan s to expose to the UI.

  • A UserPrefs object to track the user’s preferences.

Most operations passed to the Model component deal with its VersionedLoanBook. A VersionedLoanBook is simply a regular LoanBook that keeps track of its own history, for the undo/redo operation. The LoanBook itself contains:

  • A LoanIdManager that helps to assign a unique LoanId to every Loan, so that Loan s may be easily identified.

  • A list of unique Bike s.

  • A list of unique Loan s.

A unique list in the sense above is a list where no two elements are the "same":

  • A Bike is considered to be the same as another Bike if their Name s match.

  • A Loan is considered to be the same as another Loan if their LoanId s match.

We want to exclude duplicate items from these lists to make sure that identifying any given Bike or Loan is simple and without ambiguity.

To facilitate this structure, both the Bike and Loan class implement a UniqueListItem interface, and the Bike and Loan lists inherit from a UniqueList class.

Most of the commands passed into the Model are forwarded to its LoanBook, which then executes these commands on the Bike and Loan lists that it has.

A Loan contains:

  • The customer’s particulars: Their Name, Nric, Phone and Email.

  • The Bike that the customer is renting.

  • A LoanStatus used to signal whether this Loan is Ongoing, Returned or Deleted.

  • A LoanId used for identifying this Loan.

  • A LoanRate specifying the rate which the bicycle is being loaned at, in $/hr.

  • A loanStartTime and loanEndTime, specifying the scheduled start and end times of the loan. loanEndTime might be null if the loan has no scheduled return time.

  • Any number of Tag s to provide additional details for the Loan.

A Bike contains:

  • A Name used for identifying this Bike.

  • A BikeStatus reflecting the current status of this Bike, e.g. whether it’s available for rental, loaned out, under repair etc.

Currently, the BikeStatus field within the Bike class is present but not being used for anything. A future use of this field is proposed in the section on Bicycle Management.
The DataField class is used to unify the common idea that the above details for Loan s and Bike s are specifiable by a user-inputted string. The only exception is the Bike field in the Loan class, which is specified using the Bike 's Name instead.

2.5. Storage component

StorageClassDiagram
Figure 9. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the Loan Book data in xml format and read it back.

2.6. Common classes

Classes used by multiple components are in the loanbook.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. Undo/Redo feature

3.1.1. Current Implementation

The undo/redo mechanism is facilitated by VersionedLoanBook. It extends LoanBook with an undo/redo history, stored internally as a loanBookStateList and currentStatePointer. Additionally, it implements the following operations:

  • VersionedLoanBook#commit() — Saves the current loan book state in its history.

  • VersionedLoanBook#undo() — Restores the previous loan book state from its history.

  • VersionedLoanBook#redo() — Restores a previously undone loan book state from its history.

These operations are exposed in the Model interface as Model#commitLoanBook(), Model#undoLoanBook() and Model#redoLoanBook() respectively.

Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.

Step 1. The user launches the application for the first time. The VersionedLoanBook will be initialized with the initial loan book state, and the currentStatePointer pointing to that single loan book state.

UndoRedoStartingStateListDiagram

Step 2. The user executes delete 5 command to delete the 5th loan in the loan book. The delete command calls Model#commitLoanBook(), causing the modified state of the loan book after the delete 5 command executes to be saved in the loanBookStateList, and the currentStatePointer is shifted to the newly inserted loan book state.

UndoRedoNewCommand1StateListDiagram

Step 3. The user executes add n/David …​ to add a new loan. The add command also calls Model#commitLoanBook(), causing another modified loan book state to be saved into the loanBookStateList.

UndoRedoNewCommand2StateListDiagram
If a command fails its execution, it will not call Model#commitLoanBook(), so the loan book state will not be saved into the loanBookStateList.

Step 4. The user now decides that adding the loan was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoLoanBook(), which will shift the currentStatePointer once to the left, pointing it to the previous loan book state, and restores the loan book to that state.

UndoRedoExecuteUndoStateListDiagram
If the currentStatePointer is at index 0, pointing to the initial loan book state, then there are no previous loan book states to restore. The undo command uses Model#canUndoLoanBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo.

The following sequence diagram shows how the undo operation works:

UndoRedoSequenceDiagram

The redo command does the opposite — it calls Model#redoLoanBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the loan book to that state.

If the currentStatePointer is at index loanBookStateList.size() - 1, pointing to the latest loan book state, then there are no undone loan book states to restore. The redo command uses Model#canRedoLoanBook() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.

Step 5. The user then decides to execute the command list. Commands that do not modify the loan book, such as list, will usually not call Model#commitLoanBook(), Model#undoLoanBook() or Model#redoLoanBook(). Thus, the loanBookStateList remains unchanged.

UndoRedoNewCommand3StateListDiagram

Step 6. The user executes clear, which calls Model#commitLoanBook(). Since the currentStatePointer is not pointing at the end of the loanBookStateList, all loan book states after the currentStatePointer will be purged. We designed it this way because it no longer makes sense to redo the add n/David …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoNewCommand4StateListDiagram

The following activity diagram summarizes what happens when a user executes a new command:

UndoRedoActivityDiagram

3.1.2. Design Considerations

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire loan book.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of memory usage.

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory (e.g. for delete, just save the loan being deleted).

    • Cons: We must ensure that the implementation of each individual command are correct.

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Use a list to store the history of loan book states.

    • Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.

    • Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both HistoryManager and VersionedLoanBook.

  • Alternative 2: Use HistoryManager for undo/redo

    • Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.

    • Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager now needs to do two different things.

3.2. Unique Loan ID

To keep individual loan transactions identifiable, we have a Loan ID for every loan. This will be automatically assigned when the user creates a Loan using the add command, and will be unique among all Loans in the Loanbook.

3.2.1. Current Implementation

In the current implementation, the Loan IDs are represented using integers. The added loans will be assigned IDs in increasing order starting from 0 (i.e. the first loan will be assigned ID 0, the second loan will be assigned ID 1, the third loan, ID 2, and so on).

Since this value will be a represented by a Java int, the maximum possible ID has been set to be 999,999,999 to prevent any integer overflow. The user will not be allowed to add a Loan using the add command after that maximum ID has been assigned.

The running ID mechanism is facilitated by the LoanIdManager class, which is a component within the LoanBook class. After initialization using the last used Loan ID value, it stores a running integer value which increments whenever the next Loan ID is generated using getNextAvailableLoanId().

Since the LoanIdManager is a component within the LoanBook class, this makes it possible to capture the internal state of the LoanIdManager at any point for future use. It allows for simple integration with the existing undo/redo mechanism, and can be stored within the same XML file that contains all other Loanbook details.

The following steps are taken when the user adds a new Loan using the add command:

Step 1: After typing an add command, the AddCommandParser will parse the user input and construct a new Loan instance with a placeholder Loan ID, which is then used to construct a new AddCommand instance.

Step 2: When AddCommand gets executed, the next available Loan ID will be generated via the Model interface. This will increment the internal running ID within the LoanIdManager of the VersionedLoanBook.

Step 3: A new Loan instance will be generated using the generated Loan ID value. This new Loan instance gets inserted into the VersionedLoanBook and gets committed.

The following sequence diagram shows how the add command functions, including the retrieval of the next Loan ID from the Model:

LoanIdAddCommandSequenceDiagram

3.2.2. Design Considerations

Aspect: Using an Integer for the Loan ID
  • Alternative: Use a String to represent the Loan ID

    • Pros: More Loan ID values can be created given the same number of characters.

      1. There would be no technical limit to the number of Loan IDs possible. If all Loan IDs of a certain length get exhausted, the next generated Loan ID can be one character longer.

    • Cons:

      1. Generating the next available Loan ID may be more complicated for a string as compared to an integer (though not by a significant amount).

    • Possible Implementation: A running counter, starting from 0, that resets at the start of every day. The Loan ID is formed by taking the current date, in "YYYYMMDD" format, and appending it with said running counter.

      • Pros: Simple to implement.

      • Cons: Depending on the end-user, the fact that the Loan ID displays the number of loans created during that day may be undesirable.

    • Possible implementation: Generate a string with a fixed number of characters.

      • Pros: The ID does not allow the number of loans created to be determined. This may be useful if this behaviour is desirable by the end-user.

      • Cons:

        1. The model needs to keep track of all the IDs that have been used before to prevent duplicates.

        2. The ID may be troublesome to type in the command-line, especially if the Loan ID is used for other commands.

Aspect: Having a LoanIdManager class simply to keep track of a running counter
  • Alternative: Have a static integer variable in the Loan or the VersionedLoanBook class

    • Pros:

      1. The process of incrementing the running value is simpler and has lesser steps than with a LoanIdManager class.

    • Cons:

      1. It is more complicated to capture the state of the running value. The undo/redo mechanism may not work with a static variable.

3.3. Authentication

Before critical actions such as deleting a loan can be performed, authentication of user is required. This ensures that only authorized users are able to perform critical actions. This is done by requiring a password before performing a critical action.

3.3.1. Current Implementation

The following provide a walkthrough on how the authentication feature is implemented.

The authentication mechanism is provided by the Password class. It implements the following operations that facilitates authentications:

  • Password#isSamePassword(currentPass, providedPass) — Checks if both providedPass and currentPass are the same password after decryption.

  • Password#generateSalt() — Generate a hashing salt for the app.

  • Password#encrypt(providedPass, salt) — Encrypt providedPass using hashing salt.

Given below is an example usage scenario and how the authentication mechanism behaves at each step:

Step 1. The user launches the application for the first time. UserPref will be initialised with a randomly generated salt by calling Password#generateSalt(), and a default hashed password from a12345.

Example of a hashing salt is:

R9hE4pccZYI3BYfQa6etrVD5n7sLRK8G9S8KJRLUqTvdJH15oYnsR1EyrYL2FGH1xDVulTrPEYt1ByVPoVmS4uDDzKzPjZJg2i9SPvOdOMtap6xfBVZLld7ZnEqgdMuhknvLzYilwKy8yHzaEsjk0PEhR5G1XaEMd2dqw6fAhng4ML5UzvIMo2s8HmB4NU7G5pk5yyiQYvam5XrLm0k24ZLUV7zbmmhKXACYlFKcEMMI9LzQcMwBXgN5KHe08H3U

Step 2. The user executes setpass a12345 newP4sS command to change the password to newP4sS.

Step 3. Input password will be checked against the app’s password using Password#isSamePassword() to ensure that the user has sufficient elevation to change the password of the app.

Step 4 Password class will encrypt the new password using Password#encrypt(), and call Model#setPass() to changes the password of the application in UserPref.

Example of an encrypted password:

d46XwZ4pClMXcItYHiNPJ4gHc8IKRFt8QnVDJd8axQ0=
If the current password input is wrong or if the current password is the same as the new password input, it will not call Model#setPass(), so the UserPref state will not be saved.

Step 5. Password in UserPref is saved to the encrypted value of the new password input.

The following sequence diagram shows how the setpass operation works:

setPasswordLogic

The following activity diagram summarizes what happens when a user executes setpass:

setPassActivityDiagram

Step 6. The user executes a critical command delete i/1 x/a12345.

Step 7. This command runs Model#getPass() to retrieve the current password. It then call Password#isSamePassword() to determine if the input password in the command is the same as the existing password.

Step 8. Deletion of loan at index 1 will occur.

If current password input is wrong, or if the index provided is invalid, deletion will not occur.

The following sequence diagram shows how the new delete operation works:

deleteLoanWithPass

The following activity diagram summarizes what happens when a user executes delete:

deleteActivityDiagram

3.3.2. Design Considerations

The following aspects are considerations that our team had made before the implementation of this feature.

Aspect: How to authenticate users

To ensure that only authorised users can execute elevated commands, we had to consider different alternatives on how to authenticate the user.

  • Alternative 1 (current choice): Require a password every time a critical command is executed

    • Pros:

      1. Ensures that each command authenticates the user before executing

      2. Easy to implement

    • Cons:

      1. Might be inconvenient to perform multiple critical commands as repeated typing of password is required.

  • Alternative 2: A login page to authenticate users

    • Pros:

      1. Customized to each staff

      2. Provides accountability of each command execution as we can now track which staff ran which commands

      3. Scalable for a large company.

    • Cons:

      1. Difficult to implement

      2. Not effective for our target audience as bicycle shop owners are often family-owned business, which does not have a large manpower.

      3. If a staff did not log out, non-authorized users will be able to execute critical commands, making the app’s data vulnerable.

Aspect: Method of encryption

To ensure that the app’s password is properly encrypted, we had to consider between storing the password locally, and storing the password online.

  • Alternative 1 (current choice): Generate a salt to encrypt password, and store the salt locally

    • Pros:

      1. Does not require the internet, ensuring that any hack attempts to the app has to be done physically.

      2. Much less complicated to implement as compared to alternatives solutions.

    • Cons:

      1. Encrypted password and salt can be accessed in preference.json.

  • Alternative 2: Send a POST request to an online server to authenticate each login request

    • Pros:

      1. If done properly, ensures that hashed password cannot be intercepted/retrieved by hackers.

    • Cons:

      1. Requires the internet, which might not be available to bicycle shop owners as parks are not fibre-optic ready.

      2. Difficult to implement.

      3. Data can be intercepted and manipulated during POST request, as opposed to a local storage of password.

3.4. Return a Loan feature

A LoanBook application that only allows users to do loans without being able to return them would not serve its purpose. When you take out a loan, it should automatically come with the feature of being returnable. This is the functionality for that.

3.4.1. Current Implementation

The source code for this implementation can be found in ReturnCommand.java.

Returning a loan is done by setting the corresponding status of the loan to LoanStatus.RETURNED. Other than setting the enum to be returned, the cost of the loan will also be calculated and displayed to the user.

The steps that have to be done by LoanBook is as follows.

  • Check the start and end time of the loan to ensure that the loan period is valid.

  • Store the current time as the return time of the loan.

  • Change the enum of LoanStatus to become LoanStatus.RETURNED.

  • Calculate the cost of the loan and display it in the GUI as a suggestion for the user.

This is summarized in the following Activity Diagram:

ReturnFlow

These steps would change the given Loan object, and update the properties of the Loan object. The appropriate values stored within the Loan object would therefore change accordingly.

Given below is an example usage scenerio and how the internals of the Loan would behave:

Step 1. The user would have to do a search for which loan they would like to return. This will pull up a list of loans from which the user would be able to select the correct loan.

Step 2. Using the current system time as the endTime, the duration of the loan will be calculated. Should the duration ever be negative, an error message will be thrown. This is because such a scenario would not ever be possible in the LoanBook.

Step 3. The user decides to return the current loan (in the case of the above image, the user wants to return loan at index 3, as selected). User will therefore key in return i/3.

The LoanBook will save the current time into the LoanTime endTime field. This would be done by calling the constructor LoanTime(). Also, the loanStatus field would also be updated from ONGOING to RETURNED.

return loan loan2

Note that originally the loan has a null value for end time. Note that the endTime and loanStatus values has been updated.

If the loan has already been returned or deleted, a corresponding error message will notify you that you cannot return a loan that is not ongoing. This check happens during this step.

 

Step 4. The function now calculates the cost of the loan. This price would be based on the amount of time the loan was active for, as well as the loanRate that was set, by multiplying the time with the rate.

Although the time saved is to the millisecond, the time that is multiplied when we are getting the cost is rounded down to the nearest minute. In a sense, this is "pro-rating" the cost, and making it more discrete.

The result is then displayed into the GUI for the user as the cost price of the loan.

It is possible for the LoanBook to have a loan that lasts for 0 minutes. This is because there may be some weird edge case where an object is loaned for less than a minute, which gets prorated down.

3.4.2. Sequence diagram

The sequence diagram has been split into 2 parts.
First, the sequence diagram of command parsing:

ReturnSequence

 

Second, the sequence diagram of command execution:

ReturnSequenceLogic

 

3.4.3. Design considerations:

Aspect: Using an enum vs changing the location of the loan
  • Alternative 1 (current choice): To create an enum that will store the status of the linked list.

    • Pros: Easy to implement the return a loan feature, and do not need to create new data storage features.

    • Cons: When using the summary function, it will take a longer amount of time, as the LoanBook would have to do a check at every step to ensure the correct data is appended to the correct place.

  • Alternative 2: Create new ArrayLists of Loans for each possible status of the Loan objects.

    • Pros: Computing the summary of the LoanBook would be much easier, and quicker.

    • Cons: return functionality would run much slower, as there will be empty slots in the ArrayList after shifting the Loan objects around. Searching for loans would also be much more difficult, as the results from the various Loan ArrayLists has to be appended together.

3.5. Summary Feature

In order to allow the user to use the LoanBook effectively as an auditing tool, they must be able to get a summary of all the loans that they have made. This summary should ideally give them an idea of the statistics of each of their transactions at a glance.

3.5.1. Current Implementation

The source code for this implementation can be found in SummaryCommand.java.

Getting the summary of all the loans is simply done by looping through all the stored loans before and aggregating the statistics of all the loans.

Currently, the summary command displays to the user the following statistics:

  • Total number of ongoing loans

  • Total number of loans ever taken out

  • Total revenue from the loan service

This would give a very handy summary page as well as a printable audit page for the bicycle shop owners.

Given below is an example usage scenario and how the internals of the LoanBook behaves:

Step 1. The user simply has to type the summary keyword into the CLI.

Step 2. The LoanBook proceeds to loop through all the loans and aggregate the statistics of the loans.

Step 3. The LoanBook updates the display with the aggregated statistics, and this can be screenshotted by the user as a summary page.

3.5.2. Design considerations:

Aspect: Getting the summary
  • Alternative 1 (current choice): Looping through the entire list of loans to get the summary of the content

    • Pros: Easy to implement, and do not need to create new data storage features.

    • Cons: Looping through all loans will take more time, as the LoanBook would have to check every loan and recalculate.

  • Alternative 2: Use a cache to store previously calculated values of the summaries.

    • Pros: Quicker response time to the users.

    • Cons: Much harder to implement, cache might break if user edits past loans.

3.5.3. Future features

We plan to include a feature which allows users to summarize the LoanBook based on certain parameters. For example, in future implementations, the user will be able to filter the statistics based on the identity of a bicycle.

These are the proposed features you would be able to search based on:

  • Bicycle

  • Time period

  • Person who took out the loan

As well, having to recalculate all the loans would be a bad idea when the LoanBook contains many loans. A proposed solution would be to cache the results of the main summary method, so that the LoanBook would only have to recalculate the numbers based on loans that have not been added to the final tally.

Another idea would be to display all the loans in a printable table, when the summary feature is called. Perhaps this would generate a pdf file which shows all the list of loans that the LoanBook is keeping track of. Users can then take use this as a more stringent auditing tool.

3.6. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.7, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

3.7. Configuration

Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json).

3.8. Bicycle Management

As bicycles potentially have a lot of properties, statuses and actions associated with them, they are represented in the code base as a dedicated Bike class.

Each LoanBook has its own list of Bike s, representing all the bicycles that a bicycle shop owner has at their disposal. Please refer to the class diagram under the Model section of this guide for a full view of how Bike s are handled within LoanBook.

Every bicycle has the following members:

  • name: A name that uniquely identifies the bicycle, e.g. the bicycle’s serial number.

  • status: The bicycle’s current status that represents where it is or what it is doing, e.g. AVAILABLE, LOANED_OUT.

Bike s are uniquely identified by their name, playing the same role as the id field for Loan s.

This implementation of bicycles affects existing commands in the following ways:

  • When add ing a new Loan to the LoanBook, LoanBook first checks if the Bike name provided by the command refers to an existing Bike within the LoanBook, and will reject the command if LoanBook does not. This check can only be done at the execution stage of the command (opposed to the parsing stage), as it requires specific knowledge of the model that it is acting on. A sequence diagram describing this behaviour in further detail is shown below:

BicycleManagementAddLoanSequenceDiagram
  • Similarly, when editing a loan, LoanBook also checks that the new Bike assigned to the loan exists within the LoanBook before it accepts the operation.

The list of bicycles is maintained in largely the same way as the list of loans. The equivalent commands of add, edit, delete and list for maintaining the list of Bike s are addbike, editbike, deletebike and listbikes respectively.

Please refer to the User Guide for the basic functionality for these commands. The following subsections describe additional points to note for these commands, such as differences from the corresponding commands for loans.

3.8.1. Adding a bike: addbike

addbike checks if there is a Bike with the same name already in the LoanBook, and rejects the command if so.

On the other hand, add does not check for "duplicate loans" as loans are automatically differentiated via their LoanID, even if all other parameters are the same.

3.8.2. Editing a bike’s details: editbike

editbike similarly checks if the Bike to be edited exists within the LoanBook, and whether the Bike 's new name, if specified, is the same as another Bike in the LoanBook.

In the ideal scenario, when a Bike is updated within LoanBook, all Loan s associated with it should be updated as well. Due to time constraints however, this feature has not been implemented. Here are two possible ways to implement this feature:

  • Make the Bike class mutable, so that by having a reference to the Bike object, Loan s will always be updated to the latest information. However, having a mutable class within the LoanBook may break the undo/redo feature, thus you should be careful when implementing this.

  • Whenever a Bike is updated, every Loan that references the Bike is manually updated. However, this is computationally unfavourable and there is more room for error, such as having un-updated Bike s left in the LoanBook.

3.8.3. Deleting a bike: deletebike

Deleting Bike s from the LoanBook is currently quite unrestrictive: no constraints are imposed on the Bike that is being deleted.

This idea has not been implemented yet, but due to the dependency of Loan s on Bike s, it can be enforced that any bicycle that has been loaned out at least once should not be deleted from the system, merely archived and labelled Decommissioned instead. The Bike can still be deleted completely if all of the Loan s associated with it are deleted (i.e. invalidated) first.

3.8.4. Viewing the list of bikes in the UI: listbikes

The only purpose of the listbikes command is to toggle the UI to show the list of bikes. To return to viewing the list of loans, use the list command.

Internally, the MainWindow class of LoanBook keeps track of two separate UI elements: a BikeListPanel and a LoanListPanel, both inheriting from a ListPanel object. It also keeps track of which list is currently active (i.e. displayed to the user) by using a ListPanel pointer, so that only the ListPanel referred to by this pointer is displayed. When the MainWindow receives a signal (an event thrown by list or listbikes) to toggle which list is being displayed, it simply reassigns this pointer and rebuilds the display.

3.8.5. Future Considerations

Tracking the status of bikes

As mentioned in the Model section earlier in this guide, a BikeStatus is currently present in the Bike class but does not do anything.

This sub-feature aims to enhance the existing purpose of the Bicycle Management feature: keep better track of bicycles and prevent invalid transactions from being carried out.

One way which this sub-feature can accomplish this is by using a BikeStatus to indicate whether the Bike is available or not for rental.

The activity diagram below demonstrates how this sub-feature could be implemented for the loaning out of bicycles:

BikeStatusAddLoanActivityDiagram

And this acitivty dragram demonstrates the same for returning bicycles:

BikeStatusReturnLoanActivityDiagram

Additionally, the deletion of a Bike can be denied if the Bike does not currently have a suitable status, such as being loaned out.

This sub-feature can be further expanded with additional BikeStatus es other than AVAILABLE and LOANED_OUT. Such statuses could reflect an unavailability for rental for reasons other than already being loaned out. For example:

  • NEEDS_REPAIR: Signals that the Bike is not suitable for rental as it is damaged, thus creating a hazard for customers.

  • UNDER_REPAIR: Signals that the Bike is not available for rental as it is currently with a repairman undergoing servicing.

  • ON_DISPLAY: Signals that the Bike is not suitable for rental as it is currently being used for display, to attract customers.

  • MISSING: Signals that the Bike is not available for rental as its location is unaccounted for; perhaps the last customer lost it, or it was stolen during the night.

  • DECOMMISSIONED: Signals that the Bike is no longer for rental for whatever reason (e.g. not economically feasible to repair), but remains in the LoanBook for administrative or archiving purposes.

Tracking the loans of a bike

Loan s already refer to Bike s, but Bike s can also track a list of references to the Loan s that loan them out. This could be especially useful for digging up the history of a particular Bike for investigative or analytical purposes.

This will make the LoanBook 's list of loans a little harder to manage, due to the extra references. To manage the deletion of loans, the loan will have to be dereferenced from the Bike it references before it gets deleted. The sequence diagram below demonstrates this process:

DeleteLoanSequenceDiagram
More bike properties

Additional fields can be added to the Bike class to track more properties specific to those Bike s. For example:

  • The bicycle’s brand, make and model.

  • The bicycle’s material and weight.

  • The bicycle’s length, height and wheel size.

  • The bicycle’s top speed and braking power.

  • How many gears the bicycle has, if at all.

These fields can then be filtered to find Bike s that could better suit a customer’s needs.

3.8.6. Design Considerations

Aspect: Internal representation of bicycles
  • Alternative 1 (current choice): Have a dedicated Bike class.

    • Pros: OO design. Easier to track status of bicycles. Bicycles can support more properties and features.

    • Cons: Complex internal representation. Additional memory used for tracking Bikes.

  • Alternative 2: Identify bikes by their names used in the Loans.

    • Pros: Simpler internal representation. Less data to manage internally or in storage.

    • Cons: Data on bikes have to be algorithmically searched for. Bicycles are assumed to exist and there is no verification for mistyped bicycle names. More difficult to implement bike-specific features.

3.9. Hide private information

Users must provide some important and personal information when adding a loan, e.g. Nric, so it is our responsibility to protect their privacy.

As a result, we have added a new feature to hide the private information from the Window.
Example: The customer’s Nric as stored in the database is Nric: T0248272F, but our LoanCard will only show Nric: Txxxxx72F.

LoanListPanelView

3.9.1. Current implementation

Hidden private information is facilitated by an interface called Censor. It is implemented by classes Nric, Phone and Email.

When showing a new loan, it will do the following operations:

  • LoanCard#LoanCard() — the constructor will assign values to each of the labels shown in the LoanCard.

  • Nric#getCensored() — censor the Nric value and hide the first five digital numbers. Then it returns the censored String.

  • Nric#doCensoring(int length) — Take in the length of the Nric String and return the censored part ("x" part) according to the length.

  • Phone#getCensored() — censor the phone number and hide the first five digital numbers. Then it returns the censored String.

  • Phone#doCensoring(int length) — Take in the length of the Phone String and return the censored part ("x" part) according to the length.

  • Email#getCensored() — censor the email address and hide every characters except the last two characters in the local-part and the domain.

  • Email#doCensoring(int length) — Take in the length of the Email String and return the censored part ("x" part) according to the length.

Given below is an example usage scenario and how this mechanism behaves at each step.

Step1. The user adds a new loan to the LoanBook. A new LoanCard object will be created.
The LoanCard object will contain information on the loan: LoanID, BikeID, Name, Nric, Phone, Email, LoanRate and LoanTime.

Step2. Assign the value of each of the components to their corresponding labels.
For example: name.setText(loan.getName().value); will directly assign the name String of this loan to the name label.

However, the values of Nric, Phone and Email need censoring before assigning their values, so they will call their own getCensored() method in their class.
getCensored() in each of these class will call their corresponding doCensoring(int) method. Then combine the censored part and remain part and return.
For example: phone.setText(loan.getPhone().getCensored().value); will censor the value of the phone String of this loan and then assign the censored value to the phone label.

The following sequence diagram shows how this operation works:

HideInfoSequenceDiagrams

3.9.2. Design Considerations

Aspect: How to execute getCensored() and doCensoring(int)

  • Alternative 1 (current choice): each class implements from Censor interface.

    • Pros: Easy to implement.

  • Alternative 2: Loan class implements from Censor interface.

    • Cons: The method might be bulky.

3.10. Send Reminder Email feature

3.10.1. Current Implementation

The email sending mechanism is supported by the JavaMail library. This feature is composed of three commands: checkemail, setemail and remind.

  • checkemail command: display the email address that the user has set to send emails from. The app will censor the email address.

    If user has not set an email, the email address will be an invalid email and the app show a "You have not set your email yet!" message.
  • setemail command: set the email address used for sending reminders to the customers.

    Only gmail is accepted by the app.
  • remind command: automatically generate an email containing some core information about the loan, and send it to the corresponding customer from user’s email. This command will use the JavaMail library.

    The email will contain the Name, BikeId, LoanStartTime and LoanRate of the loan.

Given below is an example usage scenario and how the reminder email mechanism behaves at each step.

Step 1. The user launches the application for the first time. The user’s email address is invalid by default.

Step 2. The user executes checkemail. The following sequence diagram shows how checkemail works:

CheckEmailSequenceDiagram

The command executes Model#getMyEmail(), which calls UserPrefs#getDefaultEmail() and returns user’s email stored in UserPref. The app checks if user’s email equals to "default". In this case, they are equal, so it throws a CommandException.

Step 3. The user executes setemail e/EMAIL x/PASSWORDFORAPP. This command does a few checks first:

  1. The app then checks if EMAIL equals to the user email that has been already set to the app. If so, then a CommandException is thrown, to warn the user that they are setting the same email as last time.

  2. Otherwise, SetEmailCommand#isValidGmail(EMAIL) is called to check if EMAIL is a valid gmail. If not, a CommandException is thrown to warn the user that they are setting invalid email address.

Step 4. The user forgets what email has been set before, so the user executes checkemail again. The procedure is the same as in Step 2. However, the app detects that user’s email is not the default invalid string this time, so it creates an Email(userEmail) object and executes Email#getCensored(). Then, the function returns a CommandResult with a success message and the censored user email.

Step 5. The user sends a reminder email to a customer by executing remind pw/EMAILPASSWORD id/LOAN_ID. This command implements the following operations:

  1. Execute Model#getLoanById() and return an optional<Loan> object called targetLoan.

  2. Check if targetLoan.isPresent() returns a non-null loan, then check if its LoanStatus is ONGOING.

  3. If its LoanStatus is ONGOING, create a SendReminder(model, EMAILPASSWORD, loan) object and execute SendReminder#send() to send email.
    The SendReminder#send() method calls SendReminder#createReminderEmail(session, userEmail) to create the content of the email, connects user’s email using EMAILPASSWORD and finally send the email.

    For Steps (b), if the next step cannot be executed, a CommandException with corresponding failure message will be thrown.
    If it throws a CommandException with authentication failed message in step (c), please check if user’s email and password are correct. If they are, please make sure that the Less secure apps setting of user’s email is enabled. If it is not, enable it and refresh the page.

3.10.2. Design considerations

Aspect: use user’s personal email vs share an immutable common email

  • Alternative 1 (current choice): Use a user-specified email.

    • Pros:

      • The user has a choice of what email address to use.

      • The user needs to type in the correct password if they want to send a reminder email, which ensures the security and privacy of the email.

    • Cons:

      • The user needs to enable the Less secure apps setting, which could be a security concern.

  • Alternative 2: Use a common email provided by the app.

    • Pros:

      • The user does not need to set their own email when sending reminder emails, reducing hassle when setting up.

      • The user does not need to type in a password when sending a reminder email.

    • Cons:

      • The common email is less secure, as its password can be found within the program.

      • The password of the common email can be changed by malicious users.

      • The email might get overloaded.

      • Customers might receive spam emails, as the remind command does not require a password.

3.10.3. Future considerations

We plan to add a feature that can automatically send an e-receipt to the customer after return ing a loan.

The e-receipt will contain the Name, BikeID, LoanStartTime, LoanEndTime and TotalCost of the loan.

3.11. [Proposed] Improved delete functionality

3.11.1. Current Implementation

When the user deletes a loan from the LoanBook, it deletes the correct loan, then commits the result into memory. What this entails is that the entire XML file gets overwritten when each delete command is being called.

3.11.2. Proposed implementation:

From above, there is already a LoanStatus that has been implemented. What this does is that it allows the loan to be marked as deleted by setting the enum to the appropriate value. This would result in the LoanBook not needing to recommit every change.

From here, when the user decides to close the LoanBook, it will then loop through all the loans and filter out the ones which have not been deleted. These are the loans that would be kept and saved in the LoanBook.

3.11.3. Design Considerations

Aspect: Deleting a loan

  • Alternative 1 (current choice): Remove each loan as they are deleted

    • Pros: Easy to implement

    • Cons: Large time taken for memory operations as reading and writing to the data is required.

  • Alternative 2 (proposed implementation): Use an enum to implement lazy deletion.

    • Pros: Much more time efficient due to lazy deletion

    • Cons: Implementation is harder, and changing to this implementation would affect the undo and redo features.

  • Alternative 3: Fix the LoanBook storage manager so that it does not rewrite the entire XML file after each delete operation.

    • Pros: All operations are now lazy. Much better performance of the application because commit operations do not need to change the large XML file.

    • Cons: Large refactoring work required. May potentially affect the timeline of development of the other functions.

4. Documentation

This section describes the way we document our project. We use asciidoc for writing documentation.

We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

4.1. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

4.2. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

4.3. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 10. Saving documentation as PDF files in Chrome

4.4. Site-wide Documentation Settings

The build.gradle file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.

Attributes left unset in the build.gradle file will use their default value, if any.
Table 1. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items.

not set

4.5. Per-file Documentation Settings

Each .adoc file may also specify some file-specific asciidoc attributes which affects how the file is rendered.

Asciidoctor’s built-in attributes may be specified and used as well.

Attributes left unset in .adoc files will use their default value, if any.
Table 2. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

4.6. Site Template

The files in docs/stylesheets are the CSS stylesheets of the site. You can modify them to change some properties of the site’s design.

The files in docs/templates controls the rendering of .adoc files into HTML5. These template files are written in a mixture of Ruby and Slim.

Modifying the template files in docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

5. Testing

We write extensive tests to prevent regression and allow other developers to build on our work without fear of breaking other parts. This section provides the necessary steps to run tests.

5.1. Running Tests

There are three ways to run tests.

The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

5.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in loanbook.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. loanbook.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. loanbook.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. loanbook.logic.LogicManagerTest

The tests within the add command parsers do not take into account the feedback string from within the display. This is because of the time sensitive nature of the add command. It does not take in a specific LoanTime when the loan is created and populates the current system time into the Loan. Therefore, the strings that is returned for each command is time dependant. Hence it is impossible to do a string matching on the returned display string and a hard coded value.

In this case, we commented out the check for equality of the string from the add command in the GUI against the expected hard coded string.

However we do not need to test the equality of the displayed strings to ensure that the add command is working properly. We can simply check that the size of the list of Loans in the LoanBook has increased by 1, as well as to ensure that the Loan that was added into the LoanBook has the same base property as the default Loan that was added.

An alternative would be to use a regex to do string pattern matching on the added Loan. This can be combined with getting the current system time of the tests to check that the display strings match, and the saved Loans are correctly added. However, this modification would take time to develop and to ensure that it is correct. As such, such developments would be left to be completed in future releases of LoanBook.

5.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, HelpWindow.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

6. Dev Ops

This sections provides a brief summary of the tools we use in the project, and how to integrate them into the project.

6.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

6.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

6.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

6.4. Code Quality

We use Codacy to track the code quality of our projects. See UsingCodacy.adoc for more details.

6.5. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

6.6. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

6.7. Managing Dependencies

A project often depends on third-party libraries. For example, Loan Book depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

LoanBook targets users who run a bicycle rental business in Singapore. Users who will find this product especially useful are those who are familiar with desktop applications, and who prefer using a command-line interface (CLI) over a graphic user interface (GUI) for input. A reason for this preference is that using a mouse to access app features may be slower and more accident-prone than typing on a keyboard.

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

bicycle rental shop owner who wants to manage my bicycles

register my bicycles into the app

keep track of my bicycles within the app

* * *

bicycle rental shop owner who wants to manage my bicycles

edit details for my bicycles within the app

update the system with the latest bicycle statuses (e.g. got damaged, repaired, changed name or ID)

* * *

bicycle rental shop owner who wants to manage my bicycles

remove bicycles from the app

reflect the decommissioning of bicycles in the app

* *

bicycle rental shop owner who wants to manage my bicycles

track the features of my bicycles (e.g. wheel size, gears, has a bell)

search for particular bicycles or types of bicycles better, especially when serving customers

*

bicycle rental shop owner who wants to manage my bicycles

mark a bicycle with a repair end date

I can be reminded by the app on when to collect my bike from the repairman

* * *

bicycle rental shop owner who wants to manage my loans

initiate new loans into the app

keep track of my loans within the app

* *

bicycle rental shop owner who wants to manage my loans

edit a loan’s details

correct a typo or adapt to a customer’s change of mind

* * *

bicycle rental shop owner who wants to manage my loans

register the completion of a loan (i.e. return of the bike) in the system

update the system (make the bike available again, receive payment etc.)

* * *

bicycle rental shop owner who wants to manage my loans

cancel a loan

update the system in case the loan is invalidated (e.g. the customer changes their mind)

* *

bicycle rental shop owner who wants to manage my loans

be automatically notified and given details if the same customer tries to rent two bikes simultaneously

not loan out another bicycle to a customer who has not returned their previous bike

* *

bicycle rental shop owner who wants to manage my loans

be automatically notified and given details if two customers try to loan the same bike simultaneously

not loan out an already loaned bike

* * *

bicycle rental shop owner who wants to manage my bicycles and loans

view a list of all bicycles or loans

get a big picture of my current state

* * *

bicycle rental shop owner who wants to manage my bicycles and loans

choose to view the full details of a particular bicycle or loan

get details about a particular bicycle or loan as needed or desired

* *

bicycle rental shop owner who wants to manage my bicycle paraphernalia

tag loans that loan additional items (e.g. helmets, attachable headlights)

keep track of which transactions concern them

* * *

bicycle rental shop owner who wants to accommodate customer demands

register rentals in advance and reserve bicycles

be reminded of reservations, and not accidentally loan the bicycle out thus becoming unable to meet the reservation

* *

bicycle rental shop owner who wants to manage my loans

see at a glance what loans are overdue

automatically remind people who are still in possession of my bicycles to return them

*

bicycle rental shop owner who wants to manage my loans

get an automatic reminder when a loan is overdue

contact the customer to tell them that their time is up

* * *

bicycle rental shop owner that has to deal with an overdue loan

view the contact details of bicycle loaners

contact them to check on their (and my bicycle’s) status

* * *

bicycle rental shop owner

view past loans of my bicycles

analyze my own history or present records for whatever purpose (accounting, investigation, legal etc.)

* *

bicycle rental shop owner

view loan activity over a specific period of time (e.g. Jun to Aug 2018)

analyze my own history for a focussed period of time

*

bicycle rental shop owner who wants to optimize my business

see which bicycles are the least popular to be rented

investigate them for issues or possibly decommission them

* * *

bicycle rental shop owner who is proficient with a CLI

use a CLI for all operations within the app

give commands faster and with less error

* * *

bicycle rental shop owner who wants to meet customer demands promptly

readily view a list of available bicycles

make recommendations to customers to serve them faster

* *

bicycle rental shop owner who wants to manage my bicycles and loans

have my list of bicycles or loans sorted (by name, date etc.)

search for a particular entry much more easily

* *

bicycle rental shop owner who wants to manage my bicycles and loans

filter my list of bicycles or loans with a keyword

search for a particular entry much more easily

* *

bicycle rental shop owner who wants to manage my bicycles and loans

view only a subset of my bikes or loans with entries in a particular range (e.g. date, loan rate)

search for a particular entry much more easily

* *

bicycle rental shop owner who wants to make returning a bike easy

automatically calculate the correct amount of money based on the time they loaned the bike for

I don’t have to calculate the cost manually, and thus be faster with less mistakes

* *

bicycle rental shop owner who wants to manage my bicycles

add tags to bicycles

categorize and later search for them more easily

* *

bicycle rental shop owner who wants to manage my bicycles and loans

undo and redo recent changes in the app

quickly recover in the event of a command mistake

*

bicycle rental shop owner who wants to expand my service capabilities

access the same data on different machines simultaneously

manage multiple service counters efficiently and accurately

*

bicycle rental shop owner who wants to expand my service capabilities

hook the app up with the national database

automatically retrieve some customer details (e.g. name, gender) when I type in their NRIC

*

bicycle rental shop owner who wants to expand my service capabilities

integrate the app with an NRIC barcode scanner

automate entering a customer’s details

* *

bicycle rental shop owner who respects my customer’s privacy

partially or fully hide some of the customers’ personal data (e.g. NRIC, phone)

minimize the chance of leaking those details out to third parties

*

bicycle rental shop owner who is concerned about the data’s security

encrypt the app’s data

ensure no one can access or modify it with third-party software

*

bicycle rental shop owner who is concerned about the data’s security

set a username and password for accessing the app

lock unknown parties out from the app

* *

bicycle rental shop owner who is concerned about the data’s security

prompt the user (myself) for a confirmation or a password before I delete entries

prevent accidental or hasty deletions of data

*

bicycle rental shop owner who is concerned about the data’s security

back-up the app’s data

recover quickly in the case of data corruption or just general data unavailability

* * *

new or inexperienced user

readily access the Help page for the app

have a quick and comprehensive reference on how to get started and do things

* * *

new or inexperienced user

see command usage instructions whenever I mistype

correct myself on the spot

Appendix C: Use Cases

(For all use cases below, the System is the LoanBook and the Actor is the user, unless specified otherwise)

Use case: Delete loan

MSS

  1. User requests to list loans.

  2. LoanBook shows a list of loans.

  3. User requests to delete a loan in the list by specifying a list index and the app’s password.

  4. LoanBook deletes the loan.

  5. LoanBook displays a prompt to the user indicating success.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. LoanBook shows an error message.

      Use case resumes at step 2.

  • 4a. The given password is invalid.

    • 3a1. LoanBook shows an error message.

      Use case resumes at step 2.

Use case: Add a bike into loan book

MSS

  1. User requests to add a bike with the specific parameters into the LoanBook.

  2. LoanBook adds the bike.

  3. LoanBook displays a prompt to the user indicating success.

    Use case ends.

Extensions

  • 1a. The user input is not of the correct format.

    • 1a1. LoanBook shows an error message giving the user an example command, as well as correct format of the command.

  • 1b. Any one of the inputs fails their respective validation checks.

    • 1b1. LoanBook shows an error message telling the user how to rectify their command.

      Use case ends.

Use case: Add a loan into loan book

MSS

  1. User requests to add a loan with the specific parameters into the LoanBook.

  2. LoanBook adds the loan.

  3. LoanBook displays a prompt to the user indicating success.

    Use case ends.

Extensions

  • 1a. The user input is not of the correct format.

    • 1a1. LoanBook shows an error message giving the user an example command, as well as correct format of the command.

  • 1b. Any one of the inputs fails their respective validation checks.

    • 1b1. LoanBook shows an error message telling the user how to rectify their command.

      Use case ends.

Use case: Mark a loan as returned in the LoanBook

MSS

  1. User requests to search for a loan with a specific name or tag.

  2. LoanBook shows a list of loans that fulfills the condition.

  3. User requests to return a specific loan from the list by specifying a LoanID.

  4. LoanBook marks the loan as returned.

  5. LoanBook prompts the user to charge the customer a certain amount of money, based on the prevailing rates and rental times in the loan book.

    Use case ends.

Extensions

  • 2a. The list is empty.

  • 2a1. LoanBook feedbacks to the user that no loans fulfilling the condition were found.

    Use case ends.

  • 3a. The given LoanID is invalid.

    • 3a1. LoanBook shows an error message.

  • 3b. The loan at the given LoanID has been returned.

    • 3b1. LoanBook shows an error message, stating the time which the loan has been returned.

      Use case resumes at step 2.

Use case: Reset the entire loanbook

MSS

  1. User requests to reset the loanbook.

  2. LoanBook prompts the user for their password.

  3. User enters the correct password.

  4. LoanBook clears its entire history of loans.

    Use case ends.

Extensions

  • 3a. The user inputs an incorrect password.

    • 3a1. LoanBook shows an error message telling the user that the password that they entered is incorrect.

      Use case ends.

Use case: Give a summary of all transactions

MSS

  1. User requests for a summary of all transactions.

  2. LoanBook gives a summary of all the transactions that that had happened.

    Use case ends.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 9 or higher installed.

  2. Should be able to hold up to 1000 loans without a noticeable sluggishness in performance for typical usage.

  3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

{More to be added}

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
If this is your first time using the app, note that the default password of the app is set to a12345.

F.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

F.2. Adding a bike

  1. Adding a bike whose name contains alphanumeric characters, spaces, and hyphens

    1. Prerequisites: List all bikes using the listbikes command.

    2. Test case: addbike B i-k e 0 01-
      Expected: Bike B i-k e 0 01- is added to the bike list. Details of the updated bike is shown in the status message. Timestamp in the status bar is updated.

  2. Adding a bike that already exists in the bike list

    1. Prerequisites: List all bikes using the listbikes command. Bike1 exists in the list.

    2. Test case: addbike Bike1
      Expected: No bike is added as a bike with the same name already exists within the LoanBook. Error details shown in the status message. Status bar remains the same.

F.3. Adding a loan

  1. Adding a loan while all loans are listed

    1. Prerequisites: List all loans using the list command. Multiple loans in the list. A Bike with the name Bike001 is present in the Loanbook. The last used Loan ID is smaller than 999,999,999, or the Loanbook is new.

    2. Test case: add n/David ic/T0248272F p/98765432 e/david@example.com b/Bike001 lr/3.5 t/Overnight
      Expected: A new loan is added to the list.

      1. The editable details of the loan (e.g. Name, NRIC, etc.) match the fields specified in the command.

      2. The Loan ID of the new loan is larger than that of all existing loans.

      3. The "Start Time" field is equal to the time of execution of the add command.

      4. The tags that appear are "Ongoing" and "Overnight".

  2. Adding a loan with a bike that does not exist

    1. Prerequisites: A bike with the name MissingBike does not exist in the Loanbook.

    2. Test case: add n/David ic/T0248272F p/98765432 e/david@example.com b/MissingBike lr/3.5 t/Overnight
      Expected: No new loan is created. The following error message should appear: "No bike with that name exists within the loan book."

F.4. Edit a bike

  1. Editing a bike while all bikes are listed

    1. Prerequisites: List all bikes using the listbikes command. BikeToRename, Bike001 and Bike002 are in the list.

    2. Test case: editbike BikeToRename n/New Bike
      Expected: The bike BikeToRename will be renamed to "New Bike". Details of the updated bike is shown in the status message. Timestamp in the status bar is updated.

    3. Test case: editbike Bike002 n/Bike001
      Expected: No bike is edited due to the specified name matching that of an existing bike, thus creating a duplicate bike if the command proceeds. Error details shown in the status message. Status bar remains the same.

    4. Test case: editbike ThisBikeDoesNotExist n/New Bike
      Expected: No bike is edited due to the specified name not matching that of any existing bike. Error details shown in the status message. Status bar remains the same.

    5. Other incorrect delete commands to try: editbike
      Expected: Similar to previous.

F.5. Deleting a bike

  1. Deleting a bike while all bikes are listed

    1. Prerequisites: List all bikes using the listbikes command. Bike001 and B i-k e 0 01- are in the list.

    2. Test case: deletebike B i-k e 0 01- x/a12345
      Expected: The bike B i-k e 0 01- is deleted from the list. Details of the deleted bike is shown in the status message. Timestamp in the status bar is updated.

    3. Test case: deletebike NotAnExistingBike x/a12345
      Expected: No bike is deleted due to the supplied name not referring to an existing bike. Error details shown in the status message. Status bar remains the same.

    4. Test case: deletebike Bike001 x/wr0ngP4sS
      Expected: No bike is deleted due to invalid password supplied. Error details shown in the status message. Status bar remains the same.

    5. Other incorrect delete commands to try: deletebike
      Expected: Similar to previous.

F.6. Deleting a loan

  1. Deleting a loan while all loans are listed

    1. Prerequisites: List all loans using the list command. Multiple loans in the list.

    2. Test case: delete i/1 x/a12345
      Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete i/0 x/a12345
      Expected: No loan is deleted due to invalid index supplied. Error details shown in the status message. Status bar remains the same.

    4. Test case: delete i/1 x/wr0ngP4sS
      Expected: No loan is deleted due to invalid password supplied. Error details shown in the status message. Status bar remains the same.

    5. Other incorrect delete commands to try: delete, delete i/x x/a12345 (where x is larger than the list size)
      Expected: Similar to previous.

F.7. Changing password

  1. Changing the password of the application

    1. Prerequisites: Both existing and new password should be alphanumeric of length between 6 and 10 inclusive. Existing password is currently a12345.

    2. Test case: setpass wrongPass newWorld
      Expected: Password remains the same as current password entered is wrong.

    3. Test case: setpass a12345
      Expected: Password remains the same as new password entered is not alphanumeric.

    4. Test case: setpass a12345 newPass
      Expected: Password is now changed to newPass.

    5. Other incorrect setpass commands to try: setpass, setpass a12345.

F.8. Search loans

  1. Search loans within a specific time frame

    1. Prerequisites: Input date is of the correct format (YYYY-MM-DD)

    2. Test case: search 2018-01-01 2018-01-01 Expected: Shows loan that was created on 2018-01-01.

    3. Test case: search 2018-02-30 2018-02-30 Expected: Does not filter loans as 2018-02-30 is not a valid date.

    4. Test case: search 2018-01-02 2018-01-01 Expected: Does not filter loans as start date should be before end date.

    5. Test case: search 2018-01-01 2018-12-31 Expected: Shows all loan created in year 2018.

    6. Other incorrect search commands to try: search, search 2018-01-01.

F.9. Return a loan functionality

Prerequisites: List all loans using the list command. Ensure there are multiple loans in the list.

  1. Returning a valid loan

    1. Choose a loan that has not been returned. These loans are marked with the ONGOING tag. Find the index number of it and run return i/<INDEX>.
      Expected: The loan is returned successfully. The display message details that the loan has been deleted, and gives a suggestion for the cost price of the duration of the loan.

    2. Choose another ongoing loan from the list and run return i/<INDEX>.
      Expected: The loan is returned successfully as well. The display message details that the loan has been deleted, and gives a suggestion for the cost price of the duration of the loan.

  2. Negative tests:

    1. Test case: return i/0
      Expected: No loan is returned. Error details shown in the status message, the message should say "the specified index is invalid" in this case. Status bar remains the same.

    2. Other incorrect return commands to try:

      1. return

      2. return i/x (where x is larger than the list size)

      3. return i/aaa (Where aaa is a string)

      4. return i/y (where y is the index of a loan that is not ONGOING).

F.10. Summary functionality

Use the summary command on various sizes of the LoanBook to test this functionality

  1. Create an empty LoanBook and run the summary command.
    Expected: The summary should report the following:

    • Total loans: 0

    • Loans in progress: 0

    • Returned loans: 0

    • Total product loan time: 0 minutes

    • Total revenue: $0.00

  2. Add 1 loan with a rate of $30/hr. Run the summary command.
    Expected: The displayed page should report the following:

    • Total loans: 1

    • Loans in progress: 1

    • Returned loans: 0

    • Total product loan time: 0 minutes

    • Total revenue: $0.00

  3. Add 3 more loans with rates $120/hr. Run the summary command.
    Expected: The displayed page should report the following:

    • Total loans: 4

    • Loans in progress: 4

    • Returned loans: 0

    • Total product loan time: 0 minutes

    • Total revenue: $0.00

  4. Return the first loan that you have added from above after exactly 1 minute, and run the summary command.
    Expected: The displayed page should report the following:

    • Total loans: 4

    • Loans in progress: 3

    • Returned loans: 1

    • Total product loan time: 1 minutes

    • Total revenue: $0.50

  5. Return 2 of the 3 loans that you have added from above after exactly 1 and 2 minutes from when you added them, respectively. Run the summary command.
    Expected: The displayed page should report the following:

    • Total loans: 4

    • Loans in progress: 1

    • Returned loans: 3

    • Total product loan time: 4 minutes

    • Total revenue: $6.50

F.11. Sending Reminder Emails

Prerequisites: Make sure the system is connected to the network and the network allows smtp. The email must be gmail and its Less secure apps option is enabled.

  1. Send a reminder email successfully. Please follow these steps:

    • add a loan with a valid customer’s email (but you own it of course).

    • Execute the checkemail command. Then use the setemail command to set the sender’s email.

    • After that, execute remind pw/CORRECTWMAILPW id/ONGOING_LOAN_ID.

Expected: Receive a success message "Email sent!" on the screen. A reminder email should have been sent from the sender’s email (check the Sent folder). The same email should also be in the inbox of the customer’s email account that you have set.

  1. Negative tests:

    1. Test case: setemail e/OLD.EMAIL@gmail.com x/PASSWORDFORAPP
      Expected: Failure message "The new email should not be the same as the old email!" will show. User’s email is not changed.

    2. Test case: setemail e/NEW.EMAIL@outlook.com x/PASSWORDFORAPP
      Expected: Failure message "Your new email address must be valid gmail." will show. User’s email is not changed.

    3. Test case: remind pw/CORRECTPASSWORD id/NON_EXIST_ID
      Expected: Failure message "There is no Loan with this ID in the LoanBook!" will show. The email is not sent.

    4. Test case: remind pw/CORRECTPASSWORD id/RETURNED_ID
      Expected: Failure message "There is no need to send a reminder, because the loan is RETURNED" will show. The email is not sent.

    5. Test case: remind pw/WRONGPASSWORD id/VALID_LOAN_ID
      Expected: Failure message "Your password might be incorrect, or you have not enabled the \"Less secure apps\" setting in your Google account settings. Please refer to the User Guide by pressing F1!" will show. The email is not sent.

    6. Test case: Make sure your email is set, disable its less secure apps setting and refresh the setting page. Execute remind pw/CORRECTPASSWORD id/VALID_LOAN_ID
      Expected: Failure message "Your password might be incorrect, or you have not enabled the \"Less secure apps\" setting in your Google account settings. Please refer to the User Guide by pressing F1!" will show. The email is not sent.

    7. Test case: Disconnect your network. Execute remind pw/CORRECTPASSWORD id/VALID_LOAN_ID
      Expected: Failure message "No connection to the network!" will show. The email is not sent.

    8. Other incorrect commands to try: setemail, setemail x (where x can be any string), remind, remind x y z (no prefix, x, y and z can be any string).