Skip to content

Latest commit

 

History

History
1090 lines (770 loc) · 46.9 KB

DeveloperGuide.adoc

File metadata and controls

1090 lines (770 loc) · 46.9 KB

LiBerry- Developer Guide

1. Introduction

1.1. LiBerry

LiBerry is a desktop app for librarians to quickly manage their community libraries! LiBerry is optimized for librarians who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). You can type quickly and serve your long line of borrowers in a short amount of time. LiBerry can manage all your books and borrowers efficiently and meticulously.

1.2. Purpose of Developer Guide

This developer guide is targeted towards potential developers of the project and it aims to explain:

  • The design of the software architecture of the system using a top-down approach

  • The implementation and behaviour of the main features of the system.

1.3. Legend

💡
Denotes useful tips.
ℹ️
Denotes additional information.

2. Setting up

To set up LiBerry on your system, please refer to the guide here.

3. Design

In this section, we will explain the design and behaviour of the top-level components in the system, which are the following:

  • Architecture overview

  • User Interface (UI) Component

  • Logic Component

  • Model Component

  • Storage Component

3.1. Architecture

This sub-section shows the relationship between the major components at the highest level, illustrated by the following diagram.

ArchitectureDiagram
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 .puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the Using PlantUML guide to learn how to create and edit diagrams.

Main has two classes called Main and 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. The following class plays an important role at the architecture level:

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

The rest of the system consists of four components.

  • UI: The UI of the system.

  • Logic: The command executor.

  • Model: Holds the data of the system 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

 

How the architecture components interact with each other

The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command add t/Animal Farm a/George.

AddBook
Figure 3. Component interactions for add t/Animal Farm a/George command

 

In the diagram above, we can see how the components integrate together to execute a single command.
 
The sections below give more details about each component, starting of with the UI component.

3.2. UI component

This sub-section shows the structure of the User Interface (UI) and the relationship between each component in the UI.
 
The following diagram aims to illustrate how each UI sub-component is linked to one another.

UiClassDiagram
Figure 4. Structure of the UI Component

 

In the figure above, we can see the association between the different UI sub-components, as well as the classes that interact with the external Logic and Model components.

The UI consists of a MainWindow that is made up these main parts:

  • CommandBox

  • ResultDisplay

  • BookListPanel

  • Other smaller components

All these, including the MainWindow, inherit from the abstract UiPart class.

API : Ui.java

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.

  • Listens for changes to Model data so that the UI can be updated with the modified data.

Given below is the Sequence Diagram for interactions within the UI component when the user enters an add command. The exact command entered is add t/Animal Farm a/George.

UiAddBookSequenceDiagram
Figure 5. Interactions Inside the UI Component for the add t/Animal Farm a/George Command

 

In the figure above, we can see how the UI components invoke the execute method of the Logic class in order to obtain and subsequently display the result of the execution.

The following activity diagram summarizes what happens to the UI component when a user executes a new command:

UiUpdateBookListActivityDiagram
Figure 6. Flow of Events within UI

 
The activity diagram above aims to illustrate how UI only updates the BookListPanel when the catalog is being updated by a command.
We will now move on to give more details about the Logic component.

3.3. Logic component

In this sub-section, we will explain the internal workings of the Logic component, which handles the execution of the different commands.
 
The following class diagram aims to show how the 'Command Design Pattern' is used to achieve a high-level form of encapsulation of the Command object.

LogicClassDiagram
Figure 7. Structure of the Logic Component

 
In the diagram above, we can see that the LogicManager executes the Command class without knowledge of what each command does. This is achieve through polymorphism where all possible commands extend from the Command class.

API : Logic.java

  1. Logic uses the CatalogParser 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 book).

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

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("add t/Animal Farm a/George") API call.

AddBookLogicSequenceDiagram
Figure 8. Interactions Inside the Logic Component for the add t/Animal Farm a/George Command

 
In the diagram above, we can see that the Logic component’s execute is invoked by the UI component from before. A series of method calls would invoke the addBook method of the Model, moving the chain of calls further downstream.

ℹ️
The lifeline for AddCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.  

In short, the Logic component interprets the different commands and execute them accordingly. Most of these commands will have to interact with the Model component, which we will explore in the next sub-section.

3.4. Model component

The Model component is mainly composed of the Book, Borrower and Loan classes and shows how they are related to one another.  

The figure below shows the relationship between smaller components. These smaller components are modelled after real world objects.

ModelClassDiagram
Figure 9. Structure of the Model Component

 

The figure illustrates the composition of the Model component. The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Catalog data.

  • stores the Loan Records.

  • stores the Borrower Records.

  • references a borrower that is being served if the model is in serve mode.

  • references a list of filtered books which depends on the state of the model.

  • exposes an unmodifiable ObservableList<Book> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.

API : Model.java

 
When there are changes in the Model component, the system will update its in-memory via the Storage component, which will be explained in-depth in the next section.

3.5. Storage component

The Storage component is responsible for updating the memory of the system (in JSON format) whenever there are changes.

 
The figure below aims to show the different records storage that are implemented in LiBerry.

StorageClassDiagram
Figure 10. Structure of the Storage Component

 

In the figure above, we can see that we are maintaining 4 different storages. These storages aim to keep the memory of:

  • UserPrefs

  • Catalog

  • BorrowerRecords

  • LoanRecords

API : Storage.java

The Storage component,

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

  • can save LiBerry data in JSON format and read it back.

There are certain classes (eg. Utility classes) that are used by different components. In the following section, we will explain how we allow all components to access these classes.

3.6. Common classes

Classes used by multiple components are in the seedu.addressbook.commons package. These classes include (to list a few):

  • User Settings

  • Exceptions

  • Utility classes like DateUtil, FineUtil and JsonUtil

We will now move on to the next section, which aims to explain the implementation of some of our main features.

4. Implementation

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

4.1. Add book feature

This feature allows a user to add a new book to the LiBerry system.

4.1.1. Implementation

The add book function is facilitated by Catalog. The Catalog stores a list of books, representing the books in the library. Additionally, it implements the following operation:

  • Catalog#addBook(book) — Add a new book to the list of books in the catalog.

Given below is an activity diagram of a book being added to the catalog.

AddBookActivityDiagram
Figure 11. Activity Diagram for adding a book

 

Given below is a class diagram of a book.

BookClassDiagram
Figure 12. Class Diagram for Book

 

Notice how the book can hold either 1 or 0 loans, depending on whether it is currently loaned out or not.
 

Given below is the object diagram of a newly added book.

BookObjectDiagram
Figure 13. Object Diagram for Book

 

We can see that the book holds a Optional<Loan> in order to have either 0 or 1 Loan objects. This makes it consistent with the class diagram of Book above.

4.1.2. Design Considerations

Aspect: Data structure to store books.
  • Alternative 1 : Store them only in a ObservableList as per the original AddressBook implementation.

    • Pros: Will be easy to implement.

    • Cons: May have performance issues in terms of efficiency in retrieving books.

  • Alternative 2 (current choice): Store them in a HashMap.

    • Pros: Will be easier (and more readable in code) to retrieve books by serial number.

    • Cons: Will incur additional memory to maintain the HashMap.

Aspect: Generating a unique serial number.

Since we allow librarians to provide a valid serial number to new books if they wish so, we cannot generate the serial number using the number of books or the largest serial number.

Eg: The system now has "B00009" and "B00010".

If we generate based on number of books, we get the serial number "B00003", wasting the serial numbers "B00001" and "B00002".

If we generate based on the largest serial number, we get the serial number "B00011", wasting all the unused serial numbers before it.

We need to come up with a solution to give us "B00001" in the given example.

  • Alternative 1 (current choice): Use a TreeMap to store current serial numbers such that we can efficiently determine the next serial number in running order.

    • Pros: Will be efficient in generating the next valid serial number.

    • Cons: Will incur additional memory to maintain the TreeMap.

  • Alternative 2: Use brute force to start iterating from "B00001" to obtain the first unused serial number.

    • Pros: Will be easy to implement.

    • Cons: Will be inefficient once the number of books grow.

4.2. Undo/Redo feature

4.2.1. Implementation

The undo/redo mechanism is facilitated by CommandHistory. It contains a undo/redo command history, stored internally as an commandHistoryList and currentCommandPointer. Additionally, it implements the following operations:

  • CommandHistory#commit() — Saves the current reversible command in its command history.

  • CommandHistory#undo() — Undoes the most recent reversible command.

  • CommandHistory#redo() — Redoes the most recent previously undone command.

These operations are exposed in the Model interface as Model#commitCommand(), Model#undoCommand() and Model#redoCommand() respectively.

The undo/redo mechanism only works for commands that implements the ReversibleCommand interface. The ReversibleCommand interface specifies that the commands these two operations:

  • ReversibleCommand#getUndoCommand() — Returns a command that undo the ReversibleCommand.

  • ReversibleCommand#getRedoCommand() — Returns a command that redo the ReversibleCommand.

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 CommandHistory will be initialized with an empty commandHistoryList.

UndoRedoState0
Figure 14. Initial state of CommandHistroy

Step 2. The user executes delete 5 command to delete the 5th book in the catalog. The delete command calls Model#commitCommand(), causing the delete 5 command to be saved in the commandHistoryList, and the currentCommandPointer is pointed to the newly inserted command.

UndoRedoState1
Figure 15. State of CommandHistory after delete 5

Step 3. The user executes add t/Animal Farm …​ to add a new book. The add command also calls Model#commitCommand(), causing the add command to be saved into the catalogHistoryList.

UndoRedoState2
Figure 16. State of CommandHistory after add t/Animal Farm
ℹ️
If a command fails its execution, it will not call Model#commitCommand(), so the command will not be saved into the commandHistoryList.

Step 4. The user now decides that adding the book was a mistake, and decides to undo that action by executing the UndoCommand. During the execution of the UndoCommand, Model#undoCommand() will be called. This would call CommandHistory#undo(), which will retrieve the most recent ReversibleCommand that was executed, which is the add command. ReversibleCommand#getUndoCommand() would then be called and the Command returned would be executed, undoing the add command. This will then shift the currentCommandPointer once to the left, pointing it to the previous ReversibleCommand in the commandListHistory.

UndoRedoState3
Figure 17. State of CommandHistory after undo
ℹ️
If the currentCommandPointer is at index -1, pointing to no command, then there are no previous command to undo. The undo command uses Model#canUndoCommand() 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:

UndoSequenceDiagram
Figure 18. Sequence diagram for undo command
ℹ️
The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

The redo command does the opposite — it calls Model#redoCommand(), which shifts the currentCommandPointer once to the right, pointing to the previously undone Command, and executes the redo command from ReversibleCommand#getRedoCommand().

ℹ️
If the currentCommandPointer is at index catalogHistoryList.size() - 1, pointing to the latest command, then there are no undone command to redo. The redo command uses Model#canRedoCommand() 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 help. Commands that do not modify the model, such as help, will usually not call Model#commitCommand(),Model#undoCommand() or Model#redoCommand(). Thus, the commandHistoryList remains unchanged.

UndoRedoState4
Figure 19. State of CommandHistory after help

Step 6. The user executes clear, which calls Model#commitCommand(). Since the currentCommandPointer is not pointing at the end of the commandHistoryList, all commands after the currentCommandPointer will be purged. We designed it this way because it no longer makes sense to redo the add t/Animal Farm …​ command. This is the behavior that most modern desktop applications follow.

UndoRedoState5
Figure 20. State of CommandHistory after clear

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

CommitActivityDiagram
Figure 21. Activity diagram for committing Command

4.2.2. Design Considerations

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Individual command knows how to undo/redo by itself.

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

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

  • Alternative 2: Saves the entire catalog.

    • Pros: Easy to implement.

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

Considering our target audience, community libraries, which may be poor. They might be not able to afford a large amount of data storage. As a library may contain many books, borrowers and loans, storing a state of application for each command can be memory intensive. Hence, we chose to implement Alternative 1 so as to reduce the amount of memory usage.

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Use a list to store the commands for undo and redo.

    • Pros: Only need to maintain one data structure.

    • Cons: Harder for new developers to understand the mechanism for undo and redo.

  • Alternative 2: Use two stacks to store a list of undoable and redoable commands.

    • Pros: Easy for future developers to understand as there are two separate stacks to keep track of the command to undo and redo.

    • Cons: Additional time required to add and pop from the stack.

We chose alternative 1 as it is easier to maintain a single data structure.

4.3. Generate Loan Slip feature

4.3.1. Proposed Implementation

The printing of loan slip feature is facilitated by LoanSlipUtil. Essentially, it implements the following operations:

  • LoanSlipUtil#mountLoan() — Mounts a loan slip in preparation for generation of loan slip in pdf form.

  • LoanSlipUtil#unmountLoan() — Unmounts a loan slip ,usually after generating a pdf version of it.

  • LoanSlipUtil#createLoanSlipInDirectory() — Creates a pdf version of the mounted loan slip in the loan_slips folder.

Given below is the sequence diagram of the generation of loan slip during the loan of a book.

LoanSlipGeneration
Figure 22. Sequence Diagram for the generation of a loan slip

 

The following describes the sequence of events displayed in the figure above.

1) The LoanCommand is executed
2) The LoanCommand retrieves the Book and the Borrower
3) The LoanCommand creates a new Loan
4) The LoanCommand mounts the new loan in LoanSlipUtil
5) The Storage component creates and save a new LoanSlipDocument in a saved folder
6) The Logic component opens the newly generated LoanSlipDocument for the librarian to print it immediately
7) The Logic component unmounts the LoanSlipDocument at the end of the process

4.3.2. Design Considerations

Aspect: How to create and use an instance of a LoanSlipDocument.
  • Alternative 1 : Use the LoanSlipDocument constructor directly.

    • Pros: Will be straightforward to implement.

    • Cons: The Logic component and the LoanCommand object needs to have knowledge on all individual methods of LoanSlipDocument to be able to create a loan slip.

  • Alternative 2 (current choice): Create a Facade class LoanSlipUtil to facilitate creation of LoanSlipDocument.

    • Pros: The Logic component and the LoanCommand object can now use the full functionality of LoanSlipDocument via the static class LoanSlipUtil without knowing the internal implementation of LoanSlipDocument.

    • Cons: There is more code to be written and we must consider how to save state within a static class such that it can be continually reused.

We have decided to go with Alternative 2. The Facade class provides the system with a simplified view of generating a loan slip, making it easier to use. It also decouples the code, making it easier to modify in the future.

Aspect: Implementation to allow extension (loan multiple books at one go).
  • Alternative 1 (current choice): Mount a loan in LoanSlipUtil for each book.

    • Pros: Will be easy to extend in the future as we can just mount multiple loans using LoanSlipUtil before generating all loans in a single loan slip.

    • Cons: Will require more code when mounting loans in the Facade class.

  • Alternative 2: Re-create LoanSlipDocument whenever a new loan comes in.

    • Pros: Will only need to make adjustments to Logic component to contain an Optional<LoanSlipDocument> field and update accordingly whenever a new Loan comes in.

    • Cons: Violates Single Responsibility Principle as the Logic class will now have to change if we change the implementation of LoanSlipDocument.

4.4. 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 4.5, “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

4.5. Configuration

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

4.6. Book Loaning Features

4.6.1. Functionalities

The functionalities and commands associated with the book loaning feature are:

  • loan sn/BOOK_SN

    • Loans out a book based on its serial number.

  • return INDEX [-all]

    • Returns a book based on the index of that book shown on the GUI.

  • renew INDEX [-all]

    • Renews a book based on the index of that book shown on the GUI.

  • reserve sn/BOOK_SN or reserve INDEX

    • Reserves a book based on its serial number or index of that book shown on the GUI.

  • pay AMOUNT

    • Pay a fine amount for overdue books.

The rationale for the different types of arguments for the loan, return, renew and reserve may not be apparent at first, but it is actually very simple.

  • When borrowers come to the librarian (our user) to borrow a book, the book itself would have a serial number. Thus, the user just types in the serial number of the book to be loaned out, instead of using the find command to locate that book in LiBerry to use its index.

  • Whereas, when a borrower comes to return a book, the librarian only sees a limited list of book that was loaned out by the borrower on the GUI. Hence, the librarian need not type the longer serial number to return that book, and instead, types its index shown in the GUI list. Likewise, the same idea is applied to renewing books

  • When reserving books for borrowers, it is possible that the book is found through the GUI, and thus, index is used. Or, the borrower brings the physical book forward to reserve it as he/she does not want to borrow it now.

4.6.2. Implementation

This feature is mainly facilitated by the Loan association class between a Book and a Borrower. The object diagram just after a book is loaned out can be seen below.

LoanObjectDiagram
Figure 23. Loan object diagram after a new Loan is created

 

In this instance, the Borrower with BorrowerId K0789 currently has a Book with SerialNumber B00456 loaned out. The Loan associated to this loan, with LoanId L000123, is stored in the LoanRecords class of the model component.

Both the Book and Borrower objects also have access to this Loan object.

In each Loan object, only the BorrowerId of the Borrower and SerialNumber of the Book is stored to reduce circular dependency.

The LoanRecords class stores all the Loan objects tracked by LiBerry in a HashMap, where the key is its LoanId.

Immutability of each object is supported to ensure correctness of undo and redo functionality.

Loaning

The following activity diagram summarizes what happens when a user enters a loan command:

LoaningActivityDiagram
Figure 24. Activity diagram when a loan command is entered

 

ℹ️
The else branch of each branch node should have a guard condition [else] but due to a limitation of PlantUML, they are not shown.

When a book is successfully loaned out by a borrower, a new Loan object is created. The LoanId is automatically generated according to the number of loans in the LoanRecords object in the model. The startDate is also automatically set to today’s date. The endDate is automatically set according to the loan period set in the user settings. This Loan object is added to LoanRecords through the call to Model#addLoan(loan).

The new Borrower instance is created by copying the details of the borrower from the original object, and also with this Loan object being added into its currentLoanList. The new borrower object then replaces the old borrower object in the BorrowerRecords object in the model. These two steps are done through the method call to Model#servingBorrowerNewLoan(loan). The new Book instance is also created by copying the details of the original book object, and likewise, with this Loan object added into it. Similarly, the new book object replaces the old book object in the Catalog object in the model through the call to Model#setBook(bookToBeLoaned, loanedOutBook). These were done to support immutability of the objects.

Returning

The following activity diagram summarizes what happens when a user enters a return command:

ReturningActivityDiagram
Figure 25. Activity diagram when a book is being returned.

 

ℹ️
The else branch of each branch node should have a guard condition [else] but due to a limitation of PlantUML, they are not shown.

When a loaned out book is successfully returned by a borrower, the associated Loan object is moved from the borrower’s currentLoanList to returnedLoanList. Inside the book object, this Loan object is also removed. Inside this loan object, the returnDate is added according to today’s date. The remainingFineAmount of this loan object is also calculated based on the fine amount set in the user settings.

Similarly, the creation of new objects for replacement are also done to support immutability. They are supported by the methods Model#setBook(bookToBeReturned, returnedBook) and Model#servingBorrowerReturnLoan(returningLoan).

Renewing

When a book is successfully renewed by a borrower, the renewCount of the Loan object is incremented by 1 and its dueDate is also increased by the renew period set in the user settings.

Similarly, the creation of new objects for replacement are also done to support immutability.

Reserving

When a book is successfully reserved by a borrower, the Book object is marked as reserved and is added to the borrower’s reservedBookList.

Similarly, the creation of new objects for replacement are also done to support immutability.

Paying fines

When a fine amount is successfully paid by a borrower, the remainingFineAmount and paidFineAmount of the loans in the borrower’s returnedLoanList is updated accordingly.

Similarly, the creation of new objects for replacement are also done to support immutability.

4.6.3. Design Considerations

Aspect: File storage of loans

Inside the model, for each current loan (loans that are not returned yet), the Book, the Borrower and the LoanRecords point to the same the same Loan object. LiBerry’s storage system is such that Catalog stores the books, BorrowerRecords stores the borrowers and LoanRecords stores the loans. Thus, a decision was made to decide how these loans are serialized and stored in the user’s file system.

  • Alternative 1: Save the whole Loan object in each book in the catalog.json and save the the whole of every Loan object in each borrower in borrowerrecords.json. The Loan object is also duplicated in loanrecords.json.

    • Pros: Easy to implement. No need to read storage files in a specific order.

    • Cons: Storage memory size issues. Same information is duplicated and stored in all 3 storage files.

  • Alternative 2 (selected choice): Save only the LoanId of each Loan object in each book in the catalog.json and save a list of LoanId in each borrower in borrowerrecords.json. The whole Loan object is only saved in loanrecords.json. When reading the storage files at the start of the application, loanrecords.json need to be read in first, before the borrowers and books can be read in as they would get the loan objects from the LoanRecords based on their LoanId s.

    • Pros: Uses less memory as only LoanId is stored for the books and borrowers, instead of the whole serialized loan objects. Also, LoanRecords thus serve as a single source of truth.

    • Cons: Must ensure that the reading of stored files are in the correct order, and also correct Loan objects are referenced after reading in borrowerrecords.json and catalog.json. Method used to retrieve a Loan object from its LoanId must also be fast enough as there can be hundreds of thousands of loans.

Aspect: Data structure to support recording of loans in LoanRecords
  • Alternative 1: Use a list data structure, such as an ArrayList to store the loans in the model component.

    • Pros: Easy to implement. Easy to obtain insertion order of the loans and sort through the list.

    • Cons: Slow to search for a Loan based on its LoanId, i.e., O(n) time, as the list must be traversed to find the correct associated Loan object. The additional time taken adds up when reading the storage files during the starting up of the application. Thus, it can make the application feel laggy and unresponsive at the start.

  • Alternative 2 (selected choice): Use a HashMap to store the loans, where the key is its LoanId.

    • Pros: Fast to retrieve a Loan object based on its LoanId, i.e., O(1) time.

    • Cons: Insertion order is not preserved. Have to traverse through all the loan objects in the HashMap to check their startDate in order to obtain their insertion order.

4.7. Book Finding

The command for finding a book in the catalog is as follows:
find [NUMBER] { [t/TITLE] [a/AUTHOR] [g/GENRE]…​ [sn/BOOK_SN]] [-overdue] [-loaned] [-available] }

4.7.1. Implementation

ModelManager contains a FilteredList of Books (filteredBooks), which is used to display books on the LiBerry GUI. Book finding works by starting converting the command string in to a BookPredicate object, then updating filteredBooks with that predicate.

The parsing of the command string to create the required BookPredicate object is done with the help of the ArgumentTokenizer object. ArgumentTokenizer tokenizes the command string to generate an ArgumentMultimap, which is internally a HashMap of predicate values paired to prefix keys. The FindCommandParser then extracts all the values from the ArgumentMultimap prefix by prefix and building the predicate through functions such as setTitle(), setGenres() setLoanStatus etc.

The diagram below shows a simplifed execution sequence of a 'find t/Animal Farm a/George' command string.

FindSequenceDiagram
Figure 26. Sequence Diagram showing the execution of a Find Command input

The BookPredicate class stores in its fields the specific values to match. Default values are mostly null, which will indicate that there is no need to filter for that field. Below is an example.

Object Diagram showing the fields present in an empty (left) and partially filled (right) BookPredicate object

BookPredicateEmptyObjectDiagram BookPredicateFilledObjectDiagram

The figure above shows what happens when we are trying to filter for books with title 'harry' and 'Potter' that are loaned out, showing up to 5 books only. Notice that the rest of the fields in the object are null.

4.7.2. Design Considerations

Aspect: Ensuring only 1 Loan Status Flag

In order for LiBerry to display only books that are loaned, available or overdue, flags are used. All flags have the prefix -, and the ArgumentTokenizer is able to detect this. However, a user can technically enter more than 1 of such loan status flags eg. -loaned -available. This is not meaningful, as there can be multiple interpretations of this statement. The user could be looking for both types of books (which will show every book), or books that are both loaned and available (which will show none). To prevent such meaningless confusion, there is a need for only 1 such flag to be accepted in the BookPredicate.

  • Alternative 1: Hard code a priority for loan status flags and accept the highest one

    • Pros: Easy to implement

    • Cons: Does not make it clear for the user why an unintended display is shown

  • Alternative 2 (Currently Used): Raise an exception whenever there are more than 1 loan status flags

    • Pros: Helps user clarify misconception of using more than 1 loan status flag

    • Cons: Slightly more complicated code where the output ArgumentMultimap

Aspect: Limiting the Number of Books to Display

As users generally do not want to be flooded with information when using the find command, a display limit [NUMBER] is used. Users can ask for a limited number of books to display. However, the FilteredList JavaFx class that is used to implement the list of filtered books does not have an API that sets a hard limit on the number of books to show. A work-around has to be made.

  • Alternative 1: Create an new class that extends the JavaFx FilteredList class

    • Pros: Does not require a change in other parts of the code

    • Cons: Hard to implement. Need to know the ins and outs of FilteredList

  • Alternative 2 (Currently Used): Create a counter variable in BookPredicate that decrements after every passed test

    • Pros: Easy to implement.

    • Cons: Not the most cleanest way of implementation

4.8. Register borrower feature

4.8.1. Implementation

The register borrower feature is facilitated by BorrowerRecords. The BorrowerRecords stores a list of borrowers, representing the borrowers registered into the library system. The command to register a borrower into the library system is as followed:

register n/NAME p/PHONE_NUMBER e/EMAIL

Given below is an activity diagram of a borrower being registered into the Borrower Records of the library.

Activity Diagram for registering a borrower

RegisterBorrowerActivityDiagram  

Given below is a class diagram of a book.

BorrowerClassDiagram
Figure 27. Class Diagram for Borrower

 

Given below is the object diagram of a newly registered borrower.

BorrowerObjectDiagram
Figure 28. Object Diagram for Borrower

 

4.8.2. Design Considerations

Aspect: Generating a unique borrower ID

Every time a new borrower is being registered, the system will automatically generate a borrower ID for the borrower which the borrower will have to use every time the borrower borrows books from the library. Initially, what we proposed is that, every time a new borrower is being registered into the system, we find the size of the list of borrowers, we add 1 and set it as the borrower ID of the new borrower.

Eg: There are 100 borrowers in the system. The new borrower’s ID will be "K0101".

However, we decided to implement a new function, which is to allow borrowers to be removed from the library system. Therefore, this method does not work anymore. So we decided to change to generate the new ID based on the first-found available ID.

5. Documentation

Refer to the guide here.

6. Testing

Refer to the guide here.

7. Dev Ops

Refer to the guide here.

8. Appendices

8.1. Product Scope

Target user profile:

  • a librarian in a small town library that has to serve many library users (borrowers) quickly

  • has a need to manage a significant number of books and borrowers

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

Value proposition: Many people visit the neighborhood library to borrow books and also donate their books. There is always a long queue in this small library and the librarian would have to type quickly to handle the long queue. LiBerry can manage a library system faster than a typical mouse/GUI driven app.

8.2. User Stories

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

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

* * *

librarian

add a book brought/donated by people to the library

maintain a record of all the books in the library

* * *

librarian

delete books that are no longer available

maintain a record of all the books in the library

* * *

helpful librarian

search for certain book by the title/author/genre

help borrowers check if it is available

* * *

forgetful librarian

mark a book as loaned

tell borrowers that the book is loaned out and unavailable for borrowing

* * *

forgetful librarian

mark a book as available

let borrowers know that the book will now be available for borrowing

* * *

librarian

generate a list of overdue books and their borrowers

know which borrower has overdue books and which books are overdue

* * *

librarian

generate a list of currently loaned / available books

do inventory checks

* * *

meticulous librarian

record the movement of books in and out

keep track of available books here

* * *

helpful librarian

register a new borrower in the system

help new borrowers start borrowing books

* * *

librarian

search for certain book by the author

recommend other books of the same author

* * *

librarian

search for certain book by its genre

recommend other books of the same genre

* * *

meticulous librarian

different physical books to have different serial numbers

distinguish between books of the same title

* * *

librarian

set the default loan period, renew period and fine amount

customize the app to suit my library’s policies

* *

librarian

extend a book’s loan

help borrowers to borrow the book for a longer period

* *

lazy librarian

generate and record the fine of overdue books

keep track of overdue fines incurred by borrowers

* *

dutiful librarian

record that a fine is paid

keep track of accounting and prevent duplicate payments

* *

librarian

view details of a book

know more information about the book - author, genre, synopsis, etc

* *

careless librarian

be able to undo a command

undo my input mistakes

* *

careless librarian

be able to redo a command

undo my undo commands, in case I need it, without having to type out a possibly lengthy command

* *

health conscious, night-working librarian

change the user interface into a night mode

reduce the impact of light and glare on my eyes when I am working at night

* *

impatient librarian

have my command inputs returned within 1 sec

serve my customers quickly

* *

forgetful librarian

look at the help section

be reminded of the commands available

* *

helpful librarian

be able to reserve a currently on-loan book

allow borrowers to borrow the book once it is returned

* *

librarian

be able to see an image of the book cover

borrowers can know how the book looks like

*

helpful librarian

be able generate a list of most popular books

recommend books to borrowers

*

helpful librarian

add a borrowers rating to the book

recommend books based on ratings

*

receptive librarian

add a borrower’s review to the book

recommend books based on reviews

*

lazy librarian

be able to auto-complete book title searches

reduce my search time and give me nearby titles when I submit a book title query

*

diligent librarian

search for user profiles by name

pull up his donate, borrowing, fine and payment history

8.3. Use Cases

The use case diagram below illustrates the main use cases of LiBerry.

UseCases
Figure 29. General Use Cases for LiBerry

 

(For all use cases below, the System is LiBerry and the Actor is the user, who is a librarian, unless specified otherwise)

Use case: Delete a book

MSS

  1. User searches for books by name, genre or author

  2. LiBerry shows a list of books

  3. User requests to delete a specific book in the list

  4. LiBerry deletes the book

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends


  • 3a. The given index is invalid.

    • 3a1. LiBerry shows an error message.

      Use case resumes at step 2.

Use case: Add a book

MSS

  1. User adds a book by specifying its details

  2. LiBerry shows a success message

Use case ends.

  • 1a. The arguments provided are invalid.

    • 1a1. LiBerry shows an error message.

      Use case ends.

  • 1b. Serial Number is not provided.

    • 1b1. Serial Number is auto-generated.

      Use case resumes at step 2.

Use case: Loan a book

MSS

  1. Borrower comes to user to borrow a book.

  2. User enters the borrower’s ID.

  3. LiBerry shows that the borrower is being served.

  4. User loans out the book to the borrower.

  5. LiBerry shows the book as being successfully loaned out.

    Use case ends.

Extensions

  • 2a. LiBerry cannot find the ID in its system.

    • 2a1. LiBerry requests for a valid and registered ID.

    • 2a2. User enters new ID.

    • Steps 2a1-2a2 are repeated until the ID entered is valid.

      Use case resumes at step 3.


  • 3a. The book cannot be loaned out.

    • 3a1. LiBerry shows an error message.

      Use case ends.


  • *a. At any time, the user makes a typo in the input.

    • *a1. User undoes the last command entered.

    • *a2. User re-types the input.

      Use case resumes at the step preceding this.

{More to be added}

8.4. Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.

  2. Should be able to manage up to 20000 books, 5000 borrower records and 500000 loan records 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}

8.5. Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

UI

User Interface

8.6. Product Survey

Product Name

Author: …​

Pros:

  • …​

  • …​

Cons:

  • …​

  • …​

8.7. 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.

8.7.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.

{ more test cases …​ }

8.7.2. Deleting a book

  1. Deleting a book while there are books are listed

    1. Prerequisites: Books are displayed in the list on the UI.

    2. Test case: delete 1
      Expected: First book is deleted from the list. Details of the deleted book shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete 0
      Expected: No book is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size) {give more}
      Expected: Similar to previous.

{ more test cases …​ }

8.7.3. Saving data

  1. Dealing with missing/corrupted data files

    1. {explain how to simulate a missing/corrupted file and the expected behavior}

{ more test cases …​ }