Developer Guide
- Introduction
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
- Appendix: Effort
Introduction
Purpose
This document is written to describe the architecture and software design decisions for the desktop application, bookmark
Scope
The goal of this document is to cover the high-level system architecture and design. It will also cover the implementation of each feature in bookmark.
Audience
The intended audience is anyone looking to understand the system architecture and design of bookmark.
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
.puml
files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides 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 methods where necessary.
Commons
represents a collection of classes used by multiple other components.
The rest of the App consists of four components.
-
UI
: The UI of the App. -
Logic
: The command executor. -
Model
: Holds the data of the App in memory. -
Storage
: Reads data from, and writes data to, the hard disk.
Each of the four components,
- defines its API in an
interface
with the same name as the Component. - exposes its functionality using a concrete
{Component Name}Manager
class (which implements the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component (see the class diagram given below) defines its API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class which implements the Logic
interface.
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 delete 1
.
The sections below give more details of each component.
UI component
API :
Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, BookListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class.
The UI
component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
.
The UI
component,
- Executes user commands using the
Logic
component. - Listens for changes to
Model
data so that the UI can be updated with the modified data. - Has two different states, a default summarised view, and a detailed view.
Logic component
API :
Logic.java
-
Logic
uses theCommandParser
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("delete 1")
API call:
DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Model component
API : Model.java
The Model
,
- stores a
UserPref
object that represents the user’s preferences. - stores a
Library
object that represents the data added by the user. - stores a
WordBank
that contains instances of words in Library. - 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. - does not depend on any of the other three components.
Storage component
API : Storage.java
The Storage
component,
- can save
UserPref
objects in json format and read it back. - can save the library data in json format and read it back.
Common classes
Classes used by multiple components are in the seedu.bookmark.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Changing Ui view
Implementation
bookmark’s UI supports two types of views: the default, summarised view which displays summarised information regarding the books stored, and the detailed view which displays detailed information about a particular book.
BookListPanel
BookCard
as well as its corresponding subclasses DetailedBookListPanel
and DetailedBookCard
facilitates
the display of book information.
In the summarised view, MainWindow
renders BookListPanel
which displays the book information using BookCard
,
while in the detailed view, DetailedBookListPanel
is rendered which displays the book information using
DetailedBookCard
. DetailedBookCard
will in turn use other components to display the book information,
such as NoteCard
to display the notes added to a book.
Both BookListPanel
and DetailedBookListPanel
makes use of JavaFX’s ListView
to display BookCard
or DetailedBookCard
respectively.
The class diagram below shows the relevant classes involved:
Switching between the two views
MainWindow
and CommandResult
facilitates the switching between the two views.
MainWindow#executeCommand()
initializes all changes to what is displayed by the UI by calling Logic#execute()
which returns a CommandResult
. MainWindow#executeCommand()
is called when user enters a command into the application.
From the returned CommandResult
, CommandResult#getViewType()
indicates which view the UI should switch to according
to which ViewType
is returned:
-
ViewType.DEFAULT
- Instructs UI to switch to the default, summarised view. -
ViewType.DETAILED
- Instructs UI to switch to the detailed, single book view. -
ViewType.MOST_RECENTLY_USED
- Instructs UI to stay in whatever view it is in.
Based on the type of ViewType
returned by CommandResult#getViewType()
, MainWindow#resetView()
,
MainWindow#changeToDetailedView()
, or no method is called accordingly.
The activity diagram below illustrates the flow of execution when the UI decides which view to use:
Below is a sequence diagram that shows a scenario whereby the UI switches from the default summarised view to the detailed view:
Design considerations
Aspect: What to display DetailedBookCard with
-
Alternative 1 (current choice): Use JavaFX ListView
- Pros: Easy to keep UI up to sync with model by overriding ListCell’s updateItem method.
- Cons: Extra care must be taken to not allow multiple books to be displayed as detailed view is only meant to show one book.
-
Alternative 2: Use other JavaFX layouts
- Pros: More in-line with the purpose of the detailed view of showing only one book
- Cons: More work has to be done to sync up the UI with the model.
General bookmark Command Logic
bookmark’s commands generally follow the same procedure. The following section will explain the general flow of events for the following commands:
add
list
view
edit
delete
The listed commands follow a similar sequence diagram as adding a book. These commands are
facilitated by LogicManager
, ModelManager
, XYZCommandParser
and XYZCommand
where XYZ refers to the specific command.
eg. List, View, Edit etc.
LogicManager
will handle the user input by first identifying the type of command (eg. Add, List, View, Edit etc.) and create a
XYZCommandParser
object that will handle the remaining user input if any to produce a XYZCommand
.
XYZCommand
will then handle the specific command through XYZCommand#execute()
. This method
interacts with and modifies ModelManager
to display the appropriate
list of book(s). A CommandResult
object which contains the return message
to the User will be created by XYZCommand
and passed to the UI component to be displayed to the User.
If the commands are in incorrect format, an exception message will be passed to the UI component to be displayed to the User
Implementation
In this section, Add
command will be used as an example.
This feature is facilitated mainly by LogicManager
, ModelManager
, AddCommandParser
and AddCommand
.
Step 1: LogicManager#execute()
handles the command to identify the type of command (eg. Add)
to create AddCommandParser
to parse the remaining inputs. AddCommandParser#parse()
tokenizes each prefix to create a Book
object.
Step 2: This Book
object will be passed as a parameter to create a AddCommand
that will be returned to LogicManager
If there are missing or invalid prefixes, an exception will be thrown with a message to the User.
Step 3: LogicManager#execute()
will call AddCommand#execute()
to add the Book
into
the Model
via ModelManager#addBook()
. Subsequently, ModelManager#sortByDefaultComparator()
would be called
to sort the internal book list according to the user’s sorting preference, if any.
This method will return a CommandResult
object which contains the return message to be displayed for the user.
Step 4: CommandResult
is returned as a feedback to the user.
If there is an existing book with the same name, an exception will be thrown with a message to the User
Below is an activity diagram which illustrates the flow of events for adding a book:
Below is a sequence diagram which illustrates the a scenario where a User adds a valid book:
Command: add n/Harry Potter g/Fiction tp/1000 b/100
Find feature
Implementation
bookmark allows users to find books by a specified field.
This feature is facilitated mainly by ModelManager
, FindCommandParser
and FindCommand
.
The find mechanism is facilitated by ModelManager
, specifically, the ModelManager#updateFilteredBookList()
method.
ModelManager#updateFilteredBookList()
takes in a single parameter, a predicate
, and applies the predicate
on all elements of the observable book list. Books that satisfy the predicate
remain displayed in the user’s
view, while elements that do not are hidden from the user’s view. Currently, the find
command
supports finding by name, genre and tag fields, and can also find all completed or non-completed books.
Given below is an example usage scenario and how the find mechanism alters FilteredList
at each step:
Step 1. The user launches the application for the first time. FilteredList
is initialised with the user’s book data.
Step 2. The user executes find n/Harry
command to find all books with Harry in the Name field. The
NameContainsKeywordsPredicate
predicate is generated and is used as a filter in this scenario.
Filtering the FilteredList
The FindCommandParser#parse()
parses the find
command input, and checks for input errors for which if found,
would throw an error. Subsequently, FindCommandParser#predicateGenerator()
generates a predicate
based on the
user’s input keyword(s) and filtering prefix. The resulting predicate
is used to generate a new FindCommand
object,
and when FindCommand#execute()
is called, the predicate
is passed to ModelManager#updateFilteredBookList()
,
where the filtering of the observable book list based on the predicate
occurs.
The activity diagram below illustrates the flow of execution when the user inputs a find
command:
Below is a sequence diagram that shows a scenario whereby the user decides to find the keyword Harry
in the book name
field:
Command : find n/Harry
Design considerations
Aspect: Finding within user specified field or in all fields
-
Alternative 1 (current choice): Finding keywords within specified field
- Pros: Allows the user to streamline their search and find their desired book quicker.
- Cons: Could be a drawback if the user forgets which field he used the keyword in.
-
Alternative 2: Finding keywords in all fields
- Pros: Allows the user to find all books with the keyword in any input field, which could be an advantage if the user uses the keyword for multiple fields.
- Cons: Might not be easy to find specific books, i.e. cannot streamline the search as well.
Sort feature
Implementation
bookmark allows Users to sort books by a specified field.
This feature is facilitated mainly by ModelManager
, SortCommandParser
and SortCommand
.
ModelManager#sortFilteredBookList()
takes in a single parameter, a comparator
, and applies the comparator
on all elements
of the internal observable book list. Books are sorted according to the input comparator in ascending order.
ModelManager#setSortingPreference()
takes in a single parameter, a newSortingPreference
, and updates the sorting preference
in the preferences.json
file. Thereafter, the specified sorting mechanism will apply until the user decides to change
the sorting mechanism using the sort command.
Currently, the sort
command supports sorting by name, genre, bookmark, and reading progress.
Sorting the ObservableList
The SortCommandParser#parse()
parses the sort
command input, and checks for input errors. If there are input errors,
the method would throw an error. Subsequently, ComparatorGenerator#comparatorGenerator()
generates a comparator based on the
user’s inputPrefix
. inputPrefix
and the resultant comparator
are used to generate a new SortCommand
object.
When SortCommand#execute()
is called, inputPrefix
is passed to ModelManager#setSortingPreference()
to
update the user’s sorting preferences in preferences.json
occurs. comparator
is passed to ModelManager#sortFilteredBookList()
,
where the internal observable list is sorted based on the comparator
.
The activity diagram below illustrates the flow of execution when the user calls for a sort
command:
Below is a sequence diagram that shows a scenario whereby the user decides to sort books by the name field:
Command : sort n/
Design considerations
Aspect: Sorting visible observable list or internal observable list
-
Alternative 1 (current choice): Sorting internal observable list
- Pros: Allows user to set sorting preference. When the user exits and reopens the application, the books would remain in the sorted order.
- Cons: Could be a drawback if the user only wants to sort for temporary view.
-
Alternative 2: Sorting visible observable list
- Pros: Sorts the user’s book list temporarily, which is useful if the user only wants the sorted view momentarily.
- Cons: Book list would always return to default view after subsequent commands which could be distracting.
Suggestion feature
Implementation
bookmark’s Suggestion feature uses the Damerau-Levenshtien algorithm to calculate the distance between the user-input word and the words in the application WordBank.
The Edit Distance
refers to the number of steps needed to change difference between two words.
This mechanism is implemented through 3 operations:
- Addition
- Deletion
- Transposition (Swapping)
For example:
- Hbrry -> Harry (Edit Distance: 1)
- Hrry -> Harry (Edit Distance: 1)
- Hrary -> Harry (Edit Distance 1)
- Haarry -> Harry (Edit Distance 1)
The suggestion mechanism is facilitated by Logic
Component through FindCommand
which calls on SuggestionAlgorithm
if
no books can be found.
SuggestionAlgorithm
will call on WordBank
in Model
for the stored words required to complete the mechanism.
SuggestionAlgorithm
will implement the following operations:
-
SuggestionAlgorithm#findSuggestions()
— Filters the relevant words to be returned as a suggestion. -
SuggestionAlgorithm#calculateDistance()
— Calculate the EditDistance of the source word and words in the WordBank.
The class diagram below shows the relevant classes involved:
Given below is an example usage scenario and how a Suggestion mechanism behaves at each step.
Step 1: The user inputs the command find n/h@rry
to find books with harry
in their names.
harry is deliberately typed wrongly
FindCommand#execute()
is activated. As the keyword has a typing error, the model’s FilteredList
will be empty since it cannot find any matching books.
Step 2: Since no books are found, FindCommand#execute()
will call on SuggestionAlgorithm#findSuggestions()
to find the closest matching word
in the appropriate WordStore
of WordBank
.
Step 3: SuggestionAlgorithm#findSuggestions()
will call upon
SuggestionAlgorithm#calculateDistance()
to calculate the edit distance of h@rry
and the words in nameWordBank
.
Words found to be within the predefined DISTANCE_LIMIT
will be added to an ArrayList
to be returned to the FindCommand
within FindCommand#execute()
.
Step 4: FindCommand#execute()
will add each word into a PriorityQueue
and poll out the word with the smallest distance
to be used as the suggested word.
If no words are within the DISTANCE_LIMIT
in Step 3, there will not be any words in the PriorityQueue
and FindCommand#execute()
will return a Standard Message for no suggestion.
Within the SuggestionAlgorithm#FindSuggestion()
Within FindCommand#execute()
Below is a sequence diagram that shows a scenario where a suggestion is provided when a typing error is committed:
Design Consideration
Aspect: Data structure for storing words
-
Alternative 1: Using Damerau-Levenstien Algorithm (Currently Implemented)
The Damerau-Levenstien Algorithm will cover calculation for the edit distance for 3 kinds of String Differences as mentioned above
- Pros:
Will provide an accurate measure of the closest matching word based on the distance
First letter need not match the keyword (First letter might be typed wrongly too) -
Cons:
Incurs either a large time complexity (3^n) where n is length of string to be compared with (No Memoization)
Incurs a large space consumption (construction of 2D Array) (With Memoization) -
Alternative 2: Storing all words in a Trie
Store all words into a Trie data structure and uses recursion to find the matching words. - Pros: Easy to implement and consumes less time and space than Alternative 1.
- Cons:
Words must start with the same letter. Hence it is based on the assumption that the 1st letter of the word is not typed wrongly.
Difficult to find transpositional errors (Eg. Harry vs Hrary)
- Pros:
Alternative 1 was ultimately chosen as it is difficult to predict what type of typing errors will be committed. Hence, analysing missing, additional or transpositional letters are equally important. It is also naive to assume that users will not type the first letter wrongly too. In addition, there was no noticeable degradation of performance during testing using Alternative 1. Since Alternative 1 provided a better solution to a Suggestion Feature with no noticeable performance slowdown, it was hence chosen.
Add Goal feature
Implementation
bookmark allows Users to add their reading goal to a book.
This feature is facilitated mainly by LogicManager
, GoalCommandParser
, GoalCommand
and Book
.
LogicManager#execute()
(not shown in the diagram above) handles the command word to create
GoalCommandParser
to parse the remaining inputs. GoalCommandParser#parse()
tokenizes each prefix
to create a Goal
object. This Goal
object will be passed as a parameter to create a GoalCommand
that will be returned to LogicManager
.
If there are missing or invalid parameters in command, an exception will be thrown with a
message to the User.
Below is an activity diagram which illustrates the flow of events for adding a Goal:
Below is a sequence diagram which illustrates a scenario where a User adds a valid Goal
to a valid book:
Command: goal 1 p/69 d/10-12-2020
Add Note feature
Implementation
bookmark allows Users to add notes to a book.
This feature is facilitated mainly by AddNoteCommandParser
and AddNoteCommand
.
LogicManager#execute()
(not shown in the diagram above) handles the command word to create AddNoteCommandParser
to parse the remaining inputs.
AddNoteCommandParser#parse()
tokenizes each prefix to create a Note
object. This Note
object will be
passed as a parameter to create a AddNoteCommand
that will be returned to LogicManager
.
If there are missing or invalid prefixes, an exception will be thrown with a message to the User.
LogicManager#execute()
will call AddNoteCommand#execute()
to add the Book
with the added Note
into
the Model
and return a CommandResult
as a feedback to the user.
If there is an existing note with the same title and text, an exception will be thrown with a message to the User
Below is an activity diagram which illustrates the flow of events for adding a note:
Below is a sequence diagram which illustrates the a scenario where a User adds a valid note to a valid book:
Command: note 1 n/Thoughts txt/Something
Undo/redo feature
Implementation
The undo/redo mechanism is implemented by storing the state of the application after each command. The state of the application can be divided into two components:
The state of the Model is managed by HistoryManager
. It does so by storing State
objects. Each State
object contains a
ReadOnlyLibrary
, ReadOnlyUserPrefs
and a Predicate
used to decide which books stored in the Model should be visible to
users. HistoryManager
maintains a current state property representing the current state of the Model. It also stores
previous states that can be restored via a redo or undo.
-
HistoryManager#addNewState()
— Adds a new state to be used as the current state -
HistoryManager#undo()
— Restores the most recent previous state. -
HistoryManager#redo()
— Restores the most recently undone state.
The state of the Ui is managed by ViewTypeManager
.
The state of the Ui refers to what ViewType
the Ui is in (see Changing Ui View).
ViewTypeManager
maintains a mapping between State
objects and a ViewType
. This mapping indicates what
ViewType
the Ui should use when provided with a particular State
of the Model.
-
ViewTypeManager#addViewTypePairing()
— Adds a new pairing between a givenState
andViewType
-
ViewTypeManager#getViewType()
— Get the correctViewType
for the givenState
Whenever the user enters any commands EXCEPT:
help
exit
-
undo
andredo
itself
the method Model#save()
will be called which adds a new State
, representing the new state of the Model, to HistoryManager
and causes HistoryManager
to store the previous State
. Additionally, ViewTypeManager#addViewTypePairing()
will be
called to create a new pairing between the newly created State
and the appropriate ViewType
to use to display the contents of the
Model to the user. The stored States
and its corresponding ViewType
pairing will be used to change the application
state accordingly when a undo or redo operation, exposed as Model#undo()
and Model#redo()
respectively, is executed.
The class diagram below illustrates the classes that facilitates the undo and redo feature:
The next section will go into more detail about how the state of the application is managed as well as how undo and redo executes.
How state is managed
HistoryManager
manages state by keeping a current state variable as well as two deques, an undo deque and a redo deque.
The undo deque stores the states to be recovered via an undo command, while the redo deque stores previously undone states
to be recovered via a redo command. ViewTypeManager
maintains a map that maps State
objects to a corresponding
ViewType
.
Below is an example to illustrate how undoing and redoing works in a typical scenario:
Step 1. The user launches the application for the first time. The HistoryManager
will be inititalised with the
initial state of the model as the current state, i.e State 1. Undo and redo deques will be empty. ViewTypeManager
’s map
will also only have 1 entry, mapping the initial State
to ViewType.DEFAULT
.
Step 2. The user executes add command to add a new book. The command will call the Model#save()
method
which will create a new State
, State 2, representing the new state of the Model and add it into HistoryManager
via the
HistoryManager#addNewState()
method. State 2 will now be the current state while the previous current state, State 1,
will be pushed into the undo deque. ViewTypeManager
’s map will be updated accordingly by calling the
ViewTypeManager#addViewTypePairing()
method with State 2 and the correct ViewType
to be used to display State 2 to
the user.
Model#save()
, so the state will not be saved.
Step 3. The user decides that adding the book was a mistake and decides to undo the action by using the undo command.
This causes the current state, State 2 to be pushed to the redo deque. State 1 will be popped from the undo deque and
made the current state. ViewTypeManager#getViewType()
will be called with State 1 in order to get the correct
ViewType
to display State 1 with.
The following sequence diagram shows how the undo operation works:
Step 4. The user changes his mind again, deciding that he wants to add the book. He redoes the action by using the redo
command, causing the current state, State 1 to be pushed back into the undo deque and State 2 to be popped from the redo
deque and made the current state. Again, ViewTypeManager#getViewType()
will be called to get the correct ViewType
to display State 2 with.
Step 5. Now suppose the user adds a book and then edits a book, causing State 3 and State 4 to be created. He then
undoes the edit command. HistoryManager
will now look like this:
Step 6: The user decides to then delete a book, causing State 5 to be created and made the current state. The previous current state, State 3, will be pushed into the undo deque while the redo deque is cleared and hence State 4 is deleted.
This design choice of clearing the redo deque when a new state is added was made because states existing in the redo deque cannot be represented in a linear, sequential path together with newly added states. Hence, it will be confusing to allow users to redo to these states. To see this, we can plot the evolution of state changes in a sequential manner:
To get a clearer picture, we consider what could occur if the redo deque is not
cleared upon adding new state into the HistoryManager
.
Consider a scenario where the redo deque originally contains some state. The user subsequently enters 5 commands that each create a new state and the redo deque is never cleared upon new states being added. Then, the user enters the redo command, causing the top-most state in the redo deque to be popped and made the current state. As a result, all the changes that the user has done through the 5 commands are removed in a single redo command, which is not the intended behaviour.
Furthermore, to prevent excessive memory usage, a cap on the number of states stored by HistoryManager
’s undo deque
can be set in HistoryManager#MAX_UNDO_COUNT
. If a new state is added but the undo deque is already at max capacity,
then the oldest state in the undo deque will be deleted to make room. A similar strategy is employed for ViewTypeManager
,
with its map being implemented as a FIFO cache with a fixed maximum size.
The activity diagram below explains the flow of execution when a new state is added to HistoryManager
:
Design considerations
Aspect: How undo & redo executes
-
Alternative 1 (current choice): Saves copies of the entire
Library
andUserPrefs
inState
.- Pros: Easy to implement.
- Cons: May have performance issues in terms of memory usage.
-
Alternative 2: Individual command knows how to undo/redo by
itself.
- Pros: Will use less memory (e.g. for
delete
, just save the book being deleted). - Cons: We must ensure that the implementation of undoing/redoing of each individual command are correct, complexity builds up as more commands are added.
- Pros: Will use less memory (e.g. for
Alternative 1 was eventually chosen as there was no noticable performance degradation during testing with a reasonable cap (10) on the number of states stored. It is also much more scalable and less prone to breaking upon addition or modification of commands.
Aspect: How to decide which actions should create and save state
-
Alternative 1 (current choice): Expose a method in the
Model
interface that when called creates and saves state.- Pros: More declarative, easier to see when the model will create and save state.
- Cons: Worse separation of concerns, the responsibility of deciding when to create and save state is moved away from the model and to the components that interact with the model.
-
Alternative 2: The methods implemented by
ModelManager
to modify the model such asModelManager#addBook()
also creates and save state.- Pros: Better separation of concerns, the model is responsible for deciding what actions constitute a modification and thus warrants the creation and saving of state.
- Cons: The creation and saving of state becomes a side effect, not immediately clear that it occurs.
Alternative 1 was eventually chosen as we liked the declarative nature of it as well as the clarity it provides. We also felt that alternative 2 could cause a lot of problems if a command needed to modify the model in multiple ways thus would cause multiple states to be created for a single command.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- has a need to keep track of which page they stopped reading at for multiple books
- has a need to set reading goals and write reflections about what they read
- prefer desktop apps over other types
- can type fast
- prefers typing to mouse interactions
- is reasonably comfortable using CLI apps
Value proposition: Manage bookmarks faster than a typical mouse/GUI driven app. Removes the need for physical bookmarks. Help maintain the motivation to read by making reading more interactive.
User stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * |
user | add a new book | track my progress |
* * * |
user | delete a book | remove books that I no longer read |
* * * |
user | find a book by name | locate details of books without having to go through the entire list |
* * * |
user | see all the data about a particular book (title, bookmark, progress) | get all necessary information about my book |
* * * |
user | view a list of all the books and their progress | conveniently check my progress on all my books |
* * * |
user | update the data of a book | |
* * * |
user | have an easy to use user interface | easily navigate through the system with minimal difficulty |
* * * |
user | have a help page | find out how to use the app |
* * |
user | classify my books according to genre | organise my books better |
* * |
analytical user | write down my thoughts on a book | re-read my analysis later |
* * |
user | create my own classifications for genres not in the application | organise the books to my liking |
* * |
Kindle user | have a clearer representation of my progress | conveniently get my progress without navigating to the Kindle screen |
* * |
user | place tags or notes at different pages | stop using sticky notes |
* * |
user | have a few functions that I can use seamlessly | learn the app easily without bothering with large numbers of functions |
* * |
user | be able to edit my tags and reflections | edit mistakes and typos |
* * |
careless user | undo any wrong commands that i’ve entered | |
* * |
user | be able to filter my books by Lexicographic order, Date, Reading, Finished | get the information I’m looking for |
* * |
beginner user | have default settings | skip configuring them at the start |
* |
user | have a home page that summarises important details | conveniently access the most important information |
* |
user | have colour coded tags | so that I can easily differentiate important tags from the rest |
* |
user | customise the look of the application | make it feel personal |
* |
user | have a summary page for statistics on the books I’ve read | |
* |
user | be able to resize the application | fit it to different screens |
* |
user | see and modify my current settings | |
* |
user | customise the background by uploading my own photo | customerize the look of the app to my liking |
* |
forgetful user | keep track of my loaned books expiry date | remember to return the book or extend the loan |
* |
user | have a visual representation of my loans expiry period | instantly know the urgency of each loan |
* |
user | set reminders for a book loan | be reminded to return my book on time |
Use cases
For all use cases below, the System is bookmark
and the Actor is the user
, unless specified otherwise
Use case: UC01 - Add a book
MSS
- User requests to add a book.
-
bookmark adds the book.
Use case ends.
Extensions
-
1a. User provides the wrong command.
- 1a1. bookmark returns an error message.
Use case ends.
-
1b. User provides a non-numeral for total pages or bookmarked page.
- 1b1. bookmark returns an error message.
Use case ends.
-
1c. Provided bookmarked page is larger than total pages.
- 1c1. bookmark returns an error message.
Use case ends.
Use case: UC02 - View a book
MSS
- User requests to list all books (UC05).
- User requests to view a specific book using its displayed index.
-
bookmark returns all the book’s information to user.
Use case ends.
Extensions
-
2a. The index given is invalid.
-
2a1. bookmark returns an error message.
Use case resumes at step 2.
-
Use case: UC03 - Delete a book
MSS
- User requests to list all books (UC05).
- User requests to delete a specific book using its displayed index.
-
bookmark deletes the book.
Use case ends.
Extensions
-
2a. The index given is invalid.
-
2a1. bookmark returns an error message.
Use case resumes at step 2.
-
Use case: UC04 - Edit a book
MSS
- User requests to list all books (UC05).
- User requests to edit a specific book using its displayed index.
-
The requested entry is updated.
Use case ends.
Extensions
-
2a. The index given is invalid.
- 2a1. bookmark returns an error message.
Use case resumes at step 2.
-
2b. The requested entry to update is invalid.
- 2b1. bookmark returns an error message.
Use case resumes at step 2.
-
2c. The provided value for the entry to update is invalid.
- 2c1. bookmark returns an error message.
Use case resumes at step 2.
Use Case: UC05 - List all books
MSS
- User requests to list all books.
-
bookmark returns a list of all books.
Use case ends.
Extensions
- 1a. The book list is empty.
- 1a1. bookmark returns a message informing user that the book list is empty.
Use case ends.
Use Case: UC06 - Help
MSS
- User requests to see the Help page.
-
bookmark returns the Help page.
Use case ends.
Use Case: UC07 - Undo
MSS
- User requests to undo last action.
-
bookmark undoes the last user action.
Use case ends.
Extensions
- 1a. There is no action to undo.
- 1a1. bookmark returns a message informing user that there is no action to undo.
Use case ends.
Use Case: UC08 - Adding a goal
MSS
- User requests to add a goal to a specific book using its displayed index.
- User specifies the goal target and deadline.
-
The goal is added to the book.
Use case ends.
Extensions
- 1a. User provides an invalid index.
- 1a1. bookmark returns an error message.
Use case returns at step 1.
- 2a. User provides an invalid page.
- 2a1. bookmark returns an error message.
Use case resumes at step 2.
- 2b. User provides an invalid deadline.
- 2b1. bookmark returns an error message.
Use case resumes at step 2.
- 3a. There is already a goal for the book.
- 3a1. bookmark replaces the existing goal with the new goal.
Use case resumes at step 3.
Use Case: UC09 - Adding a note
MSS
- User requests to add a note to a specific book using its displayed index.
-
The note is added to the specified book.
Use case ends.
Extensions
-
1a. The index given is invalid.
- 1a1. bookmark returns an error message.
Use case ends.
-
1b. The provided prefix is invalid.
- 1b1. bookmark returns an error message.
Use case ends.
-
1c. The provided value for note title and text is invalid.
- 1c1. bookmark returns an error message.
Use case ends.
Use Case: UC10 - Deleting a note
MSS
- User requests to delete a note from a specific book using its displayed index.
-
The requested note is deleted from the specified book.
Use case ends.
Extensions
-
1a. The index given is invalid.
- 1a1. bookmark returns an error message.
Use case ends.
-
1b. The note index provided is invalid.
- 1b1. bookmark returns an error message.
Use case ends.
Use Case: UC11 - Sorting the book list
MSS
- User requests to sort the displayed book list by a specified field.
-
bookmark sorts the displayed book list by the specified field.
Use case ends.
Extensions
-
1a. The field provided is invalid.
- 1a1. bookmark returns an error message.
Use case ends.
Use Case: UC12 - Finding keywords
MSS
- User requests to find keyword(s) in the displayed book list by a specified field.
-
Books that contain keyword(s) in the specified field are returned to the user in a new list.
Use case ends.
Extensions
-
1a. The field provided is invalid.
- 1a1. bookmark returns an error message.
Use case ends.
-
1b. bookmark does not find any matching books but is able to find similar words to the input keyword(s) stored in the application to provide as recommendations.
- 1b1. bookmark returns a message informing user that no matching books were found and provides the suggestion of a similar keyword that is found in the application.
Use case ends.
-
1c. bookmark does not find any matching books and is unable to find any similar words to the input keyword(s) stored in the application.
- 1c1. bookmark returns a message informing user that neither matching books nor alternative keyword suggestions were found.
Use case ends.
Use Case: UC13 - Delete a goal
MSS
- User requests to delete the goal of a specific book using its displayed index.
-
The goal is removed from the book.
Use case ends.
Extensions
- 1a. User provides an invalid index.
- 1a1. bookmark returns an error message.
Use case returns at step 1.
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or above installed. - Should be able to hold up to 100 books 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.
- Should be backwards compatible with data from previous versions.
- Should not crash when data is given in compatible formats.
- The app should be accessible via the downloaded JAR file without any other installations needed.
{More to be added}
Glossary
- Mainstream OS: Windows, Linux, Unix, OS-X
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
Launch and shutdown
-
Initial launch
a. Download the bookmark.jar file and copy into an empty folder
b. Double-click the bookmark.jar file Expected: Shows the GUI with a set of sample books. The window size may not be optimum.
Note: If double clicking does not work, navigate to the folder containing the jar file using your terminal or command prompt and enterjava -jar bookmark.jar
-
Saving window preferences
a. Resize the window to an optimum size. Move the window to a different location. Close the window.
b. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
Viewing a book
-
Viewing a particular book
a. Prerequisites: At least 1 book being displayed in the book list.
b. Test case:
view 1
Expected: View the first book in the book list. The Ui will switch to only show more detailed information about the selected book and will not show all other books. The displayed book will have anINDEX
of 1 since it is the first and only book being displayed.
Deleting a book
-
Deleting a book while all books are being shown.
a. Prerequisites: List all books using the
list
command. Multiple books in the list.b. Test case:
delete 1
Expected: First book is deleted from the list. Details of the deleted book shown in the status message.c. Test case:
delete 0
Expected: No book is deleted. Error details shown in the status message.d. Other incorrect delete commands to try:
delete
,delete x
,...
(where x is larger than the list size)
Expected: Similar to previous.
Adding a book
-
Adding a book while all books are being shown.
a. Test case:
add n/Test book g/Test genre tp/1000 b/50
Expected: A book with the name of “Test book”, genre of “Test genre”, 1000 total pages and bookmark placed at page 50 will be created and added to the list.b. Test case:
add n/Test book g/T@st genre tp/1000 b/50
Expected: No book is added. Error details shown in the status message.c. Other incorrect add commands to try:
add n/Test g/Test tp/X b/Y
where Y is larger than X.
Expected: Similar to previous. -
Adding a book while only some books are being shown.
a. Test case:
add n/Test book g/Test genre tp/1000 b/50
Expected: A book with the name of “Test book”, genre of “Test genre”, 1000 total pages and bookmark placed at page 50 will be created and added to the list. All books will be shown.
Editing a book
-
Editing a book while all books are being shown.
a. Prerequisites: List all books using the
list
command. Multiple books in the list.b. Test case:
edit 1 n/Edited Name
Expected: First book in the list is edited to have a name of “Edited Name” Details of the edited book shown in the status message.c. Test case:
edit 1 t/Good t/Educational
Expected: First book in the list has all existing tags removed and the “Good” and “Educational” tags added.d. Test case:
edit 0 n/Edited Name
Expected: No book is edited. Error details shown in the status message.e. Other incorrect delete commands to try:
edit
,edit x
,...
(where x is larger than the list size)
Expected: Similar to previous. -
Editing a book while only some books are being shown.
a. Prerequisites: Book list filtered by a command (e.g
find
command). At least 1 book shown in the list.b. Test case:
edit 1 n/Edited Name
Expected: First book in the list is edited to have a name of “Edited Name”. Details of the edited book shown in the status message. The edited book remains in the list regardless of how it was edited.
Finding books
-
Finding books by keywords.
a. Prerequisites: At least 1 book being displayed in the book list.
b. Test case:
find n/XXX
whereXXX
is a keyword you want to find for.
Expected: Books withXXX
in their names will be shown, books that do not will not be shown.
Note:find
only matches full words, it will not match substrings of a word.c. Test case:
find g/YYY
whereYYY
is a keyword you want to find for.
Expected: Books withYYY
in their genres will be shown, books that do not will not be shown.
Sorting the book list
-
Sorting the book list while some books are being shown.
a. Prerequisites: Book list is showing at least 2 books to see the sorting effect.
b. Test case:
sort n/
Expected: The books shown in the list will be sorted by their names in lexicographical order. -
Adding a book into a sorted book list.
a. Prerequisites: Book list is initially showing at least 1 book to see the sorting effect.
b. Test case:sort n/
followed byadd
command to add another book. Expected: After the sort command, the books shown in the list will be sorted by their names in lexicographical order. The new book will be added to the book list in the correct position that maintains the sorted order.
Undo
-
Using undo when there are no commands to undo.
a. Prerequisites: Must not have made any commands since starting the application.
b. Test case:
undo
Expected: Nothing is undone. Error details shown in status message. -
Using undo when there are commands to undo.
a. Test case: Any command except
help
andexit
followed byundo
Expected: The command entered is undone. The application reverts to exactly how it was before the command was made. Success message shown in status message.
Redo
-
Using redo when there are no undone commands to redo.
a. Prerequisites: Must not have used
undo
command since starting the application.b. Test case:
redo
Expected: Nothing is redone. Error details shown in status message. -
Using redo when there are undone commands to redo.
a. Test case: Any command except
help
andexit
, followed byundo
and thenredo
Expected: The command entered is first undone, then on usingredo
, it will be redone. The application will be exactly as it was after the first command was entered. Success message shown in status message. -
Using redo not immediately after an undo command.
a. Test case:
- Any command except
help
andexit
, -
undo
, - Any command except
help
andexit
-
redo
Expected: The redo command will not execute. Error details shown in status message.
- Any command except
Adding a note
-
Adding a note to a book.
a. Prerequisites: Book list is showing at least 1 book, not viewing a particular book with the
view
command.b. Test case:
note 1 n/Hello txt/This is a note
Expected: A note with the title “Hello” and body “This is a note” will be added to the first book in the displayed book list. Only the title of the note will be displayed. Doingview 1
will allow you to see the body of the note added. -
Adding a note to a book being viewed.
a. Prerequisites: Viewing a particular book using the
view
command.b. Test case:
note 1 n/Hello txt/This is a note
Expected: A note with the title “Hello” and body “This is a note” will be added to the book being viewed. Both the body and the title of the note will be displayed.
Deleting a note
-
Deleting a note from a book.
a. Prerequisites: Book list is showing at least 1 book that has at least one note added.
b. Test case:
notedel X 1
where X is the Xth book in the displayed book list. The Xth book must have at least one note added.
Expected: The first note of the selected book will be deleted.
Adding a goal
-
Adding a goal to a book.
a. Prerequisites: At least one book being displayed in the book list.
b. Test case:
goal 1 p/X g/DD-MM-YYYY
Expected: Goal to read up till X pages by DD-MM-YYYY will be added to the first book.
Note: X must be more than the bookmarked page and less than the total number of pages in the first book, DD-MM-YYYY must not be in the past.
Deleting a goal
-
Deleting a goal from a book.
a. Prerequisities: At least one book with a goal being displayed in the book list.
b. Test case:
goaldel X
where X is the Xth book in the displayed book list. The Xth book must have a goal set.
Expected: The goal of the selected book will be deleted. Success message shown in status message.
Saving data
-
Dealing with missing data files
a. Prerequisites: Must have started the application at least once.
b. Test case: In the folder containing
bookmark.jar
, delete thedata/library.json
file. Open the application. Make some commands and then close the application with theexit
command.
Expected: The application starts up with some sample data. On exit, thedata/library.json
file is created again, containing the new data. -
Dealing with corrupted data files
a. Prerequisites: Must have started the application at least once. At least one book stored in the data file.
b. Test case: In the folder containing
bookmark.jar
, open thedata/library.json
file and edit it by removing the “name”” property from one of the JSON objects. This simulates a corrupted data file. Open the application. Make some commands and then close the application with theexit
command.
Expected: The application starts up with no books being displayed. On exit, thedata/library.json
file is recreated, containing the new data and will overwrite the “corrupted” data file.Note: In normal usage, users are not expected to manually make changes to the JSON data file.
Appendix: Effort
Model
The Model
of bookmark is definitely more complex than that of AB3. In AB3, the Person class had fields that were mostly
served as simple attributes for Person objects.
In bookmark, each book has more complex attributes, such as Note
and Goal
. Each note can be added and deleted individually
and and each goal also has to keep track of multiple attributes such as the deadline and the target page. Additionally,
the different attributes have to interact with one another to ensure that they are valid. For instance, we have to make sure
that Bookmark
’s value does not exceed the TotalPages
’s value as the bookmarked page cannot be greater than the total
number of pages in a book. The logic behind can be implemented in various ways and we went through different approaches
to keep the logic as simple as possible.
Additionally, the Model
of bookmark supports even more complex functionality such as undoing and redoing as well as
additions to support the suggestions feature which was not present in AB3.
Logic
As the complexity of Model
increased, the complexity of Logic
would also have to increase. bookmark’s Logic
supports
many more Commands compared to AB3, including an improved find
Command, sort
Command, note
Command and many more.
Significant effort was put in to implement new logic for bookmark as the Logic
of bookmark is responsible for
a lot more compared to AB3. For example, a new ViewTypeManager
class was created to keep track of what ViewType
to display
a given state of the application with so that the undo
and redo
commands can work well. We also put significant
effort into sanitising and validating user input as well as increasing the testing rigour to ensure that bookmark’s parsers
can handle unexpected inputs smoothly.
Storage
The Storage
was extended to support more options for user preferences such as sorting preference. Furthermore, the
more complex attributes of each Book
mentioned earlier have to be stored into the JSON file in an appropriate format.
We spent quite a while discussing and deciding on the best way to represent each new attribute in the JSON file.
Ui
A lot of effort was put into the Ui
. Our team put in a lot of effort in ensuring that our interface is pleasing
to the eye and user-friendly. We also introduced a “view switching” feature which allows users to switch between different
pages which made the Ui a lot more interactive compared to the single page view of AB3’s Ui.
Overall
As a whole, the process was not an easy one for the bookmark team. We started off with plans for many features that we eventually decided were not suitable. Hence, in the first iterations, we spent a considerable amount of time coming up with new ideas to be implemented. Nevertheless, we put in consistent effort throughout the weeks to build, test and improve the new features within the timeline of the iterations. Although not perfect, the end product is something that we are proud of and we can say for sure that we did not spare any effort throughout the process.