diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 00000000000..0cd51c5fb97
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,25 @@
+name: MarkBind Action
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install Graphviz
+ run: sudo apt-get install graphviz
+ - name: Install Java
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ - name: Build & Deploy MarkBind site
+ uses: MarkBind/markbind-action@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ rootDirectory: './docs'
+ baseUrl: '/tp' # assuming your repo name is tp
+ version: '^5.2.0'
diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..eab4c7db6a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+docs/_markbind/logs/
diff --git a/README.md b/README.md
index 13f5c77403f..4ff534831d9 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,34 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![CI Status](https://github.com/AY2324S2-CS2103T-F11-2/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2324S2-CS2103T-F11-2/tp/actions/workflows/gradle.yml)
![Ui](docs/images/Ui.png)
+## About MyBookshelf
+* MyBookshelf is designed to improve and ease the management of public community libraries.
+* MyBookshelf helps to keep track of the person who has borrowed which books from the library.
+* MyBookShelf helps to keep track of the book transactions within the library.
+* MyBookShelf helps to keep track of the merit score of each user of the library.
+* This helps to prevent irresponsible behaviour that violates the library's borrowing guidelines, easing management duties.
+
+
+## Project Details
+ * The project is CLI based to make it quick to use and easy for users who prefer typing commands over mouse operations.
+ * The librarians can add, edit, find or delete users in order to keep track of the people who is using their library
+ * The librarians can keep track of borrowed books and the borrower's information as well as return transactions.
+ * The librarians can keep track donated books and the information of the donors.
+ * The project simulates an ongoing software project for a desktop application (called AddressBook) used for managing contact details.
+ * It is written in OOP fashion. It provides a reasonably well-written code base bigger (around 6 KLoC), without being overwhelmingly big.
+ * It comes with a reasonable level of user and developer documentation.
+
+* Example usages:
+ * Allows librarian to **add** new user
+ * Allows librarian to **list** all user
+ * Allows librarian to **edit** an existing user
+ * Allows librarian to **find** user whose names contain the given keywords
+ * Allows librarian to **delete** an existing user
+ * Allows librarian to create a **borrow** transaction
+ * Allows librarian to create a **return** transaction
+ * Allows librarian to create a **donate** transaction
+
+## Acknowledgement
+* For the detailed documentation of this project, see the **[MyBookshelf Product Website](https://ay2324s2-cs2103t-f11-2.github.io/tp/)**.
-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
diff --git a/build.gradle b/build.gradle
index a2951cc709e..9d95acc1925 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,6 +16,10 @@ repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
+run {
+ enableAssertions = true
+}
+
checkstyle {
toolVersion = '10.2'
}
@@ -66,7 +70,7 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'mybookshelf.jar'
}
defaultTasks 'clean', 'test'
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000000..1748e487fbd
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,23 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+_markbind/logs/
+
+# Dependency directories
+node_modules/
+
+# Production build files (change if you output the build to a different directory)
+_site/
+
+# Env
+.env
+.env.local
+
+# IDE configs
+.vscode/
+.idea/*
+*.iml
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..8352db9286c 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,59 +1,61 @@
---
-layout: page
-title: About Us
+ layout: default.md
+ title: "About Us"
---
+# About Us
+
We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Ho Jin Han
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/jinhanfromNUS)]
+[[portfolio](team/jinhanfromNUS.md)]
-* Role: Project Advisor
+* Role: Developer
+* Responsibilities: Features and Documentation
-### Jane Doe
+### Khoo Jing Xiang
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/meowwtama)]
+[[portfolio](team/meowwtama.md)]
-* Role: Team Lead
+* Role: Developer
* Responsibilities: UI
-### Johnny Doe
+### Ashley Chang
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/ashleyclx)] [[portfolio](team/ashleyclx.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Features and Documentation
-### Jean Doe
+### Tan Yi-Sheng
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/Ty-stan0417)]
+[[portfolio](team/Ty-stan0417.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Features and Documentation
-### James Doe
+### Shenoy Suraj Bantwal
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/Darkarche3)]
+[[portfolio](team/Darkarche3.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Testing
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 13cf0faea16..32f6255f3b9 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -1,6 +1,8 @@
---
-layout: page
-title: Configuration guide
+ layout: default.md
+ title: "Configuration guide"
---
+# Configuration guide
+
Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`).
diff --git a/docs/DevOps.md b/docs/DevOps.md
index d2fd91a6001..8228c845e86 100644
--- a/docs/DevOps.md
+++ b/docs/DevOps.md
@@ -1,12 +1,15 @@
---
-layout: page
-title: DevOps guide
+ layout: default.md
+ title: "DevOps guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# DevOps guide
---------------------------------------------------------------------------------------------------------------------
+
+
+
+
## Build automation
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 1b56bb5d31b..8b93686e296 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -1,15 +1,27 @@
---
-layout: page
-title: Developer Guide
+ layout: default.md
+ title: "Developer Guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+
+# MyBookshelf Developer Guide
+
+
+
--------------------------------------------------------------------------------------------------------------------
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+_{ list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well }_
+1. This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+1. Library storage was based on JinHan's IP (https://github.com/jinhanfromNUS/ip/).
+1. Youtube video to check Java version (https://www.youtube.com/watch?v=3nOmkqO0-SM).
+1. Download Java 11 (https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html).
+1. How to deal with issue of opening command prompt (https://www.youtube.com/watch?v=pBheH2QtktI&t=92s).
+1. Download Java 11 (https://www.azul.com/downloads/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx).
+1. How to deal with issues of opening terminal (https://discussions.apple.com/thread/366608?sortBy=best).
+1. ASCII table (https://www.javatpoint.com/java-ascii-table).
--------------------------------------------------------------------------------------------------------------------
@@ -21,39 +33,34 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
## **Design**
-
-
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
-
-
### Architecture
-
+
-The ***Architecture Diagram*** given above explains the high-level design of the App.
+The ***Architecture Diagram*** given above explains the high-level design of the application.
Given below is a quick overview of main components and how they interact with each other.
**Main components of the architecture**
**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
-* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
+* At app launch, it initialises the other components in the correct sequence, and connects them up with each other.
* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
The bulk of the app's work is done by the following four components:
-* [**`UI`**](#ui-component): The UI of the App.
+* [**`UI`**](#ui-component): The UI of the application.
* [**`Logic`**](#logic-component): The command executor.
-* [**`Model`**](#model-component): Holds the data of the App in memory.
+* [**`Model`**](#model-component): Holds the data of the application in memory.
* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
**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 *Sequence Diagram* below shows how the components interact with each other for the scenario where the library manager issues the command `delete 1`.
-
+
Each of the four main components (also shown in the diagram above),
@@ -62,23 +69,23 @@ Each of the four main components (also shown in the diagram above),
For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
-
+
The sections below give more details of each component.
### UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java).
-![Structure of the UI Component](images/UiClassDiagram.png)
+
The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
-The `UI` component uses the 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`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+The `UI` component uses the 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`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml).
The `UI` component,
-* executes user commands using the `Logic` component.
+* executes library manager commands using the `Logic` component.
* listens for changes to `Model` data so that the UI can be updated with the modified data.
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
@@ -89,14 +96,20 @@ The `UI` component,
Here's a (partial) class diagram of the `Logic` component:
-
+
+
+The following is a partial class diagram showing the interaction between the abstract Command class and the various command classes.
+
+
The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example.
-![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
+
+
+
-
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
-
+**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
+
How the `Logic` component works:
@@ -108,7 +121,7 @@ How the `Logic` component works:
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
-
+
How the parsing works:
* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
@@ -117,33 +130,57 @@ How the parsing works:
### Model component
**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
-
+
The `Model` component,
* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` 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.
-* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
-* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
+* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` object.
+* stores a `Library` object that represents the library data. This is exposed to the outside as a `ReadOnlyLibrary` object.
+* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components).
+
+
+
+The `Person` data,
+
+* consists of a `Name`, `Phone`, `Email`, `Address`, list of `Tag` objects, `MeritScore`, list of `Book` objects.
+
+The `Library` data
+
+* consists of a list of `Book` objects and `Threshold`
+* `Book` objects are exposed to outsiders as an unmodifiable `ObservableList` 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.`
+
+
+
+**Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+Similarly, `Library` can also have a list of known `Book` objects separate from the list of available `Book` objects that can help keep track of the count of a `Book`. This way, new `Book` objects will not have to be created for duplicate books.
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+
-
+
-
+
+How the library updates in Model:
+
+* After the Borrow Command is created, the index used in the command is used to retrieve the `Person` object corresponding to the index.
+* The `Merit Score` is retrieved from the `Person` object and compared to `Threshold` from the `Library` to check if `Person` can borrow a book.
+* If `Person` can borrow a book, the `Book` object is removed from the list of available books in `Library` object and added to the book list in `Person` object.
### Storage component
**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
-
+
The `Storage` component,
* can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
+* can save library data in .txt format, and read them back into corresponding objects.
* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
-* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
+* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`).
+* due to complications, `LibraryStorage` currently works separately from `Storage` but there are plans for `Storage` to inherit from `LibraryStorage` in the [Future Enhancements](#future-enhancements-team-members-5)).
### Common classes
@@ -155,6 +192,118 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
+### Merit Score feature
+
+**Merit Score Attribute:**
+* Added to the `Person` class.
+* Represents a measurement of a library user's credibility.
+* Default merit score of 0 assigned to every library user upon instantiate to the contact list.
+
+**Operations Affecting Merit Score:**
+* Donating books: Increases the library user's merit score by 1.
+* Returning books: Increases the library user's merit score by 1.
+* Borrowing books: Decreases the library user's merit score by 1.
+
+**Threshold Check Before Borrowing:**
+* Check is performed before library user borrows a book.
+* Library users must have a merit score greater than or equal to the threshold to borrow books successfully.
+
+**Threshold Setting:**
+* Library users can set the threshold using the `limit` command.
+* Allows customisation of the threshold limit, which determines the minimum merit score required before allowing a library user to borrow a book.
+* This provides the library manager with the flexibility to determine what's best for the library.
+
+These changes aim to regulate borrowing behavior, preventing excessive borrowing and ensuring fair access to library resources based on library user's credibility as measured by their merit score.
+
+### Library and LibraryStorage feature
+
+**Library Class:**
+* This class represents all available books in a library.
+* It contains methods for adding, deleting, and listing books, as well as checking if a person can borrow a book based on their merit score.
+* It internally uses an ObservableList to manage the list of books.
+* It also manages a Threshold object to determine if a person can borrow a book.
+* Notably, it does not directly handle storage operations such as loading or saving books from/to a file. Instead, it delegates these responsibilities to the LibraryStorage class.
+
+**LibraryStorage Class:**
+* This class manages the loading and saving of available books to a text file.
+* It uses a file path to determine where to store the data.
+* It handles loading threshold and book data from a file into an ObservableList and Threshold object respectively.
+* It also saves threshold and book data from an ReadOnlyLibrary object (which is implemented by the Library class) to a file.
+
+These two classes work together to provide functionality for managing a library's collection of books, with `Library` handling operations directly related to book management and `LibraryStorage` handling file I/O operations.
+
+This separation of concerns helps in keeping the code modular and maintainable.
+
+`Library` now acts as a similar entity to the `AddressBook` and `UserPrefs` and is now composited into `Model`, and implements the ReadOnlyLibrary Interface.
+
+`Model` now contains useful `Library` operations such as:
+* `Threshold` operations.
+* `Book` operations on the book list in a library.
+* Checks for if library users can borrow books in a library.
+
+### Limit Command and Threshold feature
+
+This command is facilitated through the use of `Threshold` as an attribute in the `Library` class.
+
+Any library user has to have a `Merit Score` greater or equal to the set `Threshold` in order to borrow from the `Library`.
+
+As `Threshold` is now an attribute of `Library`, the library user's ability to borrow now depends on the Library instance and not within the Borrow Command.
+
+Limit Command sets the `Threshold` of the `Library`, resulting in all library users being affected by the change at the same time when the Limit Command is called.
+
+The default value of a `Threshold` is set as `-3`. Any calls to the Limit Command with the same value of the current `Threshold` will result in a duplicate threshold detected message.
+
+`Library` now has a method to check if a library user can borrow a book from the library by internally comparing the user's `Merit Score` and the library's `Threshold`. Borrow Command now utilises this function to check if a user is able to Borrow a book from the Library instead of handling the check within the Borrow Command itself.
+
+#### Desired Usage
+
+Library managers can retroactively disallow library users from borrowing books from the Library, with library users having to meet the limit set before being able to borrow again.
+
+1. Library starts in a default state. After some borrowing occurred.
+ * Library user A has `Merit Score`:0.
+ * Library user B has `Merit Score`: -2.
+ * Both library user A and library user B can borrow books from the library.
+1. Library manager calls `limit 0`.
+ * `Threshold`: 0.
+ * Library user A has `Merit Score`: 0 (greater than or equal to `Threshold`).
+ * Library user B has `Merit Score`: -2 (less than `Threshold`).
+ * Library user A can borrow but library user B cannot borrow from the Library.
+1. Library manager calls `limit -2`.
+ * `Threshold`: -2.
+ * Library user A has `Merit Score`: 0 (greater than or equal to `Threshold`).
+ * Library user B has `Merit Score`: -2 (greater than or equal to `Threshold`).
+ * Both library user A and library user B can borrow books from the library.
+1. Library user B borrows a book.
+ * `Threshold`: -2.
+ * Library user A has `Merit Score`: 0 (greater than or equal to `Threshold`).
+ * Library user B has `Merit Score`: -3 (greater than or equal to `Threshold`).
+ * Library user A can borrow but library user B cannot borrow from the Library.
+1. Library manager calls `limit 1`.
+ * `Threshold`: 1.
+ * Library user A has `Merit Score`: 0 (less than `Threshold`).
+ * Library user B has `Merit Score`: -3 (less than `Threshold`).
+ * Both library user A and library user B cannot borrow books from the library.
+1. Library user A returns a book and library user B donates a book each.
+ * `Threshold`: 1
+ * Library user A has `Merit Score`: 1 (greater than or equal to `Threshold`).
+ * Library user B has `Merit Score`: -2 (less than `Threshold`).
+ * Both library user A and library user B can donate and return to the library.
+ * Library user A can borrow but library user B still cannot borrow from the Library.
+
+#### Alternative Implementation
+
+It is also plausible for `Threshold` to be implemented as an attribute within each library user.
+
+This would also change the implementation for the `limit` command to now individually set limits to each specified library user.
+
+This would give the library manager greater flexibility to vary each of the library user's individual ability to borrow books.
+
+This implementation was decided against as setting a standardised limit would give an easier time for library managers to manage all library users at the same time, and not having to individually manage each user's `Threshold`.
+
+Individual library user's ability to borrow can also be increased and decreased indirectly by changing the library user's merit score.
+
+> Note: the library user's Merit Score cannot be decreased without altering the library user's borrowing book list.
+
### \[Proposed\] Undo/redo feature
#### Proposed Implementation
@@ -169,60 +318,69 @@ These operations are exposed in the `Model` interface as `Model#commitAddressBoo
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 `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+Step 1. The library manager launches the application for the first time. The `VersionedAddressBook` will be initialised with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+
+
+
+Step 2. The library manager executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+
+
-![UndoRedoState0](images/UndoRedoState0.png)
+Step 3. The library manager executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+
-![UndoRedoState1](images/UndoRedoState1.png)
+
-Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+**Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
-![UndoRedoState2](images/UndoRedoState2.png)
+
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+Step 4. The library manager now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
-
+
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
-![UndoRedoState3](images/UndoRedoState3.png)
+
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
+**Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the library manager rather
than attempting to perform the undo.
-
+
The following sequence diagram shows how an undo operation goes through the `Logic` component:
-![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png)
+
-
:information_source: **Note:** 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.
+
-
+**Note:** 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.
+
+
Similarly, how an undo operation goes through the `Model` component is shown below:
-![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png)
+
The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
+
+
+**Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the library manager rather than attempting to perform the redo.
-
+
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+Step 5. The library manager then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
-![UndoRedoState4](images/UndoRedoState4.png)
+
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+Step 6. The library manager executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
-![UndoRedoState5](images/UndoRedoState5.png)
+
-The following activity diagram summarizes what happens when a user executes a new command:
+The following activity diagram summarises what happens when a library manager executes a new command:
-
+
#### Design considerations:
@@ -232,16 +390,49 @@ The following activity diagram summarizes what happens when a user executes a ne
* 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.
+* **Alternative 2:** Individual command knows how to undo/redo by itself.
* Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
* Cons: We must ensure that the implementation of each individual command are correct.
-_{more aspects and alternatives to be added}_
-### \[Proposed\] Data archiving
+--------------------------------------------------------------------------------------------------------------------
-_{Explain here how the data archiving feature will be implemented}_
+## **Future Enhancements (Team members: 5)**
+
+1. More specific constraints for the `EMAIL` parameter.
+ * Add checks for top-level domain (.com, .net, .co etc) into regular expression.
+1. Handling of extreme values to be added.
+ * Add checks for `INDEX` and `THRESHOLD` to be parsed within the boundaries of Java's `Integer.MIN_VALUE` and `Integer.MAX_VALUE`.
+ * Add a limit on the String length for `NAME`, `PHONE-NUMBER`, `EMAIL`, `ADDRESS`, `TAG`, `KEYWORD`, `BOOKTITLE` to a reasonable length.
+1. Enhance `Book` class.
+ * To allow each individual book to have its own unique ID.
+ * This is to allow the library manager to decide which copy of a book with the same title is to be used in a command.
+ * Currently, the application preemptively chooses the first copy of 2 books with the same title to be used in each command.
+ * With the unique ID field in each `Book`, the library manager can specifically choose which book to use in the commands.
+1. Change the definition of a duplicate Person.
+ * Currently only checks if the `NAME` field are exactly the same.
+ * Can be changed to check if `PHONE` or `EMAIL` are exactly the same as they are unique identifiers and not `NAME`.
+ * Allows for `NAME` to be case-insensitive (John Doe and john doe are the same person).
+ * Allows for library users with the same name to exist (John Doe with phone number 123 is different from John Doe with phone number 911).
+ * Can throw warnings if `NAME` differs by only by white spaces (John Doe and John Doe are similar and could be duplicates).
+1. Improve clarity of command results.
+ * Reduce information related to Person displayed when command successfully executes to reduce bloat. Can be done by:
+ * Editing toString() to display less information.
+ * Reformatting toString() to include new lines for easier readability.
+ * Format specific fields to be displayed into the command result.
+ * Change message when `clear` command is executed to use the words "Contact List" instead of "Address book" for clarity.
+ * Improve error message when `INDEX` entered by library manager is greater than the length of the Contact List to be clearer (e.g. Index is larger than the number of people in the list).
+ * Improve the usage message for commands that changes like `borrow`, `return`, `edit`.
+ * Change the phrasing of "Edits the book list" to be clearer (E.g. "Remove book from library user's book list" in `return` command).
+ * Remove the phrase "in the last person listing" as it is confusing.
+1. Add labels under each library user in the contact list panel in the UI.
+ * Label each field to allow for easier readability, especially between email and address (e.g. e: example@email.com, a: Kent Ridge View).
+1. Improve code architecture.
+ * Currently, there is a separate `LibraryStorage` class outside of `StorageManager` class that handles the data for the `Library`.
+ * Can extract methods from `LibraryStorage` to inside `StorageManager` to be more consistent with the code architecture.
+1. Fix grammar errors in messages.
+ * Error message when library user cannot borrow due to insufficient `Merit Score` has an extra `'s` in the word `User's`.
+ * Many messages do not end with period.
--------------------------------------------------------------------------------------------------------------------
@@ -262,71 +453,420 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+* has a need to manage a significant number of contacts.
+* prefer desktop apps over other types.
+* can type fast.
+* prefers typing to mouse interactions.
+* is reasonably comfortable using CLI apps.
+* needs to keep track of which library user borrowed which book.
+* needs to keep track of which library user returned which book.
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: manage library users and keeps track of borrowing and returning of books faster than a typical mouse/GUI driven app.
### User stories
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`.
+
+| Priority | As a … | I want to … | So that I can… |
+|----------|-----------------------------------------------------|-------------------------------------------------------------|-----------------------------------------------------------------------------------|
+| `* * *` | new library manager | see usage instructions | refer to instructions when I forget how to use the App. |
+| `* * *` | library manager | add a new library user | record a new user's information. |
+| `* * *` | library manager | delete a library user | remove entries that I no longer need. |
+| `* * *` | library manager | find a library user by name | locate details of persons without having to go through the entire list. |
+| `*` | library manager with many users in the Contact List | sort library user by name | locate a person easily. |
+| `* * *` | library manager | record the phone number of the library user | send SMS reminders to notify them that someone else is looking for the book. |
+| `* * *` | library manager | record the email address of the library user | send an email reminders to notify them that someone else is looking for the book. |
+| `* * *` | library manager | record the postal address of the library user | send a warning letter when breaching community guidelines. |
+| `* * *` | library manager | record the book title of all books in the library | keep track of the books available in the library at the moment. |
+| `* * *` | library manager | record the book title the library user has borrowed | keep track of the books the borrower has borrowed. |
+| `* *` | library manager | be able to decide the threshold merit score for the library | decide the limit of books to borrow to the users. |
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-*{More to be added}*
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `MyBookshelf` and the **Actor** is the `Community Library Manager (CLM)`, unless specified otherwise)
+
+#### Use case: UC1 - Add library user to Contact List
+
+#### MSS:
+
+1. Library user provides the following details:
+ * Name.
+ * Phone number.
+ * Email.
+ * Address.
+ * Optionally, additional tags.
+2. Library manager enters the provided information.
+3. MyBookshelf adds the library user to the contact list.
+4. MyBookshelf notifies library manager that the library user has been successfully added.
+
+***Use case ends***
+
+#### Extensions:
+* 2a. MyBookshelf detects an error in the information entered.
+ * 2a1. MyBookshelf requests for the valid information.
+ * 2a2. Library manager requests information from user.
+ * 2a3. Library manager enters new information.
+ Steps 2a1-2a3 are repeated until the information entered is valid.
+
+ ***Use case resumes from step 3***
+
+* *a. At any time, library manager chooses to cancel the addition of user.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends***
+
+#### Use case: UC2 - List all library users in the Contact List
+
+#### MSS:
+
+1. Library manager intends to list all library users.
+2. Library manager enters the command.
+3. MyBookshelf retrieves the information from the contact list.
+4. MyBookshelf displays a list of all library users, including their names, contact information, and any other relevant details.
+5. Library manager views the list of library users.
+
+***Use case ends***
+
+#### Extensions:
+* *a. At any time, library manager chooses to cancel the addition of library user.
+
+ * *a1. library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+#### Use case: UC3 - Edit a library user's information
+
+#### MSS:
+
+1. Library manager intends to edit a library user's information.
+2. Library manager enters the command.
+3. MyBookshelf updates the library user's information according to the provided changes.
+4. MyBookshelf notifies library manager that the library user has been successfully edited.
+
+***Use case ends***
+
+#### Extensions:
+* 2a. MyBookshelf detects invalid index.
+ * 2a1. MyBookshelf notifies library manager with an error message.
+ * 2a2. Library manager finds the library user in the contact list (UC4).
+ * 2a3. If user does not exist, library manager adds the new library user to the contact list (UC1), else records down the index for later use in the edit process.
+
+ ***Use case resumes from step 2.***
+
+* 2b. MyBookshelf detects an error in the entered information.
+ * 2b1. MyBookshelf requests for the valid information.
+ * 2b2. Library manager requests information from the library user.
+ * 2b3. Library manager enters new information.
+ Steps 2b1-2b3 are repeated until the information entered is valid.
+
+ ***Use case resumes from step 3.***
+
+* *a. At any time, library manager chooses to cancel the addition of the library user.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC4 - Find a library user in the Contact List
+#### MSS:
+1. Library manager intends to find library users using keyword(s).
+2. Library manager enters the command.
+3. MyBookshelf searches for library users whose names matches any of the provided keyword(s).
+4. MyBookshelf returns a list of library users matching at least one keyword.
+5. Library manager views the list of library users returned by the search.
+
+***Use case ends***
+
+#### Extensions:
+* *a. At any time, Library manager chooses to cancel the finding process of library users.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC5 - Delete a library user from the Contact List
+#### MSS:
+1. Library manager intends to delete a user.
+2. Library manager enters the command.
+3. MyBookshelf deletes the user from the contact list.
+4. MyBookshelf notifies library manager that the user has been successfully deleted.
+
+***Use case ends***
+
+#### Extensions:
+* 2a. MyBookshelf detects an invalid index.
+ * 2a1. MyBookshelf notifies library manager with an error message.
+ * 2a2. Library manager performs Find a library user in the Contact List (UC4).
+ * 2a3. Library manager performs Delete a library user from the Contact List (UC5).
+
+ ***Use case resumes from step 2.***
+
+* *a. At any time, library manager chooses to cancel the process of deleting the library user.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC6 - Library user borrow a book
+#### MSS:
+1. Library user intends to borrow a book from the library.
+2. Library manager reads the book title of the book provided by the library user.
+3. Library manager enters the command using the book title.
+4. MyBookshelf removes the book from library and adds the book to the library user's book list.
+5. MyBookshelf notifies library manager that the library user has successfully borrowed a book.
+
+***Use case ends***
+
+#### Extensions:
+* 3a. MyBookshelf detects an invalid index for library user.
+ * 3a1. MyBookshelf notifies library manager with an error message.
+ * 3a2. Library manager performs Find a library user in the Contact List (UC4) and Library user borrows a book (UC6).
+ * 3a3. If the library user does not exist, library manager performs Add library user to the Contact List (UC1) and Library user borrows a book (UC6).
+
+ ***Use case resumes from step 3.***
+
+* 3b. MyBookshelf detects an error in the book title.
+ * 3b1. MyBookshelf notifies library manager with an error message.
+ * 3b2. Library manager confirms with the user if the book title provided is correct.
+ * 3b3. Library manager enters the corrected book title.
+ Steps 3b1-3b3 are repeated until the book title entered is valid.
+
+ ***Use case resumes from step 3.***
+
+* *a. At any time, library manager chooses to cancel the borrowing process of the library user.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC7 - Library user returns a book
+#### MSS:
+1. Library user intends to return a book to the library.
+2. Library manager reads the book title of the book provided by the library user.
+3. Library manager enters the command to return the book.
+4. MyBookshelf adds the book to the library and removes the book from the library user's book list.
+5. MyBookshelf notifies library manager that the library user has successfully returned the book.
+
+***Use case ends***
+
+#### Extensions:
+* 3a. MyBookshelf detects invalid index for library user.
+ * 3a1. MyBookshelf notifies library manager with an error message.
+ * 3a2. Library manager performs Find a library user in the Contact List (UC4).
+ * 3a3. Library manager performs Library user returns a book (UC7).
+
+ ***Use case resumes from step 3.***
+
+* 3b. MyBookshelf detects an error in the book title.
+ * 3b1. MyBookshelf notifies library manager with an error message.
+ * 3b2. Library manager checks if the book title he entered is correct.
+ * 3b3. Library manager enters new book title.
+ Steps 3b1-3b3 are repeated until the book title entered is valid.
+
+ ***Use case resumes from step 3.***
+
+* *a. At any time, Library manager chooses to cancel the returning process of the library user.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
-**Use case: Delete a person**
+#### Use case: UC8 - Library user donates a book
+#### MSS:
+1. Library user intends to donate a book to the library.
+2. Library manager reads the book title of the book provided by the library user.
+3. Library manager enters the command to donate the book.
+4. MyBookshelf adds the book to the library.
+5. MyBookshelf notifies library manager that the library user has successfully donated a book.
-**MSS**
+***Use case ends***
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+#### Extensions:
+* 3a. MyBookshelf detects invalid index for the library user.
+ * 3a1. MyBookshelf notifies library manager with an error message.
+ * 3a2. Library manager performs Find a library user in the Contact List (UC4) and Library user donates a book (UC8).
+ * 3a3. If library user does not exist, library manager performs Add library user to the Contact List (UC1) and Library user donates a book (UC8).
- Use case ends.
+ ***Use case resumes from step 3.***
-**Extensions**
+* 3b. MyBookshelf detects an error in the book title.
+ * 3b1. MyBookshelf notifies library manager with an error message.
+ * 3b2. Library manager checks if the book title he entered is correct.
+ * 3b3. Library manager enters new book title.
+ Steps 3b1-3b3 are repeated until the book title entered is valid.
-* 2a. The list is empty.
+ ***Use case resumes from step 3.***
- Use case ends.
+* *a. At any time, library manager chooses to cancel the donation process.
+ * *a1. Library manager clears the command line using the backspace key.
-* 3a. The given index is invalid.
+ ***Use case ends.***
- * 3a1. AddressBook shows an error message.
- Use case resumes at step 2.
+#### Use case: UC9 - Add a book to the library
+#### MSS:
+1. Library manager intends to add a book to the library.
+2. Library manager enters the command to add the book.
+3. MyBookshelf adds the book to the library.
+4. MyBookshelf notifies library manager that the book has been successfully added to the library.
-*{More to be added}*
+***Use case ends***
+
+#### Extensions:
+* 2b. MyBookshelf detects an error in the book title.
+ * 2b1. MyBookshelf notifies library manager with an error message.
+ * 2b2. Library manager checks if the book title he entered is correct.
+ * 2b3. Library manager enters new book title.
+ Steps 2b1-2b3 are repeated until the book title entered is valid.
+
+ ***Use case resumes from step 2.***
+
+* *a. At any time, library manager chooses to cancel the addition process.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC10 - Delete a book from the library
+#### MSS:
+1. Library manager intends to delete a book from the library.
+2. Library manager enters the command to delete the book.
+3. MyBookshelf deletes the book from the library.
+4. MyBookshelf notifies the library manager that the book has been successfully deleted from the library.
+
+***Use case ends***
+
+#### Extensions:
+* 2b. MyBookshelf detects an error in the book title.
+ * 2b1. MyBookshelf notifies library manager with an error message.
+ * 2b2. Library manager checks if the book title he entered is correct.
+ * 2b3. Library manager enters new book title.
+ Steps 2b1-2b3 are repeated until the book title entered is valid.
+
+ ***Use case resumes from step 2.***
+
+* *a. At any time, Library manager chooses to cancel the deletion process.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC11 - View the limit for library
+#### MSS:
+1. Library manager intends to view the limit of the library.
+2. Library manager enters the command to view the current limit.
+3. MyBookshelf displays the current limit of the library.
+
+***Use case ends***
+
+#### Extensions:
+* *a. At any time, library manager chooses to cancel the lookup process.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC12 - Set the limit for library
+#### MSS:
+1. Library manager intends to set a new limit for library's threshold.
+2. Library manager enters the command to set the new limit.
+3. MyBookshelf sets the limit of the library.
+4. MyBookshelf notifies library manager that the limit has been successfully updated for the library.
+
+***Use case ends***
+
+#### Extensions:
+* 2a. MyBookshelf detects invalid limit.
+ * 2a1. MyBookshelf notifies library manager with an error message.
+ * 2a2. Library manager reenters the limit.
+ Steps 2a1-2a2 are repeated until the limit entered is valid.
+
+ ***Use case resumes from step 3.***
+
+* *a. At any time, library manager chooses to cancel the setting of limit.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC13 - Clear the Contact List
+#### MSS:
+1. Library manager requests to clear the contact list (delete all library users).
+2. Library manager enters the command.
+3. MyBookshelf clears all library users in the contact list.
+4. MyBookshelf notifies library manager that all library users have been successfully removed.
+
+***Use case ends***
+
+#### Extensions:
+* *a. At any time, library manager chooses to cancel the process of clearing the contact list.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC14 - Help
+#### MSS:
+1. Library manager intends to access the help window.
+2. Library manager enters the command.
+3. MyBookshelf pops up the help window.
+
+***Use case ends***
+
+#### Extensions:
+* *a. At any time, library manager chooses to cancel the process of accessing the help window.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
+
+
+#### Use case: UC15 - Exit MyBookshelf
+#### MSS:
+1. Library manager intends to exit the application.
+2. Library manager enters the command.
+3. MyBookshelf exits successfully.
+
+***Use case ends***
+
+#### Extensions:
+* *a. At any time, library manager chooses to cancel the exit process.
+ * *a1. Library manager clears the command line using the backspace key.
+
+ ***Use case ends.***
### Non-Functional Requirements
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
+3. A library manager 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.
+4. Should be easy to use and fast to learn for library managers who are new to this application.
+5. Response time should be fast enough that it does not take the library manager a long time to use it.
+6. Should be easy to recognise and remember necessary commands to minimise need for library manager to check what command to use.
-*{More to be added}*
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **CLI**: Command-Line Interface, a tool you employ to communicate with your operating system via your keyboard.
+* **GUI**: Graphical User Interface, a graphical interface where users engage with visual elements like icons, buttons, and menus.
+* **JSON**: JavaScript Object Notation, a text format for storing and transporting data.
+* **Parameter**: Data that users input into commands.
+* **Alphanumeric**: A character that is either a letter or a number.
+* **Mainstream OS**: Windows, Linux, Unix, MacOS.
+* **Library Manager**: Community Library Managers (CLM) are the people using the MyBookshelf application. CLMs are responsible for adding, storing, and updating the entire library database via *MyBookshelf*.
+* **Library User**: The people that are saved into the Contact List of *MyBookshelf*. Sometimes referred as to "borrowers".
+* **Personal Information**: Personal Information of a library user, e.g. name, phone number, email, address and tags, but not borrowed books and merit score.
+* **Book**: Identified by its `BOOKTITLE`. Appears in both the `Library User`'s book list and the `Library Book List`.
+* **Borrow**: An action where a library user borrows a book from the library.
+* **Return**: An Action where a library user returns the book which they borrowed from the library.
+* **Donate**: An action where a person donates a book to the library.
+* **Merit Score**: A score associated with each `Library User`. This score provides an estimate of the number of books a library user can borrow.
+* **Threshold**: A threshold for merit score. A library user must higher merit score than threshold in order to borrow books. Threshold can be set to the library by library manager anytime.
+* **Contact List**: Refers to the list of library users currently stored in the *MyBookshelf* application. It appears in the left column of the User Interface.
+* **Library Book List**: Refers to the list of available `Book`(s) currently stored in the *MyBookshelf* application. It appears in the right column of the User Interface. Sometimes referred as "library" or "available books".
+
--------------------------------------------------------------------------------------------------------------------
@@ -334,49 +874,241 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Given below are instructions to test the app manually.
-
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
+
+> **Note:** These instructions only provide a starting point for testers to work on;
testers are expected to do more *exploratory* testing.
-
### Launch and shutdown
-1. Initial launch
+1. Initial launch.
+ 1. Download `mybookshelf.jar` and save it into an empty folder.
+ 1. Using the command prompt / terminal, navigate to the directory (using `cd`) where `mybookshelf.jar` is saved.
+ 1. Launch the app by running `java -jar mybookshelf.jar`.
+ 1. Expected: Shows the GUI with a set of sample contacts.
+
+
+2. Saving window preferences.
+ 1. After resizing the window to your preferred size and moving it to your preferred location, these preferences will be auto-saved at the end of each session.
+ 1. Closing and re-launching the app loads this changes automatically.
+ 1. Expected: The most recent window size and location is retained.
+
+
+3. Exiting the app using `exit` command.
+ 1. Type `exit` to the command box.
+ 1. Expected: The app window closes.
+
+
+4. Exiting the app by clicking the close button.
+ 1. Navigate to the top right corner of MyBookshelf.
+ 1. Click the close button.
+ 1. Expected: The app window closes.
+
+
+5. Exiting the app by clicking the `Exit` button in `File` tab
+ 1. Navigate to the top left corner of MyBookshelf.
+ 1. Click the `File` tab.
+ 1. Click the `Exit` button.
+ 1. Expected: The app window closes.
+
+
+> **Warning:** The following sections assume that you are using the sample data populated with the initial start-up. Follow the steps in order for the desired results.
+
+
+### Deleting a library user
+
+1. Deleting a library user with all library users in the contact list shown.
+ 1. Prerequisites: List all library users using the `list` command.
+ 1. Test case: `delete 6`.
+ 1. Expected: Last contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+
+
+2. Attempting to delete a library user of an invalid index.
+ 1. Test case: `delete 6`.
+ 1. Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+
+
+3. Attempting to use the `delete` command inappropriately.
+ 1. Other incorrect `delete` commands to try: `delete`, `delete x`, `...` (where x is any invalid parameter).
+ 1. Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+
+
+### Adding a book to the library
+
+1. Add a book into the library book list.
+ 1. Prerequisite: `BOOKTITLE` must not contain the character sequence ` b/` within it. (Note the whitespace before 'b').
+ 1. Test case: `addbook b/Percy Jackson`.
+ 1. Expected: Book 'Percy Jackson' is successfully added to the library book list.
+
+
+2. Attempting to add a book without a title.
+ 1. Test case: `addbook b/`.
+ 1. Expected: No book is added to the library book list as `BOOKTITLE` cannot be empty. Error details shown in the status message. Status bar remains the same.
+
+
+3. Attempting to add a book with ` b/` within its title.
+ 1. Test case: `addbook b/Hello b/World` (`BOOKTITLE` is interpreted to be "Hello b/World").
+ 2. Expected: Since `b/` is used as the parser for our application, the use of multiple `b/` will result in an error message due to the use of multiple `b/`.
+
+
+4. Attempting to use the `addbook` command inappropriately.
+ 1. Other incorrect `addbook` commands to try: `addbook`, `addbook x`, `...` (where x is any invalid parameter).
+ 1. Expected: No book is added to the library. Error details shown in the status message. Status bar remains the same.
+
+
+### Deleting a book from the library
+
+1. Delete a book from the library book list.
+ 1. Prerequisite: `BOOKTITLE` must not contain the character sequence ` b/` within it. (Note the whitespace before 'b').
+ 1. Test case: `delbook b/Percy Jackson`.
+ 1. Expected: Book 'Percy Jackson' has been successfully removed from the library book list.
+
+
+2. Attempting to delete a book without a title.
+ 1. Test case: `delbook b/`.
+ 1. Expected: No book is removed from the library book list as `BOOKTITLE` cannot be empty. Error details shown in the status message. Status bar remains the same.
+
+
+3. Attempting to delete a book that is not in the library.
+ 1. Test case: `delbook b/Not In Library`.
+ 1. Expected: No book is removed from the library book list as the book titled `Not In Library` is not in the library book list.
+
+
+4. Attempting to use the `delbook` command inappropriately.
+ 1. Other incorrect `delbook` commands to try: `delbook`, `delbook x`, `...` (where x is any invalid parameter).
+ 1. Expected: No book is deleted from the library. Error details shown in the status message. Status bar remains the same.
+
+
+### Donating a book to the library
+
+1. Library user donates a book to the library.
+ 1. Prerequisite: `BOOKTITLE` must not contain the character sequence ` b/` within it. (Note the whitespace before 'b').
+ 1. Test case: `donate 1 b/Percy Jackson`.
+ 1. Expected: Book 'Percy Jackson' is successfully added to the library book list. Book 'Percy Jackson' will be displayed in library book list upon successful donation. Person at index 1's merit score will also increase by 1.
+
+
+2. Attempting to donate a book without a title.
+ 1. Test case: `donate 1 b/`.
+ 1. Expected: No book is added to the library book list as `BOOKTITLE` cannot be empty. Error details shown in the status message. Status bar remains the same.
+
+
+3. Attempting to donate a book with ` b/` within its title.
+ 1. Test case: `donate 1 b/Hello b/World` (`BOOKTITLE` is interpreted to be "Hello b/World").
+ 2. Expected: Since `b/` is used as the parser for our application, the use of multiple `b/` will result in an error message due to the use of multiple `b/`.
+
+
+4. Attempting to use the `donate` command inappropriately.
+ 1. Other incorrect `donate` commands to try: `donate`, `donate x`, `...` (where x is any invalid parameter).
+ 1. Expected: No book is donated to the library. Error details shown in the status message. Status bar remains the same.
+
+
+### Borrowing a book from the library
+
+1. Library user borrows a book from the library.
+ 1. Prerequisite: `BOOKTITLE` must not contain the character sequence ` b/` within it. (Note the whitespace before 'b').
+ 1. Test case: `borrow 1 b/Percy Jackson`.
+ 1. Expected: Book 'Percy Jackson' is successfully removed from the library book list and added to the library user's book list. Book 'Percy Jackson' will be displayed in library user's book list. The library user's merit score also decreases by 1.
+
+
+2. Attempting to borrow a book without a title.
+ 1. Test case: `borrow 1 b/`.
+ 1. Expected: No book is added to the library user's book list as `BOOKTITLE` cannot be empty. Error details shown in the status message. Status bar remains the same.
+
+
+3. Attempting to borrow a book with ` b/` within its title.
+ 1. Test case: `borrow 1 b/Hello b/World` (`BOOKTITLE` is interpreted to be "Hello b/World").
+ 2. Expected: Since `b/` is used as the parser for our application, the use of multiple `b/` will result in an error message due to the use of multiple `b/`.
+
+
+4. Attempting to use the `borrow` command inappropriately.
+ 1. Other incorrect `borrow` commands to try: `borrow`, `borrow x`, `...` (where x is any invalid parameter).
+ 1. Expected: No book is donated to the library. Error details shown in the status message. Status bar remains the same.
+
+
+### Returning a book to the library
+
+1. Library user returns a book to the library.
+ 1. Prerequisite: `BOOKTITLE` must not contain the character sequence ` b/` within it. (Note the whitespace before 'b').
+ 1. Test case: `return 1 b/Percy Jackson`.
+ 1. Expected: Book 'Percy Jackson' is successfully added to library book list and removed from the library user's book list. Book 'Percy Jackson' will be displayed in the library book list. User's merit score increases by 1.
+
+
+2. Attempting to return a book without a title.
+ 1. Test case: `return 1 b/`.
+ 1. Expected: No book is removed from the library user's book list as `BOOKTITLE` cannot be empty. Error details shown in the status message. Status bar remains the same.
+
+
+3. Attempting to return a book that is not in the library user's book list.
+ 1. Test case: `return 1 b/Not In Book List`.
+ 1. Expected: No book is removed from the library user's book list as the book titled `Not In Book List` is not in the library user's book list.
+
+
+4. Attempting to use the `return` command inappropriately.
+ 1. Other incorrect `return` commands to try: `return`, `return x`, `...` (where x is any invalid parameter).
+ 1. Expected: No book is returned to the library. Error details shown in the status message. Status bar remains the same.
+
+
+### Viewing the current threshold of the library
+
+1. Check the current limit threshold of the library.
+ 1. Prerequisite: The library has a valid `THRESHOLD`. Default `THRESHOLD` is `-3`.
+ 1. Test case: `limit`.
+ 1. Expected: The result box shows the current `THRESHOLD` of the library.
+
+
+### Setting a new threshold to the library
- 1. Download the jar file and copy into an empty folder
+1. Setting a new threshold to the library.
+ 1. Prerequisite: The library has a valid `THRESHOLD`. The new `THRESHOLD` is an integer between `-2147483648` and `2147483647`. Provided the new `THRESHOLD` is different from the old `THRESHOLD`.
+ 1. Test case: `limit -3` (Which is the same as the current default `THRESHOLD`).
+ 1. Expected: `THRESHOLD` remains the same.
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-1. Saving window preferences
+2. Setting a new threshold to the library.
+ 1. Test case: `limit 0`.
+ 1. Expected: `THRESHOLD` of the library is set to `0`.
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
- 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
+### Loading data
-1. _{ more test cases … }_
+1. Dealing with missing library user's data file.
+ 1. MyBookshelf handles the issue where library user's data file is missing.
+ 1. MyBookshelf is unable to find specific file located at `data/addressbook.json`.
+ 1. MyBookshelf creates a new empty file located at `data/addressbook.json`.
+ 1. MyBookshelf loads the empty `data/addressbook.json` file.
-### Deleting a person
-1. Deleting a person while all persons are being shown
+2. Dealing with corrupted library user's data file.
+ 1. MyBookshelf handles the issue where library user's data file is corrupted.
+ 1. Prerequisites: The data file exists and is located at `data/addressbook.json`.
+ 1. MyBookshelf detects an error while reading a specific file located at `data/addressbook.json`.
+ 1. MyBookshelf discards all data of `data/addressbook.json`.
+ 1. MyBookshelf loads the empty `data/addressbook.json` file.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+3. Dealing with missing library book list's data file.
+ 1. MyBookshelf handles the issue where library book list's data file is missing.
+ 1. MyBookshelf is unable to find specific file located at `data/library.txt`.
+ 1. MyBookshelf creates a new empty file located at `data/library.txt`.
+ 1. MyBookshelf loads the empty `data/library.txt` file.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+4. Dealing with corrupted library book list's data file.
+ 1. MyBookshelf handles the issue where library book list's data file is corrupted.
+ 1. Prerequisites: The data file exists and is located at `data/library.txt`.
+ 1. MyBookshelf loads data from `data/library.txt`.
+ 1. MyBookshelf detects an error while reading a specific data in `data/library.txt`.
+ 1. MyBookshelf discards the specific data.
+ 1. MyBookshelf continues to load data from `data/library.txt`.
-1. _{ more test cases … }_
### Saving data
-1. Dealing with missing/corrupted data files
+1. Saving library user's data.
+ 1. Prerequisites: The data file exists and is located at `data/addressbook.json`. Data in data file is valid.
+ 1. MyBookshelf will automatically save the newest information upon any successful commands.
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-1. _{ more test cases … }_
+2. Saving library book list's data.
+ 1. Prerequisites: The data file exists and is located at `data/library.txt`. Data in data file is valid.
+ 1. MyBookshelf will automatically save the newest information upon any successful commands.
diff --git a/docs/Documentation.md b/docs/Documentation.md
index 3e68ea364e7..082e652d947 100644
--- a/docs/Documentation.md
+++ b/docs/Documentation.md
@@ -1,29 +1,21 @@
---
-layout: page
-title: Documentation guide
+ layout: default.md
+ title: "Documentation guide"
+ pageNav: 3
---
-**Setting up and maintaining the project website:**
-
-* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation.
-* The `docs/` folder is used for documentation.
-* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html).
-* Note these points when adapting the documentation to a different project/product:
- * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar.
- * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format).
-* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping)
+# Documentation Guide
+* We use [**MarkBind**](https://markbind.org/) to manage documentation.
+* The `docs/` folder contains the source files for the documentation website.
+* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation.
**Style guidance:**
* Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style).
+* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html).
-* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html)
-
-**Diagrams:**
-
-* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html)
-**Converting a document to the PDF format:**
+**Converting to PDF**
-* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html)
+* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html).
diff --git a/docs/Gemfile b/docs/Gemfile
deleted file mode 100644
index c8385d85874..00000000000
--- a/docs/Gemfile
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
-
-gem 'jekyll'
-gem 'github-pages', group: :jekyll_plugins
-gem 'wdm', '~> 0.1.0' if Gem.win_platform?
-gem 'webrick'
diff --git a/docs/Logging.md b/docs/Logging.md
index 5e4fb9bc217..589644ad5c6 100644
--- a/docs/Logging.md
+++ b/docs/Logging.md
@@ -1,8 +1,10 @@
---
-layout: page
-title: Logging guide
+ layout: default.md
+ title: "Logging guide"
---
+# Logging guide
+
* We are using `java.util.logging` package for logging.
* The `LogsCenter` class is used to manage the logging levels and logging destinations.
* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..03df0295bd2 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -1,27 +1,32 @@
---
-layout: page
-title: Setting up and getting started
+ layout: default.md
+ title: "Setting up and getting started"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Setting up and getting started
+
+
--------------------------------------------------------------------------------------------------------------------
## Setting up the project in your computer
-
:exclamation: **Caution:**
+
+**Caution:**
Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps.
-
+
First, **fork** this repo, and **clone** the fork into your computer.
If you plan to use Intellij IDEA (highly recommended):
1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**.
-1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
+1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
+
+ Note: Importing a Gradle project is slightly different from importing a normal Java project.
+
1. **Verify the setup**:
1. Run the `seedu.address.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
@@ -34,10 +39,11 @@ If you plan to use Intellij IDEA (highly recommended):
If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours.
-
:bulb: **Tip:**
+
+ **Tip:**
Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
-
+
1. **Set up CI**
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..78ddc57e670 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -1,12 +1,15 @@
---
-layout: page
-title: Testing guide
+ layout: default.md
+ title: "Testing guide"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Testing guide
---------------------------------------------------------------------------------------------------------------------
+
+
+
+
## Running tests
@@ -19,8 +22,10 @@ There are two ways to run tests.
* **Method 2: Using Gradle**
* Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`)
-
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
-
+
+
+**Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
+
--------------------------------------------------------------------------------------------------------------------
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 7abd1984218..cdff55fa5ab 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,198 +1,855 @@
---
-layout: page
-title: User Guide
+layout: default.md
+title: "User Guide"
+pageNav: 3
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+# MyBookshelf User Guide
-* Table of Contents
-{:toc}
+Community libraries are a testament to the goodwill of the community, but **what is stopping bad actors from taking advantage of your library?**
+
+Welcome to *MyBookshelf*, a desktop application for **Community Library Managers** like you!
+
+*MyBookshelf* is here to provide you with a **framework** to better manage library user contacts, books, and borrowing activity.
+
+*MyBookshelf* provides you with a **contact list** to help keep track of library users, a **library** to keep track of your books and a novel **Merit Score** system to help manage library users' ability to borrow books.
+
+This application is tailored for fast typists, optimised for use via a [**Command Line Interface (CLI)**](#glossary) while not compromising on the benefits of a [**Graphical User Interface (GUI)**](#glossary).
+
+If you need help with running *MyBookshelf*, this user guide is designed to help you with:
+* Installation,
+* Understanding the user interface and commands,
+* Troubleshooting issues and answer queries and,
+* Giving you a sneak peek to future updates!
+
+
+
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+## Quick Installation
+
+### Windows
+1. Ensure that you have `Java 11` or above installed in your computer.
+ - You can refer to this [YouTube video](https://www.youtube.com/watch?v=3nOmkqO0-SM) to check your current Java version.
+ - If you do not have `Java 11`, you are recommended to download it from [here](https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html)
+
+
+2. Download the latest `mybookshelf.jar` from [here](https://github.com/AY2324S2-CS2103T-F11-2/tp/releases).
+
+3. Copy and paste the file to you will use as the _home folder_ for the *MyBookshelf* app.
+ - Open your _file explorer_ with Windows Key + E or clicking the _file explorer_ icon.
+ - Navigate to the _Downloads_ folder (Default directory for downloaded files will be the _Downloads_ folder).
+ - Copy the downloaded `mybookshelf.jar` and paste it to the folder you will use as the _home folder_ for the *MyBookshelf* app.
+
+
+4. Access the command prompt.
+ - Open the Run menu with Windows Key + R, then type "cmd".
+ ![windows run with CMD](images/QuickStart/Windows/WindowsRtypeCMD.png)
+ - Click "Ok".
+ - Command prompt window will appear in a few seconds. Command prompt window should look similar to the image below.
+ ![command prompt](images/QuickStart/Windows/CommandPrompt.png)
+ - If you have issues opening command prompt, you can refer this [YouTube video](https://www.youtube.com/watch?v=pBheH2QtktI&t=92s).
+
+
+5. `cd` into the folder you placed `mybookshelf.jar` file in, and enter the following command to run the application.
+ ```
+ java -jar mybookshelf.jar
+ ```
+ - You can refer to the image below.
+ _Note_: _YOUR_USERNAME_ and _HOME_FOLDER_ might be different from yours.
+ ![command prompt with java -jar mybookshelf.jar](images/QuickStart/Windows/jarmybookshelf.png)
+
+
+6. A window similar to the one below should appear in a few seconds. Notice that the app initialises with some sample data.
+
+ ![windows default Ui](images/QuickStart/Windows/default.png)
+
+7. Type the command into the command box and press Enter to execute it. e.g. typing [**`help`**](#viewing-help--help) and pressing Enter will open the help window.
+
+8. You can refer to [Glossary](#glossary) for the definition of some words used and [Command Summary](#command-summary) for a quick overview of the commands. Refer to the [Features](#features) below for more details of each command.
+
+
+### MacOS
+1. Ensure that you have `Java 11` or above installed in your computer.
+ - You can refer to this [YouTube video](https://www.youtube.com/watch?v=3nOmkqO0-SM) to check your current Java version.
+ - If you do not have `Java 11`, you are recommended to download from [here](https://www.azul.com/downloads/?version=java-11-lts&os=macos&architecture=arm-64-bit&package=jdk-fx)
+
+
+2. Download the latest `mybookshelf.jar` from [here](https://github.com/AY2324S2-CS2103T-F11-2/tp/releases).
+
+3. Copy and paste the file to you will use as the _home folder_ for the *MyBookshelf* app.
+ - Open your _finder_ by clicking the _finder_ icon.
+ - Navigate to the _Downloads_ folder (Default directory for downloaded files will be the _Downloads_ folder).
+ - Copy the downloaded `mybookshelf.jar` and paste it to the folder you will use as the _home folder_ for the *MyBookshelf* app.
+
+
+4. Access the terminal.
+ - Open the Spotlight search window using Command + Space, then type "Terminal".
+ ![finder search terminal result](images/QuickStart/MacOS/search.png)
+ - Double-click on Terminal in search result.
+ - Terminal window will appear in a few seconds. Terminal window should look similar to the image below.
+ ![terminal](images/QuickStart/MacOS/terminal.png)
+ - If you have issues opening Terminal window, you can refer [here](https://discussions.apple.com/thread/366608?sortBy=best)
+
+
+5. `cd` into the folder you placed `mybookshelf.jar` file in, and enter the following command to run the application.
+ ```
+ java -jar mybookshelf.jar
+ ```
+ - You can refer to the image below.
+ _Note_: Your directory might be different from the image.
+ ![terminal java -jar mybookshelf.jar](images/QuickStart/MacOS/jarmybookshelf.png)
+
+
+6. A window similar to the one below should appear in a few seconds. Notice that the app initialises with some sample data.
+
+ ![macos default Ui](images/QuickStart/MacOS/default.png)
+
+7. Type the command into the command box and press Enter to execute it. E.g. typing [**`help`**](#viewing-help--help) and pressing Enter will open the help window.
+
+8. You can refer to [Glossary](#glossary) for the definition of some words used and [Command Summary](#command-summary) for a quick overview of the commands. Refer to the [Features](#features) below for more details of each command.
-1. Ensure you have Java `11` or above installed in your Computer.
+--------------------------------------------------------------------------------------------------------------------
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+## A Quick Walkthrough of MyBookshelf
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
+### Overview:
+![result for 'Ui_Annotated'](images/cmdimages/Ui_Annotated.png)
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
- * `list` : Lists all contacts.
+1. **Taskbar:** Where you can access the `File` and `Help` tabs.
+2. **Command Box:** Where you can type in their commands into *MyBookshelf*.
+3. **Result Box:** This is where *MyBookshelf* returns the relevant output to the commands you had typed in.
+4. **Contact List:** Contains the list of **Library User Information**.
+5. **Library User Information:** This is where the particulars of individual library users are displayed at. More details to follow in the next part.
+6. **Library:** Contains the list of **Books** currently available in your library.
+7. **Books:** Identified by their titles, this is where each book in your library is displayed.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+### Library User Information:
+![result for 'Contact_List_Annotated'](images/cmdimages/Contact_List_Annotated.png)
- * `clear` : Deletes all contacts.
- * `exit` : Exits the app.
+1. **Name:** The name of the library user.
+2. **Index:** The sorted position of the library user in the contact list.
+3. **Merit Score:** The merit score associated with the library user. This term is defined below at the *Glossary* section.
+4. **Tags:** Tags associated with the library user.
+5. **Phone Number:** The phone number of the library user.
+6. **Address:** The home address of the library user.
+7. **Email:** The email address of the library user.
+8. **User's Book List:** The list of books that the library user is currently borrowing from the library.
-1. Refer to the [Features](#features) below for details of each command.
--------------------------------------------------------------------------------------------------------------------
## Features
-
+
-**:information_source: Notes about the command format:**
+### **Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+
* Items in square brackets are optional.
e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+
* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+ e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend` (i.e. 1 times), `t/friend t/family t/TAGS ...` (i.e. multiple times).
+
* Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+
* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+
* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-
+
+
+
+
+### **Notes about the parameters used:**
+
+* `NAME`: The name of the library user to be added.
+ * Only alphanumeric names with spaces can be used.
+ * Not allowed to add special characters like `/`, `-` and `,` in names.
+ * `NAME` is case-sensitive.
+
+
+* `PHONE_NUMBER`: The phone number of the library user.
+ * Requires a minimum of 3 digits.
+ * No maximum limit currently set on the phone number.
+
+
+* `EMAIL`: The email address of the library user.
+ * Valid as long as it follows the format of `local-part@domain`.
+ * `local-part` contains alphanumeric characters and some special characters such as `+`, `_`, `.` and `-`.
+ * `local-part` may not start or end with any special characters.
+ * `local-part` must be followed with an `@`.
+ * `domain` is made up of one or more `domain label`.
+ * Each `domain label` is separated by a `.`.
+ * Each `domain label` must be at least 2 characters long.
+ * Each `domain label` must start and end with alphanumeric characters.
+ * Each `domain label` contains alphanumeric characters, separated only by `-`, if any.
+
+
+* `ADDRESS`: The home address of the library user.
+ * Can take on any values, but should not be blank.
+
+
+* `TAG`: To associate library users with extra information.
+ * Only alphanumeric tags can be used.
+ * No spaces allowed within a tag (only a single word per tag).
+
+
+* `INDEX`: The number associated with the position of each library user in the current displayed Contact List.
+ * Assigned to library users based on the order added into the contact list.
+ * Takes in a **positive integer** (e.g. 1, 2, 3, …) up to the last `INDEX` in the contact list.
+ * Can only take up to 2147483647.
+
+
+* `KEYWORD`: The part of the word you are searching for.
+ * `KEYWORD` is case-insensitive.
+
+
+* `BOOKTITLE`: The title of the book.
+ * Can take on any values, but should not be blank.
+ * `BOOKTITLE` is case-sensitive.
+
+
+* `THRESHOLD`: The merit score limit set for each library user.
+ * Takes in integer values.
+ * Can only take in values from -2147483648 to 2147483647.
+
+
+
+
+
+### **Others:**
+
+* This application is designed for use in **English**. We cannot guarantee the performance when used with other languages.
+* **Duplicated library users** are defined as library users with the same name (case-sensitive). We currently do not allow duplicated library users to be added.
+
+
+
+--------------------------------------------------------------------------------------------------------------------
### Viewing help : `help`
-Shows a message explaning how to access the help page.
+Shows a message explaining how to access the help page.
![help message](images/helpMessage.png)
Format: `help`
-### Adding a person: `add`
+### Adding a library user: `add`
-Adds a person to the address book.
+Adds a new library user to the contact list. Parameters are populated with the library user's personal information.
Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+
-Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+**Tip:** A user can have any number of tags (including 0).
+
+
+
+
+
+**Note:** User with name identical to another user is deemed as a duplicate (case-sensitive), even when other information is different.
+
+
-### Listing all persons : `list`
+Example:
+* `add n/Kokoro Tsurumaki p/980101296 e/kokoro@bandori.com a/311, Hanasakigawa Ave 2, #08-08`
-Shows a list of all persons in the address book.
+
+![result for 'add Kokoro'](images/cmdimages/addKokoro.png)
+
+
+![result for 'add Kokoro result'](images/cmdimages/addresultKokoro.png)
+
+### Listing all library users : `list`
+
+Displays a list of all library users in the contact list.
Format: `list`
-### Editing a person : `edit`
-Edits an existing person in the address book.
+![result for 'list'](images/cmdimages/list.png)
+
+
+![result for 'list result'](images/cmdimages/listresult.png)
+
+### Editing a library user : `edit`
+
+Edits an existing library user's personal information from the contact list.
Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* Edits the library user at the specified `INDEX`. This index refers to the index number of the target library user in the contact list.
+* At least one of the optional parameters must be provided.
+* Existing values in the contact list will be updated to the input values.
+* When editing tags, the existing tags of the library user will be removed i.e adding of tags is not cumulative.
+* You can remove all the library user’s tags by typing `t/` without specifying any tags after it.
-Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+Example:
+* `edit 2 n/Mashiro Kurata` edits the name of the 2nd person to be `Mashiro Kurata`.
+
+
+ Before edit:
+
+
+![result for 'edit before'](images/cmdimages/editbeforeMashiro.png)
+
+
+ Edit:
+
+
+![result for 'edit'](images/cmdimages/editMashiro.png)
-### Locating persons by name: `find`
-Finds persons whose names contain any of the given keywords.
+![result for 'edit result'](images/cmdimages/editresultMashiro.png)
+
+### Locating library users by name: `find`
+
+Finds library users whose names matches any of the given keywords.
Format: `find KEYWORD [MORE_KEYWORDS]`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
+* The search (`KEYWORD`) is case-insensitive. E.g `hans` will match `Hans`.
+* The order of the keywords does not matter. E.g. `Hans Bo` will match `Bo Hans`.
* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+* Only full words will be matched. E.g. `Han` will not match `Hans`.
+* All library users matching at least one keyword will be returned (i.e. `OR` search).
+ E.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+Example:
+* `find kokoro mashiro` returns `Kokoro Tsurumaki`, `Mashiro Kurata`.
+ ![result for 'find'](images/cmdimages/find.png)
-### Deleting a person : `delete`
-Deletes the specified person from the address book.
+ ![result for 'find result'](images/cmdimages/findresult.png)
+
+### Deleting a library user : `delete`
+
+Deletes the specified library user from the contact list through its index.
Format: `delete INDEX`
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+* Deletes the library user at the specified `INDEX`.
+* The index refers to the index number associated with the target library user.
+
+
+
+**Warning:** `delete` removes all the target user's data, including their merit score and borrowing book list.
+
+
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+* `list` followed by `delete 2` deletes the 2nd user in the contact list.
+* `find kokoro mashiro` followed by `delete 2` deletes the 2nd user in the results of the `find` command.
+
+
+ Before delete:
+
+
+![result for 'delete before'](images/cmdimages/deletebefore.png)
+
+
+ Delete:
+
+
+![result for 'delete'](images/cmdimages/delete.png)
+
+
+![result for 'delete result'](images/cmdimages/deleteresult.png)
+
+### Add a book to Library : `addbook`
+
+Adds a book to the library's book list.
+
+Format: `addbook b/BOOKTITLE`
+
+* Adds a book `BOOKTITLE` to the library and stores it.
+
+Example:
+* `addbook b/Tales of Kokoro` will add a book titled "Tales of Kokoro" into the library.
+
+
+![result for 'addbook b/Tales of Kokoro'](images/cmdimages/addbookTalesofKokoro.png)
+
+
+![result for 'addbook b/Tales of Kokoro'](images/cmdimages/addbookresultTalesofKokoro.png)
+
+### Delete a book from Library : `delbook`
+
+Removes a book from the library's book list.
+
+Format: `delbook b/BOOKTITLE`
+
+* Remove the first book titled `BOOKTITLE` from the library.
+
+
+
+**Warning:** To avoid accidental deletion, `delbook` only removes the first book which matches `BOOKTITLE`, even there are multiple books with identical `BOOKTITLE`.
+
+
+
+Example:
+* `delbook b/Tales of Kokoro` will remove a book titled "Tales of Kokoro" from the library.
+
+
+![result for 'delbook b/Tales of Kokoro'](images/cmdimages/delbookTalesofKokoro.png)
+
+
+![result for 'delbook b/Tales of Kokoro'](images/cmdimages/delbookresultTalesofKokoro.png)
+### Borrowing a book: `borrow`
+
+Library user borrows a book. A book is removed from the library's book list and added to the library user's book list.
+
+Format: `borrow INDEX b/BOOKTITLE`
+
+* Library user at position `INDEX` in the contact list borrows a book titled `BOOKTITLE`.
+* This index refers to the index number associated with the target library user in the contact list.
+* Borrowing a book decreases the library user's merit score by 1.
+
+Example:
+* `borrow 1 b/Tales of Kokoro` will record the user index 1 borrowing a book titled "Tales of Kokoro".
+
+
+![result for 'borrow b/Tales of Kokoro'](images/cmdimages/borrowTalesofKokoro.png)
+
+### Returning a book : `return`
+
+Library user returns a book. A book is removed from the library user's book list and added to the library's book list.
+
+Format: `return INDEX b/BOOKTITLE`
+
+* Library user at position `INDEX` in the contact list returns a book titled `BOOKTITLE`.
+* This index refers to the index number associated with the target library user in the contact list.
+* Returning a book increases the library user's merit score by 1.
+
+Example:
+* `return 1 b/Tales of Kokoro` returns a book titled "Tales of Kokoro" from the user at index 1.
+
+
+![result for 'return b/Tales of Kokoro'](images/cmdimages/returnTalesofKokoro.png)
+
+### Donating a book : `donate`
+
+Library user donates a book. A book is added to the library's book list.
+
+Format: `donate INDEX b/BOOKTITLE`
+
+* Library user at position `INDEX` in the contact list donates a book titled `BOOKTITLE`.
+* This index refers to the index number associated with the target library user in the contact list.
+* Donating a book increases the library user's merit score by 1.
+
+
+
+**Note:** This differs from `addbook` as this command also increases the merit score of the associated library user.
+
+
+
+Example:
+* `donate 1 b/Tales of Kokoro` will record user index 1 donating a book titled "Tales of Kokoro".
-### Clearing all entries : `clear`
-Clears all entries from the address book.
+![result for 'donate b/Tales of Kokoro'](images/cmdimages/donateTalesofKokoro.png)
+
+### Set the merit score threshold of the library: `limit`
+
+Sets the limit of the library such that only users with a merit score more than or equal to the set limit can borrow.
+
+Format: `limit [THRESHOLD]`
+
+* Sets the limit of the merit score to the specified `THRESHOLD`.
+* The limit refers to the threshold such that any library user with a merit score less the `THRESHOLD` is not allowed to borrow from the library.
+* `THRESHOLD` is optional. Typing `limit` without `THRESHOLD` will display the currently set threshold.
+* The default threshold set for libraries is -3.
+
+Examples:
+* `limit` will display the current merit score limit.
+
+
+![result for 'limit'](images/cmdimages/limitresult.png)
+
+
+* `limit -10` will set the merit score limit of the library to -10.
+
+
+![result for 'limit'](images/cmdimages/limitnumresult.png)
+
+### Clearing all library users : `clear`
+
+Clears all entries of library users from the contact list.
Format: `clear`
+
+
+**Note:** `clear` only supports clearing all users in the contact list. To clear all books from the library, refer to the tutorial on [clearing the library](#how-to-clear-the-whole-library).
+
+
+
### Exiting the program : `exit`
Exits the program.
Format: `exit`
+[//]: # (### )
+
+[//]: # ()
+[//]: # (Description)
+
+[//]: # ()
+[//]: # (Format: ``)
+
### Saving the data
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+MyBookshelf offers an automated data saving feature.
+
+This ensures that any modifications to your library's records will be preserved on your hard disk without necessitating manual intervention.
+
+This functionality simplifies your workflow by automatically saving your progress, enabling seamless continuation from your last session upon reopening the application.
+
+MyBookshelf also focuses on data integrity, guaranteeing that only accurate records are maintained within the system.
+
+Invalid data for available books will be automatically discarded and will not be saved.
-### Editing the data file
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+### Editing the data files
-
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-
+User data in *MyBookshelf* is saved automatically as a JSON file at `[JAR file location]/data/addressbook.json`.
-### Archiving data files `[coming in v2.0]`
+Library book data in *MyBookshelf* is saved automatically as a .txt file at `[JAR file location]/data/library.txt`.
-_Details coming soon ..._
+Experienced users are welcome to make changes to the data directly by editing these data files.
+
+
+
+**Warning:**
+If your changes to the data file makes its format invalid, MyBookshelf will discard all saved data and start with an empty data file at the next run.
+Hence, it is recommended to have a backup of the file ready before editing it.
+Furthermore, certain edits can cause the MyBookshelf to behave in unexpected ways (e.g., if a value entered is outside the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
+
+
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Things to Note
+
+1. Every library user starts with a default merit score of 0.
+1. **ONLY** library users with a **Merit Score >= Threshold** are allowed to borrow books.
+1. Library users can borrow multiple books as long as their merit score is above the threshold.
+1. The default threshold is set at -3. Use the [`limit`](#set-the-merit-score-threshold-of-the-library--limit) command to set the threshold.
+1. [`edit`](#editing-a-library-user--edit) can only be used to change the library user's personal information. This includes his/her name, phone number, email address, home address and tags. (Not merit score and user's book list).
+1. [`add`](#adding-a-library-user--add) and [`edit`](#editing-a-library-user--edit) command **DOES NOT** support the direct adding and/or editing of merit score or user's book list.
+1. [`add`](#adding-a-library-user--add), [`delete`](#deleting-a-library-user--delete), [`edit`](#editing-a-library-user--edit), [`clear`](#clearing-all-library-users--clear) and [`find`](#locating-library-users-by-name--find) commands are for managing users, while [`addbook`](#add-a-book-to-library--addbook), [`delbook`](#delete-a-book-from-library--delbook), [`borrow`](#borrowing-a-book--borrow), [`donate`](#donating-a-book--donate) and [`return`](#returning-a-book--return) commands are for managing books.
+1. The parameter `BOOKTITLE` is case-sensitive to allow books of similar titles to be differentiated.
+1. While there are no restrictions on `BOOKTITLE` (expect that it cannot be empty), we cannot guarantee the performance when books with titles in other languages are inserted.
+1. [`delbook`](#delete-a-book-from-library--delbook) deletes one book at a time to prevent accidental deletion of all entries with the same book title.
+1. We allow entries with the same email and phone number into the contact list as there may be cases where two users share the same contact details. An example would be when a child does not own a mobile phone nor has an email and has to share with his/her parent.
+1. Commands that modifies book lists will reference the book using their respective book titles instead of their indexes. This is because the [`findbook`](#introducing-the-findbook-command) command has not been implemented yet and would make indexing specific books in a large book list unfeasible.
+1. We allow [`delete`](#deleting-a-library-user--delete) to remove the library user's book list. This allows for more flexibility in managing the library. If books are returned, the library manager can use the [`return`](#returning-a-book--return) command to account for the books before deleting the person.
+1. Do not use words that start with b/, a/ as part of booktitles and addressess respectively. Tip: If you have a word starting with b/ or a/, you can type a filler character before.
+ E.g. addbook b/Book with .b/ in the title.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Troubleshooting
+
+You might see something similar to the image below when the command does not run successfully.
+![failing command](images/Troubleshoot/FailingCommand.jpg)
+
+
+### Unable to add/donate/return a book to the library
+1. Error message received: `Book title cannot be empty!`
+ * Reason one: You attempted to add/donate/return a book with an empty book title.
+ * Resolve this issue by adding character(s) to the `BOOKTITLE`.
+ * Reason two: You attempted to add/donate/return a book which only consists of white space(s).
+ * Resolve this issue by altering the `BOOKTITLE` so it consists at least one non-white space character.
+
+1. Error message received: `Multiple values specified for the following single-valued field(s): b/`
+ * Reason one: You attempted to add/donate/return a book with `BOOKTITLE` containing " b/". (Note the white space before "b"). In this case, `BOOKTITLE` is interpreted to be "Please b/ careful".
+ * Since `b/` is the prefix of `Book`, MyBookshelf restricts commands with `BOOKTITLE` containing " b/".
+ * Resolve this issue by removing "/" in `BOOKTITLE` (e.g. Please b careful) or;
+ * Resolve this issue by adding a filler character (e.g. ".") in front of "b/" in `BOOKTITLE` (e.g. Please ./b careful).
+ * Reason two: You attempted to add/donate/return multiple books (e.g. addbook b/Book 1 b/Book 2)
+ * MyBookshelf restricts adding/donating/returning multiple books in a single command.
+ * Resolve this issue by adding/donating/returning the books one by one.
+
+### Unable to delete/borrow a book from the library
+1. Error message received: `Book title cannot be empty!`
+ * Reason one: You attempted to delete/borrow a book with an empty book title.
+ * Resolve this issue by adding character(s) to the `BOOKTITLE`.
+ * Reason two: You attempted to delete/borrow a book which only consists of white space(s).
+ * Resolve this issue by altering the `BOOKTITLE` so it consists at least one non-white space character.
+
+1. Error message received: `Multiple values specified for the following single-valued field(s): b/`
+ * Reason one: You attempted to delete/borrow a book with `BOOKTITLE` containing " b/" (note the white space before "b"). In this case, `BOOKTITLE` is interpreted to be "Please b/ careful".
+ * Since `b/` is the prefix of `Book`, MyBookshelf restricts commands with `BOOKTITLE` containing " b/".
+ * Resolve this issue by removing "/" in `BOOKTITLE` (e.g. Please b careful) or;
+ * Resolve this issue by adding a filler character (e.g. ".") in front of "b/" in `BOOKTITLE` (e.g. Please ./b careful).
+ * Reason two: You attempted to delete/borrow multiple books (e.g. delbook b/Book 1 b/Book 2).
+ * MyBookshelf restricts deleting/borrowing multiple books in a single command.
+ * Resolve this issue by deleting/borrowing the books one by one.
+
+1. Error message received: `Book: BOOKTITLE is not available in the library.`
+ * Reason one: You attempted to delete/borrow a book which does not exist in the library.
+ * You are not allowed to delete/borrow a non-existing book.
+ * Reason two: You attempted to delete/borrow a book, but you misspelt the `BOOKTITLE`.
+ * Please check the spelling of `BOOKTITLE` of the book you want to delete/borrow and run the command with correct `BOOKTITLE`.
+ * Reason three: You attempted to delete/borrow a book with a `BOOKTITLE` that does not match any of the `BOOKTITLE`(s) in the library.
+ * Please check if the book with the title `BOOKTITLE` exists in the library.
+
+### Unable to add/edit tags
+1. Error message received: `Tags names should be alphanumeric.`
+ * Reason: You attempted to add/edit tags with non-alphanumeric characters (including white space) (e.g. Frequent User).
+ * Resolve this by altering the tag you want to add/edit into non-alphanumeric characters only (e.g. FrequentUser).
+
+### Library user is unable to donate a book
+1. Error message received: `The person index provided is invalid.`
+ * Reason one: You mistyped the index of the library user.
+ * Please check the validity of `INDEX` of the library user and run the command with correct `INDEX`.
+ * Ensure that the `INDEX` exists in the library user contact list.
+ * Reason two: Library user information is not added to the contact list.
+ * Resolve this by adding the library user to the contact list first before running the command again.
+
+1. Other error message received:
+ * `Book title cannot be empty!` or `Multiple values specified for the following single-valued field(s): b/`
+ * Please check [Unable to add/donate/return a book from the library](#unable-to-add-donate-return-a-book-to-the-library).
+
+### Library user is unable to borrow a book
+1. Error message received: `The person index provided is invalid.`
+ * Reason one: You mistyped the index of the library user.
+ * Please check the validity of `INDEX` of the library user and run the command with correct `INDEX`.
+ * Reason two: Library user information is not added to the contact list.
+ * Resolve this by adding the library user to the contact list first before running the command again.
+
+1. Error message received: `User has insufficient Merit Score.`
+ * Reason one: Library user's merit score is lower than the threshold of the library.
+ * Library user can donate/return book(s) to increase his/her merit score, so that he/she can borrow a book again, OR;
+ * Resolve this by lowering the threshold of the library using `limit` command.
+
+1. Other error message received:
+ * `Book title cannot be empty!` or `Multiple values specified for the following single-valued field(s): b/` or `Book: BOOKTITLE is not available in the library.`
+ * Please check [Unable to delete/borrow a book from the library](#unable-to-delete-borrow-a-book-from-the-library).
+
+### Library user is unable to return a book
+1. Error message received: `The person index provided is invalid.`
+ * Reason one: You mistyped the index of the library user.
+ * Please check the validity of `INDEX` of the library user and run the command with correct `INDEX`.
+
+1. Error message received: `Person is currently not borrowing any books!`
+ * Reason one: You mistyped the index of the library user.
+ * Please confirm the correct `INDEX` of the library user and run the command with correct `INDEX`.
+
+1. Error message received: `Person does not have this book borrowed currently!`
+ * Reason one: You mistyped the index of the library user.
+ * Please confirm the correct `INDEX` of the library user and run the command with correct `INDEX`.
+ * Reason two: You misspelt the index of the library user.
+ * Please check the spelling of `BOOKTITLE` of the book the user wants to return and run the command with correct `BOOKTITLE`.
+
+1. Other error message received:
+ * `Book title cannot be empty!` or `Multiple values specified for the following single-valued field(s): b/`
+ * Please check [Unable to add/donate/return a book to the library](#unable-to-add-donate-return-a-book-to-the-library).
+
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Future Features
+
+### Introducing more flexibility for `clear`
+1. Will be adding a `clearlib` command to clear all books currently in your library.
+2. Will rename the current `clear` command to `clearuser` to clear all the data of library users.
+3. Will be adding a `clearall` command to clear both library book and library user data.
+
+### Introducing the `findbook` command
+1. As the number of books in your library increases, you will have to spend more time scrolling through the list to search for a book.
+2. The `findbook` command will allow the library manager quickly check for the existence of a specific book in the library.
+3. This will also allow you to explore the use of indexing to run commands that modify the book list instead of having to do so with the lengthy book title.
+
+### Introducing the `undo` and `redo` commands
+1. Even the most meticulous and fastest typists are bound to make some unintended errors.
+2. With the `undo` command, your experience will be enhanced as these mistakes can be reverted with a simple yet effective command.
+3. Similarly, the addition of the `redo` command further improves your experience by allowing users to effortlessly revert back to undone actions, refining the process.
--------------------------------------------------------------------------------------------------------------------
## FAQ
**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous MyBookshelf home folder.
+
+**Q**: How do I manually change the merit score of a library user that I have added?
+**A**: Please read the tutorial section on [increasing](#how-to-increase-merit-score-of-a-library-user) and [decreasing](#how-to-decrease-merit-score-of-a-library-user) merit score.
+
+**Q**: Can I clear both the contact list and the library at the same time?
+**A**: Unfortunately, *MyBookshelf* currently cannot clear both the contact list and the library at the same time. You can use the [`clear`](#clearing-all-library-users--clear) command to clear the contact list and refer to [this](#how-to-clear-the-whole-library) section to clear the library.
--------------------------------------------------------------------------------------------------------------------
## Known issues
-1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+MyBookshelf is always a Work-In-Progress as we are constantly refining our application for your needs. Listed below are some of our planned enhancements as well as current limitations, to keep you abreast of the current state of our application.
+
+1. **When using multiple screens**, if you move the application to a secondary screen, and switch back to the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+2. Email does not check for presence of **top-level domain**, such as `.com`, `.net` and `.org`. Refer to the Developer Guide for more information.
+3. When a value beyond the range `-2147483648 to 2147483647` is used for `INDEX` and `THRESHOLD`, the wrong error message is displayed. Will be fixed in the future. Refer to the Developer Guide for more information.
+4. There can be different individuals with the same name, but our current implementation does not support this due to our definition of duplicated people. Will be fixed in the future. Refer to the Developer Guide for more information.
+5. Duplicated library users can be added with the same name but different capitalisation, due to our definition of duplicated people. Will be fixed in the future. Refer to the Developer Guide for more information.
+6. All parameters except `INDEX` and `THRESHOLD` do not have a limit to the number of characters. Refer to the Developer Guide for more information.
+7. The [`clear`](#clearing-all-library-users--clear) command only clears the library user data. The usage pertaining to this command will be made clearer with the implementation of future features. Refer to the [Future Features](#future-features) section above for more information.
+8. UI may not display special characters as intended.
+9. Some languages may cause the UI to display unexpectedly. For example, Arabic characters will cause the number next to book titles to appear from right to left.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Tutorials
+
+### How to increase Merit Score of a library user
+
+With the current features, there is no direct command to manually increase the merit score of a library user.
+
+You can use the following steps below to indirectly increase the merit score of an individual library user:
+1. Make the library user whose merit score you want to increase donate a placeholder book using [`donate`](#donating-a-book--donate).
+1. Repeat step 1 by the amount you want to increase the library user's merit score by.
+1. Delete **all** the placeholder books that you had added from the library using [`delbook`](#delete-a-book-from-library--delbook).
+
+Example: You want to increase the merit score of the third library user by 2.
+1. Enter `donate 3 b/placeholder book`.
+1. Enter `donate 3 b/placeholder book`.
+1. Enter `delbook b/placeholder book`.
+1. Enter `delbook b/placeholder book`.
+
+### How to decrease Merit Score of a library user
+
+With the current features, there is no direct command to manually decrease the merit score of a library user.
+
+#### Method 1: Within MyBookshelf
+
+
+
+**Warning:** Due to current limitations, following this method **will force** the library user's book list to change.
+
+
+
+You can use the following steps below to indirectly increase the merit score of an individual library user:
+1. Add placeholder books from the library using [`addbook`](#add-a-book-to-library--addbook).
+1. Repeat step 1 by the amount you want to decrease the library user's merit score by.
+1. Make the library user whose merit score you want to increase borrow **all** the placeholder books you added using [`borrow`](#borrowing-a-book--borrow).
+
+Example: You want to decrease the merit score of the second library user by 2.
+1. Enter `addbook b/~~~ignore book~~~`.
+1. Enter `addbook b/~~~ignore book~~~`.
+1. Enter `borrow 2 b/~~~ignore book~~~`.
+1. Enter `borrow 2 b/~~~ignore book~~~`.
+
+
+
+**Tips:** Since these placeholder books will stay in the library user's book list, you should name your placeholder books with names that can be easily differentiated and ignored.
+
+Keeping the placeholder books titled the same and using characters that can be sorted to the bottom alphabetically (use larger values in this [chart](https://www.javatpoint.com/java-ascii-table)) can also help.
+
+
+
+#### Method 2: Editing the data file
+
+Using this method will not alter the library user's book list. Please read the above section on [Editing Data Files](#editing-the-data-files) before proceeding.
+
+1. Locate the data file at this file location: `[JAR file location]/data/addressbook.json`.
+1. Open the file with any valid application of your choice. For this example, we will open the file with Notepad on Windows.
+1. Locate the library user you wish to edit and edit their merit score.
+
+![Edit Merit Score Tutorial](images/TutorialEditData.png)
+
+### How to clear the whole library
+
+The current [`clear`](#clearing-all-library-users--clear) command is designed to clear the contact list only but there is **no command** to clear books from the library.
+
+The **safest** method to clear the library would be to individually delete each book from the library using the `delbook` command.
+
+You can quickly delete books from the library by directly editing the data file. Please read the above section on [Editing Data Files](#editing-the-data-files) before proceeding.
+
+1. Locate the data file at this file location: `[JAR file location]/data/library.txt`.
+1. Open the file with any text editor of your choice. For this example, we will open the file with Notepad on Windows.
+1. Delete all lines after the first line (first line is the number you have set as the limit for your library).
+
+![Clear Library Tutorial](images/TutorialClearLibrary.png)
+
+
+
+**Warning:** In this version of *MyBookshelf*, you can put in an invalid input into the `library.txt` file (E.g. non-number for merit score/whitespace for book title) and the data file will be treated as corrupted, causing the program to start up with an empty library.
+
+While this is a method you can also use to clear the library, we highly advise against purposefully corrupting your own data files as you will reset the limit you have set for your library.
+
+We also cannot guarantee that this will not cause unexpected behaviour in other parts of *MyBookshelf* and this may not work for future versions of *MyBookshelf*.
+
+
--------------------------------------------------------------------------------------------------------------------
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+| Action | Format, Examples |
+|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/goodDonater t/sponsor` |
+| **Clear** | `clear` |
+| **Delete** | `delete INDEX` e.g., `delete 3` |
+| **Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com` |
+| **Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake` |
+| **List** | `list` |
+| **Help** | `help` |
+| **AddBook** | `addbook b/BOOKTITLE` e.g., `addbook b/Tales of Kokoro` |
+| **DelBook** | `delbook b/BOOKTITLE` e.g., `delbook b/Tales of Kokoro` |
+| **Borrow** | `borrow INDEX b/BOOKTITLE` e.g., `borrow 1 b/Tales of Kokoro` |
+| **Return** | `return INDEX b/BOOKTITLE` e.g., `return 1 b/Tales of Kokoro` |
+| **Donate** | `donate INDEX b/BOOKTITLE` e.g., `donate 1 b/Tales of Kokoro` |
+| **Limit** | `limit [THRESHOLD]` e.g. `limit 0` |
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Glossary
+
+1. **CLI**: Command-Line Interface, a tool you employ to communicate with your operating system via your keyboard.
+
+1. **GUI**: Graphical User Interface, a graphical interface where users engage with visual elements like icons, buttons, and menus.
+
+1. **JSON**: JavaScript Object Notation, a text format for storing and transporting data.
+
+1. **Parameter**: Data that users input into commands.
+
+1. **Alphanumeric**: A character that is either a letter or a number.
+
+1. **Library User**: The people that are saved into the contact list of *MyBookshelf*. Sometimes referred to as "borrowers" or "person".
+
+1. **Library Manager**: Community Library Managers (CLM) are the people using the MyBookshelf application. CLMs are responsible for adding, storing, and updating the entire library database via *MyBookshelf*.
+
+1. **Contact List**: Refers to the list of library users currently stored in the *MyBookshelf* application. It appears in the left column of the User Interface.
+
+1. **Book**: Identified by its `BOOKTITLE`. Appears in both the `User's Book List` and the `Library Book List`.
+
+1. **User's Book List**: Refers to the list of `Book`(s) currently borrowed by an individual library user.
+
+1. **Library Book List**: Refers to the list of available `Book`(s) currently stored in the *MyBookshelf* application. It appears in the right column of the User Interface. Sometimes referred to as "available books" or simply "library".
+
+1. **Command**: Instructions given to the application to perform specific tasks.
+
+1. **Merit Score**: A score associated with each `Library User`. This score provides an estimate of the number of books a library user can borrow.
+
+1. **Limit**: Refers to limit set for the library and is synonymous with `THRESHOLD`, where library users are only allowed to borrow books when their merit score is greater or equal than the limit that has been set.
+
+1. **Threshold**: The minimum merit score required for a library user to borrow books from the library.
+
+1. **Data Saving**: The automated feature of the application that preserves modifications to the library's records by saving them on the hard disk.
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..bb8afcaf6a0 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "MyBookshelf"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "https://github.com/AY2324S2-CS2103T-F11-2/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_data/projects.yml b/docs/_data/projects.yml
deleted file mode 100644
index 8f3e50cb601..00000000000
--- a/docs/_data/projects.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-- name: "AB-1"
- url: https://se-edu.github.io/addressbook-level1
-
-- name: "AB-2"
- url: https://se-edu.github.io/addressbook-level2
-
-- name: "AB-3"
- url: https://se-edu.github.io/addressbook-level3
-
-- name: "AB-4"
- url: https://se-edu.github.io/addressbook-level4
-
-- name: "Duke"
- url: https://se-edu.github.io/duke
-
-- name: "Collate"
- url: https://se-edu.github.io/collate
-
-- name: "Book"
- url: https://se-edu.github.io/se-book
-
-- name: "Resources"
- url: https://se-edu.github.io/resources
diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html
deleted file mode 100644
index 8559a67ffad..00000000000
--- a/docs/_includes/custom-head.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{% comment %}
- Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons:
-
- 1. Head over to https://realfavicongenerator.net/ to add your own favicons.
- 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet.
-{% endcomment %}
diff --git a/docs/_includes/head.html b/docs/_includes/head.html
deleted file mode 100644
index 83ac5326933..00000000000
--- a/docs/_includes/head.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
- {%- include custom-head.html -%}
-
- {{page.title}}
-
-
diff --git a/docs/_includes/header.html b/docs/_includes/header.html
deleted file mode 100644
index 33badcd4f99..00000000000
--- a/docs/_includes/header.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
+
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
+Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
-
+
If you are stuck, check out the sample
[here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a).
@@ -244,7 +247,7 @@ Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/s
**`PersonCard.java`:**
-``` java
+```java
@FXML
private Label remark;
```
@@ -276,11 +279,11 @@ We change the constructor of `Person` to take a `Remark`. We will also need to d
Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`!
-
+
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
+Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
-
+
Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
@@ -291,11 +294,11 @@ AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the
While the changes to code may be minimal, the test data will have to be updated as well.
-
+
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
+You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
-
+
Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
to see what the changes entail.
@@ -308,7 +311,7 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c
**`PersonCard.java`:**
-``` java
+```java
public PersonCard(Person person, int displayedIndex) {
//...
remark.setText(person.getRemark().value);
@@ -328,7 +331,7 @@ save it with `Model#setPerson()`.
**`RemarkCommand.java`:**
-``` java
+```java
//...
public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s";
public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s";
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..c73bd379e5e 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -1,8 +1,11 @@
---
-layout: page
-title: "Tutorial: Removing Fields"
+ layout: default.md
+ title: "Tutorial: Removing Fields"
+ pageNav: 3
---
+# Tutorial: Removing Fields
+
> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
>
> — Antoine de Saint-Exupery
@@ -10,17 +13,17 @@ title: "Tutorial: Removing Fields"
When working on an existing code base, you will most likely find that some features that are no longer necessary.
This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class.
-
+
**If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.
However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.**
-
+
-* Table of Contents
-{:toc}
+
+
## Safely deleting `Address`
@@ -50,10 +53,10 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`.
1. Remove the usages of `address` and select `Do refactor` when you are done.
-
+
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
-
+ **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
+
1. Repeat the steps for the remaining usages of `Address`
@@ -71,7 +74,7 @@ A quick look at the `PersonCard` class and its `fxml` file quickly reveals why i
**`PersonCard.java`**
-``` java
+```java
...
@FXML
private Label address;
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..2b1b0f2d6b7 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -1,26 +1,30 @@
---
-layout: page
-title: "Tutorial: Tracing code"
+ layout: default.md
+ title: "Tutorial: Tracing code"
+ pageNav: 3
---
+# Tutorial: Tracing code
+
+
> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …\[Therefore,\] making it easy to read makes it easier to write.
>
> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command.
-* Table of Contents
-{:toc}
+
+
## Before we start
Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components.
-![ArchitectureDiagram](../images/ArchitectureDiagram.png)
+
It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App.
-
+
Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works.
@@ -37,16 +41,16 @@ As you know, the first step of debugging is to put in a breakpoint where you wan
In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to the `Logic` component.
-
+
According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
-
+
-:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-
+**Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
+
A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
@@ -67,14 +71,14 @@ public interface Logic {
But apparently, this is an interface, not a concrete implementation.
That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells us that components interact through interfaces. Here's the relevant diagram:
-
+
Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`.
-
+
-:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
-
+**Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
+
![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png)
@@ -87,10 +91,10 @@ Now let’s set the breakpoint. First, double-click the item to reach the corres
Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
-
+
-:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
-
+**Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
+
1. To start the debugging session, simply `Run` \> `Debug Main`
@@ -110,7 +114,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
**LogicManager\#execute().**
- ``` java
+ ```java
@Override
public CommandResult execute(String commandText)
throws CommandException, ParseException {
@@ -142,7 +146,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
![StepOver](../images/tracing/StepOver.png)
1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below):
- ``` java
+ ```java
public Command parseCommand(String userInput) throws ParseException {
...
final String commandWord = matcher.group("commandWord");
@@ -157,7 +161,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command).
- ``` java
+ ```java
...
case EditCommand.COMMAND_WORD:
return new EditCommandParser().parse(arguments);
@@ -166,8 +170,10 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`.
-
:bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
-
+
+
+ **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them!
+
1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
@@ -175,17 +181,17 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
![EditCommand](../images/tracing/EditCommand.png)
1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component.
-
+
1. Let’s continue stepping through until we return to `LogicManager#execute()`.
The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png)
+
1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below):
**`EditCommand#execute()`:**
- ``` java
+ ```java
@Override
public CommandResult execute(Model model) throws CommandException {
...
@@ -205,25 +211,28 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
* it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked.
-
+
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component)
1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component:
-
+
+
* :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component)
1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component.
-
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
-
+
+
+ **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
+
-1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
+1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
**`JsonSerializableAddressBook` constructor:**
- ``` java
+ ```java
/**
* Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
*
@@ -243,7 +252,8 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format.
1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
-
+
+
* :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component)
1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint).
@@ -251,7 +261,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in:
**`ResultDisplay#setFeedbackToUser()`**
- ``` java
+ ```java
public void setFeedbackToUser(String feedbackToUser) {
requireNonNull(feedbackToUser);
resultDisplay.setText(feedbackToUser);
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 3d6bd06d5af..b65b2aa348d 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -6,6 +6,7 @@
import java.util.logging.Logger;
import javafx.application.Application;
+import javafx.collections.ObservableList;
import javafx.stage.Stage;
import seedu.address.commons.core.Config;
import seedu.address.commons.core.LogsCenter;
@@ -19,12 +20,17 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyLibrary;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+import seedu.address.model.library.Threshold;
import seedu.address.model.util.SampleDataUtil;
import seedu.address.storage.AddressBookStorage;
import seedu.address.storage.JsonAddressBookStorage;
import seedu.address.storage.JsonUserPrefsStorage;
+import seedu.address.storage.LibraryStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
import seedu.address.storage.UserPrefsStorage;
@@ -36,7 +42,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(1, 2, 1, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -44,6 +50,7 @@ public class MainApp extends Application {
protected Logic logic;
protected Storage storage;
protected Model model;
+ protected LibraryStorage libraryLogic;
protected Config config;
@Override
@@ -77,6 +84,8 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
Optional addressBookOptional;
ReadOnlyAddressBook initialData;
+ ReadOnlyLibrary libraryData;
+ libraryLogic = new LibraryStorage();
try {
addressBookOptional = storage.readAddressBook();
if (!addressBookOptional.isPresent()) {
@@ -90,7 +99,26 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
initialData = new AddressBook();
}
- return new ModelManager(initialData, userPrefs);
+ ObservableList booksData;
+ Threshold thresholdData;
+ try {
+ libraryLogic.loadLibraryFromFile();
+ thresholdData = libraryLogic.getThreshold();
+ if (libraryLogic.hasNoAvailableBooks()) {
+ logger.info("Creating a new library data file " + libraryLogic.getLibraryFilePath()
+ + " populated with a empty book list.");
+ //TODO Make sample book list
+ }
+ booksData = libraryLogic.getAvailableBooks();
+ libraryData = new Library(booksData, thresholdData);
+
+ } catch (DataLoadingException e) {
+ logger.warning("Data file at " + libraryLogic.getLibraryFilePath() + " could not be loaded."
+ + " Will be starting with an empty Library.");
+ libraryData = new Library();
+ }
+
+ return new ModelManager(initialData, userPrefs, libraryData);
}
private void initLogging(Config config) {
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java
index a97a86ee8d7..164c770e97a 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/seedu/address/commons/core/GuiSettings.java
@@ -13,7 +13,7 @@
public class GuiSettings implements Serializable {
private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+ private static final double DEFAULT_WIDTH = 1000;
private final double windowWidth;
private final double windowHeight;
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
similarity index 71%
rename from src/main/java/seedu/address/logic/Messages.java
rename to src/main/java/seedu/address/commons/core/Messages.java
index ecd32c31b53..e0639f0963a 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -1,4 +1,4 @@
-package seedu.address.logic;
+package seedu.address.commons.core;
import java.util.Set;
import java.util.stream.Collectors;
@@ -11,13 +11,19 @@
* Container for user visible messages.
*/
public class Messages {
-
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
+ public static final String MESSAGE_EMPTY_BOOK_INPUT_FIELD = "Book title cannot be empty!";
+ public static final String MESSAGE_EMPTY_BOOKLIST_FIELD = "Person is currently not borrowing any books!";
+ public static final String MESSAGE_BOOK_DOES_NOT_EXIST = "Person does not have this book borrowed currently!";
+ public static final String MESSAGE_FILLED_BOOKLIST_FIELD = "Person has reached his maximum borrowing limit!";
+ public static final String MESSAGE_INSUFFICIENT_MERIT_SCORE = "User's has insufficient Merit Score."
+ + "\nMerit Score threshold is set at: %s";
+ public static final String MESSAGE_BOOK_NOT_IN_LIBRARY = "Book: %s is not available in the library.";
/**
* Returns an error message indicating the duplicate prefixes.
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..51c721ba57e 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -65,4 +65,22 @@ public static boolean isNonZeroUnsignedInteger(String s) {
return false;
}
}
+
+ /**
+ * Returns true if {@code s} represents an integer
+ * e.g. 1, 2, -3, ..., {@code Integer.MAX_VALUE}
+ * Will return false for any other non-null string input
+ * e.g. empty string, "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters)
+ * @throws NullPointerException if {@code s} is null.
+ */
+ public static boolean isInteger(String s) {
+ requireNonNull(s);
+
+ try {
+ Integer.parseInt(s);
+ return !s.startsWith("+"); // "+1" is successfully parsed by Integer#parseInt(String)
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..eb19d3a1c10 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -8,6 +8,7 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Person;
/**
@@ -33,6 +34,7 @@ public interface Logic {
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ ObservableList getLibraryBookList();
/**
* Returns the user prefs' address book file path.
*/
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 5aa3b91c7d0..ff4d45b7019 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -15,7 +15,9 @@
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Person;
+import seedu.address.storage.LibraryStorage;
import seedu.address.storage.Storage;
/**
@@ -32,6 +34,7 @@ public class LogicManager implements Logic {
private final Model model;
private final Storage storage;
private final AddressBookParser addressBookParser;
+ private final LibraryStorage libraryLogic;
/**
* Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
@@ -40,6 +43,7 @@ public LogicManager(Model model, Storage storage) {
this.model = model;
this.storage = storage;
addressBookParser = new AddressBookParser();
+ libraryLogic = new LibraryStorage();
}
@Override
@@ -52,6 +56,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
try {
storage.saveAddressBook(model.getAddressBook());
+ libraryLogic.saveBooksToFile(model.getLibrary());
} catch (AccessDeniedException e) {
throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
} catch (IOException ioe) {
@@ -71,6 +76,10 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getLibraryBookList() {
+ return model.getLibraryBookList();
+ }
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
diff --git a/src/main/java/seedu/address/logic/commands/AddBookCommand.java b/src/main/java/seedu/address/logic/commands/AddBookCommand.java
new file mode 100644
index 00000000000..1deca6118a3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddBookCommand.java
@@ -0,0 +1,67 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOK;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.book.Book;
+
+
+
+/**
+ * Adds a book to the Library.
+ */
+public class AddBookCommand extends Command {
+ public static final String COMMAND_WORD = "addbook";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a book to the library "
+ + "Example: " + COMMAND_WORD + " " + PREFIX_BOOKLIST + "The Book of Answers";
+
+ public static final String MESSAGE_ADD_BOOK_SUCCESS = "Added book: %1$s successfully";
+
+ private final Book book;
+
+ /**
+ * @param book Book to be added to the library.
+ */
+ public AddBookCommand(Book book) {
+ requireAllNonNull(book);
+ this.book = book;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ if (book.equals(new Book(""))) {
+ throw new CommandException(Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ model.addBook(book);
+ model.updateFilteredLibraryList(PREDICATE_SHOW_ALL_BOOK);
+ return new CommandResult(generateSuccessMessage(book));
+ }
+
+ /**
+ * Generates a command execution success message when book {@code book} is successfully
+ * added to the library.
+ */
+ private String generateSuccessMessage(Book book) {
+ return String.format(MESSAGE_ADD_BOOK_SUCCESS, book);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddBookCommand)) {
+ return false;
+ }
+
+ AddBookCommand e = (AddBookCommand) other;
+ return book.equals(e.book);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 5d7185a9680..d4d1e0ebc79 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -7,8 +7,8 @@
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
diff --git a/src/main/java/seedu/address/logic/commands/BorrowCommand.java b/src/main/java/seedu/address/logic/commands/BorrowCommand.java
new file mode 100644
index 00000000000..eff9ce16f38
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/BorrowCommand.java
@@ -0,0 +1,112 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOK;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.book.Book;
+import seedu.address.model.person.Person;
+
+/**
+ * Adds a book to the book list to the specific borrower.
+ */
+public class BorrowCommand extends Command {
+ public static final String COMMAND_WORD = "borrow";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the book list of the person identified "
+ + "by the index number used in the last person listing. "
+ + "Existing borrow will be overwritten by the input.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_BOOKLIST + "[borrow]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_BOOKLIST + "Likes to swim.";
+
+ public static final String MESSAGE_ADD_BORROW_SUCCESS = "Added book: %1$s to Person: %2$s";
+
+ private final Index index;
+ private final Book book;
+
+ /**
+ * @param index of the person in the filtered person list to edit the
+ * bookTitle
+ * @param bookList of the person to be updated to
+ */
+ public BorrowCommand(Index index, Book bookList) {
+ requireAllNonNull(index, bookList);
+ this.index = index;
+ this.book = bookList;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ // Check whether the borrowed book is empty field
+ if (book.equals(new Book(""))) {
+ throw new CommandException(Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+
+ // Check whether personToEdit has sufficient merit score
+ if (!model.canLendTo(personToEdit)) {
+ throw new CommandException(String.format(Messages.MESSAGE_INSUFFICIENT_MERIT_SCORE, model.getThreshold()));
+ }
+
+ // Check whether book is present in library
+ if (!model.hasBookInLibrary(book)) {
+ throw new CommandException(String.format(Messages.MESSAGE_BOOK_NOT_IN_LIBRARY, book));
+ }
+ Book borrowedBook = model.popBookFromLibrary(book);
+ requireNonNull(borrowedBook);
+ model.updateFilteredLibraryList(PREDICATE_SHOW_ALL_BOOK);
+
+ ArrayList updatedBookList = personToEdit.getBookListWithNewBook(borrowedBook);
+
+ Person editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), personToEdit.getMeritScore().decrementScore(), updatedBookList,
+ personToEdit.getTags());
+
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+
+ return new CommandResult(generateSuccessMessage(book, editedPerson));
+ }
+
+ /**
+ * Generates a command execution success message when book {@code book} is successfully added
+ * {@code personToEdit}.
+ */
+ private String generateSuccessMessage(Book book, Person personToEdit) {
+ return String.format(MESSAGE_ADD_BORROW_SUCCESS, book, personToEdit);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof BorrowCommand)) {
+ return false;
+ }
+
+ BorrowCommand e = (BorrowCommand) other;
+ return index.equals(e.index)
+ && book.equals(e.book);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteBookCommand.java b/src/main/java/seedu/address/logic/commands/DeleteBookCommand.java
new file mode 100644
index 00000000000..b435a7e5f53
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteBookCommand.java
@@ -0,0 +1,78 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOK;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.book.Book;
+
+/**
+ * Removes a book with the specified book title from the Library.
+ */
+public class DeleteBookCommand extends Command {
+ public static final String COMMAND_WORD = "delbook";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the book specified "
+ + "by its book title. "
+ + PREFIX_BOOKLIST + "[borrow]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_BOOKLIST + "Likes to swim.";
+
+ public static final String MESSAGE_DELETE_BOOK_SUCCESS = "Deleted Book: %1$s";
+
+ private final Book book;
+
+ /**
+ * Creates a DeleteBookCommand object.
+ */
+ public DeleteBookCommand(Book book) {
+ this.book = book;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // Checks whether the book is an empty field.
+ if (book.equals(new Book(""))) {
+ throw new CommandException(Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ // Check whether book is present in library
+ if (!model.hasBookInLibrary(book)) {
+ throw new CommandException(String.format(Messages.MESSAGE_BOOK_NOT_IN_LIBRARY, book));
+ }
+
+ Book deletedBook = model.popBookFromLibrary(book);
+ requireNonNull(deletedBook);
+ model.updateFilteredLibraryList(PREDICATE_SHOW_ALL_BOOK);
+
+ return new CommandResult(generateSuccessMessage(book));
+ }
+
+ /**
+ * Generates a command execution success message when book {@code book} is successfully
+ * removed from the library.
+ */
+ private String generateSuccessMessage(Book book) {
+ return String.format(MESSAGE_DELETE_BOOK_SUCCESS, book);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteBookCommand)) {
+ return false;
+ }
+
+ DeleteBookCommand otherDeleteBookCommand = (DeleteBookCommand) other;
+ return book.equals(otherDeleteBookCommand.book);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..8d48b76d1e6 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -4,9 +4,9 @@
import java.util.List;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
diff --git a/src/main/java/seedu/address/logic/commands/DonateCommand.java b/src/main/java/seedu/address/logic/commands/DonateCommand.java
new file mode 100644
index 00000000000..b6906431564
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DonateCommand.java
@@ -0,0 +1,98 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOK;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.book.Book;
+import seedu.address.model.person.Person;
+
+/**
+ * Recorded the book from the specific person and add the merit score to the specific person.
+ */
+public class DonateCommand extends Command {
+ public static final String COMMAND_WORD = "donate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the merit score of the person identified "
+ + "by the index number used in the last person listing. "
+ + "Merit score will be overwritten to existing score + 1\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_BOOKLIST + "[borrow]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_BOOKLIST + "The Book of Answers";
+
+ // todo : later need to edit this MESSAGE when the bookTitle recorded to the database.
+ public static final String MESSAGE_DONATE_SUCCESS = "Donated book from person: %1$s\n"
+ + "%2$s is added to library";
+
+ private final Index index;
+ private final Book book;
+
+ /**
+ * @param index of the person in the filtered person list to edit the
+ * merit score
+ * @param book of the person donated to be updated to the database
+ */
+ public DonateCommand(Index index, Book book) {
+ requireAllNonNull(index, book);
+
+ this.index = index;
+ this.book = book;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ if (book.equals(new Book(""))) {
+ throw new CommandException(Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+ Person editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), personToEdit.getMeritScore().incrementScore(),
+ personToEdit.getBookList(), personToEdit.getTags());
+
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+
+ model.addBook(book);
+ model.updateFilteredLibraryList(PREDICATE_SHOW_ALL_BOOK);
+ return new CommandResult(generateSuccessMessage(editedPerson, book));
+ }
+
+ /**
+ * Generates a command execution success message when book title is successfully removed
+ * {@code personToEdit}.
+ */
+ private String generateSuccessMessage(Person personToEdit, Book book) {
+ return String.format(MESSAGE_DONATE_SUCCESS, personToEdit, book);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DonateCommand)) {
+ return false;
+ }
+
+ DonateCommand e = (DonateCommand) other;
+ return index.equals(e.index)
+ && book.equals(e.book);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..0b688302d8f 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -8,6 +8,7 @@
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -15,14 +16,16 @@
import java.util.Optional;
import java.util.Set;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.MeritScore;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -99,9 +102,14 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ MeritScore updatedMeritScore = editPersonDescriptor.getMeritScore().orElse(personToEdit.getMeritScore());
+
+ ArrayList updatedBookList = personToEdit.getBookList();
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ // return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress,
+ updatedMeritScore, updatedBookList, updatedTags);
}
@Override
@@ -138,6 +146,8 @@ public static class EditPersonDescriptor {
private Email email;
private Address address;
private Set tags;
+ private MeritScore meritScore;
+ private ArrayList bookList;
public EditPersonDescriptor() {}
@@ -150,6 +160,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setPhone(toCopy.phone);
setEmail(toCopy.email);
setAddress(toCopy.address);
+ setMeritScore(toCopy.meritScore);
+ setBookList(toCopy.bookList);
setTags(toCopy.tags);
}
@@ -192,6 +204,22 @@ public Optional getAddress() {
return Optional.ofNullable(address);
}
+ public void setBookList(ArrayList bookList) {
+ this.bookList = bookList;
+ }
+
+ public Optional> getBookList() {
+ return Optional.ofNullable(bookList);
+ }
+
+ public void setMeritScore(MeritScore meritScore) {
+ this.meritScore = meritScore;
+ }
+
+ public Optional getMeritScore() {
+ return Optional.ofNullable(meritScore);
+ }
+
/**
* Sets {@code tags} to this object's {@code tags}.
* A defensive copy of {@code tags} is used internally.
@@ -225,7 +253,9 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(tags, otherEditPersonDescriptor.tags)
+ && Objects.equals(bookList, otherEditPersonDescriptor.bookList)
+ && Objects.equals(meritScore, otherEditPersonDescriptor.meritScore);
}
@Override
@@ -235,6 +265,8 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
+ .add("meritScore", meritScore)
+ .add("bookTitle", bookList)
.add("tags", tags)
.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..4f52a0c5f8a 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -13,6 +13,7 @@ public class ExitCommand extends Command {
@Override
public CommandResult execute(Model model) {
+
return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index 72b9eddd3a7..8b788adc47d 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -2,8 +2,8 @@
import static java.util.Objects.requireNonNull;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.person.NameContainsKeywordsPredicate;
diff --git a/src/main/java/seedu/address/logic/commands/LimitCommand.java b/src/main/java/seedu/address/logic/commands/LimitCommand.java
new file mode 100644
index 00000000000..0d3cf29194e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LimitCommand.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.library.Threshold;
+
+/**
+ * Sets the limit threshold for Merit Score to not allow borrowers from borrowing books from the library.
+ */
+public class LimitCommand extends Command {
+ public static final String COMMAND_WORD = "limit";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sets the limit for the Merit Score such that any "
+ + "borrower with less than or equal to the limit cannot borrow any books from the library.\n"
+ + "Parameters: INTEGER (cannot include spaces or + signs)\n"
+ + "Example: " + COMMAND_WORD + " -3";
+
+ public static final String MESSAGE_LIMIT_THRESHOLD_SUCCESS = "Limit set to: %s";
+ public static final String MESSAGE_HAS_NO_ARGUMENT = "Current limit: %s\n"
+ + "To change the current limit, use parameters with limit.\n"
+ + "Parameters: INTEGER (cannot include spaces or + signs)\n"
+ + "Example: " + COMMAND_WORD + " -3";;
+ public static final String MESSAGE_DUPLICATE_LIMIT = "Library already has the same limit set";
+ public final Threshold threshold;
+ private boolean hasNoArgument = false;
+
+ /**
+ * @param threshold Limit for the Merit Score during borrowing.
+ */
+ public LimitCommand(Threshold threshold) {
+ requireNonNull(threshold);
+
+ this.threshold = threshold;
+ }
+
+ /**
+ * @param hasNoArgument Limit for the Merit Score during borrowing.
+ */
+ public LimitCommand(boolean hasNoArgument) {
+ assert(hasNoArgument);
+
+ this.threshold = new Threshold();
+ this.hasNoArgument = hasNoArgument;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ if (hasNoArgument) {
+ return new CommandResult(String.format(MESSAGE_HAS_NO_ARGUMENT, model.getThreshold()));
+ }
+
+ if (model.hasThreshold(threshold)) {
+ throw new CommandException(MESSAGE_DUPLICATE_LIMIT);
+ }
+ model.setThreshold(threshold);
+ return new CommandResult(String.format(MESSAGE_LIMIT_THRESHOLD_SUCCESS, threshold));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof LimitCommand)) {
+ return false;
+ }
+
+ LimitCommand otherLimitCommand = (LimitCommand) other;
+ return threshold.equals(otherLimitCommand.threshold);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ReturnCommand.java b/src/main/java/seedu/address/logic/commands/ReturnCommand.java
new file mode 100644
index 00000000000..4662eec92cc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ReturnCommand.java
@@ -0,0 +1,106 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOK;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.book.Book;
+import seedu.address.model.person.Person;
+
+/**
+ * Removes a book from the book list of the specific borrower.
+ */
+public class ReturnCommand extends Command {
+ public static final String COMMAND_WORD = "return";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the book list of the person identified "
+ + "by the index number used in the last person listing. "
+ + "If book exists in the person's book list, it will be removed. \n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_BOOKLIST + "[borrow]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_BOOKLIST + "Likes to swim.";
+
+ public static final String MESSAGE_RETURN_BOOK_SUCCESS = "Removed book: %1$s from Person: %2$s";
+
+ private final Index index;
+ private final Book book;
+
+ /**
+ * Creates a ReturnCommand object.
+ *
+ * @param index The index of the person returning the book.
+ */
+ public ReturnCommand(Index index, Book book) {
+ requireAllNonNull(index, book);
+ this.index = index;
+ this.book = book;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ // Checks whether the borrowed book is an empty field.
+ if (book.equals(new Book(""))) {
+ throw new CommandException(Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+
+ if (personToEdit.getBookList().isEmpty()) {
+ throw new CommandException(Messages.MESSAGE_EMPTY_BOOKLIST_FIELD);
+ }
+
+ if (!personToEdit.getBookList().contains(book)) {
+ throw new CommandException(Messages.MESSAGE_BOOK_DOES_NOT_EXIST);
+ }
+
+ Person editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), personToEdit.getMeritScore().incrementScore(),
+ personToEdit.getBookListWithoutBook(book), personToEdit.getTags());
+
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+
+ model.addBook(book);
+ model.updateFilteredLibraryList(PREDICATE_SHOW_ALL_BOOK);
+
+ return new CommandResult(generateSuccessMessage(book, editedPerson));
+ }
+
+ /**
+ * Generates a command execution success message when book {@code book} is successfully removed
+ * {@code personToEdit}.
+ */
+ private String generateSuccessMessage(Book book, Person personToEdit) {
+ return String.format(MESSAGE_RETURN_BOOK_SUCCESS, book, personToEdit);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ReturnCommand)) {
+ return false;
+ }
+
+ ReturnCommand e = (ReturnCommand) other;
+ return index.equals(e.index)
+ && book.equals(e.book);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddBookCommandParser.java b/src/main/java/seedu/address/logic/parser/AddBookCommandParser.java
new file mode 100644
index 00000000000..6ce72ff139f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddBookCommandParser.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+
+import java.util.NoSuchElementException;
+
+import seedu.address.logic.commands.AddBookCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.book.Book;
+
+/**
+ * Parses input arguments and creates a new AddBookCommand object
+ */
+
+public class AddBookCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddBookCommand
+ * and returns an AddBookCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddBookCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_BOOKLIST);
+
+ String bookTitle;
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_BOOKLIST);
+ try {
+ bookTitle = argMultimap.getValue(PREFIX_BOOKLIST).get();
+ } catch (NoSuchElementException nee) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddBookCommand.MESSAGE_USAGE), nee);
+ }
+
+ return new AddBookCommand(new Book(bookTitle));
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..1c24b74ef16 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
@@ -17,6 +17,7 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+//import seedu.address.model.person.BookList;
import seedu.address.model.tag.Tag;
/**
@@ -43,8 +44,8 @@ public AddCommand parse(String args) throws ParseException {
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ // BookList bookTitle = new BookList("");
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
Person person = new Person(name, phone, email, address, tagList);
return new AddCommand(person);
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..9902e44a0f7 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -1,22 +1,28 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.AddBookCommand;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.BorrowCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.DeleteBookCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DonateCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.LimitCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ReturnCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -77,6 +83,24 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case BorrowCommand.COMMAND_WORD:
+ return new BorrowCommandParser().parse(arguments);
+
+ case ReturnCommand.COMMAND_WORD:
+ return new ReturnCommandParser().parse(arguments);
+
+ case DonateCommand.COMMAND_WORD:
+ return new DonateCommandParser().parse(arguments);
+
+ case LimitCommand.COMMAND_WORD:
+ return new LimitCommandParser().parse(arguments);
+
+ case DeleteBookCommand.COMMAND_WORD:
+ return new DeleteBookCommandParser().parse(arguments);
+
+ case AddBookCommand.COMMAND_WORD:
+ return new AddBookCommandParser().parse(arguments);
+
default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
index 21e26887a83..5691611e523 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
@@ -7,7 +7,7 @@
import java.util.Optional;
import java.util.stream.Stream;
-import seedu.address.logic.Messages;
+import seedu.address.commons.core.Messages;
import seedu.address.logic.parser.exceptions.ParseException;
/**
diff --git a/src/main/java/seedu/address/logic/parser/BorrowCommandParser.java b/src/main/java/seedu/address/logic/parser/BorrowCommandParser.java
new file mode 100644
index 00000000000..3a7e0a23497
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/BorrowCommandParser.java
@@ -0,0 +1,43 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+
+import java.util.NoSuchElementException;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.BorrowCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.book.Book;
+
+/**
+ * Parses input arguments and creates a new BorrowCommand object
+ */
+public class BorrowCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the BorrowCommand
+ * and returns an BorrowCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public BorrowCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_BOOKLIST);
+
+ Index index;
+ String bookTitle;
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_BOOKLIST);
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ bookTitle = argMultimap.getValue(PREFIX_BOOKLIST).get();
+ } catch (IllegalValueException ive) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, BorrowCommand.MESSAGE_USAGE), ive);
+ } catch (NoSuchElementException nee) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, BorrowCommand.MESSAGE_USAGE), nee);
+ }
+
+ return new BorrowCommand(index, new Book(bookTitle));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..cfd83a88b03 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -11,5 +11,7 @@ public class CliSyntax {
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_BOOKLIST = new Prefix("b/");
+ public static final Prefix PREFIX_MERITSCORE = new Prefix("/s");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteBookCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteBookCommandParser.java
new file mode 100644
index 00000000000..68f6c2ba715
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteBookCommandParser.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+
+import java.util.NoSuchElementException;
+
+import seedu.address.logic.commands.DeleteBookCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.book.Book;
+
+/**
+ * Parses input arguments and creates a new DeleteBookCommand object
+ */
+public class DeleteBookCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the
+ * BorrowCommand
+ * and returns an BorrowCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteBookCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_BOOKLIST);
+
+ String bookTitle;
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_BOOKLIST);
+ try {
+ bookTitle = argMultimap.getValue(PREFIX_BOOKLIST).get();
+ } catch (NoSuchElementException nee) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteBookCommand.MESSAGE_USAGE),
+ nee);
+ }
+
+ return new DeleteBookCommand(new Book(bookTitle));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 3527fe76a3e..522b93081cc 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.DeleteCommand;
diff --git a/src/main/java/seedu/address/logic/parser/DonateCommandParser.java b/src/main/java/seedu/address/logic/parser/DonateCommandParser.java
new file mode 100644
index 00000000000..aa495e771aa
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DonateCommandParser.java
@@ -0,0 +1,43 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+
+import java.util.NoSuchElementException;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.DonateCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.book.Book;
+
+
+/**
+ * Parses input arguments and creates a new DonateCommand object
+ */
+public class DonateCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DonateCommand
+ * and returns an DonateCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DonateCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_BOOKLIST);
+
+ Index index;
+ String bookTitle;
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_BOOKLIST);
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ bookTitle = argMultimap.getValue(PREFIX_BOOKLIST).get();
+ } catch (IllegalValueException ive) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DonateCommand.MESSAGE_USAGE), ive);
+ } catch (NoSuchElementException nee) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DonateCommand.MESSAGE_USAGE), nee);
+ }
+
+ return new DonateCommand(index, new Book(bookTitle));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..d51ec3da928 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -1,7 +1,7 @@
package seedu.address.logic.parser;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 2867bde857b..4fb71f23103 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import java.util.Arrays;
diff --git a/src/main/java/seedu/address/logic/parser/LimitCommandParser.java b/src/main/java/seedu/address/logic/parser/LimitCommandParser.java
new file mode 100644
index 00000000000..1ccf2d19320
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/LimitCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.LimitCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.library.Threshold;
+
+/**
+ * Parses input arguments and creates a new LimitCommand object
+ */
+public class LimitCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the LimitCommand
+ * and returns a DeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LimitCommand parse(String args) throws ParseException {
+ try {
+ if (args.equals("")) {
+ return new LimitCommand(true);
+ }
+ Threshold threshold = ParserUtil.parseThreshold(args);
+ return new LimitCommand(threshold);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, LimitCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..ecea533758d 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -9,6 +9,7 @@
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.library.Threshold;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
@@ -21,6 +22,8 @@
public class ParserUtil {
public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+ public static final String MESSAGE_INVALID_THRESHOLD = "Threshold is not an integer.";
+
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
@@ -35,6 +38,20 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
return Index.fromOneBased(Integer.parseInt(trimmedIndex));
}
+ /**
+ * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
+ * trimmed.
+ * @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
+ */
+ public static Threshold parseThreshold(String threshold) throws ParseException {
+ requireNonNull(threshold);
+ String trimmedThreshold = threshold.trim();
+ if (!StringUtil.isInteger(trimmedThreshold)) {
+ throw new ParseException(MESSAGE_INVALID_THRESHOLD);
+ }
+ return new Threshold(Integer.parseInt(trimmedThreshold));
+ }
+
/**
* Parses a {@code String name} into a {@code Name}.
* Leading and trailing whitespaces will be trimmed.
diff --git a/src/main/java/seedu/address/logic/parser/ReturnCommandParser.java b/src/main/java/seedu/address/logic/parser/ReturnCommandParser.java
new file mode 100644
index 00000000000..6ef79ff87dc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ReturnCommandParser.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BOOKLIST;
+
+import java.util.NoSuchElementException;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.ReturnCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.book.Book;
+
+/**
+ * Parses input arguments and creates a new ReturnCommand object
+ */
+public class ReturnCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ReturnCommand
+ * and returns a ReturnCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ReturnCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_BOOKLIST);
+
+ Index index;
+ String bookTitle;
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_BOOKLIST);
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ bookTitle = argMultimap.getValue(PREFIX_BOOKLIST).get();
+ } catch (IllegalValueException | NoSuchElementException ive) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReturnCommand.MESSAGE_USAGE), ive);
+ }
+
+ return new ReturnCommand(index, new Book(bookTitle));
+ }
+}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..0e219f7ef5e 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,6 +5,8 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Threshold;
import seedu.address.model.person.Person;
/**
@@ -13,6 +15,7 @@
public interface Model {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_BOOK = unused -> true;
/**
* Replaces user prefs data with the data in {@code userPrefs}.
@@ -84,4 +87,61 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Replaces library data with the data in {@code library}.
+ */
+ void setLibrary(ReadOnlyLibrary library);
+
+ /** Returns the Library */
+ ReadOnlyLibrary getLibrary();
+
+ /**
+ * Updates the filter of the filtered book list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredLibraryList(Predicate predicate);
+ /** Returns the list of books in the Library **/
+ ObservableList getLibraryBookList();
+
+ /**
+ * Adds {@code book} to the Library.
+ */
+ void addBook(Book book);
+
+ /**
+ * Deletes book at {@code index} from the Library.
+ */
+ void deleteBook(int index);
+
+ /**
+ * Returns true if {@code person} can borrow from the library.
+ */
+ boolean canLendTo(Person person);
+
+ /**
+ * Sets threshold of the library.
+ */
+ void setThreshold(Threshold threshold);
+
+ /**
+ * Returns the threshold of the library.
+ */
+ Threshold getThreshold();
+
+ /**
+ * Returns true if threshold of library is the same as {@code threshold}.
+ */
+ boolean hasThreshold(Threshold threshold);
+
+ /**
+ * Returns true if book is inside the library book list.
+ */
+ boolean hasBookInLibrary(Book book);
+
+ /**
+ * Returns the {@code book} if it is present in the library and removes it from the library book list.
+ * Returns null if book is not present in the library book list.
+ */
+ Book popBookFromLibrary(Book book);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..cf47586171b 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,6 +11,9 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+import seedu.address.model.library.Threshold;
import seedu.address.model.person.Person;
/**
@@ -22,22 +25,25 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
-
+ private final FilteredList filteredBooks;
+ private final Library library;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, ReadOnlyLibrary library) {
+ requireAllNonNull(addressBook, userPrefs, library);
logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ this.library = new Library(library);
+ filteredBooks = new FilteredList<>(this.library.getBookList());
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new UserPrefs(), new Library());
}
//=========== UserPrefs ==================================================================================
@@ -128,6 +134,69 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ //=========== Library =============================================================
+ @Override
+ public void setLibrary(ReadOnlyLibrary library) {
+ this.library.resetData(library);
+ }
+
+ @Override
+ public ReadOnlyLibrary getLibrary() {
+ return library;
+ }
+
+ @Override
+ public ObservableList getLibraryBookList() {
+ return filteredBooks;
+ }
+
+ @Override
+ public void updateFilteredLibraryList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredBooks.setPredicate(predicate);
+ }
+
+ @Override
+ public void addBook(Book book) {
+ library.addBook(book);
+ updateFilteredLibraryList(PREDICATE_SHOW_ALL_BOOK);
+ }
+
+ @Override
+ public void deleteBook(int index) {
+ library.deleteBook(index);
+ }
+
+ @Override
+ public boolean canLendTo(Person person) {
+ return library.canLendTo(person);
+ }
+
+ @Override
+ public void setThreshold(Threshold threshold) {
+ library.setThreshold(threshold);
+ }
+
+ @Override
+ public Threshold getThreshold() {
+ return library.getThreshold();
+ }
+
+ @Override
+ public boolean hasThreshold(Threshold threshold) {
+ return library.hasThreshold(threshold);
+ }
+
+ @Override
+ public boolean hasBookInLibrary(Book book) {
+ return library.hasBookInLibrary(book);
+ }
+
+ @Override
+ public Book popBookFromLibrary(Book book) {
+ return library.popBookFromLibrary(book);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -142,7 +211,7 @@ public boolean equals(Object other) {
ModelManager otherModelManager = (ModelManager) other;
return addressBook.equals(otherModelManager.addressBook)
&& userPrefs.equals(otherModelManager.userPrefs)
- && filteredPersons.equals(otherModelManager.filteredPersons);
+ && filteredPersons.equals(otherModelManager.filteredPersons)
+ && library.equals(otherModelManager.library);
}
-
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyLibrary.java b/src/main/java/seedu/address/model/ReadOnlyLibrary.java
new file mode 100644
index 00000000000..b8bda1c66f3
--- /dev/null
+++ b/src/main/java/seedu/address/model/ReadOnlyLibrary.java
@@ -0,0 +1,20 @@
+package seedu.address.model;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Threshold;
+
+/**
+ * Unmodifiable view of a library.
+ */
+public interface ReadOnlyLibrary {
+ /**
+ * Returns an unmodifiable view of the book list.
+ */
+ ObservableList getBookList();
+
+ /**
+ * Returns an unmodifiable view of the threshold.
+ */
+ Threshold getThreshold();
+}
diff --git a/src/main/java/seedu/address/model/book/Book.java b/src/main/java/seedu/address/model/book/Book.java
new file mode 100644
index 00000000000..dda73614413
--- /dev/null
+++ b/src/main/java/seedu/address/model/book/Book.java
@@ -0,0 +1,66 @@
+package seedu.address.model.book;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Book in the book list.
+ * Guarantees: immutable;
+ */
+public class Book {
+ public static final String MESSAGE_CONSTRAINTS = "Book titles can take any values, and it should not be blank";
+ /*
+ * The book title can be empty string to represent no book present or,
+ * The first character of the book title must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "(|\\S.*)";
+ public final String bookTitle;
+
+ /**
+ * Constructs a {@code Book}.
+ *
+ * @param bookTitle A valid tag name.
+ */
+ public Book(String bookTitle) {
+ requireNonNull(bookTitle);
+ checkArgument(isValidBookTitle(bookTitle), MESSAGE_CONSTRAINTS);
+ this.bookTitle = bookTitle;
+
+ }
+
+ /**
+ * Returns true if a given string is a valid book title.
+ * Empty titles or titles that are not blank are valid.
+ */
+ public static boolean isValidBookTitle(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Book)) {
+ return false;
+ }
+
+ Book otherBook = (Book) other;
+ return bookTitle.equals(otherBook.bookTitle);
+ }
+
+ @Override
+ public int hashCode() {
+ return bookTitle.hashCode();
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ public String toString() {
+ return bookTitle;
+ }
+}
diff --git a/src/main/java/seedu/address/model/library/Library.java b/src/main/java/seedu/address/model/library/Library.java
new file mode 100644
index 00000000000..acedf2d9140
--- /dev/null
+++ b/src/main/java/seedu/address/model/library/Library.java
@@ -0,0 +1,207 @@
+package seedu.address.model.library;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Comparator;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.ReadOnlyLibrary;
+import seedu.address.model.book.Book;
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a collection of books in a library.
+ */
+public class Library implements ReadOnlyLibrary {
+
+ /**
+ * Comparator for comparing books alphabetically by title.
+ */
+ private static Comparator bookComparator = new Comparator() {
+ @Override
+ public int compare(Book book1, Book book2) {
+ return book1.bookTitle.compareTo(book2.bookTitle);
+ }
+ };
+
+ private ObservableList bookList;
+ private Threshold threshold;
+
+ /**
+ * Construct an empty library.
+ */
+ public Library() {
+ bookList = FXCollections.observableArrayList();
+ threshold = new Threshold();
+ }
+
+ /**
+ * Construct a library with the specified list of books and threshold.
+ *
+ * @param toBeCopied The read only library from data.
+ */
+ public Library(ReadOnlyLibrary toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ /**
+ * Construct an empty library with preset threshold.
+ */
+ public Library(int i) {
+ bookList = FXCollections.observableArrayList();
+ threshold = new Threshold(i);
+ }
+
+ /**
+ * Construct a library with the specified list of books.
+ *
+ * @param bookList The list of books to initialize the library with.
+ */
+ public Library(ObservableList bookList) {
+ ObservableList expectedBookList = FXCollections.observableArrayList();
+ for (Book book : bookList) {
+ expectedBookList.add(book);
+ }
+ this.bookList = expectedBookList;
+ threshold = new Threshold();
+ }
+
+ /**
+ * Construct a library with the specified list of books and threshold.
+ *
+ * @param bookList The list of books to initialize the library with.
+ * @param threshold The threshold limit
+ */
+ public Library(ObservableList bookList, Threshold threshold) {
+ ObservableList expectedBookList = FXCollections.observableArrayList();
+ for (Book book : bookList) {
+ expectedBookList.add(book);
+ }
+ this.bookList = expectedBookList;
+ this.threshold = threshold;
+ }
+
+ public void addBook(Book book) {
+ this.bookList.add(book);
+ }
+
+ public void deleteBook(int index) {
+ this.bookList.remove(index - 1);
+ }
+
+ /**
+ * Sort the books in the library alphabetically by title.
+ */
+ public void sortAlphabetically() {
+ bookList.sort(bookComparator);
+ }
+
+ /**
+ * Retrieve a list of books from the library, sorted alphabetically by title.
+ *
+ * @return An ArrayList containing the books in the library, sorted alphabetically.
+ */
+ public ObservableList list() {
+ this.sortAlphabetically();
+ return this.bookList;
+ }
+
+ //TODO use this in BorrowCommand
+
+ /**
+ * Checks if person is able to borrow a book from the library.
+ *
+ * @param person Person attempting to borrow a book.
+ * @return if the person can borrow the book or not.
+ */
+ public boolean canLendTo(Person person) {
+ return threshold.isLessThanOrEqualTo(person.getMeritScore());
+ }
+
+ @Override
+ public ObservableList getBookList() {
+ sortAlphabetically();
+ return this.bookList;
+ }
+
+ public void setBookList(ObservableList bookList) {
+ ObservableList expectedBookList = FXCollections.observableArrayList();
+ for (Book book : bookList) {
+ expectedBookList.add(book);
+ }
+ this.bookList = expectedBookList;
+ }
+
+ public void setThreshold(Threshold threshold) {
+ this.threshold = threshold;
+ }
+
+ @Override
+ public Threshold getThreshold() {
+ return threshold;
+ }
+
+ public boolean hasThreshold(Threshold threshold) {
+ return this.threshold.equals(threshold);
+ }
+
+ public boolean hasBookInLibrary(Book book) {
+ return bookList.contains(book);
+ }
+
+ /**
+ * Removes {@code book} from library book list.
+ * @param book book to be removed from library
+ * @return returns the book removed from library
+ */
+ public Book popBookFromLibrary(Book book) {
+ if (hasBookInLibrary(book)) {
+ int i = bookList.indexOf(book);
+ return bookList.remove(i);
+ }
+ return null;
+ }
+
+ /**
+ * Resets the existing data of this {@code Library} with {@code newData}.
+ */
+ public void resetData(ReadOnlyLibrary newData) {
+ requireNonNull(newData);
+
+ setThreshold(newData.getThreshold());
+ setBookList(newData.getBookList());
+ }
+
+ @Override
+ public String toString() {
+ String result = "";
+ for (int i = 1; i < this.list().size() + 1; i++) {
+ result += i + ". " + this.bookList.get(i - 1).bookTitle.toString();
+ if (i != this.list().size() - 1) {
+ result += "\n";
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof Library) {
+ Library otherLibrary = (Library) other;
+ if (this.list().size() != otherLibrary.list().size()) {
+ return false;
+ }
+ for (int i = 0; i < this.list().size(); i++) {
+ if (!this.bookList.get(i).equals(otherLibrary.bookList.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/model/library/Threshold.java b/src/main/java/seedu/address/model/library/Threshold.java
new file mode 100644
index 00000000000..58588213483
--- /dev/null
+++ b/src/main/java/seedu/address/model/library/Threshold.java
@@ -0,0 +1,52 @@
+package seedu.address.model.library;
+
+import seedu.address.model.person.MeritScore;
+
+/**
+ * Acts as the limit for {@link seedu.address.model.person.MeritScore} during borrowing.
+ * Default threshold is -3.
+ */
+public class Threshold {
+ private final int threshold;
+
+ /**
+ * @param threshold The limit for Merit Score.
+ */
+ public Threshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ /**
+ * Constructs threshold with a default value of -3.
+ */
+ public Threshold() {
+ this.threshold = -3;
+ }
+
+ public int getThreshold() {
+ return this.threshold;
+ }
+
+ public boolean isLessThanOrEqualTo(MeritScore meritScore) {
+ return threshold <= meritScore.getMeritScoreInt();
+ }
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Threshold)) {
+ return false;
+ }
+
+ Threshold otherThreshold = (Threshold) other;
+ return threshold == otherThreshold.threshold;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(threshold);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/MeritScore.java b/src/main/java/seedu/address/model/person/MeritScore.java
new file mode 100644
index 00000000000..5d713d7321f
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/MeritScore.java
@@ -0,0 +1,129 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * This Merit Score is the class of reputation of a borrower
+ */
+public class MeritScore {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Merit Score must be >= 0";
+
+ /**
+ * The first character of the address must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "-?\\d+";
+
+ public final String meritScore;
+
+ /**
+ * Constructs a {@code MeritScore}.
+ *
+ * @param meritScore A valid merit score.
+ */
+ public MeritScore(int meritScore) {
+ this.meritScore = String.valueOf(meritScore);
+ }
+
+ /**
+ * Constructs a {@code MeritScore} from String.
+ *
+ * @param meritScore A valid merit score.
+ */
+ public MeritScore(String meritScore) {
+ requireNonNull(meritScore);
+ checkArgument(isValidMeritScore(meritScore), MESSAGE_CONSTRAINTS);
+ this.meritScore = meritScore;
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidMeritScore(String test) {
+
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Convert merit score into int and return as int.
+ * This function also checks the validity of merit score and the ability to be parsed as int.
+ *
+ * @return merit score of int type
+ */
+ public int getMeritScoreInt() {
+ // Check if meritScore is null or empty
+ if (meritScore == null || meritScore.isEmpty()) {
+ throw new IllegalArgumentException("meritScore is null or empty");
+ }
+
+ // Check if meritScore consists only of digits
+ if (!meritScore.matches("-?\\d+")) {
+ throw new IllegalArgumentException("meritScore contains non integer or non minus sign characters");
+ }
+
+ try {
+ // Parse meritScore as an integer
+ return Integer.parseInt(meritScore);
+ } catch (NumberFormatException e) {
+ // Handle the case where meritScore cannot be parsed as an integer
+ throw new IllegalArgumentException("meritScore cannot be parsed as an integer: " + meritScore);
+ }
+ }
+
+ /**
+ * Increases the merit score by 1.
+ *
+ * @return new incremented MeritScore
+ */
+ public MeritScore incrementScore() {
+ int score = Integer.parseInt(meritScore);
+ score++;
+ return new MeritScore(String.valueOf(score));
+ }
+
+ /**
+ * Decreases the merit score by 1.
+ *
+ * @return new decremented MeritScore
+ */
+ public MeritScore decrementScore() {
+ int score = Integer.parseInt(meritScore);
+ score--;
+ return new MeritScore(String.valueOf(score));
+ }
+
+ public String getScore() {
+ return meritScore;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(meritScore);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof MeritScore)) {
+ return false;
+ }
+
+ MeritScore otherName = (MeritScore) other;
+ return meritScore.equals(otherName.meritScore);
+ }
+
+ @Override
+ public int hashCode() {
+ // Integer score = meritScore;
+ // return score.hashCode();
+ return meritScore.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..8e43f4b9b3d 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -2,12 +2,15 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.book.Book;
import seedu.address.model.tag.Tag;
/**
@@ -23,11 +26,15 @@ public class Person {
// Data fields
private final Address address;
+ private final MeritScore meritScore;
+ private final ArrayList bookList;
private final Set tags = new HashSet<>();
/**
+ * Constructs a person without any books borrowed.
* Every field must be present and not null.
*/
+
public Person(Name name, Phone phone, Email email, Address address, Set tags) {
requireAllNonNull(name, phone, email, address, tags);
this.name = name;
@@ -35,6 +42,24 @@ public Person(Name name, Phone phone, Email email, Address address, Set tag
this.email = email;
this.address = address;
this.tags.addAll(tags);
+ this.meritScore = new MeritScore(0);
+ this.bookList = new ArrayList<>();
+ }
+
+ /**
+ * Constructs a person with books borrowed.
+ * Every field must be present and not null.
+ */
+ public Person(Name name, Phone phone, Email email, Address address,
+ MeritScore meritScore, ArrayList bookList, Set tags) {
+ requireAllNonNull(name, phone, email, address, tags);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.tags.addAll(tags);
+ this.meritScore = meritScore;
+ this.bookList = bookList;
}
public Name getName() {
@@ -61,6 +86,60 @@ public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ /**
+ * @return the merit score of the person
+ */
+ public MeritScore getMeritScore() {
+ return meritScore;
+ }
+
+ /**
+ * Returns an immutable book list, consisting of books in the person's book list.
+ *
+ * @return The book list.
+ */
+ public ArrayList getBookList() {
+ return new ArrayList<>(Collections.unmodifiableList(this.bookList));
+ }
+
+ /**
+ * Returns an immutable book list, consisting of books in the person's book list
+ * without the book passed in.
+ *
+ * @param book The book to be removed.
+ * @return An immutable copy of the person's book list without the book.
+ */
+ public ArrayList getBookListWithoutBook(Book book) {
+ ArrayList mutableCopy = new ArrayList<>(this.getBookList());
+ mutableCopy.remove(book);
+ return new ArrayList<>(Collections.unmodifiableList(mutableCopy));
+ }
+
+ /**
+ * Returns an immutable book list, consisting of books in the person's book list
+ * with an additional book, which is the book passed in.
+ *
+ * @param book The book to be added.
+ * @return An immutable copy of the person's book list with the new book.
+ */
+ public ArrayList getBookListWithNewBook(Book book) {
+ ArrayList mutableCopy = new ArrayList<>(this.getBookList());
+ mutableCopy.add(book);
+ return new ArrayList<>(Collections.unmodifiableList(mutableCopy));
+ }
+
+ public String getBookListToStringWithIndex() {
+ this.bookList.sort(Comparator.comparing(book -> book.bookTitle));
+ String result = "";
+ for (int i = 0; i < bookList.size(); i++) {
+ result += (i + 1) + ". " + this.bookList.get(i).bookTitle.toString();
+ if (i != bookList.size() - 1) {
+ result += "\n";
+ }
+ }
+ return result;
+ }
+
/**
* Returns true if both persons have the same name.
* This defines a weaker notion of equality between two persons.
@@ -94,13 +173,15 @@ public boolean equals(Object other) {
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
&& address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
+ && tags.equals(otherPerson.tags)
+ && meritScore.equals(otherPerson.meritScore)
+ && bookList.equals(otherPerson.bookList);
}
@Override
public int hashCode() {
// use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
+ return Objects.hash(name, phone, email, address, meritScore, bookList, tags);
}
@Override
@@ -111,6 +192,8 @@ public String toString() {
.add("email", email)
.add("address", address)
.add("tags", tags)
+ .add("Merit score", meritScore)
+ .add("book borrowed", bookList)
.toString();
}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..55e45f67a42 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,13 +1,16 @@
package seedu.address.model.util;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.MeritScore;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -17,25 +20,28 @@
* Contains utility methods for populating {@code AddressBook} with sample data.
*/
public class SampleDataUtil {
+ public static final ArrayList EMPTY_BORROW = new ArrayList<>();
+ public static final MeritScore EMPTY_SCORE = new MeritScore(0);
+
public static Person[] getSamplePersons() {
return new Person[] {
new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
+ new Address("Blk 30 Geylang Street 29, #06-40"), EMPTY_SCORE, EMPTY_BORROW,
getTagSet("friends")),
new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_SCORE, EMPTY_BORROW,
getTagSet("colleagues", "friends")),
new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), EMPTY_SCORE, EMPTY_BORROW,
getTagSet("neighbours")),
new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), EMPTY_SCORE, EMPTY_BORROW,
getTagSet("family")),
new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
+ new Address("Blk 47 Tampines Street 20, #17-35"), EMPTY_SCORE, EMPTY_BORROW,
getTagSet("classmates")),
new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
+ new Address("Blk 45 Aljunied Street 85, #11-31"), EMPTY_SCORE, EMPTY_BORROW,
getTagSet("colleagues"))
};
}
@@ -57,4 +63,12 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ /**
+ * Returns a book array list containing the list of strings given.
+ */
+ public static ArrayList getBookList(String... strings) {
+ return Arrays.stream(strings)
+ .map(Book::new)
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedBook.java b/src/main/java/seedu/address/storage/JsonAdaptedBook.java
new file mode 100644
index 00000000000..abfc5e7c7de
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedBook.java
@@ -0,0 +1,57 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.book.Book;
+
+/**
+ * Jackson-friendly version of {@link Book}.
+ */
+public class JsonAdaptedBook {
+
+ private final String bookTitle;
+
+ /**
+ * Constructs a {@code JsonAdaptedBook} with the given book title.
+ *
+ * @param bookTitle The title of the book.
+ */
+ @JsonCreator
+ public JsonAdaptedBook(String bookTitle) {
+ this.bookTitle = bookTitle;
+ }
+
+ /**
+ * Converts a given {@code Book} into this class for Jackson use.
+ *
+ * @param source The source book.
+ */
+ public JsonAdaptedBook(Book source) {
+ bookTitle = source.bookTitle;
+ }
+
+ /**
+ * Returns the book title.
+ *
+ * @return The book title.
+ */
+ @JsonValue
+ public String getBookTitle() {
+ return this.bookTitle;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted book object into the model's {@code Book} object.
+ *
+ * @return The model's Book object.
+ * @throws IllegalValueException If there were any data constraints violated in the adapted book.
+ */
+ public Book toModelType() throws IllegalValueException {
+ if (!Book.isValidBookTitle(bookTitle)) {
+ throw new IllegalValueException(Book.MESSAGE_CONSTRAINTS);
+ }
+ return new Book(bookTitle);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..26b8053bdf4 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,8 +10,10 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.MeritScore;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -28,6 +30,8 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
+ private final String meritScore;
+ private final ArrayList bookList = new ArrayList<>();
private final List tags = new ArrayList<>();
/**
@@ -36,11 +40,17 @@ class JsonAdaptedPerson {
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
@JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("meritScore") String meritScore,
+ @JsonProperty("bookList") List bookList,
@JsonProperty("tags") List tags) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.meritScore = meritScore;
+ if (bookList != null) {
+ this.bookList.addAll(bookList);
+ }
if (tags != null) {
this.tags.addAll(tags);
}
@@ -54,21 +64,31 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ meritScore = source.getMeritScore().meritScore;
+ bookList.addAll(source.getBookList().stream()
+ .map(JsonAdaptedBook::new)
+ .collect(Collectors.toList()));
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
}
/**
- * Converts this Jackson-friendly adapted person object into the model's {@code Person} object.
+ * Converts this Jackson-friendly adapted person object into the model's
+ * {@code Person} object.
*
- * @throws IllegalValueException if there were any data constraints violated in the adapted person.
+ * @throws IllegalValueException if there were any data constraints violated in
+ * the adapted person.
*/
public Person toModelType() throws IllegalValueException {
final List personTags = new ArrayList<>();
for (JsonAdaptedTag tag : tags) {
personTags.add(tag.toModelType());
}
+ final List personBooks = new ArrayList<>();
+ for (JsonAdaptedBook book : bookList) {
+ personBooks.add(book.toModelType());
+ }
if (name == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
@@ -102,8 +122,18 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
+ if (meritScore == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ MeritScore.class.getSimpleName()));
+ }
+ if (!MeritScore.isValidMeritScore(meritScore)) {
+ throw new IllegalValueException(MeritScore.MESSAGE_CONSTRAINTS);
+ }
+ final MeritScore modelMeritScore = new MeritScore(meritScore);
+
+ final ArrayList modelBookList = new ArrayList<>(personBooks);
+
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelMeritScore, modelBookList, modelTags);
}
-
}
diff --git a/src/main/java/seedu/address/storage/LibraryStorage.java b/src/main/java/seedu/address/storage/LibraryStorage.java
new file mode 100644
index 00000000000..6c9f80d800d
--- /dev/null
+++ b/src/main/java/seedu/address/storage/LibraryStorage.java
@@ -0,0 +1,188 @@
+package seedu.address.storage;
+
+import static seedu.address.commons.util.StringUtil.isInteger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Paths;
+import java.util.Comparator;
+import java.util.logging.Logger;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.StringUtil;
+import seedu.address.model.ReadOnlyLibrary;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Threshold;
+
+/**
+ * The LibraryLogic Class manages the loading and saving of available books to a txt file.
+ */
+public class LibraryStorage {
+ private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class);
+
+ /**
+ * Comparator for comparing books alphabetically by title.
+ */
+ private static Comparator bookComparator = new Comparator() {
+ @Override
+ public int compare(Book book1, Book book2) {
+ return book1.bookTitle.compareTo(book2.bookTitle);
+ }
+ };
+
+ private String filePath;
+ private ObservableList availableBooks;
+ private Threshold threshold;
+
+ /**
+ * Constructs a LibraryLogic object with the given file path.
+ *
+ * @param filePath the file path where books are stored
+ */
+ public LibraryStorage(String filePath) {
+ this.filePath = filePath;
+ this.availableBooks = FXCollections.observableArrayList();
+ this.threshold = new Threshold();
+ }
+
+ /**
+ * Constructs a LibraryLogic object with default file path
+ */
+ public LibraryStorage() {
+ this.filePath = Paths.get("data", "library.txt").toString();
+ this.availableBooks = FXCollections.observableArrayList();
+ this.threshold = new Threshold();
+ }
+
+ /**
+ * Validates if the book title is present.
+ *
+ * @param bookTitle the book title of a book
+ * @return true if the book title is valid, false otherwise
+ */
+ public static boolean isValidBook(String bookTitle) {
+ if (bookTitle == null || bookTitle.trim().isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sorts the given book list alphabetically by title.
+ *
+ * @param bookList the list of books to be sorted
+ */
+ public void sortAlphabetically(ObservableList bookList) {
+ bookList.sort(bookComparator);
+ }
+
+ private void createFileIfNotExists() {
+ File file = new File(this.filePath);
+
+ if (!file.exists()) {
+ try {
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdir();
+ }
+ file.createNewFile();
+ logger.info("File " + filePath + " not found. Creating a new data file at " + filePath);
+ } catch (IOException e) {
+ logger.warning("Failed to create file: " + filePath);
+ throw new RuntimeException("Failed to create file: " + filePath, e);
+ }
+ }
+ }
+
+ /**
+ * Loads threshold books from the file and store it inside the availableBooks.
+ */
+ public void loadLibraryFromFile() throws DataLoadingException {
+ File file = new File(filePath);
+ createFileIfNotExists();
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String line;
+
+ // load first line as threshold
+ line = reader.readLine();
+
+ if (line == null) {
+ throw new IllegalValueException("Error loading library data from file: Missing data");
+ }
+
+ if (!isInteger(line.trim())) {
+ throw new IllegalValueException("Error loading threshold from file: Bad threshold input");
+ }
+ int i = Integer.parseInt(line);
+ threshold = new Threshold(i);
+
+ // load rest as books
+ while ((line = reader.readLine()) != null) {
+ if (!isValidBook(line.trim())) {
+ continue;
+ }
+ Book currentBook = new Book(line.trim());
+ availableBooks.add(currentBook);
+ }
+ sortAlphabetically(availableBooks);
+ } catch (IOException e) {
+ logger.warning("Failed to load library file : " + StringUtil.getDetails(e));
+ } catch (IllegalValueException ive) {
+ throw new DataLoadingException(ive);
+ }
+ }
+
+ /**
+ * Get the loaded available books.
+ *
+ * @return the list of books loaded from the file
+ */
+ public ObservableList getAvailableBooks() {
+ return availableBooks;
+ }
+
+ public Threshold getThreshold() {
+ return threshold;
+ }
+
+ public boolean hasNoAvailableBooks() {
+ return availableBooks.isEmpty();
+ }
+
+ /**
+ * Saves threshold and books to the file.
+ *
+ * @param library the list of books to be saved
+ * @throws IOException if an I/O error occurs while saving the books
+ */
+ public void saveBooksToFile(ReadOnlyLibrary library) throws IOException {
+ createFileIfNotExists();
+ ObservableList toBeSavedAvailableBooks = library.getBookList();
+ sortAlphabetically(toBeSavedAvailableBooks);
+ try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) {
+ writer.println(library.getThreshold());
+ for (Book availableBook : toBeSavedAvailableBooks) {
+ if (!isValidBook(availableBook.toString())) {
+ continue;
+ }
+ writer.println(availableBook);
+ logger.info("Book saving: " + availableBook.toString());
+ }
+ logger.info("Books saved to file: " + filePath);
+ } catch (IOException e) {
+ logger.warning("Error saving books to file : " + StringUtil.getDetails(e));
+ throw e; // Re-throw the exception to propagate it to the caller
+ }
+ }
+
+ public String getLibraryFilePath() {
+ return filePath;
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..ecf192dd5a0 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-f11-2.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/LibraryCard.java b/src/main/java/seedu/address/ui/LibraryCard.java
new file mode 100644
index 00000000000..63064893056
--- /dev/null
+++ b/src/main/java/seedu/address/ui/LibraryCard.java
@@ -0,0 +1,40 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.book.Book;
+
+/**
+ * An UI component that displays information of a {@code Library}.
+ */
+public class LibraryCard extends UiPart {
+
+ private static final String FXML = "LibraryListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Book book;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label bookName;
+
+ /**
+ * Creates a {@code BookCode} with the given {@code Book} and index to display.
+ */
+ public LibraryCard(Book book, int displayedIndex) {
+ super(FXML);
+ this.book = book;
+ String bookTitle = book.toString();
+ bookName.setText(displayedIndex + ". " + bookTitle);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/LibraryListPanel.java b/src/main/java/seedu/address/ui/LibraryListPanel.java
new file mode 100644
index 00000000000..2a4f5219b31
--- /dev/null
+++ b/src/main/java/seedu/address/ui/LibraryListPanel.java
@@ -0,0 +1,53 @@
+package seedu.address.ui;
+
+import java.util.Comparator;
+import java.util.logging.Logger;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.book.Book;
+
+/**
+ * Panel containing the list of persons.
+ */
+public class LibraryListPanel extends UiPart {
+ private static final String FXML = "LibraryListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(LibraryListPanel.class);
+
+ @FXML
+ private ListView libraryListView;
+
+ /**
+ * Creates a {@code LibraryListPanel} with the given {@code ObservableList}.
+ */
+ public LibraryListPanel(ObservableList libraryList) {
+ super(FXML);
+ ObservableList sortedLibraryList = FXCollections.observableArrayList(libraryList);
+ sortedLibraryList.sort(Comparator.comparing(Book::toString));
+ libraryListView.setItems(libraryList);
+ libraryListView.setCellFactory(listView -> new LibraryListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Library} using a {@code LibraryCard}.
+ */
+ class LibraryListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Book book, boolean empty) {
+ super.updateItem(book, empty);
+
+ if (empty || book == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new LibraryCard(book, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..1ec14befa8b 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -4,6 +4,7 @@
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
+import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCombination;
@@ -32,6 +33,7 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private LibraryListPanel libraryListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
@@ -46,10 +48,18 @@ public class MainWindow extends UiPart {
@FXML
private StackPane resultDisplayPlaceholder;
+ @FXML
+ private StackPane libraryListPanelPlaceholder;
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private Label userList;
+
+ @FXML
+ private Label library;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -66,6 +76,13 @@ public MainWindow(Stage primaryStage, Logic logic) {
setAccelerators();
helpWindow = new HelpWindow();
+
+ userList.setText("Contact List");
+
+ library.setText("Library");
+
+ userList.setStyle(" -fx-text-fill: greenyellow;");
+ library.setStyle(" -fx-text-fill: greenyellow;");
}
public Stage getPrimaryStage() {
@@ -116,6 +133,9 @@ void fillInnerParts() {
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
+ libraryListPanel = new LibraryListPanel(logic.getLibraryBookList());
+ libraryListPanelPlaceholder.getChildren().add(libraryListPanel.getRoot());
+
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..65ee9e81ff9 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -40,6 +40,11 @@ public class PersonCard extends UiPart {
private Label email;
@FXML
private FlowPane tags;
+ @FXML
+ private Label borrow;
+
+ @FXML
+ private Label meritScore;
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
@@ -52,8 +57,41 @@ public PersonCard(Person person, int displayedIndex) {
phone.setText(person.getPhone().value);
address.setText(person.getAddress().value);
email.setText(person.getEmail().value);
+ meritScore.setText(person.getMeritScore().meritScore);
+ meritScore.setStyle(getMeritScoreStyle(person.getMeritScore().meritScore));
+ String borrowedBookList = "";
+ if (person.getBookList().isEmpty()) {
+ borrowedBookList = "------------------------------------------------------------"
+ + "\n This user is not borrowing any books at the moment!"
+ + "\n------------------------------------------------------------";
+ } else {
+ borrowedBookList = "------------------------------------ \n"
+ + "The user is currently borrowing:\n" + person.getBookListToStringWithIndex()
+ + "\n------------------------------------";
+ }
+ borrow.setText(borrowedBookList);
person.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
.forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
}
+
+ /**
+ * Updates the background color of the meritScore label based on the score.
+ */
+ public String getMeritScoreStyle(String meritScore) {
+ String color;
+ int threshold;
+ int score = Integer.parseInt(meritScore);
+ if (score > 5) {
+ color = "green";
+ } else if (score <= -3) {
+ color = "red";
+ } else {
+ color = "yellowgreen";
+ }
+
+ // meritScore.setStyle("-fx-background-color: " + color + "; -fx-text-fill: white;");
+ //return " -fx-text-fill: " + color + ";";
+ return " -fx-text-fill: white;";
+ }
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..27b8d835c4b 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/mybookshelf_32.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/mybookshelf_32.png b/src/main/resources/images/mybookshelf_32.png
new file mode 100644
index 00000000000..44fcf03e6fd
Binary files /dev/null and b/src/main/resources/images/mybookshelf_32.png differ
diff --git a/src/main/resources/view/LibraryListCard.fxml b/src/main/resources/view/LibraryListCard.fxml
new file mode 100644
index 00000000000..b1bda1c5297
--- /dev/null
+++ b/src/main/resources/view/LibraryListCard.fxml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/LibraryListPanel.fxml b/src/main/resources/view/LibraryListPanel.fxml
new file mode 100644
index 00000000000..d47dda612bf
--- /dev/null
+++ b/src/main/resources/view/LibraryListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..4c63b503bc4 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -3,18 +3,20 @@
+
-
+
+
+ title="MyBookshelf" minWidth="800" minHeight="600" onCloseRequest="#handleExit">
-
+
@@ -24,7 +26,7 @@
-
+
@@ -40,18 +42,48 @@
+ maxHeight="100.0" minHeight="200.0" minWidth="230.0" prefHeight="200.0" prefWidth="230.0">
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f5e812e25e6..be910a6a836 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -25,12 +25,22 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..5a8315656c9 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -4,11 +4,15 @@
"phone": "94351253",
"email": "alice@example.com",
"address": "123, Jurong West Ave 6, #08-111",
+ "meritScore": "1",
+ "bookTitle" : "",
"tags": [ "friends" ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
"email": "pauline@example.com",
+ "meritScore": "1",
+ "bookTitle" : "",
"address": "4th street"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..365ff274f65 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,8 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
- "address": "4th street"
+ "address": "4th street",
+ "meritScore": "1",
+ "bookTitle" : ""
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..7efde24dad1 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -5,42 +5,80 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
+ "meritScore": "0",
+ "bookList" : [ ],
"tags" : [ "friends" ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
+ "meritScore": "1",
+ "bookList" : [ "Valid" ],
"tags" : [ "owesMoney", "friends" ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
+ "meritScore": "1",
+ "bookList" : [ ],
"tags" : [ ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
+ "meritScore": "1",
+ "bookList" : [ ],
"tags" : [ "friends" ]
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
"address" : "michegan ave",
+ "meritScore": "1",
+ "bookList" : [ ],
"tags" : [ ]
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
"address" : "little tokyo",
+ "meritScore": "1",
+ "bookList" : [ ],
"tags" : [ ]
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
"address" : "4th street",
+ "meritScore": "1",
+ "bookList" : [ ],
"tags" : [ ]
- } ]
+ }, {
+ "name" : "Jacker",
+ "phone" : "87173548",
+ "email" : "jacker@hotmail.com",
+ "address" : "420, Curry Tasty, #69-420",
+ "meritScore": "1",
+ "bookList" : [ ],
+ "tags" : [ "friends" ]
+ }, {
+ "name" : "Kepler",
+ "phone" : "89402749",
+ "email" : "kepler@hotmail.com",
+ "address" : "54, Stuttgart, #12-23",
+ "meritScore": "1",
+ "bookList" : [ "How To Become a Better Reader?" ],
+ "tags" : [ "teachers" ]
+ }, {
+ "name" : "Joe Meme",
+ "phone" : "89899898",
+ "email" : "joe@gmail.com",
+ "address" : "123A, King's Street, #13-5",
+ "meritScore": "-100",
+ "bookList" : [ ],
+ "tags" : [ "LowMerit" ]
+ }]
}
diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/address/commons/core/ConfigTest.java
index d3ba2a52a89..4529989e397 100644
--- a/src/test/java/seedu/address/commons/core/ConfigTest.java
+++ b/src/test/java/seedu/address/commons/core/ConfigTest.java
@@ -1,6 +1,7 @@
package seedu.address.commons.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -23,5 +24,12 @@ public void equalsMethod() {
assertTrue(defaultConfig.equals(defaultConfig));
}
+ @Test
+ public void equals_failure() {
+ Config defaultConfig = new Config();
+ // other instance
+ assertFalse(defaultConfig.equals(3));
+ }
+
}
diff --git a/src/test/java/seedu/address/commons/core/GuiSettingsTest.java b/src/test/java/seedu/address/commons/core/GuiSettingsTest.java
index b7876c4349d..66be775eac0 100644
--- a/src/test/java/seedu/address/commons/core/GuiSettingsTest.java
+++ b/src/test/java/seedu/address/commons/core/GuiSettingsTest.java
@@ -1,6 +1,9 @@
package seedu.address.commons.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
@@ -13,4 +16,18 @@ public void toStringMethod() {
+ guiSettings.getWindowCoordinates() + "}";
assertEquals(expected, guiSettings.toString());
}
+
+ @Test
+ public void equalsMethod() {
+ GuiSettings defaultGui = new GuiSettings();
+ assertNotNull(defaultGui);
+ assertTrue(defaultGui.equals(defaultGui));
+ }
+
+ @Test
+ public void equals_failure() {
+ GuiSettings defaultGui = new GuiSettings();
+ // other instance
+ assertFalse(defaultGui.equals(3));
+ }
}
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java
index c56d407bf3f..3798e7ac575 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java
@@ -38,13 +38,46 @@ public void isNonZeroUnsignedInteger() {
assertFalse(StringUtil.isNonZeroUnsignedInteger("1 0")); // Spaces in the middle
// EP: number larger than Integer.MAX_VALUE
- assertFalse(StringUtil.isNonZeroUnsignedInteger(Long.toString(Integer.MAX_VALUE + 1)));
+ assertFalse(StringUtil.isNonZeroUnsignedInteger("2147483648"));
// EP: valid numbers, should return true
assertTrue(StringUtil.isNonZeroUnsignedInteger("1")); // Boundary value
assertTrue(StringUtil.isNonZeroUnsignedInteger("10"));
}
+ //---------------- Tests for isNonZeroUnsignedInteger --------------------------------------
+
+ @Test
+ public void isInteger() {
+
+ // EP: empty strings
+ assertFalse(StringUtil.isInteger("")); // Boundary value
+ assertFalse(StringUtil.isInteger(" "));
+
+ // EP: not a number
+ assertFalse(StringUtil.isInteger("a"));
+ assertFalse(StringUtil.isInteger("aaa"));
+
+ // EP: zero as prefix
+ assertTrue(StringUtil.isInteger("01"));
+
+ // EP: plus signed number
+ assertFalse(StringUtil.isInteger("+1"));
+
+ // EP: numbers with white space
+ assertFalse(StringUtil.isInteger(" 10 ")); // Leading/trailing spaces
+ assertFalse(StringUtil.isInteger("1 0")); // Spaces in the middle
+
+ // EP: number larger than Integer.MAX_VALUE
+ assertFalse(StringUtil.isInteger("2147483648"));
+
+ // EP: valid numbers, should return true
+ assertTrue(StringUtil.isInteger("1"));
+ assertTrue(StringUtil.isInteger("0"));
+ assertTrue(StringUtil.isInteger("-1"));
+ assertTrue(StringUtil.isInteger("10"));
+ assertTrue(StringUtil.isInteger("-100"));
+ }
//---------------- Tests for containsWordIgnoreCase --------------------------------------
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..68904eb01f5 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -1,8 +1,8 @@
package seedu.address.logic;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
-import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
@@ -21,6 +21,7 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.ListCommand;
+//import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
@@ -123,7 +124,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getLibrary());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
diff --git a/src/test/java/seedu/address/logic/commands/AddBookCommandTest.java b/src/test/java/seedu/address/logic/commands/AddBookCommandTest.java
new file mode 100644
index 00000000000..a7976d05514
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddBookCommandTest.java
@@ -0,0 +1,83 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+
+public class AddBookCommandTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String BOOK_TITLE_1_STUB = "Book Stub 1";
+ private static final String BOOK_TITLE_2_STUB = "Book Stub 2";
+ private static final String EMPTY_BOOK_STUB = "";
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+
+ @Test
+ public void execute_addbookUnfilteredList_success() {
+ Book bookStub = new Book(BOOK_TITLE_STUB);
+
+ AddBookCommand addBookCommand = new AddBookCommand(bookStub);
+
+ Model initialModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
+ expectedModel.addBook(bookStub);
+
+ String expectedMessage = String.format(AddBookCommand.MESSAGE_ADD_BOOK_SUCCESS, bookStub);
+
+ assertCommandSuccess(addBookCommand, initialModel, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_emptyBookTitleFilteredList_throwsCommandException() {
+ Book emptyBookStub = new Book(EMPTY_BOOK_STUB);
+
+ AddBookCommand addBookCommand = new AddBookCommand(emptyBookStub);
+
+ assertCommandFailure(addBookCommand, model, Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ @Test
+ public void equals() {
+ Book bookStub1 = new Book(BOOK_TITLE_1_STUB);
+ Book bookStub2 = new Book(BOOK_TITLE_2_STUB);
+ Book emptyBookStub = new Book(EMPTY_BOOK_STUB);
+ AddBookCommand addBookCommand1 = new AddBookCommand(bookStub1);
+ AddBookCommand addBookCommand2 = new AddBookCommand(bookStub2);
+ AddBookCommand addBookCommand3 = new AddBookCommand(emptyBookStub);
+
+ // same object -> returns true
+ assertTrue(addBookCommand1.equals(addBookCommand1));
+
+ // same values -> returns true
+ AddBookCommand addBookCommand1Copy = new AddBookCommand(bookStub1);
+ assertTrue(addBookCommand1.equals(addBookCommand1Copy));
+
+ // different types -> returns false
+ assertFalse(addBookCommand1.equals(1));
+
+ // null -> returns false
+ assertFalse(addBookCommand1.equals(null));
+
+ // different book -> returns false
+ assertFalse(addBookCommand1.equals(addBookCommand2));
+
+ // empty book -> returns false
+ assertFalse(addBookCommand1.equals(addBookCommand3));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index 162a0c86031..600c295dd67 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -2,12 +2,13 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import seedu.address.logic.Messages;
+import seedu.address.commons.core.Messages;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
@@ -23,14 +24,15 @@ public class AddCommandIntegrationTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
}
@Test
public void execute_newPerson_success() {
Person validPerson = new PersonBuilder().build();
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(),
+ model.getLibrary());
expectedModel.addPerson(validPerson);
assertCommandSuccess(new AddCommand(validPerson), model,
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..14778eb5be8 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -16,12 +16,15 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.logic.Messages;
+import seedu.address.commons.core.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyLibrary;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Threshold;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -157,6 +160,66 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void setLibrary(ReadOnlyLibrary library) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyLibrary getLibrary() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredLibraryList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getLibraryBookList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addBook(Book book) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteBook(int index) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean canLendTo(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setThreshold(Threshold threshold) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Threshold getThreshold() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasThreshold(Threshold threshold) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasBookInLibrary(Book book) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Book popBookFromLibrary(Book book) {
+ throw new AssertionError("This method should not be called.");
+ }
}
/**
diff --git a/src/test/java/seedu/address/logic/commands/BorrowCommandTest.java b/src/test/java/seedu/address/logic/commands/BorrowCommandTest.java
new file mode 100644
index 00000000000..3bb49fd6fa5
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/BorrowCommandTest.java
@@ -0,0 +1,130 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalBooks.BOOK_IN_LIBRARY;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
+import static seedu.address.testutil.TypicalIndexes.INDEX_BAD_MERIT_NOT_BORROWING_JOE;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class BorrowCommandTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String EMPTY_BOOK_STUB = "";
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+
+ @Test
+ public void execute_addBorrowUnfilteredList_success() {
+ Book bookStub = BOOK_IN_LIBRARY;
+ Person beforeBorrowPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ int afterBorrowMeritScore = beforeBorrowPerson.getMeritScore().getMeritScoreInt() - 1;
+ Person afterBorrowPerson = new PersonBuilder(beforeBorrowPerson).withBooks(BOOK_IN_LIBRARY.bookTitle)
+ .withMeritScore(afterBorrowMeritScore).build();
+
+ BorrowCommand borrowCommand = new BorrowCommand(INDEX_FIRST_PERSON, bookStub);
+
+ Model initialModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ model.getLibrary());
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ model.getLibrary());
+ expectedModel.setPerson(beforeBorrowPerson, afterBorrowPerson);
+ expectedModel.popBookFromLibrary(bookStub);
+
+ String expectedMessage = String.format(BorrowCommand.MESSAGE_ADD_BORROW_SUCCESS, bookStub,
+ afterBorrowPerson);
+
+ assertCommandSuccess(borrowCommand, initialModel, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_addBorrowUnfilteredList_unsuccess() {
+ Book bookObjectStub = new Book(BOOK_TITLE_STUB);
+
+ BorrowCommand borrowCommand = new BorrowCommand(INDEX_BAD_MERIT_NOT_BORROWING_JOE, bookObjectStub);
+
+ String expectedMessage = String.format(Messages.MESSAGE_INSUFFICIENT_MERIT_SCORE,
+ getTypicalLibrary().getThreshold());
+
+ assertThrows(CommandException.class, expectedMessage, () -> borrowCommand.execute(model));
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ BorrowCommand borrowCommand = new BorrowCommand(outOfBoundIndex, new Book(BOOK_TITLE_STUB));
+
+ assertCommandFailure(borrowCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_emptyBookTitleFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ BorrowCommand borrowCommand = new BorrowCommand(INDEX_FIRST_PERSON, new Book(EMPTY_BOOK_STUB));
+
+ assertCommandFailure(borrowCommand, model, Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ @Test
+ public void execute_missingBookFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ BorrowCommand borrowCommand = new BorrowCommand(INDEX_FIRST_PERSON, new Book(BOOK_TITLE_STUB));
+
+ String expectedMessage = String.format(Messages.MESSAGE_BOOK_NOT_IN_LIBRARY, BOOK_TITLE_STUB);
+
+ assertCommandFailure(borrowCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void equals() {
+ BorrowCommand borrowCommand1 = new BorrowCommand(INDEX_FIRST_PERSON, new Book(EMPTY_BOOK_STUB));
+ BorrowCommand borrowCommand2 = new BorrowCommand(INDEX_SECOND_PERSON, new Book(EMPTY_BOOK_STUB));
+ BorrowCommand borrowCommand3 = new BorrowCommand(INDEX_SECOND_PERSON, new Book(BOOK_TITLE_STUB));
+
+ // same object -> returns true
+ assertTrue(borrowCommand1.equals(borrowCommand1));
+
+ // same values -> returns true
+ BorrowCommand returnFirstCommandCopy = new BorrowCommand(INDEX_FIRST_PERSON, new Book(EMPTY_BOOK_STUB));
+ assertTrue(borrowCommand1.equals(returnFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(borrowCommand1.equals(1));
+
+ // null -> returns false
+ assertFalse(borrowCommand1.equals(null));
+
+ // different person -> returns false
+ assertFalse(borrowCommand1.equals(borrowCommand2));
+
+ // different book -> returns false
+ assertFalse(borrowCommand1.equals(borrowCommand3));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..bc994dd37a4 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -1,6 +1,7 @@
package seedu.address.logic.commands;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
@@ -22,8 +23,8 @@ public void execute_emptyAddressBook_success() {
@Test
public void execute_nonEmptyAddressBook_success() {
- Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
expectedModel.setAddressBook(new AddressBook());
assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/DeleteBookCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteBookCommandTest.java
new file mode 100644
index 00000000000..84deef17a63
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteBookCommandTest.java
@@ -0,0 +1,93 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalBooks.BOOK_IN_LIBRARY;
+import static seedu.address.testutil.TypicalBooks.GUMIHO;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+
+public class DeleteBookCommandTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String EMPTY_BOOK_STUB = "";
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+
+ @Test
+ public void execute_deleteBookUnfilteredList_success() {
+ Book bookStubObject = BOOK_IN_LIBRARY;
+
+ DeleteBookCommand deleteBookCommand = new DeleteBookCommand(BOOK_IN_LIBRARY);
+
+ Model initialModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ model.getLibrary());
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ model.getLibrary());
+ expectedModel.popBookFromLibrary(bookStubObject);
+
+ String expectedMessage = String.format(DeleteBookCommand.MESSAGE_DELETE_BOOK_SUCCESS, bookStubObject);
+
+ assertCommandSuccess(deleteBookCommand, initialModel, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_emptyBookTitleFilteredList_throwsCommandException() {
+ Book emptyBookStub = new Book(EMPTY_BOOK_STUB);
+
+ DeleteBookCommand deleteBookCommand = new DeleteBookCommand(emptyBookStub);
+
+ assertCommandFailure(deleteBookCommand, model, Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ @Test
+ public void execute_missingBookFilteredList_throwsCommandException() {
+ Book bookStub = new Book(BOOK_TITLE_STUB);
+
+ DeleteBookCommand deleteBookCommand = new DeleteBookCommand(bookStub);
+
+ String expectedMessage = String.format(Messages.MESSAGE_BOOK_NOT_IN_LIBRARY, BOOK_TITLE_STUB);
+
+ assertCommandFailure(deleteBookCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void equals() {
+ Book bookStub1 = BOOK_IN_LIBRARY;
+ Book bookStub2 = GUMIHO;
+ Book emptyBookStub = new Book(EMPTY_BOOK_STUB);
+ DeleteBookCommand deleteBookCommand1 = new DeleteBookCommand(bookStub1);
+ DeleteBookCommand deleteBookCommand2 = new DeleteBookCommand(bookStub2);
+ DeleteBookCommand deleteBookCommand3 = new DeleteBookCommand(emptyBookStub);
+
+ // same object -> returns true
+ assertTrue(deleteBookCommand1.equals(deleteBookCommand1));
+
+ // same values -> returns true
+ DeleteBookCommand deleteBookCommand1Copy = new DeleteBookCommand(bookStub1);
+ assertTrue(deleteBookCommand1.equals(deleteBookCommand1Copy));
+
+ // different types -> returns false
+ assertFalse(deleteBookCommand1.equals(1));
+
+ // null -> returns false
+ assertFalse(deleteBookCommand1.equals(null));
+
+ // different book -> returns false
+ assertFalse(deleteBookCommand1.equals(deleteBookCommand2));
+
+ // empty book -> returns false
+ assertFalse(deleteBookCommand1.equals(deleteBookCommand3));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..aabd793febe 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -6,14 +6,15 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
@@ -25,7 +26,7 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
@Test
public void execute_validIndexUnfilteredList_success() {
@@ -35,7 +36,8 @@ public void execute_validIndexUnfilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
Messages.format(personToDelete));
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(),
+ model.getLibrary());
expectedModel.deletePerson(personToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -59,7 +61,8 @@ public void execute_validIndexFilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
Messages.format(personToDelete));
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(),
+ model.getLibrary());
expectedModel.deletePerson(personToDelete);
showNoPerson(expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/DonateCommandTest.java b/src/test/java/seedu/address/logic/commands/DonateCommandTest.java
new file mode 100644
index 00000000000..9e65a1a1a90
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DonateCommandTest.java
@@ -0,0 +1,102 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
+import static seedu.address.testutil.TypicalIndexes.INDEX_KEPLER;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON;
+import static seedu.address.testutil.TypicalPersons.KEPLER;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class DonateCommandTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String EMPTY_BOOK_STUB = "";
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+
+ @Test
+ public void execute_donateUnfilteredList_success() {
+ Book bookObjectStub = new Book(BOOK_TITLE_STUB);
+ Person modelPerson = model.getFilteredPersonList().get(INDEX_KEPLER.getZeroBased());
+ int afterDonateMeritScore = modelPerson.getMeritScore().getMeritScoreInt() + 1;
+ Person afterDonatePerson = new PersonBuilder(KEPLER).withMeritScore(afterDonateMeritScore).build();
+
+ DonateCommand donateCommand = new DonateCommand(INDEX_KEPLER, new Book(BOOK_TITLE_STUB));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary().getBookList()));
+ expectedModel.setPerson(KEPLER, afterDonatePerson);
+ expectedModel.addBook(new Book(BOOK_TITLE_STUB));
+
+ String expectedMessage = String.format(DonateCommand.MESSAGE_DONATE_SUCCESS, afterDonatePerson, bookObjectStub);
+
+ assertCommandSuccess(donateCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_SECOND_PERSON);
+
+ Index outOfBoundIndex = INDEX_THIRD_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ DonateCommand donateCommand = new DonateCommand(outOfBoundIndex, new Book(BOOK_TITLE_STUB));
+
+ assertCommandFailure(donateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+
+ @Test
+ public void empty_bookTitle_failure() {
+ DonateCommand donateCommand = new DonateCommand(INDEX_KEPLER, new Book(EMPTY_BOOK_STUB));
+
+ String expectedMessage = Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD;
+
+ assertCommandFailure(donateCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void equals() {
+ DonateCommand donateCommand1 = new DonateCommand(INDEX_SECOND_PERSON, new Book(EMPTY_BOOK_STUB));
+ DonateCommand donateCommand2 = new DonateCommand(INDEX_THIRD_PERSON, new Book(EMPTY_BOOK_STUB));
+ DonateCommand donateCommand3 = new DonateCommand(INDEX_SECOND_PERSON, new Book(BOOK_TITLE_STUB));
+
+ // same object -> returns true
+ assertTrue(donateCommand1.equals(donateCommand1));
+
+ // same values -> returns true
+ DonateCommand firstDonateCommandCopy = new DonateCommand(INDEX_SECOND_PERSON, new Book(EMPTY_BOOK_STUB));
+ assertTrue(donateCommand1.equals(firstDonateCommandCopy));
+
+ // different types -> returns false
+ assertFalse(donateCommand1.equals(true));
+
+ // null -> returns false
+ assertFalse(donateCommand1.equals(null));
+
+ // different person -> returns false
+ assertFalse(donateCommand1.equals(donateCommand2));
+
+ // different book -> returns false
+ assertFalse(donateCommand1.equals(donateCommand3));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..06943d5963d 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -11,19 +11,21 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.Messages;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.library.Library;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
@@ -33,7 +35,7 @@
*/
public class EditCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
@Test
public void execute_allFieldsSpecifiedUnfilteredList_success() {
@@ -43,7 +45,8 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
@@ -64,7 +67,8 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
expectedModel.setPerson(lastPerson, editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
@@ -77,7 +81,8 @@ public void execute_noFieldSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -93,7 +98,8 @@ public void execute_filteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..41ed2102399 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -64,6 +64,8 @@ public void toStringMethod() {
+ editPersonDescriptor.getName().orElse(null) + ", phone="
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
+ editPersonDescriptor.getEmail().orElse(null) + ", address="
+ + editPersonDescriptor.getMeritScore().orElse(null) + ", meritScore="
+ + editPersonDescriptor.getBookList().orElse(null) + ", bookTitle="
+ editPersonDescriptor.getAddress().orElse(null) + ", tags="
+ editPersonDescriptor.getTags().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..4d336b6cd9f 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -3,8 +3,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
+import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.ELLE;
import static seedu.address.testutil.TypicalPersons.FIONA;
@@ -24,8 +25,8 @@
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
*/
public class FindCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
@Test
public void equals() {
diff --git a/src/test/java/seedu/address/logic/commands/LimitCommandTest.java b/src/test/java/seedu/address/logic/commands/LimitCommandTest.java
new file mode 100644
index 00000000000..fd8f299cca8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/LimitCommandTest.java
@@ -0,0 +1,98 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_DEFAULT;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_MINUS_FOUR;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_MINUS_TWO;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ONE;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ZERO;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.library.Threshold;
+
+class LimitCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+
+ @Test
+ public void execute_hasNoArgument_success() {
+ boolean hasNoArgument = true;
+ LimitCommand limitCommand = new LimitCommand(hasNoArgument);
+
+ String expectedMessage = String.format(LimitCommand.MESSAGE_HAS_NO_ARGUMENT, model.getThreshold());
+
+ assertCommandSuccess(limitCommand, model, expectedMessage, model);
+ }
+
+ @Test
+ public void execute_hasArgumentValidDecreaseThreshold_success() {
+ // Check if original threshold is default of -3 to see if it changed
+ assertEquals(model.getThreshold(), THRESHOLD_DEFAULT);
+
+ LimitCommand limitCommand = new LimitCommand(THRESHOLD_MINUS_FOUR);
+
+ String expectedMessage = String.format(LimitCommand.MESSAGE_LIMIT_THRESHOLD_SUCCESS, THRESHOLD_MINUS_FOUR);
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getLibrary());
+ expectedModel.setThreshold(THRESHOLD_MINUS_FOUR);
+
+ assertCommandSuccess(limitCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_hasArgumentValidIncreaseThreshold_success() {
+ // Check if original threshold is default of -3 to see if it changed
+ assertEquals(model.getThreshold(), THRESHOLD_DEFAULT);
+
+ LimitCommand limitCommand = new LimitCommand(THRESHOLD_MINUS_TWO);
+
+ String expectedMessage = String.format(LimitCommand.MESSAGE_LIMIT_THRESHOLD_SUCCESS, THRESHOLD_MINUS_TWO);
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getLibrary());
+ expectedModel.setThreshold(THRESHOLD_MINUS_TWO);
+
+ assertCommandSuccess(limitCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidSameThreshold_throwsCommandException() {
+ Threshold currentModelThreshold = model.getThreshold();
+
+ LimitCommand limitCommand = new LimitCommand(currentModelThreshold);
+
+ assertCommandFailure(limitCommand, model, LimitCommand.MESSAGE_DUPLICATE_LIMIT);
+ }
+
+ // TODO: add test for invalid threshold (integer overflow) when message and checks are made in v1.5
+
+ @Test
+ public void equals() {
+ LimitCommand limitZeroCommand = new LimitCommand(THRESHOLD_ZERO);
+ LimitCommand limitOneCommand = new LimitCommand(THRESHOLD_ONE);
+
+ // same object -> returns true
+ assertTrue(limitZeroCommand.equals(limitZeroCommand));
+
+ // same values -> returns true
+ LimitCommand limitZeroCommandCopy = new LimitCommand(THRESHOLD_ZERO);
+ assertTrue(limitZeroCommand.equals(limitZeroCommandCopy));
+
+ // different types -> returns false
+ assertFalse(limitZeroCommand.equals(0));
+
+ // null -> returns false
+ assertFalse(limitZeroCommand.equals(null));
+
+ // different thresholds -> returns false
+ assertFalse(limitZeroCommand.equals(limitOneCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
index 435ff1f7275..da65f4e5da3 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
@@ -2,6 +2,7 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
@@ -22,8 +23,8 @@ public class ListCommandTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getLibrary());
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/ReturnCommandTest.java b/src/test/java/seedu/address/logic/commands/ReturnCommandTest.java
new file mode 100644
index 00000000000..0f9846c0764
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ReturnCommandTest.java
@@ -0,0 +1,122 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalBooks.getTypicalLibrary;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_JACKER;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.JACKER;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class ReturnCommandTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String EMPTY_BOOK_STUB = "";
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), getTypicalLibrary());
+
+ @Test
+ public void execute_returnUnfilteredList_success() {
+ Book bookObjectStub = new Book(BOOK_TITLE_STUB);
+ // Since we only have withBooks() function, we set before return instead of after return
+ Person modelPerson = model.getFilteredPersonList().get(INDEX_JACKER.getZeroBased());
+ int beforeReturnMeritScore = modelPerson.getMeritScore().getMeritScoreInt() - 1;
+ Person beforeReturnPerson = new PersonBuilder(JACKER).withBooks(BOOK_TITLE_STUB)
+ .withMeritScore(beforeReturnMeritScore).build();
+
+ ReturnCommand returnCommand = new ReturnCommand(INDEX_JACKER, bookObjectStub);
+
+ Model initialModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
+ initialModel.setPerson(JACKER, beforeReturnPerson);
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new Library(model.getLibrary()));
+ expectedModel.addBook(bookObjectStub);
+
+ String expectedMessage = String.format(ReturnCommand.MESSAGE_RETURN_BOOK_SUCCESS, bookObjectStub, JACKER);
+
+ assertCommandSuccess(returnCommand, initialModel, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ ReturnCommand returnCommand = new ReturnCommand(outOfBoundIndex, new Book(BOOK_TITLE_STUB));
+
+ assertCommandFailure(returnCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_emptyBookTitleFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ ReturnCommand returnCommand = new ReturnCommand(INDEX_FIRST_PERSON, new Book(EMPTY_BOOK_STUB));
+
+ assertCommandFailure(returnCommand, model, Messages.MESSAGE_EMPTY_BOOK_INPUT_FIELD);
+ }
+
+ @Test
+ public void execute_invalidBookListFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ ReturnCommand returnCommand = new ReturnCommand(INDEX_FIRST_PERSON, new Book(BOOK_TITLE_STUB));
+
+ assertCommandFailure(returnCommand, model, Messages.MESSAGE_EMPTY_BOOKLIST_FIELD);
+ }
+
+ @Test
+ public void execute_invalidBookFilteredList_throwsCommandException() {
+ ReturnCommand returnCommand = new ReturnCommand(INDEX_SECOND_PERSON, new Book("Unknown Book"));
+
+ assertCommandFailure(returnCommand, model, Messages.MESSAGE_BOOK_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void equals() {
+ ReturnCommand returnCommand1 = new ReturnCommand(INDEX_FIRST_PERSON, new Book(EMPTY_BOOK_STUB));
+ ReturnCommand returnCommand2 = new ReturnCommand(INDEX_SECOND_PERSON, new Book(EMPTY_BOOK_STUB));
+ ReturnCommand returnCommand3 = new ReturnCommand(INDEX_SECOND_PERSON, new Book(BOOK_TITLE_STUB));
+
+ // same object -> returns true
+ assertTrue(returnCommand1.equals(returnCommand1));
+
+ // same values -> returns true
+ ReturnCommand returnFirstCommandCopy = new ReturnCommand(INDEX_FIRST_PERSON, new Book(EMPTY_BOOK_STUB));
+ assertTrue(returnCommand1.equals(returnFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(returnCommand1.equals(1));
+
+ // null -> returns false
+ assertFalse(returnCommand1.equals(null));
+
+ // different person -> returns false
+ assertFalse(returnCommand1.equals(returnCommand2));
+
+ // different book -> returns false
+ assertFalse(returnCommand1.equals(returnCommand3));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddBookCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddBookCommandParserTest.java
new file mode 100644
index 00000000000..4fda697e931
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddBookCommandParserTest.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.AddBookCommand;
+import seedu.address.model.book.Book;
+
+
+public class AddBookCommandParserTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddBookCommand.MESSAGE_USAGE);
+ private AddBookCommandParser parser = new AddBookCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, CliSyntax.PREFIX_BOOKLIST + BOOK_TITLE_STUB, MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+
+ // only consists of white spaces
+ assertParseFailure(parser, " ", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ // user input is leading with one white space because of multiargu mapper issue
+ // without it will result in illegalArgumentException (i spent 30 mins on this :/)
+ // book title with no space in front and at back
+ assertParseSuccess(parser, " " + CliSyntax.PREFIX_BOOKLIST + BOOK_TITLE_STUB,
+ new AddBookCommand(new Book(BOOK_TITLE_STUB)));
+
+ // book title with spaces in front and at back
+ assertParseSuccess(parser, " " + CliSyntax.PREFIX_BOOKLIST + " " + BOOK_TITLE_STUB + " ",
+ new AddBookCommand(new Book(BOOK_TITLE_STUB)));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..747c50e6784 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
@@ -35,7 +35,7 @@
import org.junit.jupiter.api.Test;
-import seedu.address.logic.Messages;
+import seedu.address.commons.core.Messages;
import seedu.address.logic.commands.AddCommand;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..1cfb60515b1 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -2,10 +2,11 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_KEPLER;
import java.util.Arrays;
import java.util.List;
@@ -13,16 +14,24 @@
import org.junit.jupiter.api.Test;
+import seedu.address.logic.commands.AddBookCommand;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.BorrowCommand;
import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.commands.DeleteBookCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DonateCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.LimitCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ReturnCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Threshold;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -88,6 +97,59 @@ public void parseCommand_list() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
}
+ @Test
+ public void parseCommand_donate() throws Exception {
+ String bookTitle = "Some book";
+ DonateCommand command = (DonateCommand) parser.parseCommand(
+ DonateCommand.COMMAND_WORD + " " + INDEX_KEPLER.getOneBased() + " "
+ + CliSyntax.PREFIX_BOOKLIST + bookTitle);
+ assertEquals(new DonateCommand(INDEX_KEPLER, new Book(bookTitle)), command);
+ }
+
+ @Test
+ public void parseCommand_borrow() throws Exception {
+ String bookTitle = "Some book";
+ BorrowCommand command = (BorrowCommand) parser.parseCommand(
+ BorrowCommand.COMMAND_WORD + " " + INDEX_KEPLER.getOneBased() + " "
+ + CliSyntax.PREFIX_BOOKLIST + bookTitle);
+ assertEquals(new BorrowCommand(INDEX_KEPLER, new Book(bookTitle)), command);
+ }
+
+ @Test
+ public void parseCommand_return() throws Exception {
+ String bookTitle = "Some book";
+ ReturnCommand command = (ReturnCommand) parser.parseCommand(
+ ReturnCommand.COMMAND_WORD + " " + INDEX_KEPLER.getOneBased() + " "
+ + CliSyntax.PREFIX_BOOKLIST + bookTitle);
+ assertEquals(new ReturnCommand(INDEX_KEPLER, new Book(bookTitle)), command);
+ }
+
+ @Test
+ public void parseCommand_limit() throws Exception {
+ int threshold = -10;
+ LimitCommand command = (LimitCommand) parser.parseCommand(
+ LimitCommand.COMMAND_WORD + " " + threshold);
+ assertEquals(new LimitCommand(new Threshold(threshold)), command);
+ }
+
+ @Test
+ public void parseCommand_addBook() throws Exception {
+ String bookTitle = "Some book";
+ AddBookCommand command = (AddBookCommand) parser.parseCommand(
+ AddBookCommand.COMMAND_WORD + " "
+ + CliSyntax.PREFIX_BOOKLIST + bookTitle);
+ assertEquals(new AddBookCommand(new Book(bookTitle)), command);
+ }
+
+ @Test
+ public void parseCommand_deleteBook() throws Exception {
+ String bookTitle = "Some book";
+ DeleteBookCommand command = (DeleteBookCommand) parser.parseCommand(
+ DeleteBookCommand.COMMAND_WORD + " "
+ + CliSyntax.PREFIX_BOOKLIST + bookTitle);
+ assertEquals(new DeleteBookCommand(new Book(bookTitle)), command);
+ }
+
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
diff --git a/src/test/java/seedu/address/logic/parser/BorrowCommandParserTest.java b/src/test/java/seedu/address/logic/parser/BorrowCommandParserTest.java
new file mode 100644
index 00000000000..7a021462771
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/BorrowCommandParserTest.java
@@ -0,0 +1,59 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.BorrowCommand;
+import seedu.address.model.book.Book;
+import seedu.address.testutil.TypicalIndexes;
+
+public class BorrowCommandParserTest {
+ private static final String bookTitle = "Some title";
+ private static final String MESSAGE_INVALID_FORMAT = String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ BorrowCommand.MESSAGE_USAGE);
+ private BorrowCommandParser parser = new BorrowCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, CliSyntax.PREFIX_BOOKLIST + bookTitle, MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+
+ // only consists of spaces
+ assertParseFailure(parser, " ", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5", MESSAGE_INVALID_FORMAT);
+
+ // zero index
+ assertParseFailure(parser, "0", MESSAGE_INVALID_FORMAT);
+
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ // book title with no space in front and at back
+ assertParseSuccess(parser, "9 " + CliSyntax.PREFIX_BOOKLIST + bookTitle,
+ new BorrowCommand(TypicalIndexes.INDEX_KEPLER, new Book(bookTitle)));
+
+ // book title with spaces in front and at back
+ assertParseSuccess(parser, "9 " + CliSyntax.PREFIX_BOOKLIST + " " + bookTitle + " ",
+ new BorrowCommand(TypicalIndexes.INDEX_KEPLER, new Book(bookTitle)));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteBookCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteBookCommandParserTest.java
new file mode 100644
index 00000000000..f254d6ef69b
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DeleteBookCommandParserTest.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+//import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.DeleteBookCommand;
+//import seedu.address.model.book.Book;
+import seedu.address.model.book.Book;
+
+
+public class DeleteBookCommandParserTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteBookCommand.MESSAGE_USAGE);
+ private DeleteBookCommandParser parser = new DeleteBookCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, CliSyntax.PREFIX_BOOKLIST + BOOK_TITLE_STUB, MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+
+ // only consists of white spaces
+ assertParseFailure(parser, " ", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ // book title with no space in front and at back
+ assertParseSuccess(parser, " " + CliSyntax.PREFIX_BOOKLIST + BOOK_TITLE_STUB,
+ new DeleteBookCommand(new Book(BOOK_TITLE_STUB)));
+
+ // book title with spaces in front and at back
+ assertParseSuccess(parser, " " + CliSyntax.PREFIX_BOOKLIST + " " + BOOK_TITLE_STUB + " ",
+ new DeleteBookCommand(new Book(BOOK_TITLE_STUB)));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
index 6a40e14a649..27eaec84450 100644
--- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
diff --git a/src/test/java/seedu/address/logic/parser/DonateCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DonateCommandParserTest.java
new file mode 100644
index 00000000000..73353da7730
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DonateCommandParserTest.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.DonateCommand;
+import seedu.address.model.book.Book;
+import seedu.address.testutil.TypicalIndexes;
+
+
+public class DonateCommandParserTest {
+ private static final String BOOK_TITLE_STUB = "Book Stub";
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DonateCommand.MESSAGE_USAGE);
+ private DonateCommandParser parser = new DonateCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, CliSyntax.PREFIX_BOOKLIST + BOOK_TITLE_STUB, MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+
+ // only consists of white spaces
+ assertParseFailure(parser, " ", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5", MESSAGE_INVALID_FORMAT);
+
+ // zero index
+ assertParseFailure(parser, "0", MESSAGE_INVALID_FORMAT);
+
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ // book title with no space in front and at back
+ assertParseSuccess(parser, "9 " + CliSyntax.PREFIX_BOOKLIST + BOOK_TITLE_STUB,
+ new DonateCommand(TypicalIndexes.INDEX_KEPLER, new Book(BOOK_TITLE_STUB)));
+
+ // book title with spaces in front and at back
+ assertParseSuccess(parser, "9 " + CliSyntax.PREFIX_BOOKLIST + " " + BOOK_TITLE_STUB + " ",
+ new DonateCommand(TypicalIndexes.INDEX_KEPLER, new Book(BOOK_TITLE_STUB)));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..1ea3ef651c1 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
@@ -34,8 +34,8 @@
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.Messages;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.person.Address;
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
index d92e64d12f9..70f4f0e79c4 100644
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
diff --git a/src/test/java/seedu/address/logic/parser/LimitCommandParserTest.java b/src/test/java/seedu/address/logic/parser/LimitCommandParserTest.java
new file mode 100644
index 00000000000..2af26ff7981
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/LimitCommandParserTest.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ONE;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.LimitCommand;
+
+class LimitCommandParserTest {
+
+ private LimitCommandParser parser = new LimitCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsLimitCommand() {
+ // has argument
+ assertParseSuccess(parser, "1", new LimitCommand(THRESHOLD_ONE));
+
+ // has no argument
+ assertParseSuccess(parser, "", new LimitCommand(true));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, LimitCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..ba4e3c1bf3a 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -5,6 +5,9 @@
import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_MINUS_TWO;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ONE;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ZERO;
import java.util.Arrays;
import java.util.Collections;
@@ -56,6 +59,28 @@ public void parseIndex_validInput_success() throws Exception {
assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 "));
}
+ @Test
+ public void parseThreshold_invalidInput_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseThreshold("10 a"));
+ }
+
+ // TODO: Change the outcome of this test to give our a specific error when more than Integer.MAX_VALUE in v1.5
+ @Test
+ public void parseThreshold_outOfRangeInput_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseThreshold("2147483648"));
+ }
+
+ @Test
+ public void parseThreshold_validInput_success() throws Exception {
+ // No whitespaces
+ assertEquals(THRESHOLD_ONE, ParserUtil.parseThreshold("1"));
+ assertEquals(THRESHOLD_MINUS_TWO, ParserUtil.parseThreshold("-2"));
+
+
+ // Leading and trailing whitespaces
+ assertEquals(THRESHOLD_ZERO, ParserUtil.parseThreshold(" 0 "));
+ }
+
@Test
public void parseName_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> ParserUtil.parseName((String) null));
diff --git a/src/test/java/seedu/address/logic/parser/ReturnCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ReturnCommandParserTest.java
new file mode 100644
index 00000000000..1973640ba2d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ReturnCommandParserTest.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ReturnCommand;
+import seedu.address.model.book.Book;
+import seedu.address.testutil.TypicalIndexes;
+
+
+public class ReturnCommandParserTest {
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReturnCommand.MESSAGE_USAGE);
+ private ReturnCommandParser parser = new ReturnCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+
+ // only consists of white spaces
+ assertParseFailure(parser, " ", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5", MESSAGE_INVALID_FORMAT);
+
+ // zero index
+ assertParseFailure(parser, "0", MESSAGE_INVALID_FORMAT);
+
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ // space after index
+ assertParseSuccess(parser, "9 b/Some BookTitle",
+ new ReturnCommand(TypicalIndexes.INDEX_KEPLER, new Book("Some BookTitle")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..bd31e5451e5 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -89,6 +89,15 @@ public void toStringMethod() {
assertEquals(expected, addressBook.toString());
}
+ @Test
+ public void equalsMethod() {
+ // same object -> returns true
+ assertTrue(addressBook.equals(addressBook));
+
+ // other instance -> returns false
+ assertFalse(addressBook.equals("3"));
+ }
+
/**
* A stub ReadOnlyAddressBook whose persons list can violate interface constraints.
*/
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..a2183ce39de 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -15,6 +15,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.library.Library;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.testutil.AddressBookBuilder;
@@ -98,10 +99,11 @@ public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
AddressBook differentAddressBook = new AddressBook();
UserPrefs userPrefs = new UserPrefs();
+ Library library = new Library();
// same values -> returns true
- modelManager = new ModelManager(addressBook, userPrefs);
- ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs);
+ modelManager = new ModelManager(addressBook, userPrefs, library);
+ ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs, library);
assertTrue(modelManager.equals(modelManagerCopy));
// same object -> returns true
@@ -114,12 +116,12 @@ public void equals() {
assertFalse(modelManager.equals(5));
// different addressBook -> returns false
- assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs, library)));
// different filteredList -> returns false
String[] keywords = ALICE.getName().fullName.split("\\s+");
modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords)));
- assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs, library)));
// resets modelManager to initial state for upcoming tests
modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
@@ -127,6 +129,6 @@ public void equals() {
// different userPrefs -> returns false
UserPrefs differentUserPrefs = new UserPrefs();
differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
- assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs, library)));
}
}
diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/address/model/UserPrefsTest.java
index b1307a70d52..f03e70ab9d4 100644
--- a/src/test/java/seedu/address/model/UserPrefsTest.java
+++ b/src/test/java/seedu/address/model/UserPrefsTest.java
@@ -1,5 +1,7 @@
package seedu.address.model;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
@@ -18,4 +20,14 @@ public void setAddressBookFilePath_nullPath_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null));
}
+ @Test
+ public void equalsMethod() {
+ // same object -> returns true
+ UserPrefs userPref = new UserPrefs();
+ assertTrue(userPref.equals(userPref));
+
+ // other instance -> returns false
+ assertFalse(userPref.equals("3"));
+ }
+
}
diff --git a/src/test/java/seedu/address/model/book/BookTest.java b/src/test/java/seedu/address/model/book/BookTest.java
new file mode 100644
index 00000000000..c90aba581ec
--- /dev/null
+++ b/src/test/java/seedu/address/model/book/BookTest.java
@@ -0,0 +1,63 @@
+package seedu.address.model.book;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class BookTest {
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Book(null));
+ }
+
+ @Test
+ public void constructor_invalidBookTitle_throwsIllegalArgumentException() {
+ String invalidBookTitle = " ";
+ assertThrows(IllegalArgumentException.class, () -> new Book(invalidBookTitle));
+ }
+
+ @Test
+ public void isValidBookTitle() {
+ // null book title
+ assertThrows(NullPointerException.class, () -> Book.isValidBookTitle(null));
+
+ // starts with white space
+ assertFalse(() -> Book.isValidBookTitle(" "));
+ assertFalse(() -> Book.isValidBookTitle(" Harry Potter"));
+
+ // valid titles
+ assertTrue(() -> Book.isValidBookTitle(""));
+ assertTrue(() -> Book.isValidBookTitle("HarryPotter"));
+ assertTrue(() -> Book.isValidBookTitle("Harry Potter"));
+
+ }
+
+ @Test
+ public void equalsTest() {
+ Book book = new Book("Valid Book");
+
+ // same values -> returns true
+ assertTrue(book.equals(new Book("Valid Book")));
+
+ // same object -> returns true
+ assertTrue(book.equals(book));
+
+ // null -> returns false
+ assertFalse(book.equals(null));
+
+ // different types -> returns false
+ assertFalse(book.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(book.equals(new Book("Other Valid Book")));
+ }
+
+ @Test
+ public void toStringTest() {
+ Book book = new Book("Valid Book123!!!");
+ assertEquals("Valid Book123!!!", book.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/library/LibraryTest.java b/src/test/java/seedu/address/model/library/LibraryTest.java
new file mode 100644
index 00000000000..f54c9e4f99d
--- /dev/null
+++ b/src/test/java/seedu/address/model/library/LibraryTest.java
@@ -0,0 +1,181 @@
+package seedu.address.model.library;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.TypicalIndexes.INDEX_BAD_MERIT_NOT_BORROWING_JOE;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_DEFAULT;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_MINUS_ONE_THOUSAND;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ONE;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_ZERO;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.book.Book;
+import seedu.address.model.person.Person;
+
+
+public class LibraryTest {
+
+ private Library library;
+
+ @BeforeEach
+ void setUp() {
+ library = new Library();
+ }
+
+ @Test
+ void addBook_validBook_success() {
+ Book book = new Book("Book 1");
+ library.addBook(book);
+ assertEquals(1, library.getBookList().size());
+ assertEquals(book, library.getBookList().get(0));
+ }
+
+ @Test
+ void deleteBook_validBook_success() {
+ Book book1 = new Book("Book 1");
+ Book book2 = new Book("Book 2");
+ library.addBook(book1);
+ library.addBook(book2);
+
+ library.deleteBook(2);
+ assertEquals(1, library.getBookList().size());
+ assertEquals(book1, library.getBookList().get(0));
+ }
+
+ @Test
+ void sortAlphabetically_validBooks_success() {
+ Book bookA = new Book("Book A");
+ Book bookB = new Book("Book B");
+ Book bookC = new Book("Book C");
+ library.addBook(bookC);
+ library.addBook(bookB);
+ library.addBook(bookA);
+
+ library.sortAlphabetically();
+ assertEquals("Book A", library.getBookList().get(0).toString());
+ assertEquals("Book B", library.getBookList().get(1).toString());
+ assertEquals("Book C", library.getBookList().get(2).toString());
+ }
+
+ @Test
+ void list_validBooksSorted_success() {
+ Book bookA = new Book("Book A");
+ Book bookB = new Book("Book B");
+ Book bookC = new Book("Book C");
+ library.addBook(bookC);
+ library.addBook(bookB);
+ library.addBook(bookA);
+
+ ObservableList sortedList = library.list();
+ assertEquals("Book A", sortedList.get(0).toString());
+ assertEquals("Book B", sortedList.get(1).toString());
+ assertEquals("Book C", sortedList.get(2).toString());
+ }
+
+ @Test
+ void canLendTo_validPerson_success() {
+ library.setThreshold(THRESHOLD_DEFAULT);
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), library);
+ Person modelPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ assertEquals(true, library.canLendTo(modelPerson));
+ }
+
+ @Test
+ void canLendTo_validPerson_unsuccess() {
+ library.setThreshold(THRESHOLD_ONE);
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), library);
+ Person modelPerson = model.getFilteredPersonList().get(INDEX_BAD_MERIT_NOT_BORROWING_JOE.getZeroBased());
+ assertEquals(false, library.canLendTo(modelPerson));
+ }
+
+ @Test
+ void popBookFromLibrary_existingBook_success() {
+ Book book1 = new Book("Book 1");
+ Book book2 = new Book("Book 2");
+ library.addBook(book1);
+ library.addBook(book2);
+
+ Book poppedBook = library.popBookFromLibrary(book2);
+ assertEquals(1, library.getBookList().size());
+ assertEquals(book2, poppedBook);
+ }
+
+ @Test
+ void popBookFromLibrary_nonExistingBook_success() {
+ Book book1 = new Book("Book 1");
+ Book book2 = new Book("Book 2");
+ Book book3 = new Book("Book 3");
+ library.addBook(book1);
+ library.addBook(book2);
+
+ Book poppedBook = library.popBookFromLibrary(book3);
+ assertEquals(2, library.getBookList().size());
+ assertEquals(null, poppedBook);
+ }
+
+ @Test
+ void toString_validLibrary_success() {
+ Book book1 = new Book("Book 1");
+ Book book2 = new Book("Book 2");
+ Book book3 = new Book("Book 3");
+ library.addBook(book1);
+ library.addBook(book2);
+ library.addBook(book3);
+
+ String expected = "1. Book 1\n"
+ + "2. Book 23. Book 3\n";
+ String result = library.toString();
+ assertEquals(3, library.getBookList().size());
+ assertEquals(result, expected);
+ }
+
+ @Test
+ void resetData_validData_success() {
+ Library library2 = new Library();
+ library2.addBook(new Book("Book 1"));
+ library2.addBook(new Book("Book 2"));
+ library2.setThreshold(THRESHOLD_MINUS_ONE_THOUSAND);
+
+ library.resetData(library2);
+ assertEquals(2, library.getBookList().size());
+ assertEquals(THRESHOLD_MINUS_ONE_THOUSAND, library.getThreshold());
+ }
+
+ @Test
+ public void equals() {
+ Book bookStub1 = new Book("Book Stub 1");
+ Book bookStub2 = new Book("Book Stub 2");
+ ObservableList bookList1 = FXCollections.observableArrayList();
+ bookList1.add(bookStub1);
+ ObservableList bookList2 = FXCollections.observableArrayList();
+ bookList1.add(bookStub2);
+ Library firstLibrary = new Library(bookList1, THRESHOLD_ZERO);
+ Library secondLibrary = new Library(bookList2, THRESHOLD_ONE);
+
+ // same object -> returns true
+ assertTrue(firstLibrary.equals(firstLibrary));
+
+ // same values -> returns true
+ Library firstLibraryCopy = new Library();
+ assertTrue(firstLibrary.equals(firstLibrary));
+
+ // different types -> returns false
+ assertFalse(firstLibrary.equals(1));
+
+ // null -> returns false
+ assertFalse(firstLibrary.equals(null));
+
+ // different person -> returns false
+ assertFalse(firstLibrary.equals(secondLibrary));
+ }
+}
diff --git a/src/test/java/seedu/address/model/library/ThresholdTest.java b/src/test/java/seedu/address/model/library/ThresholdTest.java
new file mode 100644
index 00000000000..6ab47ac64d5
--- /dev/null
+++ b/src/test/java/seedu/address/model/library/ThresholdTest.java
@@ -0,0 +1,66 @@
+package seedu.address.model.library;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.MeritScore;
+import seedu.address.model.person.MeritScoreStub;
+
+class ThresholdTest {
+
+ @Test
+ void createThreshold() {
+ // default threshold
+ assertEquals(-3, new Threshold().getThreshold());
+
+ // other values
+ assertEquals(-5, new Threshold(-5).getThreshold());
+ assertEquals(0, new Threshold(0).getThreshold());
+ assertEquals(7, new Threshold(7).getThreshold());
+ }
+
+ @Test
+ void isLessThanOrEqualToTest() {
+ final MeritScore zeroMeritScore = new MeritScoreStub();
+
+ // threshold < merit score
+ assertTrue(new Threshold(-3).isLessThanOrEqualTo(zeroMeritScore));
+
+ // threshold = merit score
+ assertTrue(new Threshold(0).isLessThanOrEqualTo(zeroMeritScore));
+
+ // threshold > merit score
+ assertFalse(new Threshold(3).isLessThanOrEqualTo(zeroMeritScore));
+ }
+
+ @Test
+ void testEquals() {
+ final Threshold thresholdOfFive = new Threshold(5);
+
+ // same values -> returns true
+ assertTrue(thresholdOfFive.equals(new Threshold(5)));
+
+ // same object -> returns true
+ assertTrue(thresholdOfFive.equals(thresholdOfFive));
+
+ // null -> returns false
+ assertFalse(thresholdOfFive.equals(null));
+
+ // different types -> returns false
+ assertFalse(thresholdOfFive.equals(5.0f));
+
+ // different index -> returns false
+ assertFalse(thresholdOfFive.equals(new Threshold(3)));
+ }
+
+ @Test
+ void testToString() {
+ final Threshold thresholdOfFive = new Threshold(5);
+ final Threshold thresholdOfMinusFive = new Threshold(-5);
+ assertEquals(thresholdOfFive.toString(), "5");
+ assertEquals(thresholdOfMinusFive.toString(), "-5");
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/MeritScoreStub.java b/src/test/java/seedu/address/model/person/MeritScoreStub.java
new file mode 100644
index 00000000000..e3f10299180
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/MeritScoreStub.java
@@ -0,0 +1,13 @@
+package seedu.address.model.person;
+
+/**
+ * Merit Score Stub that only contains a score of 0.
+ */
+public class MeritScoreStub extends MeritScore {
+ /**
+ * Constructs a Merit Score of with a score of 0.
+ */
+ public MeritScoreStub() {
+ super(0);
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/MeritScoreTest.java b/src/test/java/seedu/address/model/person/MeritScoreTest.java
new file mode 100644
index 00000000000..a8526cdb49d
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/MeritScoreTest.java
@@ -0,0 +1,79 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class MeritScoreTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new MeritScore(null));
+ }
+
+ @Test
+ public void constructor_invalidMeritScore_throwsIllegalArgumentException() {
+ String invalidMeritScore = "";
+ assertThrows(IllegalArgumentException.class, () -> new MeritScore(invalidMeritScore));
+ }
+
+ @Test
+ public void isValidMeritScore() {
+ // null merit score
+ assertThrows(NullPointerException.class, () -> MeritScore.isValidMeritScore(null));
+
+ // invalid merit score
+ assertFalse(MeritScore.isValidMeritScore("")); // empty string
+ assertFalse(MeritScore.isValidMeritScore(" ")); // spaces only
+ assertFalse(MeritScore.isValidMeritScore("^")); // only non-alphanumeric characters
+ assertFalse(MeritScore.isValidMeritScore("peter*")); // contains non-alphanumeric characters
+ assertFalse(MeritScore.isValidMeritScore("peter jack")); // alphabets only
+ assertFalse(MeritScore.isValidMeritScore("peter the 2nd")); // alphanumeric characters
+ assertFalse(MeritScore.isValidMeritScore("Capital Tan")); // with capital letters
+
+ // valid name
+ assertTrue(MeritScore.isValidMeritScore("12345")); // numbers only
+ assertTrue(MeritScore.isValidMeritScore("-999999")); // small integer
+ assertTrue(MeritScore.isValidMeritScore("-1")); // negative integer
+ assertTrue(MeritScore.isValidMeritScore("0")); // 0
+ assertTrue(MeritScore.isValidMeritScore("1")); // positive integer
+ assertTrue(MeritScore.isValidMeritScore("999999")); // large integer
+ }
+
+ @Test
+ public void getMeritScore_failure() {
+ // not contains only digits -> throws Exception
+ assertThrows(IllegalArgumentException.class, () -> new MeritScore("123abc"));
+
+ // format not correc5 -> throws Exception
+ assertThrows(IllegalArgumentException.class, () -> new MeritScore(" 122 "));
+ }
+
+ @Test
+ public void getScore_success() {
+ MeritScore meritScore = new MeritScore(1);
+ assertTrue(meritScore.getScore().equals("1"));
+ }
+
+ @Test
+ public void equals() {
+ MeritScore meritScore = new MeritScore(0);
+
+ // same values -> returns true
+ assertTrue(meritScore.equals(new MeritScore(0)));
+
+ // same object -> returns true
+ assertTrue(meritScore.equals(meritScore));
+
+ // null -> returns false
+ assertFalse(meritScore.equals(null));
+
+ // different types -> returns false
+ assertFalse(meritScore.equals("hello"));
+
+ // different values -> returns false
+ assertFalse(meritScore.equals(new MeritScore(-1)));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..bd1d319cc3a 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -11,6 +11,7 @@
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
+import static seedu.address.testutil.TypicalPersons.KEPLER;
import org.junit.jupiter.api.Test;
@@ -90,10 +91,20 @@ public void equals() {
assertFalse(ALICE.equals(editedAlice));
}
+ @Test
+ public void getBookListToStringWithIndexMethod() {
+ String newBook = "New Book";
+ Person person = new PersonBuilder(KEPLER).withAnotherBook(newBook).build();
+ String expected = "1. " + "How To Become a Better Reader?\n";
+ expected += "2. " + newBook;
+ assertEquals(expected, person.getBookListToStringWithIndex());
+ }
+
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags()
+ + ", Merit score=" + ALICE.getMeritScore() + ", book borrowed=" + ALICE.getBookList() + "}";
assertEquals(expected, ALICE.toString());
}
}
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..89e9214eefe 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -168,6 +168,15 @@ public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationEx
-> uniquePersonList.asUnmodifiableObservableList().remove(0));
}
+ @Test
+ public void equalsMethod() {
+ // same object -> returns true
+ assertTrue(uniquePersonList.equals(uniquePersonList));
+
+ // other instance -> returns false
+ assertFalse(uniquePersonList.equals("3"));
+ }
+
@Test
public void toStringMethod() {
assertEquals(uniquePersonList.asUnmodifiableObservableList().toString(), uniquePersonList.toString());
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..bd11634321d 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -6,15 +6,19 @@
import static seedu.address.testutil.TypicalPersons.BENSON;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.MeritScore;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
public class JsonAdaptedPersonTest {
@@ -22,12 +26,23 @@ public class JsonAdaptedPersonTest {
private static final String INVALID_PHONE = "+651234";
private static final String INVALID_ADDRESS = " ";
private static final String INVALID_EMAIL = "example.com";
+ private static final String INVALID_MERIT_SCORE = "abc";
+ private static final ArrayList INVALID_BOOK_LIST =
+ new ArrayList<>(Arrays.asList(" title "))
+ .stream()
+ .map(JsonAdaptedBook::new)
+ .collect(Collectors.toCollection(ArrayList::new));;
private static final String INVALID_TAG = "#friend";
private static final String VALID_NAME = BENSON.getName().toString();
private static final String VALID_PHONE = BENSON.getPhone().toString();
private static final String VALID_EMAIL = BENSON.getEmail().toString();
private static final String VALID_ADDRESS = BENSON.getAddress().toString();
+ private static final String VALID_MERIT_SCORE = BENSON.getMeritScore().toString();
+ private static final List VALID_BOOK_LIST = BENSON.getBookList()
+ .stream()
+ .map(JsonAdaptedBook::new)
+ .collect(Collectors.toCollection(ArrayList::new));
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
@@ -41,14 +56,17 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +74,17 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +92,17 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,24 +110,66 @@ public void toModelType_nullEmail_throwsIllegalValueException() {
@Test
public void toModelType_invalidAddress_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = Address.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
+ @Test
+ public void toModelType_invalidMeritScore_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, INVALID_MERIT_SCORE,
+ VALID_BOOK_LIST, VALID_TAGS);
+ String expectedMessage = MeritScore.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullMeritScore_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, null,
+ VALID_BOOK_LIST, VALID_TAGS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, MeritScore.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidBookList_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ INVALID_BOOK_LIST, VALID_TAGS);
+ String expectedMessage = Book.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullBookList_returnsPerson() throws Exception {
+ Person bensonWithNoBook = new Person(BENSON.getName(), BENSON.getPhone(), BENSON.getEmail(),
+ BENSON.getAddress(), BENSON.getMeritScore(), new ArrayList<>(), BENSON.getTags());
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ null, VALID_TAGS);
+
+ assertEquals(bensonWithNoBook, person.toModelType());
+ }
+
@Test
public void toModelType_invalidTags_throwsIllegalValueException() {
List invalidTags = new ArrayList<>(VALID_TAGS);
invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_MERIT_SCORE,
+ VALID_BOOK_LIST, invalidTags);
assertThrows(IllegalValueException.class, person::toModelType);
}
diff --git a/src/test/java/seedu/address/storage/LibraryStorageTest.java b/src/test/java/seedu/address/storage/LibraryStorageTest.java
new file mode 100644
index 00000000000..62ca5ec5a0a
--- /dev/null
+++ b/src/test/java/seedu/address/storage/LibraryStorageTest.java
@@ -0,0 +1,152 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_DEFAULT;
+import static seedu.address.testutil.TypicalThresholds.THRESHOLD_MINUS_ONE_THOUSAND;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+
+public class LibraryStorageTest {
+ private LibraryStorage libraryStorage;
+
+ @BeforeEach
+ void setUp() {
+ libraryStorage = new LibraryStorage("test_library.txt");
+ }
+
+ @Test
+ public void isValidBook() {
+ assertEquals(LibraryStorage.isValidBook(""), false);
+ }
+
+ @Test
+ void loadLibraryFromFile_booksInOrderValidFile_success() throws IOException, DataLoadingException {
+ // Prepare a test file with valid data
+ PrintWriter writer = new PrintWriter(new FileWriter("test_library.txt"));
+ writer.println(THRESHOLD_DEFAULT.getThreshold()); // Threshold
+ writer.println("Book 1");
+ writer.println("Book 2");
+ writer.close();
+
+ // Call the method under test
+ libraryStorage.loadLibraryFromFile();
+
+
+ // Assert the loaded data
+ assertEquals(2, libraryStorage.getAvailableBooks().size());
+ assertEquals(THRESHOLD_DEFAULT.getThreshold(), libraryStorage.getThreshold().getThreshold());
+
+ // Assert the book titles
+ assertEquals("Book 1", libraryStorage.getAvailableBooks().get(0).toString());
+ assertEquals("Book 2", libraryStorage.getAvailableBooks().get(1).toString());
+ }
+
+ @Test
+ void loadLibraryFromFile_booksNotInOrderValidFile_success() throws IOException, DataLoadingException {
+ // Prepare a test file with valid data
+ PrintWriter writer = new PrintWriter(new FileWriter("test_library.txt"));
+ writer.println(THRESHOLD_MINUS_ONE_THOUSAND.getThreshold()); // Threshold
+ writer.println("Book C");
+ writer.println("Book B");
+ writer.println("Book A");
+ writer.close();
+
+ // Call the method under test
+ libraryStorage.loadLibraryFromFile();
+
+
+ // Assert the loaded data
+ assertEquals(3, libraryStorage.getAvailableBooks().size());
+ assertEquals(THRESHOLD_MINUS_ONE_THOUSAND.getThreshold(), libraryStorage.getThreshold().getThreshold());
+
+ // Assert the book titles
+ assertEquals("Book A", libraryStorage.getAvailableBooks().get(0).toString());
+ assertEquals("Book B", libraryStorage.getAvailableBooks().get(1).toString());
+ assertEquals("Book C", libraryStorage.getAvailableBooks().get(2).toString());
+ }
+
+ @Test
+ void saveBooksToFile_defaultThresholdValidLibrary_success() throws IOException {
+ // Prepare test data
+ Library library = new Library();
+ String thresholdToTest = String.valueOf(library.getThreshold().getThreshold());
+
+ // Call the method under test
+ libraryStorage.saveBooksToFile(library);
+
+ // Read the saved file and assert its content
+ List lines = Files.readAllLines(Paths.get("test_library.txt"));
+ assertEquals(1, lines.size()); // Including threshold
+ assertEquals(thresholdToTest, lines.get(0));
+ }
+
+ @Test
+ void saveBooksToFile_setNewThresholdValidLibrary_success() throws IOException {
+ // Prepare test data
+ int newThreshold = THRESHOLD_MINUS_ONE_THOUSAND.getThreshold();
+ Library library = new Library(newThreshold);
+ String thresholdToTest = String.valueOf(library.getThreshold().getThreshold());
+
+ // Call the method under test
+ libraryStorage.saveBooksToFile(library);
+
+ // Read the saved file and assert its content
+ List lines = Files.readAllLines(Paths.get("test_library.txt"));
+ assertEquals(1, lines.size()); // Including threshold
+ assertEquals(thresholdToTest, lines.get(0));
+ }
+
+ @Test
+ void saveBooksToFile_booksInOrderValidLibrary_success() throws IOException {
+ // Prepare test data
+ Book book1 = new Book("Book 1");
+ Book book2 = new Book("Book 2");
+ Library library = new Library();
+ library.addBook(book1);
+ library.addBook(book2);
+
+ // Call the method under test
+ libraryStorage.saveBooksToFile(library);
+
+ // Read the saved file and assert its content
+ List lines = Files.readAllLines(Paths.get("test_library.txt"));
+ assertEquals(3, lines.size()); // Including threshold
+ assertEquals("Book 1", lines.get(1));
+ assertEquals("Book 2", lines.get(2));
+ }
+
+ @Test
+ void saveBooksToFile_booksNotInOrderValidLibrary_success() throws IOException {
+ // Prepare test data
+ Book bookA = new Book("Book A");
+ Book bookB = new Book("Book B");
+ Book bookC = new Book("Book C");
+ Library library = new Library();
+ library.addBook(bookC);
+ library.addBook(bookB);
+ library.addBook(bookA);
+
+ // Call the method under test
+ libraryStorage.saveBooksToFile(library);
+
+ // Read the saved file and assert its content
+ List lines = Files.readAllLines(Paths.get("test_library.txt"));
+ assertEquals(4, lines.size()); // Including threshold
+ assertEquals("Book A", lines.get(1));
+ assertEquals("Book B", lines.get(2));
+ assertEquals("Book C", lines.get(3));
+ }
+}
+
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..7ba82729cb1 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -1,10 +1,13 @@
package seedu.address.testutil;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
+import seedu.address.model.book.Book;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
+import seedu.address.model.person.MeritScore;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -20,11 +23,15 @@ public class PersonBuilder {
public static final String DEFAULT_PHONE = "85355255";
public static final String DEFAULT_EMAIL = "amy@gmail.com";
public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
+ public static final String DEFAULT_MERIT_SCORE = "0";
+
private Name name;
private Phone phone;
private Email email;
private Address address;
+ private MeritScore meritScore;
+ private ArrayList bookList;
private Set tags;
/**
@@ -35,6 +42,8 @@ public PersonBuilder() {
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
+ meritScore = new MeritScore(DEFAULT_MERIT_SCORE);
+ bookList = new ArrayList<>();
tags = new HashSet<>();
}
@@ -46,6 +55,8 @@ public PersonBuilder(Person personToCopy) {
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
address = personToCopy.getAddress();
+ meritScore = personToCopy.getMeritScore();
+ bookList = personToCopy.getBookList();
tags = new HashSet<>(personToCopy.getTags());
}
@@ -89,8 +100,40 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code MeritScore} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withMeritScore(String meritScore) {
+ this.meritScore = new MeritScore(meritScore);
+ return this;
+ }
+
+ /**
+ * Sets the {@code MeritScore} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withMeritScore(int meritScore) {
+ this.meritScore = new MeritScore(meritScore);
+ return this;
+ }
+
+ /**
+ * Sets the {@code BookList} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withBooks(String ... books) {
+ this.bookList = SampleDataUtil.getBookList(books);
+ return this;
+ }
+
+ /**
+ * Adds the {@code book} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withAnotherBook(String book) {
+ this.bookList.add(new Book(book));
+ return this;
+ }
+
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, address, meritScore, bookList, tags);
}
}
diff --git a/src/test/java/seedu/address/testutil/TypicalBooks.java b/src/test/java/seedu/address/testutil/TypicalBooks.java
new file mode 100644
index 00000000000..e0fc8d653c2
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalBooks.java
@@ -0,0 +1,35 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import seedu.address.model.book.Book;
+import seedu.address.model.library.Library;
+
+/**
+ * A utility class containing a list of {@code Book} objects in the Library to be used in tests.
+ */
+public class TypicalBooks {
+ public static final Book BOOK_IN_LIBRARY = new Book("Book in library");
+ public static final Book GUMIHO = new Book("Gumiho");
+
+ public static final Book FIFTY_SHADES = new Book("Fifty Shades");
+
+ private TypicalBooks() {} // prevents instantiation
+
+ /**
+ * Returns an {@code Library} with all the typical books.
+ */
+ public static Library getTypicalLibrary() {
+ Library library = new Library();
+ for (Book book : getTypicalBooks()) {
+ library.addBook(book);
+ }
+ return library;
+ }
+
+ public static List getTypicalBooks() {
+ return new ArrayList<>(Arrays.asList(GUMIHO, FIFTY_SHADES, BOOK_IN_LIBRARY));
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java
index 1e613937657..35895c9c79a 100644
--- a/src/test/java/seedu/address/testutil/TypicalIndexes.java
+++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java
@@ -9,4 +9,7 @@ public class TypicalIndexes {
public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1);
public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2);
public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3);
+ public static final Index INDEX_JACKER = Index.fromOneBased(8);
+ public static final Index INDEX_KEPLER = Index.fromOneBased(9);
+ public static final Index INDEX_BAD_MERIT_NOT_BORROWING_JOE = Index.fromOneBased(10);
}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..a4411a3cc0c 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -25,28 +25,60 @@ public class TypicalPersons {
public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
.withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
- .withPhone("94351253")
+ .withPhone("94351253").withMeritScore(0).withBooks()
.withTags("friends").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
- .withAddress("311, Clementi Ave 2, #02-25")
- .withEmail("johnd@example.com").withPhone("98765432")
+ .withAddress("311, Clementi Ave 2, #02-25").withBooks("Valid")
+ .withEmail("johnd@example.com").withPhone("98765432").withMeritScore(1)
.withTags("owesMoney", "friends").build();
public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
- .withEmail("heinz@example.com").withAddress("wall street").build();
+ .withEmail("heinz@example.com").withAddress("wall street").withMeritScore(1).withBooks().build();
public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
- .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build();
+ .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends")
+ .withMeritScore(1).withBooks().build();
public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
- .withEmail("werner@example.com").withAddress("michegan ave").build();
+ .withEmail("werner@example.com").withAddress("michegan ave")
+ .withMeritScore(1).withBooks().build();
public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
- .withEmail("lydia@example.com").withAddress("little tokyo").build();
+ .withEmail("lydia@example.com").withAddress("little tokyo")
+ .withMeritScore(1).withBooks().build();
public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
- .withEmail("anna@example.com").withAddress("4th street").build();
+ .withEmail("anna@example.com").withAddress("4th street")
+ .withMeritScore(1).withBooks().build();
// Manually added
public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424")
- .withEmail("stefan@example.com").withAddress("little india").build();
+ .withEmail("stefan@example.com").withAddress("little india")
+ .withMeritScore(1).withBooks().build();
public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
- .withEmail("hans@example.com").withAddress("chicago ave").build();
+ .withEmail("hans@example.com").withAddress("chicago ave")
+ .withMeritScore(1).withBooks().build();
+
+ // Manually added
+ public static final Person JACKER = new PersonBuilder().withName("Jacker")
+ .withAddress("420, Curry Tasty, #69-420")
+ .withEmail("jacker@hotmail.com")
+ .withPhone("87173548")
+ .withTags("friends")
+ .withMeritScore(1).build();
+
+ // Manually added
+ public static final Person KEPLER = new PersonBuilder().withName("Kepler")
+ .withAddress("54, Stuttgart, #12-23")
+ .withEmail("kepler@hotmail.com")
+ .withPhone("89402749")
+ .withTags("teachers")
+ .withBooks("How To Become a Better Reader?")
+ .withMeritScore(1).build();
+
+ // Manually added
+ public static final Person BAD_MERIT_NOT_BORROWING_JOE = new PersonBuilder().withName("Joe Meme")
+ .withAddress("123A, King's Street, #13-5")
+ .withEmail("joe@gmail.com")
+ .withPhone("89899898")
+ .withTags("LowMerit")
+ .withBooks()
+ .withMeritScore(-100).build();
// Manually added - Person's details found in {@code CommandTestUtil}
public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
@@ -71,6 +103,7 @@ public static AddressBook getTypicalAddressBook() {
}
public static List getTypicalPersons() {
- return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE));
+ return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE,
+ JACKER, KEPLER, BAD_MERIT_NOT_BORROWING_JOE));
}
}
diff --git a/src/test/java/seedu/address/testutil/TypicalThresholds.java b/src/test/java/seedu/address/testutil/TypicalThresholds.java
new file mode 100644
index 00000000000..b178173f866
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalThresholds.java
@@ -0,0 +1,16 @@
+package seedu.address.testutil;
+
+import seedu.address.model.library.Threshold;
+
+/**
+ * A utility class containing a list of {@code Threshold} objects to be used in tests.
+ */
+public class TypicalThresholds {
+ public static final Threshold THRESHOLD_DEFAULT = new Threshold();
+ public static final Threshold THRESHOLD_ZERO = new Threshold(0);
+ public static final Threshold THRESHOLD_ONE = new Threshold(1);
+ public static final Threshold THRESHOLD_MINUS_TWO = new Threshold(-2);
+ public static final Threshold THRESHOLD_MINUS_FOUR = new Threshold(-4);
+
+ public static final Threshold THRESHOLD_MINUS_ONE_THOUSAND = new Threshold(-1000);
+}
diff --git a/test_library.txt b/test_library.txt
new file mode 100644
index 00000000000..82fae5d1742
--- /dev/null
+++ b/test_library.txt
@@ -0,0 +1,4 @@
+-3
+Book A
+Book B
+Book C