By: AY1920S1-CS2103T-F13-1
Since: Sep 2019
Licence: MIT
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.
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.
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.
|
-
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.
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.
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
.
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.
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
.
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:
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.
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
-
Logic
uses theCatalogParser
class to parse the user command. -
This results in a
Command
object which is executed by theLogicManager
. -
The command execution can affect the
Model
(e.g. adding a book). -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
. -
In addition, the
CommandResult
object can also instruct theUi
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.
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.
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.
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 inJSON
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.address.commons
package.
These classes include (to list a few):
-
User Settings
-
Exceptions
-
Utility classes like
DateUtil
,FineUtil
andJsonUtil
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. Details of 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.
The else branch of each branch node should have a guard condition [else] but due to a limitation of PlantUML,
they are not shown.
|
We can clearly see how the system decides to generate a valid serial number base on whether the user input contains a valid serial number or not.
After the book is added to the system, we can now represent it with a class diagram shown below.
Book
Notice how the book can hold either 1 or 0 loans, depending on whether it is currently loaned out or not.
The current state of this newly-added book is further illustrated by the object diagram below.
Book
We can see that the book holds an Optional<Loan>
and has an empty LoanHistory
,
making 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: Iterating through the list of books to retrieve one may be inefficient.
-
-
Alternative 2 (current choice): Store them in a HashMap.
-
Pros: Will be easier (and more readable ) to retrieve books by serial number.
-
Cons: Will incur additional memory to maintain the HashMap.
-
We have decided to go with Alternative 2. There is a lot of retrieval of book objects within the Book and Loan features. Therefore, the benefits of quick retrieval of book will outweigh the additional memory costs incurred to maintain the HashMap.
Aspect: Generating a unique serial number.
Since we allow librarians to provide their own valid serial number when adding a book, we cannot use the number of books or the largest serial number to generate the next serial number.
-
Alternative 1: Use a TreeMap to store current serial numbers.
-
Pros: Will be efficient in generating the next valid serial number.
-
Cons: Will incur additional memory to maintain the TreeMap. Might also result in unexpected behaviour in some edge cases.
-
-
Alternative 2 (Current choice): Iterate from the beginning to obtain the first unused serial number.
-
Pros: Will be easy to implement.
-
Cons: Will be inefficient once the number of books grow.
-
We have decided to go with Alternative 2 and keep it simple. This is because there are some cases which leads to unexpected behaviour from Alternative 1. Furthermore, Alternative 2 is in line with the KISS (Keep it Simple, Stupid) principle of programming.
4.2. Undo/Redo feature
4.2.1. Details of 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()
— SavesReversibleCommand
in command history. -
CommandHistory#getUndoCommand()
— Returns command to undoReversibleCommand
. -
CommandHistory#getRedoCommand()
— Returns command to redoReversibleCommand
.
These operations are exposed in the Model
interface as Model#commitCommand()
,
Model#getUndoCommand()
and Model#getRedoCommand()
respectively.
The undo/redo mechanism only works for commands that implements
the ReversibleCommand
interface. Currently, this mechanism only works
for commands that modifies the catalog, loan records, borrower records
or user settings.
Below, is the list of commands that can be undone/redone:
Undoable/Redoable Commands:
-
add
,delete
,edit
,loan
,register
,renew
,return
,set
,toggleui
andunregister
.
After every serve , done and pay command, the command history
is cleared. This means that you will not be able to undo after entering
one of the commands above. This is done to ensure that the user
do not accidentally modify books that have been loaned out and
that payments are not refundable.
|
The ReversibleCommand
interface specifies that each command contains
these three operations:
-
ReversibleCommand#getUndoCommand()
— Returns command that undoes theReversibleCommand
. -
ReversibleCommand#getRedoCommand()
— Returns command that redoes theReversibleCommand
. -
ReversibleCommand#getCommandResult()
— Returns command result of theReversibleCommand
.
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
as
shown in the figure below.
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
as shown in the figure below.
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 commandHistoryList
as shown in the figure
below.
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#getUndoCommand()
will be called. This would call CommandHistory#getUndoCommand()
, 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
as shown in
the figure below.
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:
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#getRedoCommand()
,
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 commandHistoryList.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#getUndoCommand()
or
Model#getRedoCommand()
. Thus, the commandHistoryList
remains unchanged as
shown in the figure below.
help
Step 6. The user executes set lp/7
, 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.
set lp/7
The following activity diagram summarizes what happens when a user executes a new 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 and it is faster compared to alternative 2.
4.3. Generate Loan Slip feature
4.3.1. Details of Implementation
The printing of loan slip feature is facilitated by LoanSlipUtil
.
Essentially, LoanSlipUtil
implements the following operations:
-
LoanSlipUtil#mountLoan()
— Mounts a loan in the current loan session. -
LoanSlipUtil#clearSession()
— Clears the loan session by unmounting all loans. -
LoanSlipUtil#createLoanSlipInDirectory()
— Creates a pdf version of the mounted loans as a single loan slip, saved in the loan_slips folder.
Given below is the sequence diagram of the generation of loan slip during the loan of a book.
The sequence diagram above is described by the following sequence of events:
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 theLoanCommand
object needs to know all the methods ofLoanSlipDocument
to be able to create a loan slip.
-
-
Alternative 2 (current choice): Create a Facade class
LoanSlipUtil
to facilitate creation ofLoanSlipDocument
.-
Pros: The
Logic
component and theLoanCommand
object can now use the full functionality ofLoanSlipDocument
via the static classLoanSlipUtil
without knowing the internal implementation ofLoanSlipDocument
. -
Cons: There is more code to be written and maintained.
-
We have decided to go with Alternative 2 as it decouples the code, making it easier to modify in the future. On the contrary, Alternative 1 will introduce unnecessary dependencies between classes, thereby increasing coupling and reducing maintainability.
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 able to 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 anOptional<LoanSlipDocument>
field and update when a newLoan
comes in. -
Cons: Violates Single Responsibility Principle as the Logic class will now have to change if we change the implementation of
LoanSlipDocument
.
-
We have decided to go with Alternative 1 as it allows us to have flexible code that is easily extendable. Furthermore, it adheres to good programming practices as compared to Alternative 2, which violates the Single Responsibility Principle.
4.4. Book loaning feature
4.4.1. Details of Implementation
The functionalities and commands associated with the book loaning feature are loan
, return
, pay
and renew
.
This feature set is mainly facilitated by the Loan
association class between a Book
and a Borrower
.
The object diagram at the state when a book is just loaned out can be seen below.
At that instant shown in the diagram, the Borrower
with BorrowerId
K0789 currently has a Book
with SerialNumber
B00456 loaned out.
The Loan
associated with this book 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
.
The immutability of each object is supported to ensure the correctness of undo and redo functionality.
Loaning
The following activity diagram summarizes what happens when a user enters a loan command.
After LoanCommand#execute(model)
is called, this series of checks shown in the above diagram is done to determine if
the book can be loaned to the currently served borrower.
When a book is successfully loaned out to 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 the immutability of the objects.
Returning
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 set to today’s date. The remainingFineAmount
of this loan object is also
calculated based on the daily fine increment set in the user settings.
Similarly, the creation of new objects for replacement is also done to support immutability. The return
command is supported by the
methods Model#setBook(bookToBeReturned, returnedBook)
, Model#servingBorrowerReturnLoan(returningLoan)
and
Model#updateLoan(loanToBeReturned, returnedLoan)
, which updates the Catalog
, BorrowerRecords
and LoanRecords
in the model respectively.
Paying fines
When a fine amount is successfully paid by a borrower through the call to Model#payFines(amountInCents)
,
the remainingFineAmount
and paidFineAmount
of the loans in the borrower’s returnedLoanList
is updated accordingly.
The fine amount is tracked individually inside each loan object instead of as a variable inside the Borrower
instance
that stores the total fine amount incurred by that borrower.
This is done to support the ease of extension in the future.
For example, the total fine each book has garnered can be easily calculated.
Similarly, the creation of new objects for replacement is also done to support immutability.
Renewing
The following sequence diagram illustrates the series of method calls when a RenewCommand
is executed to renew book(s).
As seen in the diagram, firstly, a list of books that can be renewed is obtained through the RenewCommand#getRenewingBooks(model)
method.
For each Loan
object associated with each book, a new instance is created with its original renewCount
incremented by 1 and its
dueDate
extended by the renew period set in the user settings. This is done through the method call to Loan#renewLoan(extendedDueDate)
.
The call to Book#renewBook(renewedLoan)
then returns a new instance of this book with its loan object updated.
The method calls to Model#setBook(bookToBeRenewed, renewedBook)
, Model#servingBorrowerRenewLoan(loanToBeRenewed, renewedLoan)
and Model#updateLoan(loanToBeRenewed, renewedLoan)
then updates the Catalog
, BorrowerRecords
and LoanRecords
respectively.
4.4.2. 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 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 entire
Loan
object in each book incatalog.json
and save the entirety of every singleLoan
object associated with a borrower inborrowerrecords.json
. TheLoan
object is also duplicated inloanrecords.json
.-
Pros: Easy to implement. No need to read storage files in a specific order.
-
Cons: Storage memory size issues. The same information is duplicated and stored in all 3 storage files.
-
-
Alternative 2 (selected choice): Save only the
LoanId
of eachLoan
object in each book incatalog.json
and save a list ofLoanId
in each borrower inborrowerrecords.json
. The wholeLoan
object is only saved inloanrecords.json
. When reading the storage files at the start of the application,loanrecords.json
needs to be read in first, before the borrowers and books can be read in as they would get the loan objects from theLoanRecords
based on theirLoanId
s.-
Pros: Uses less memory as only
LoanId
is stored for the books and borrowers, instead of the whole serialized loan objects. -
Cons: The reading of stored files have to be in a certain correct order. It must be ensured that the correct
Loan
object is referenced after reading inborrowerrecords.json
andcatalog.json
, and also every time aLoan
object is updated. The method used to retrieve aLoan
object from itsLoanId
must also be fast enough as there can be hundreds of thousands of loans.
-
Alternative 2 was chosen as this significantly reduced the file size of the storage files.
If alternative 1 was used, the memory needed to store each Loan
object would be 3 times more compared to alternative 2.
Furthermore, LoanRecords
could then also serve as a single source of truth for loan data.
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 itsLoanId
, i.e., O(n) time, as the list must be traversed to find the correct associatedLoan
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 itsLoanId
.-
Pros: Fast to retrieve a
Loan
object based on itsLoanId
, 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.
-
Alternative 2 was chosen as the application frequently needs to retrieve and access a Loan
object based on its LoanId
.
Thus, using a HashMap
would greatly reduce the time needed for such operations. Moreover, the application rarely needed
to obtain the insertion order of a Loan
object.
4.5. Book finding feature
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.5.1. Details of 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 simplified command generation sequence of a 'find t/Animal Farm a/George' command, starting from the CatalogParser
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.
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.5.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 when generating
BookPredicate
*-
Pros: Easy to implement, since Flags are implemented as
Enums
. -
Cons: Can be confusing to the reader as it is not clear why an unintended display is shown / why a certain priority exists.
-
-
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 flags obtained from
ArgumentMultimap
has to be counted, checked and selected.
-
Alternative 2 was chosen in the end as it conformed with the norms of directly informing the user of his mistake through the use of exceptions and error messages.
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 that has a function that caps the number of items in the innerObservableList
.-
Pros: Does not require a change in other parts of the code. Interface is simple and elegant.
-
Cons: Hard to implement. Need to know the ins and outs of
FilteredList
andObservableList
.
-
-
Alternative 2 (Currently Used): Create a counter variable in
BookPredicate
that decrements after every passed test-
Pros: Easy to implement.
-
Cons: Not the cleanest and most developer friendly way of implementation.
-
Alternative 2 was chosen instead as the extension of FilteredList
would take too much time for the implementation of such a simple feature.
The cons of this choice will be compensated for in source code documentation to explain to developers how it works.
=== Toggle GUI theme feature
==== Details of implementation
The toggleui
command will cause switching of the LiBerry GUI between 'light' and 'dark' modes. The basic underlying mechanism
is to access the stylesheets of the MainWindow
node, delete the previous stylesheet giving the current GUI theme, and adding
the stylesheet for the new GUI theme.
However, since some parts of the GUI are dynamically styled (such as the 'serve/normal mode' label on the top right, and the 'loan box' detailing loan details to the right of each book card, as shown below), such a simple css file switch is unable to fully change all GUI elements effectively. These dynamically styled elements will remain in 'light' mode colors while the rest of the app changes to 'dark' mode, causing an ugly contrast.
The way that this was managed was through re-populating the panels every time the toggleui
command is called. This forced the recreation of the
book cards with the correct stylesheet right from their instantiation, which will cause them to display the correct loan box.
4.6. Register borrower feature
4.6.1. Details of 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.
Given below is a class diagram of a book.
Borrower
Given below is the object diagram of a newly registered borrower.
Borrower
4.6.2. Design Considerations
Aspect: Purpose of generating a borrower ID
Borrowers are issued with physical card by the library which they present to the librarian to borrower books. The library card includes the borrower’s ID which librarian will use to serve the borrowers.
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 proposed the following alternatives.
-
Alternative 1: Use a TreeMap to store current Borrower ID
-
Pros: Will be efficient in generating the next valid borrower ID.
-
Cons: Will incur additional memory to maintain the TreeMap. Might also result in unexpected behaviour in some edge cases.
-
-
Alternative 2 (Current choice): Iterate from the beginning to obtain the first unused Borrower ID.
-
Pros: Will be easy to implement.
-
Cons: Will be inefficient once the number of borrowers grow.
-
We have decided to go with Alternative 2 and keep it simple to get in line with the KISS (Keep it Simple, Stupid) principle of programming. Also, we are expecting the number of borrowers to be relatively small (less than 10000) since our target users are small community library. Therefore the inefficiency will not be significant.
4.7. 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.8, “Configuration”) -
The
Logger
for a class can be obtained usingLogsCenter.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.8. Configuration
Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json
).
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.
(For all use cases below, the System is LiBerry
and the Actor is the user
, who is a librarian, unless specified otherwise)
Use case: Help borrower find books of a particular title/author
MSS
-
Borrower comes to user requesting for a book of a particular title/from a particular author.
-
User enters that book’s title/author name.
-
LiBerry searches through all books with an author matching what is entered.
-
LiBerry displays books found.
-
User informs borrower whether book exists, including how many copies
Use case ends.
Extensions
-
2a. Borrower forgets the exact spelling of the title/author he is looking for.
-
2a1. User enters partial spelling of the title/author.
-
Use case returns to step 3.
-
-
2b. User wishes to display only the first N number of books.
-
2b1. User specifies the maximum number of books to display on top of his search conditions.
-
Use case returns to step 3. However, LiBerry displays only the first N number of books in step 4.
-
Use case: Generate the list of available/loaned out/overdue books
MSS
-
User enters command to show all available/loaned out/overdue books.
-
LiBerry searches through catalog to create a list of available/loaned out/overdue books.
-
LiBerry displays the list of all available/loaned out/overdue books.
Extensions
-
1a. User wishes to display only the first N number of books.
-
1a1. User specifies the maximum number of books to display on top of his search conditions.
-
Use case returns to step 2. However, LiBerry displays only the first N number of books in step 4.
-
Use case: Add a book
MSS
-
User adds a book by specifying its details
-
LiBerry shows a success message
Use case ends.
Extensions
-
1a. The arguments provided are invalid.
-
1a1. LiBerry shows an error message.
Use case ends.
-
-
1b. The mandatory arguments are not provided.
-
1b1. LiBerry shows an error message.
Use case ends.
-
-
1c. Serial Number is not provided.
-
1c1. Serial Number is auto-generated.
Use case resumes at step 2.
-
Use case: Delete a book
MSS
-
User searches for books by name, genre or author
-
LiBerry shows a list of books
-
User requests to delete a specific book in the list
-
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: Get information of a book
MSS
-
User searches for books by name, genre or author
-
LiBerry shows a list of books
-
User requests to view the information of a specific book from the list
-
Information regarding the book is displayed
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: Register a borrower
MSS
-
User registers a borrower by specifying its details
-
LiBerry shows a success message
Use case ends.
Extension
-
1a. The arguments provided are invalid.
-
1a1. LiBerry shows an error message.
Use case ends.
-
-
1b. Phone/Email was registered under another borrower
-
1b1. LiBerry shows an error message.
Use case ends.
-
Use case: Unregister a borrower
MSS
-
User enters unregister command for borrower by borrower ID
-
LiBerry unregisters the borrower
Use case ends.
Extensions
-
1a. App is in serve mode
-
1a1. LiBerry shows an error message.
Use case ends.
-
-
1b. Borrower ID is invalid/ not found
-
1b1. LiBerry shows an error message.
Use case ends.
-
Use case: Serve a borrower
MSS
-
User provides user with a borrower ID
-
User enters serve command for borrower by borrower ID
-
App serves borrower
Extensions
-
2a. App is in serve mode
-
2a1. LiBerry shows an error message.
Use case ends.
-
-
2b. Borrower ID is invalid/ not found
-
2b1. LiBerry shows an error message. Use case ends.
-
Use case: Loan a book
MSS
-
Borrower comes to user to borrow a book.
-
User enters the borrower’s ID.
-
LiBerry shows that the borrower is being served.
-
User loans out the book to the borrower.
-
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.
-
-
4a. The book cannot be loaned out.
-
4a1. 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.
-
Use case: Return a book
MSS
-
Borrower comes to user to return a book.
-
User enters the borrower’s ID.
-
LiBerry shows that the borrower is being served.
-
User returns the book for the borrower.
-
LiBerry shows the book as being successfully returned and shows the fine amount incurred.
Use case ends.
Use case: Renew a book
MSS
-
Borrower comes to user to renew a book.
-
User enters the borrower’s ID.
-
LiBerry shows that the borrower is being served.
-
User renews the book for the borrower.
-
LiBerry shows the book as being successfully renewed.
Use case ends.
Extensions
-
4a. The book cannot be renewed.
-
4a1. LiBerry shows an error message.
Use case ends.
-
Use case: Setting loan period
MSS
-
User wants to change the loan period of a book.
-
User enters the new loan period in days.
-
LiBerry shows the new loan period.
Use case ends.
Extensions
-
2a. The loan period entered exceeded the maximum loan period.
-
2a1. LiBerry shows an error message.
Use case ends.
-
Use case: Paying a fine
MSS
-
Borrower comes to user to pay a fine.
-
User enters the borrower’s ID.
-
LiBerry shows that the borrower is being served.
-
User deducts the given amount from the borrower’s outstanding fine.
-
LiBerry shows the fine amount which is paid, the remaining fine amount left, and the change to be given.
Use case ends.
Extensions
-
4a. The borrower has no outstanding fines.
-
4a1. LiBerry shows an error message.
Use case ends.
-
Use case: Changing GUI to dark mode
MSS
-
Sun sets and room gets dark
-
User enters command to change GUi
-
LiBerry changes GUI to dark mode
Use case ends.
8.4. Non Functional Requirements
-
Should work on any mainstream OS as long as it has Java
11
or above installed. -
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.
-
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.
8.6. Instructions for Manual Testing
Given below are instructions to test the app manually. Each of the tests are supposed to be done with sample data (The data that is preloaded when LiBerry first starts up).
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing. |
8.6.1. Launching and Shutting down
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
8.6.2. Finding a book
-
Finding books by Title
-
Test case:
find t/harry
Expected: 2 books with titles matching the word 'harry' should appear. -
Test case:
find t/the
Expected: 3 books, all of which have the word 'the' in them, should appear.
-
-
Finding books by Author
-
Test case:
find a/J K
Expected: 2 books from 'J K Rowling' should appear. -
Test case:
find a/J
Expected: 3 books from authors with names containing the letter 'J' should appear.
-
-
Finding books by Serial Number
-
Test case:
find sn/B00001
Expected: 1 book with serial number B00001 should appear.
-
-
Find books by Genre
-
Test case:
find g/fiction
Expected: 4 books with the genre 'FICTION' should appear. -
Test case: `find g/fiction g/action'
Expected: The book 'Harry Botter and the Full Blood Prince' should appear.
-
-
Find books by loan status
-
Test case:
find -available
Expected: All available books should appear. -
Test case:
find -loaned
Expected: All loaned books, including overdue ones, should appear. -
Test case:
find -overdue
Expected: All overdue books should appear.
-
-
Limiting number of books on display
-
Test case:
find 3
Expected: Only 3 books out of 9 should appear.
-
-
Resetting the display of books
-
Test case:
clear
Expected: All books should appear by serial number order.
-
-
Mixing and match different find conditions
-
Test case:
find 4 t/a -loaned
Expected: Only 4 loaned books should appear, and all should contain the letter 'a' in their title. -
Test case:
find -loaned -overdue
Expected: An error asking for only 1 flag is shown.
-
8.6.3. Adding a book
-
Add a new book to the catalog.
-
Prerequisites: Arguments are valid and mandatory parameters are provided.
-
Test case:
add t/Harry Botter a/Raylei Jolking sn/B02010 g/children
Expected: Adds a children book titled "Harry Botter" by "Raylei Jolking", with the serial number "B02010", to LiBerry. -
Test case:
add t/Harry Botter
Expected: No book is added. Error details shown in the status message. Status bar remains the same. -
Other incorrect add commands to try:
add
,add a/Harry
Expected: Similar to previous.
-
8.6.4. Deleting a book
-
Deleting a book while there are books are listed
-
Prerequisites: Books are displayed in the list on the UI.
-
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. -
Test case:
delete 0
Expected: No book is deleted. Error details shown in the status message. Status bar remains the same. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size)
Expected: Similar to previous.
-
8.6.5. Retrieving information about a book
-
Retrieving a book’s information while there are books are listed
-
Prerequisites: Books are displayed in the list on the UI.
-
Test case:
info 1
Expected: Information about first book is displayed in a new window. -
Test case:
info 0
Expected: No information is displayed. Error details shown in the status message. Status bar remains the same. -
Other incorrect info commands to try:
info
,info x
(where x is larger than the list size)
Expected: Similar to previous.
-
8.6.6. Loaning a book
-
Loaning a book while in Serve mode.
-
Prerequisites: LiBerry is in Serve mode, serial number is valid, book exists in the catalog and book is not on loan.
-
Test case:
loan sn/VALID_SERIAL_NUMBER
Expected: Book with the givenVALID_SERIAL_NUMBER
is loaned to the currently served borrower. The book is added to the list of currently loaned books in the borrower panel. A box stating "On Loan" and relevant details also appear on the book in the UI, beside the label that states its serial number. -
Using the original sample data when you first load up LiBerry without any changes made, you can enter the following test case:
serve id/K0001
followed byloan sn/B00001
to loan the first book in the list, "Harry Botter", to the borrower "Xo Xo".
Expected: "Harry Botter" is loaned to "Xo Xo". -
Other incorrect loan commands to try:
loan
,loan abc
,loan sn/INVALID_SERIAL_NUMBER
,loan sn/SERIAL_NUMBER_OF_A_BOOK_THAT_DOES_NOT_EXIST
,sn/SERIAL_NUMBER_OF_BOOK_ALREADY_ON_LOAN
Expected: Book is not loaned out. Error details shown in the command results display.
-
-
Loaning a book while not in Serve mode.
-
Prerequisites: LiBerry is not in Serve mode, command format is correct and serial number is valid.
-
Test case:
loan sn/VALID_SERIAL_NUMBER
Expected: Book is not loaned out. An error stating not in Serve mode is shown.
-
-
Loaning a book with an invalid serial number. An invalid serial number is one which does not have 5 digits following a capital 'B' character.
-
Prerequisites: Command format is correct.
-
Test Case:
loan sn/INVALID_SERIAL_NUMBER
Expected: Book is not loaned out. An error stating the correct format of a serial number is shown.
-
8.6.7. Returning a book
-
Returning a book while in Serve mode.
-
Prerequisites: LiBerry is in Serve mode and there are books listed in the borrower panel on the right.
-
Test case:
return 1
Expected: The first book (at index 1) in the borrower’s list on the right is returned and no longer on loan, i.e., removed from that list. The box on the book in the main list on the left stating that is in "On Loan" also disappears. The fine incurred for that book is shown and the "Fines: $…" in the borrower panel is updated if the fine is greater than $0. -
Test case:
return -all
Expected: All the books in the borrower’s list on the right is returned and no longer on loan. The list in the borrower panel is now empty. The boxes on the books in the main list on the left stating that they are "On Loan" also disappears. The fine incurred for each book is shown and the "Fines: $…" in the borrower panel is updated if the any fine incurred is greater than $0. -
Incorrect return commands to try:
return
,return abc
,return 0
,return x
(where x is larger than the borrower panel’s list size).
Expected: No book is returned. Error details shown in the command result display.
-
-
Returning a book while not in Serve mode.
-
Prerequisites: LiBerry is not in Serve mode and command format is correct.
-
Test case:
return 1
orreturn -all
Expected: No book is returned. An error stating not in Serve mode is shown.
-
8.6.8. Renewing a book
-
Renewing a book while in Serve mode.
-
Prerequisites: LiBerry is in Serve mode and there are books listed in the borrower panel on the right that can be renewed. Only books that are not overdue, books that have not reached the maximum renew count and books that were not just loaned or renewed in the same Serve mode session can be renewed.
-
Test case:
renew 1
Assumption: The first book (at index 1) in the borrower’s list on the right can be renewed.
Expected: This first book is renewed and its due date is extended. The box on that book shows the updated due date and the "Renewed: x times" has been incremented by 1. -
Test case:
renew -all
Assumption: There are books in the borrower’s list on the right that can be renewed.
Expected: These renewable books are renewed and their due dates are extended. The boxes on those books show the updated due dates and the "Renewed: x times" were incremented by 1. -
Incorrect renew commands to try:
renew
,renew abc
,renew 0
,renew x
(where x is larger than the borrower panel’s list size or the book at that index cannot be renewed)
Expected: No book is renewed. Error details shown in the command result display.
-
-
Renewing a book while not in Serve mode.
-
Prerequisites: LiBerry is not in Serve mode and command format is correct.
-
Test case:
renew 1
orrenew -all
Expected: No book is renewed. An error stating not in Serve mode is shown.
-
8.6.9. Paying fines
-
Paying fines while in Serve mode.
-
Prerequisites: LiBerry is in Serve mode and the borrower has outstanding fines. The "Fines: $…" in the borrower panel should not be $0.
-
Test case:
pay $2
Expected: $2 is deducted from the borrower’s fine amount shown in the borrower panel on the right. Command results display will also indicate the remaining outstanding fine the borrower still has and the change amount to be given back to the borrower. -
Test case:
pay $2.22
Expected: Similar to previous test case (pay $2
), except $2.22 is deducted instead of $2. -
Test case:
pay $0
Expected: Fine amount in borrower panel remains the same. An error stating the valid dollar amount is shown. -
Test case:
pay $abc
Expected: Same as previous test case (pay $2
). -
Test case:
pay $-1
Expected: Same as previous test case (pay $2
). -
Test case:
pay $99999999999999999999
(or any dollar amount greater than $21474836.47)
Expected: Same as previous test case (pay $2
). -
Other incorrect pay commands to try:
pay
,pay xyz
,pay 1
Expected: Fine amount in borrower panel remains the same. Error details shown in the command result display.
-
-
Paying fines while not in Serve mode.
-
Prerequisites: LiBerry is in Serve mode, command format is correct and dollar amount is valid.
-
Test case:
pay $1
Expected: No fine is paid. An error stating not in Serve mode is shown.
-
-
Using the original sample data when you first load up LiBerry without any changes made, you can enter the following positive test case to test the pay command:
serve id/K0004
followed bypay $2
Expected: "Fine of $1.10
paid by
Borrower: [K0004] Hiap Seng
Outstanding fine: $0.00
Change given: $0.90" appears on the command results display. The borrower panel now shows "Fines $0.00".
8.6.10. Setting user settings
-
Setting user settings.
-
Prerequisites: Value of each field in user settings does not exceed its limit.
-
Test case:
set
Expected: Current user settings would be shown in the result display. -
Test case:
set lp/7
Expected: Sets the loan period to 7 days and the update user settings would be displayed in the command result display. -
Test case:
set lp/0
Expected: Current user settings remain the same. An error message would be shown in the command result display. -
Test case:
set lp/366
Expected: Same as previous test case (set lp/0
). -
Test case:
set lp/-1
Expected: Same as previous test case (set lp/-1
). -
Test case:
set lp/7 rp/7 fi/5 mr/2
Expected: Sets the loan period to 7 days, renew period to 7 days, fine increment to 5 cents a days and number of max renewals of loan to 2. The updated user settings would be displayed in the command result display. -
Other incorrect pay commands to try:
set abc
,set 7
Expected: Current user settings remain the same. Error details shown in the command result display.
-
8.6.11. Undoing previous command
-
Undoing a command immediately after a executing an undoable command.
-
Prerequisites: Command must be undoable.
-
Undoable Commands:
add
,delete
,edit
,loan
,register
,renew
,return
,set
,toggleui
andunregister
. -
Test case:
add t/VALID_TITLE a/VALID_AUTHOR
, followed byundo
Expected: Whenadd t/VALID_TITLE a/VALID_AUTHOR
is entered, a book withVALID_TITLE
andVALID_AUTHOR
will be added to the catalog. Afterundo
is entered, the added book should be removed from the catalog. -
You can try the undo command with other valid undoable commands as shown above.
-
-
Undoing immediately after an undo command.
-
Prerequisites: Must have enough undoable commands to undo.
-
Test case:
add t/VALID_TITLE a/VALID_AUTHOR
, followed byregister n/VALID_NAME p/VALID_PHONE_NUMBER e/VALID_EMAIL
,undo
andundo
Expected: Whenadd t/VALID_TITLE a/VALID_AUTHOR
is entered, a book withVALID_TITLE
andVALID_AUTHOR
will be added to the catalog. Afterregister n/VALID_NAME p/VALID_PHONE_NUMBER e/VALID_EMAIL
is entered, a new borrower withVALID_NAME
,VALID_PHONE_NUMBER
andVALID_EMAIL
will be added. The followingundo
should then remove the added borrower. The nextundo
should then remove the book from the catalog.-
You can try the undo command with other valid sequence of undoable commands.
-
-
After every serve , done and pay command, the command history
is cleared. This means that you will not be able to undo after entering
one of the commands above. This is done to ensure that the user
do not accidentally modify books that have been loaned out and
that payments are not refundable.
|
8.6.12. Redoing undone command
-
Redoing an undone commands.
-
Prerequisites: No new undoable command and
serve
,done
andpay
command executed before redoing. -
Undoable Commands:
add
,delete
,edit
,loan
,register
,renew
,return
,set
,toggleui
andunregister
. -
Test case:
add t/VALID_TITLE a/VALID_AUTHOR
, followed byundo
,clear
andredo
Expected: Whenadd t/VALID_TITLE a/VALID_AUTHOR
is entered, a book withVALID_TITLE
andVALID_AUTHOR
will be added to the catalog. Afterundo
is entered, the added book will be removed from the catalog. Afterclear
is entered, the catalog view will be refreshed. Sinceclear
is not a undoable command,redo
will redo the undone command, which adds back the removed book into the catalog. -
Incorrect command to try:
redo
after an undoable command orserve
,done
andpay
Expected: No undone command is redone and an "There are no commands to redo!" will be displayed in the result display.
-
8.6.13. Serving a borrower
-
Serving a registered borrower while not in Serve mode.
-
Prerequisites: LiBerry is in not Serve mode, Borrower ID is valid
-
Test case:
serve id/VALID_BORROWER_ID
Expected: LiBerry enters Serve Mode. Borrower panel is shown. -
Using the original sample data when you first load up LiBerry without any changes made, you can enter the following test case:
serve id/K0001
. -
Other incorrect loan commands to try:
serve
,serve abc
,serve id/INVALID_BORROWER_ID
,serve id/BORROWER_ID_THAT_DOES_NOT_EXIST
Expected: Borrower is not served. Error details shown in the command results display.
-
-
Serving a borrower while already in Serve mode.
-
Prerequisites: LiBerry is in Serve mode, command format is correct and Borrower ID is valid.
-
Test case:
serve id/VALID_BORROWER_ID
Expected: Borrower is not served. An error prompting to exit Serve mode is shown.
-
-
Using serve command with an invalid borrower ID. An invalid borrower ID is one which does not have 4 digits following a capital 'K' character.
-
Prerequisites: Command format is correct.
-
Test Case:
serve id/INVALID_BORROWER_ID
Expected: Borrower is not served. An error stating the correct format of a borrower ID is shown.
-
8.6.14. Unregistering a borrower
-
Unregistering a registered borrower while not in Serve mode.
-
Prerequisites: LiBerry is in not Serve mode, Borrower ID is valid, borrower to be unregistered has no loans.
-
Test case:
unregister id/VALID_BORROWER_ID
Expected: Borrower withVALID_BORROWER_ID
will be unregistered. -
Using the original sample data when you first load up LiBerry without any changes made, you can enter the following test case:
unregister id/K0069
. -
Other incorrect loan commands to try:
unregister
,unregister abc
,unregister id/INVALID_BORROWER_ID
,unregister id/BORROWER_ID_THAT_DOES_NOT_EXIST
Expected: Borrower is not served. Error details shown in the command results display.
-
-
Unregistering the currently serving borrower.
-
Prerequisites: LiBerry is in Serve mode, command format is correct, Borrower ID is valid and borrower to be unregistered is currently being served.
-
Test case:
serve id/BORROWER_ID_CURRENTLY_SERVING
Expected: Borrower is not served. An error stating that borrower currently served cannot be unregistered.
-
-
Unregistering a borrower with loaned books.
-
Prerequisites: Command format is correct, borrower to be unregistered is not currently being served, borrower ID is valid and borrower has loans.
-
Test Case: `unregister id/BORROWER_ID_WITH_LOANS Expected: Borrower is not served. An error stating that borrower currently has loans and cannot be unregistered.
-
-
Using serve command with an invalid borrower ID. An invalid borrower ID is one which does not have 4 digits following a capital 'K' character.
-
Prerequisites: Command format is correct.
-
Test Case:
unregister id/INVALID_BORROWER_ID
Expected: Borrower is not served. An error stating the correct format of a borrower ID is shown.
-
8.6.15. Toggling UI theme
-
Converting between light and dark mode
-
testcase:
toggleui
Expected: The main GUI switches from light mode to dark mode, including those of all other sub-windows -
Testcase:
toggleui
(again)
Expected: The main GUI switches back to light mode, including those of all other sub-windows
-