diff --git a/.github/workflows/gradletest.yml b/.github/workflows/gradletest.yml
new file mode 100644
index 00000000000..3f454a0be30
--- /dev/null
+++ b/.github/workflows/gradletest.yml
@@ -0,0 +1,46 @@
+name: Java CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ platform: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.platform }}
+
+ steps:
+ - name: Set up repository
+ uses: actions/checkout@main
+
+ - name: Set up repository
+ uses: actions/checkout@main
+ with:
+ ref: master
+
+ - name: Merge to master
+ run: git checkout --progress --force ${{ github.sha }}
+
+ - name: Run repository-wide tests
+ if: runner.os == 'Linux'
+ working-directory: ${{ github.workspace }}/.github
+ run: ./run-checks.sh
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/actions/wrapper-validation@v3
+
+ - name: Setup JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ java-package: jdk+fx
+
+ - name: Build and check with Gradle
+ run: ./gradlew check coverage
+
+ - name: Upload coverage reports to Codecov
+ if: runner.os == 'Linux'
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..a6150c3d767 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+archived
diff --git a/README.md b/README.md
index 16208adb9b6..85292f01e14 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,17 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![Java CI](https://github.com/AY2425S1-CS2103T-W10-4/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2425S1-CS2103T-W10-4/tp/actions/workflows/gradle.yml)[![codecov](https://codecov.io/gh/AY2425S1-CS2103T-W10-4/tp/graph/badge.svg?token=ISBALRKKO7)](https://codecov.io/gh/AY2425S1-CS2103T-W10-4/tp)
+
+# StoreClass
+**StoreClass** is a project done by Software Engineering (SE) students.
![Ui](docs/images/Ui.png)
-* 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.
+## What is StoreClass?
+* StoreClass is designed to be used by educators in private educational institutions such as tuition centers, allowing educators to quickly
+access student information such as contact details, class details and other necessary information.
+* It is optimized for users who are comfortable using the command-line interface (CLI), which allows for faster inputs through text-based inputs.
+
+## Project Information
* 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/#contributing-to-se-edu) for more info.
+* It is named `StoreClass` as it is a play on words, stating how the class of students are stored.
+* For the detailed documentation of this project, see the **[StoreClass Documentation](https://ay2425s1-cs2103t-w10-4.github.io/tp/)**.
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
diff --git a/build.gradle b/build.gradle
index 0db3743584e..8c29cd2be20 100644
--- a/build.gradle
+++ b/build.gradle
@@ -70,3 +70,7 @@ shadowJar {
}
defaultTasks 'clean', 'test'
+
+run {
+ enableAssertions = true
+}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index ff3f04abd02..0cf61b90f70 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,61 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Prabhu NATARAJAN
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
+[[homepage](https://www.comp.nus.edu.sg/cs/people/prabhu/)]
+[[github](https://github.com/prabhu-na)]
[[portfolio](team/johndoe.md)]
* Role: Project Advisor
-### Jane Doe
+### Tan Yong Quan
-
+
-[[github](http://github.com/johndoe)]
+[[github](http://github.com/yongqqqq)]
[[portfolio](team/johndoe.md)]
* Role: Team Lead
* Responsibilities: UI
-### Johnny Doe
+### Isaac Lim Tzee Zac
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](https://github.com/isaactodo)] [[portfolio](team/johndoe.md)]
-* Role: Developer
-* Responsibilities: Data
+* Role: Documentation, Code Quality, Deliverables and Deadlines
+* Responsibilities: Integration
-### Jean Doe
+### Lei Jianwen
-
+
-[[github](http://github.com/johndoe)]
+[[github](http://github.com/jianwen0451)]
[[portfolio](team/johndoe.md)]
-* Role: Developer
+* Role: Developer Java Expert
* Responsibilities: Dev Ops + Threading
-### James Doe
+### Collin Tan Yu Qi
-
+
-[[github](http://github.com/johndoe)]
+[[github](http://github.com/tanyqcollin)]
[[portfolio](team/johndoe.md)]
* Role: Developer
* Responsibilities: UI
+
+### Tee Jing Hong
+
+
+
+[[github](http://github.com/RadieonAjax)]
+[[portfolio](team/RadieonAjax.md)]
+
+* Role: Developer
+* Responsibilities: UI
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 743c65a49d2..a6a87e303c9 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,6 +2,9 @@
layout: page
title: Developer Guide
---
+
+![Logo](images/StoreClass-Logo.png)
+
* Table of Contents
{:toc}
@@ -72,7 +75,7 @@ The **API** of this component is specified in [`Ui.java`](https://github.com/se-
![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 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. However, `DialogBox` inherits from `HBox`.
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)
@@ -83,6 +86,7 @@ The `UI` component,
* 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`.
+`CommandBox` holds a reference towards `AutocompleteParser` which helps parses user input for a list of suggestions to show to the user.
### Logic component
**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
@@ -144,6 +148,25 @@ The `Storage` component,
* can save both address book data and user preference data in JSON 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`)
+
+The following is an example JSON output for a person:
+
+```json
+{
+ "name": "John Doe",
+ "phone": "98765432",
+ "gender": "male",
+ "modules": [
+ {
+ "module": "CS2103T",
+ "grade": 85
+ }
+ ],
+ "tags": [
+ "colleague"
+ ]
+}
+```
### Common classes
@@ -155,37 +178,75 @@ Classes used by multiple components are in the `seedu.address.commons` package.
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Undo/redo feature
+
+#### Implementation Overview
+
+The undo/redo functionality is implemented in the `VersionedAddressBook`, which extends the basic `AddressBook` to include versioning capabilities. This is achieved by maintaining an internal history of address book states. The history is stored in a list (`addressBookStateList`), and the current state is tracked by the `currentStatePointer`.
+
+The key operations involved in undo/redo are:
-#### Proposed Implementation
+- **`VersionedAddressBook#save()`**: Saves the current state of the address book into the history list.
+- **`VersionedAddressBook#undo()`**: Restores the address book to the previous state.
+- **`VersionedAddressBook#redo()`**: Restores the address book to a previously undone state.
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+These operations are available to the rest of the system through the `Model` interface, specifically:
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+- `Model#saveAddressBook()`
+- `Model#undoAddressBook()`
+- `Model#redoAddressBook()`
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+---
+
+## How Undo/Redo Works
+
+The mechanism functions as follows:
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+1. **Save**: Every modification to the address book, such as `add`, `delete`, or `edit` commands, triggers a save operation to save the current state of the address book.
-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.
+2. **Undo**: The undo operation moves the `currentStatePointer` backward and restores the previous state. If no more undo operations can be performed (i.e., if the pointer is at the start of the history), the operation fails and returns an error.
+
+3. **Redo**: The redo operation moves the `currentStatePointer` forward and restores the next state that was undone. If there are no undone states, the operation fails and returns an error.
+
+---
+
+### Example Flow
+
+Here is how undo and redo would work through a sequence of user actions:
+
+---
+
+#### Step 1. Initial State
+Upon launching the application, the `VersionedAddressBook` is initialized with the default state (an empty address book, for example). This state is stored in the `addressBookStateList` as the first entry, and the `currentStatePointer` points to this state.
![UndoRedoState0](images/UndoRedoState0.png)
-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.
+---
+
+#### Step 2. Performing a Deletion
+When a user executes the `delete` command (e.g., `delete 5`), the current state of the address book is saved before the change is made. This ensures that the delete action is reversible. The new state of the address book (with the 5th person deleted) is then stored in the history.
+
+The `saveAddressBook()` operation is called internally, and the `currentStatePointer` is moved forward to point to the newly saved 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`.
+---
+
+#### Step 3. Performing an Addition
+Next, if a user executes an `add` command (e.g., `add n/David ...`), a new state of the address book is created. As before, `saveAddressBook()` is invoked to save the new state, and the `currentStatePointer` is shifted to this new state.
![UndoRedoState2](images/UndoRedoState2.png)
-
:information_source: **Note:** The lifeline for `ArchiveCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. Some details like parameters of function is omitted for simplicity.
+
+
+
+**Scenario 3 Loading from a file**
+
+In this scenario, the user is trying to load the address book from a file named `archiveFile1.json`. He enters the command `load pa/archiveFile1.json`. The data in the current working address book will be discarded. The data in `archiveFile1.json` will be loaded into the working address book.
+![Load](images/Load.png)
+
+The following sequence diagram illustrate how an archive operation is processed under `Logic` component.
+
+![LoadSequenceDiagram](images/LoadSequenceDiagram-Logic.png)
+
+:information_source: **Note:** The lifeline for `LoadCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. Some details like parameters of function is omitted for simplicity.
+
+
+
+-------------------------------------------------------------------------------
## **Documentation, logging, testing, configuration, dev-ops**
@@ -262,42 +378,64 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
+* a teacher in an educational institution (private institution, i.e. tuition centers)
+* need to manage large amount of student information
* prefer desktop apps over other types
* can type fast
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: It allows for easy and efficient retrieval or storage of student information while providing a clean and user-friendly interface. The application supports modularity, and users are able to import and export to other similar applications without relying on complex or costly software.
### User stories
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+Priorities: High (must have) - `* * *`, Medium (Good to have) - `* *`, Low (nice to have) - `*`
+
+| Priority | As a … | I want to … | So that I can… |
+|----------|----------|-----------------------------------------------------------|----------------------------------------------------------------|
+| `* * *` | educator | add students into the database | easily refer to their information when needed |
+| `* * *` | educator | list all students to view the number of students | collate that information |
+| `* * *` | educator | delete a student | remove entries that I no longer need |
+| `* * *` | educator | find a person by name | find the relevant person without scrolling through a long list |
+| `* *` | educator | clear all information | start anew for a new academic year |
+| `* *` | educator | update details easily when there are changes | have the most updated information |
+| `* *` | educator | categorize students into groups | |
+| `* *` | educator | record students grades for tests and assignments | |
+| `* *` | educator | view a summary of each student's grade | |
+| `* *` | educator | tag students with relevant labels | prioritize students based on their status |
+| `* *` | educator | record notes on student behaviour | easily track issues related to their behaviour |
+| `* *` | educator | archive old student data | keep my AB clean while being able to retrieve old information |
+| `*` | educator | set learning goals | track their progress towards these goals |
+| `*` | educator | undo/redo any changes | avoid re-entering the data during a mis-entry |
+| `*` | educator | see a list of suggested commands when typing out commands | easily type in the commands that I want and reduce typos |
+| `*` | educator | export student data | share the information with others |
+| `*` | educator | keep track of meetings with students | keep track of my commitments |
+| `*` | educator | see sample data | try out the app's feature without adding my own student data |
-| 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 `StoreClass` and the **Actor** is the `user`, unless specified otherwise)
-**Use case: Delete a person**
+#### **Use case 1: List out all students**
-**MSS**
+**Main Success Scenario**
-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
+1. User requests to list students
+2. StoreClass shows a list of students
+
+ Use case ends.
+
+#### **Use case 2: Delete a student**
+
+**Main Success Scenario**
+
+1. User requests to list students
+2. StoreClass shows a list of students
+3. User requests to delete a specific student in the list
+4. StoreClass deletes the student
Use case ends.
@@ -313,20 +451,203 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Use case resumes at step 2.
-*{More to be added}*
+#### **Use case 3: Add a student**
+
+**Main Success Scenario**
+
+1. User requests to add persons and type in the relevant information
+2. StoreClass adds the new student
+3. StoreClass show the new list of students
+ Use case ends.
+
+**Extensions**
+
+* 1a. The provided information is invalid.
+
+ * 1a1. StoreClass displays the corresponding data error message.
+
+ Use case resumes at step 1.
+
+#### **Use case 4: Search for student**
+
+**Main Success Scenario**
+1. User requests to search for student with the relevant search query.
+2. StoreClass displays the relevant student(s) matching the query.
+ :question: **Why do I need to install Java?**
+ Our program is coded in Java. So in order for you to use it, Java will need to be installed on your machine. Think of it as the engine for our program.
+
+
-1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases).
+1. Copy the file to the folder you want to use as the _home folder_ for StoreClass.
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+2. Download the latest version of the `.jar` file of our program from [here](https://github.com/AY2425S1-CS2103T-W10-4/tp/releases).
-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.:bulb: **Tip:**
-A person can have any number of tags (including 0)
+
:notebook: **Note:** Field Constraints
+- Names should only contain alphabets, hyphens, dots, commas, forward slash and spaces, and be between 1 and 100 characters long.
+- Phone numbers should only contain numbers, and be exactly 8 digits long.
+- Gender should be either `male` or `female`.
+- Module should consist of alphanumeric characters and spaces only, and it should be between 1 and 30 characters long.
+- Tag should consist of alphanumeric characters only, and it should be between 1 and 30 characters long.
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`
+* `add n/John Doe p/98765432 g/male m/Mathematics` : Adds a student named `John Doe` to StoreClass.
+* `add n/Betsy Crowe g/female p/1234567 m/Physics m/Chemistry t/OLevels t/new` : Adds a student named `Betsy Crowe` to StoreClass.
-### Listing all persons : `list`
-Shows a list of all persons in the address book.
+### Listing all students : `list`
+
+You can show a list of all students in StoreClass.
Format: `list`
-### Editing a person : `edit`
+### Editing a student : `edit`
-Edits an existing person in the address book.
+You can edit an existing student in StoreClass.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Format: `edit INDEX [n/NAME] [p/PHONE] [g/GENDER] [m/MODULE]... [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.
@@ -106,76 +137,309 @@ Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
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.
+* `edit 1 p/91234567` : Edits the phone number of the 1st person to be `91234567`.
+* `edit 2 n/Betsy Crower t/` : Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
-### Locating persons by name: `find`
+### Locating students by name: `find`
-Finds persons whose names contain any of the given keywords.
+You can find students whose names or tags contain 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`
-* Only the name is searched.
+* Only the name and tags are 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).
+* Students 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)
-### Deleting a person : `delete`
+* `find John` returns `john` and `John Doe` _(search by name)_
+* `find colleague` returns `Bernice Yu` and `Roy Balakrishnan` _(search by tag)_
+* `find alex david` returns `Alex Yeoh`, `David Li` _(search by multiple parameters)_
+
+![result for 'find alex david'](images/findResult.png)
+
+### Filter students : `filter`
-Deletes the specified person from the address book.
+You can filter students who meet all specified conditions.
+
+Format: `filter [n/name] [p/phone] [g/gender] [t/tag]... [m/module]...`
+* The filter is case-insensitive. eg `hans` will match `Hans`.
+* At least one of the optional fields must be provided.
+* Only full words will be matched e.g. `Han` will not match `Hans`, same to all parameter except phone number.
+* At least 3 digits of number must be provided to filter phone number and it will return all matching numbers that contains specified number.
+* Students matching all the given conditions will be returned (i.e. `AND` search).
+
+
⚠️ **Warning:**
+Each parameter can only contain one keyword.
+
+
+Examples:
+* `filter n/John` : returns `john` and `John Doe` (filter by name)
+* `filter g/male t/new` : returns `James Li`, `Roy Balakrishnan` and `Linus Koo`. _(filter by gender and tag)_
+* `filter g/female t/new t/OLevels` : returns `Alex Yeoh` and `David Li` _(filter by gender and multiple tags)_
+* `filter g/female t/IB m/Physics` : return `Bernice Yu` _(filter by multiple conditions)_
+
+### Deleting a student : `delete`
+
+You can delete a specified student from StoreClass.
Format: `delete INDEX`
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
+* Deletes the student at the specified `INDEX`.
+* The index refers to the index number shown in the displayed student list.
* The index **must be a positive integer** 1, 2, 3, …
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.
+### Undoing the last action: `undo`
+
+The `undo` command allows you to reverse the last action performed, helping you recover any data that may have been unintentionally deleted or modified.
+
+**Format:** `undo`
+
+#### How it works:
+- When you perform an action that modifies the address book (like adding, editing, or deleting an entry), that action is saved in memory.
+- By executing `undo`, the most recent modification is reverted, and the address book is restored to its previous state.
+
+
+:bulb: **Tips:** for Efficient Usage
+
+1. **Use Undo After a Mistake**: If you accidentally delete or modify a contact, you can quickly use `undo` to revert the last action and restore the previous state.
+2. **Undo Works Only for Modifying Commands**: Only actions that modify the address book (like `add`, `edit`, or `delete`) can be undone. Commands like `list`, `filter`, or `find` do not trigger the undo mechanism. The reasoning behind this is that we consider them as view commands, not action commands that alter student data.
+
+
+
+:question: **Common Question:**
+Why does `undo` not work after I run `list`, `filter`, or `find`?
+`undo` only works for actions that modify the address book. Since `list`, `filter`, or `find` do not modify the data, they do not impact the undo history.
+
+
+
+:rotating_light: **Warning:**
+Executing a command that modifies the address book (like `add`, `edit`, or `delete`) will **clear the redo stack**. This means once you undo an action and perform another modification, you cannot redo the previous undone action.
+
+
+#### Example Scenario:
+1. You delete a contact.
+ - *Before Delete:* [Initial State]
+ - *After Delete:* [After Delete Command]
+2. You decide to undo the delete action.
+ - *After Undo:* [After Undo Command] – The deleted contact is restored.
+
+
+
+
+
+Examples:
+* `undo` will revert the last command executed, restoring the previous state of the address book.
+
+### Redoing the Last Undone Action: `redo`
+
+The `redo` command allows you to reapply the last action that was undone, restoring the previous state of the address book. This can be useful if you change your mind after undoing an action.
+
+**Format:** `redo`
+
+- **Note:** Like `undo`, the `redo` command cannot be used with commands like `list`, `filter`, or `find`.
+
+#### How it works:
+- If you’ve used `undo` to reverse an action, the `redo` command will restore that action, essentially “re-doing” it.
+
+
+:bulb: **Tips:** for Efficient Usage
+
+1. **Use Redo to Restore Actions**: If you’ve undone an action by mistake, `redo` lets you reapply the change quickly. It’s useful when you second-guess your decision.
+2. **Redo Works Only After Undo**: `redo` will only work if an action has been undone previously. If you haven’t undone an action, `redo` will not perform anything.
+
+
+
+:question: **Common Question:**
+Why does `redo` not work after I’ve made new changes to the address book?
+`redo` only works if there is a previous action that was undone. If a new action is performed after an undo, the redo history is cleared, and there is nothing to redo.
+
+
+
+:rotating_light: **Warning:**
+Executing a command that modifies the address book (like `add`, `edit`, or `delete`) will **clear the redo stack**. This means once you undo a change and then modify the address book again, you will lose the ability to redo the previous undone action.
+
+
+#### Example Scenario:
+1. You undo a deletion of a contact.
+ - *After Undo:* [After Undo Command] – The deleted contact is restored.
+2. You decide to redo the action and restore the contact again.
+ - *After Redo:* [After Redo Command] – The contact is deleted once more.
+
+
+
+---
+
+### Grading a Module: `grade`
+
+You can assign a grade to a module that a student is taking.
+
+**Format:** `grade INDEX [m/MODULE s/GRADE]`
+
+- Assigns a numerical grade (between 0 and 100) to the module identified by the `INDEX` number shown in the displayed person list.
+- `INDEX`: The index number of the student in the displayed person list (must be a positive integer).
+- `m/MODULE`: The module code to which the grade is assigned.
+- `s/GRADE`: The numerical grade (between 0 and 100) to assign to the module.
+- You can provide multiple `m/MODULE s/GRADE` pairs to assign grades to multiple modules in a single `grade` command.
+- The grade can be any whole number between 0 and 100, inclusive.
+
+
:notebook: **Important Note:**
+- Each module specified in the `grade` command must be a module that the student is taking.
+- The number of `m/MODULE` prefixes must match the number of `s/GRADE` prefixes.
+- Grades are assigned to modules based on the order of the `m/MODULE s/GRADE` pairs provided in the command.
+
+
+
Tip:
+You can hover over each individual module to view the grades for that module.
+
+
+**Examples:**
+- `grade 1 m/Physics s/85` : assigns a grade of 85 to Physics for the first student.
+- `grade 2 m/Chemistry s/90` : assigns a grade of 90 to Chemistry for the second student.
+- `grade 3 m/English s/80 m/Chinese s/85` assigns a grade of 80 to English and 85 to Chinese for the third student.
+
+
+### Archiving data files `archive`
+
+You can archive the current address book to a specific file name.
+
+After archiving, you can find the archived file in the folder `archived` with the name `FILENAME` in the home folder that you've chosen to put the `jar` file of StoreClass in.
+
+Format: `archive pa/FILENAME`
+
+Example: `archive pa/mybook.json`
+
+The file name must end with ".json" and must not contain any slashes "/".
+
+There should be only one file name provided.
+
+
:rotating_light: **Warning:**
+When you execute the archive command, all entries in the current StoreClass will be discarded.
+
+If you choose an existing archive file as the file name when archiving, the old archive file will be overwritten.
+
+
+### Load data files `load`
+
+You can load data from an archived file into StoreClass.
+
+You can only load from files under the folder named `archived`. This folder is located in the home folder that you've chosen to put the `jar` file of StoreClass in.
+
+Format: `load pa/FILENAME`
+
+Example: `load pa/mybook.json`
+
+The file name must end with ".json", must not contain any slashes "/" and must point to an existing StoreClass .json file.
+
+There should be only one file name provided.
+
+
:rotating_light: **Warning:**
+It is recommended that you avoid loading non-StoreClass .json files, as it may result in unexpected behaviors
+
+When you execute the load command, all the entries in the current StoreClass will be overwritten. So, it is recommended that you archive the current data in StoreClass before loading.
+
+
+
+:question: **Common Question:**
+What happens to the undo/redo stack after archiving or loading the address book?
+When you call `archive` or `load`, the undo/redo history is cleared to prevent inconsistencies between the address book data and the stored history. Always ensure the current state is saved before performing these actions.
+
+
### Clearing all entries : `clear`
-Clears all entries from the address book.
+You can all the current entries in StoreClass with this command.
Format: `clear`
### Exiting the program : `exit`
-Exits the program.
+You can exit the program with this command.
Format: `exit`
+### Autocomplete
+
+The Autocomplete feature provides real-time command suggestions as you type, helping you quickly and accurately enter commands. Autocomplete identifies keywords and suggests matches, allowing you to streamline input by selecting from relevant options instead of typing full commands or field values.
+
+#### How It Works
+Autocomplete operates based on the word at your current caret position:
+
+* As you begin typing a command or field, suggestions will appear that match the word under your current caret position. For example, typing `ad` will display a list of commands beginning with `ad`, like `add`.
+ * Autocomplete matches with the text characters before your caret position of the word under your current caret position. For example, when typing in the command keyword `ad` and you move your cursor between `a` and `d`, autocomplete will show you all commands starting with `a`. You can utilize this to do some cool tricks as explained in the tips section below.
+* Autocomplete for command keywords e.g. `clear`, `delete`, `add` applies only to the first word you type in the command box. This initial word is treated as the command.
+* Autocomplete for student fields applies to all subsequent words after the first word. All subsequent words after the first are treated as student fields with specific prefixes, e.g. `m/Math`, `g/male`
+
+
:notebook: **Note:** Autocomplete will **not** match subsequent words after the first word you entered with command keywords! Similarly, Autocomplete will **not** match the first word you entered with student fields!
+
+#### Supported Commands
+Autocomplete supports all existing command keywords when matching.
+
+#### Supported Fields
+Autocomplete currently supports the following fields with these prefixes:
+
+| Prefix | Field | Description |
+|----------|------------------|-------------------------------------------------|
+| `m/` | Modules | Matches **all existing** module names |
+| `t/` | Tags | Matches **all existing** tags |
+| `g/` | Gender | Matches gender values: `male` or `female` |
+| `pa/` | File Paths | Matches **all existing** archived file paths |
+
+When these prefixes are detected, autocomplete automatically displays a list of suggestions related to these fields. The list of suggestions are generated through the existing list of students inside StoreClass.
+
+#### Example Usage
+If you begin typing `edit 1 m/M`, Autocomplete will provide suggestions for available modules starting with the letter `M`, helping you to quickly select the correct module name. Similarly, typing `t/` after the command will bring up a list of tags, allowing you to specify tags accurately without needing to remember or retype exact names.
+
+
:notebook: **Note:** Autocomplete is **case-insensitive**!
+
+
+:question: **Common Question:**
+Why are there no suggestions when I type in `m/`, `t/` or `pa/`?
+Autocomplete searches for suggestions relevant to these fields based on the existing data in StoreClass. If there are no data or students inside StoreClass, then no suggestions will be
+generated for these fields. This usually occurs after a `clear` command.
+
+
+
+:bulb: **Tips:** for Efficient Usage
+
+1. **Start with the command**: Autocomplete only activates for commands when typing the first word.
+2. **Remember to use prefixes**: For fields, make sure to use the correct prefix (`m/`, `t/`, `g/`, `pa/`) to activate Autocomplete for those fields.
+3. **Select from suggestions using arrow keys**: Save time by selecting from the suggestion list using arrow keys rather than typing full names or values.
+4. **Typos**: When you accidentally type in the wrong name for an existing field, instead of holding backspace and retyping the entire field, simply move the caret position over to the
+prefix, and select from the list of suggestions. Autocomplete will replace the entire field with your selection for you.
+
+
+By utilizing Autocomplete, you can input commands more quickly, reduce typos, and improve your overall efficiency in navigating the software!
+
+![Autocomplete example when keying in gender](images/AutocompleteExample.png)
+
### 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.
+StoreClass data are saved in the hard disk automatically after any command you enter that changes the data. There is no need to save manually!
### 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.
+StoreClass 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.
: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.
+If your changes to the data file makes its format invalid, StoreClass 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 StoreClass 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.
-### Archiving data files `[coming in v2.0]`
-_Details coming soon ..._
--------------------------------------------------------------------------------------------------------------------
## 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 StoreClass home folder.
--------------------------------------------------------------------------------------------------------------------
@@ -190,10 +454,16 @@ _Details coming soon ..._
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`
+**Add** | `add n/NAME g/GENDER p/PHONE_NUMBER m/MODULE... [t/TAG]…`
e.g., `add n/James Ho g/male p/83216574 m/English m/Chemistry t/new t/IB`
**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`
+**Edit** | `edit INDEX [n/NAME] [g/GENDER] [p/PHONE_NUMBER] [m/MODULE] [t/TAG]…`
e.g.,`edit 2 n/James Lee`
**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake`
+**Grade** | `grade INDEX [m/MODULE s/GRADE]`
e.g., `grade 1 m/History s/85`
+**Undo** | `undo`
+**Redo** | `redo`
**List** | `list`
**Help** | `help`
+**Archive** | `archive pa/PATH`
+**Load** | `load pa/PATH`
+
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..eb9ceb66583 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "StoreClass"
theme: minima
header_pages:
@@ -6,9 +6,23 @@ header_pages:
- DeveloperGuide.md
- AboutUs.md
+sidebar:
+ - title: "User Guide"
+ url: UserGuide.html
+ path: "UserGuide.md"
+ toc: true
+ - title: "Developer Guide"
+ url: DeveloperGuide.html
+ path: "DeveloperGuide.md"
+ toc: true
+ - title: "About Us"
+ url: AboutUs.html
+ path: "AboutUs.md"
+ toc: false
+
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2425S1-CS2103T-W10-4/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
index e092cd572e0..96995229520 100644
--- a/docs/_layouts/default.html
+++ b/docs/_layouts/default.html
@@ -1,18 +1,81 @@
- {%- include head.html -%}
+{%- include head.html -%}
-
+
- {%- include header.html -%}
+{%- include header.html -%}
-
-
- {{ content }}
-
-
+
+
+
-
+
+
+ {{ content }}
+
+
+
+
+
+
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..865aa38046d 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "StoreClass";
font-size: 32px;
}
}
diff --git a/docs/diagrams/ArchiveAndLoadInitialState.puml b/docs/diagrams/ArchiveAndLoadInitialState.puml
new file mode 100644
index 00000000000..d0154abf2a3
--- /dev/null
+++ b/docs/diagrams/ArchiveAndLoadInitialState.puml
@@ -0,0 +1,19 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+
+title Initial State
+
+package data as DataPkg {
+ class State1 as "
addressBook.json:File "
+}
+
+package archive as ArchivePkg {
+ class State2 as "
archiveFile1.json:File "
+}
+
+class Pointer as "Current Working AddressBook" #FFFFFF
+Pointer -up-> State1
+@end
diff --git a/docs/diagrams/ArchiveSequenceDiagram-Logic.puml b/docs/diagrams/ArchiveSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..f8ab96e2b98
--- /dev/null
+++ b/docs/diagrams/ArchiveSequenceDiagram-Logic.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "u:ArchiveCommand" as ArchiveCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+box Storage STORAGE_COLOR_T1
+participant "StorageManager" as Storage STORAGE_COLOR
+end box
+
+[-> LogicManager : execute(archive pa/...)
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand(archive pa/...)
+activate AddressBookParser
+
+create ArchiveCommand
+AddressBookParser -> ArchiveCommand
+activate ArchiveCommand
+
+ArchiveCommand --> AddressBookParser
+deactivate ArchiveCommand
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> "Storage" : saveArchivedAddressBook()
+activate "Storage"
+
+"Storage" --> LogicManager :
+deactivate "Storage"
+
+LogicManager -> ArchiveCommand : execute()
+activate ArchiveCommand
+
+
+ArchiveCommand -> Model : setAddressBook(new\n AddressBook())
+activate Model
+
+Model --> ArchiveCommand
+deactivate Model
+
+ArchiveCommand --> LogicManager : result
+deactivate ArchiveCommand
+ArchiveCommand -[hidden]-> LogicManager : result
+destroy ArchiveCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ArchiveToExistingFile.puml b/docs/diagrams/ArchiveToExistingFile.puml
new file mode 100644
index 00000000000..faedc8675e4
--- /dev/null
+++ b/docs/diagrams/ArchiveToExistingFile.puml
@@ -0,0 +1,24 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+
+title After Command "archive pa/archiveFile1.json"
+
+package data as DataPkg {
+ class State1 as "
addressBook.json:File "
+}
+
+package archive as ArchivePkg {
+ class State2 as "
archiveFile1.json:File "
+}
+
+
+class Pointer1 as "The data here is cleared" #FFFFFF
+Pointer1 -up-> State1
+
+class Pointer2 as "Containing data from addressBook.json" #FFFFFF
+Pointer2 -up-> State2
+note right on link: The original data in this file \n will be overwritten.
+@end
diff --git a/docs/diagrams/ArchiveToNewFile.puml b/docs/diagrams/ArchiveToNewFile.puml
new file mode 100644
index 00000000000..c32bb3ea491
--- /dev/null
+++ b/docs/diagrams/ArchiveToNewFile.puml
@@ -0,0 +1,26 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+
+title After Command "archive pa/archiveFile2.json"
+
+package data as DataPkg {
+ class State1 as "
addressBook:File "
+}
+
+package archive as ArchivePkg {
+ class State2 as "
archiveFile1:AddressBook "
+ class State3 as "
archiveFile2:AddressBook "
+}
+
+
+class Pointer1 as "Current working addressBook" #FFFFFF
+Pointer1 -up-> State1
+note right on link: The original data in this file \n will be discarded.
+
+class Pointer2 as "Containing data of original ab0" #FFFFFF
+Pointer2 -up-> State3
+
+@end
diff --git a/docs/diagrams/Load.puml b/docs/diagrams/Load.puml
new file mode 100644
index 00000000000..b0e7f6a7d1c
--- /dev/null
+++ b/docs/diagrams/Load.puml
@@ -0,0 +1,22 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+
+title After Command "archive pa/archiveFile1.json"
+
+package data as DataPkg {
+ class State1 as "
addressBook.json:File "
+}
+
+package archive as ArchivePkg {
+ class State2 as "
archiveFile1.json:File "
+}
+
+
+class Pointer1 as "Current working addressBook\n Containing data from archiveFile1.json" #FFFFFF
+Pointer1 -up-> State1
+note right on link: The original data in this file \n will be discarded.
+
+@end
diff --git a/docs/diagrams/LoadSequenceDiagram-Logic.puml b/docs/diagrams/LoadSequenceDiagram-Logic.puml
new file mode 100644
index 00000000000..4546635a5bf
--- /dev/null
+++ b/docs/diagrams/LoadSequenceDiagram-Logic.puml
@@ -0,0 +1,71 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "u:LoadCommand" as LoadCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+box Storage STORAGE_COLOR_T1
+participant "StorageManager" as Storage STORAGE_COLOR
+end box
+
+[-> LogicManager : execute(load pa/...)
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand(load pa/...)
+activate AddressBookParser
+
+create LoadCommand
+AddressBookParser -> LoadCommand
+activate LoadCommand
+
+LoadCommand --> AddressBookParser
+deactivate LoadCommand
+
+AddressBookParser --> LogicManager : u
+deactivate AddressBookParser
+
+LogicManager -> "Storage" : saveArchivedAddressBook()
+activate "Storage"
+
+"Storage" --> LogicManager :
+deactivate "Storage"
+
+LogicManager -> LoadCommand : execute()
+activate LoadCommand
+
+LoadCommand --> LogicManager : result
+deactivate LoadCommand
+LoadCommand -[hidden]-> LogicManager : result
+
+
+LogicManager -> LogicManager : UpdateModelWithStorage()
+activate LogicManager
+
+LogicManager -> "Storage" : ReadAddressBook(...)
+activate "Storage"
+
+"Storage" --> LogicManager
+deactivate "Storage"
+
+LogicManager -> Model : SetAddressBook(...)
+activate Model
+
+Model --> LogicManager
+deactivate Model
+
+LogicManager --> LogicManager
+deactivate LogicManager
+
+destroy LoadCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..599b94c521b 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -14,11 +14,11 @@ Class UserPrefs
Class UniquePersonList
Class Person
-Class Address
-Class Email
Class Name
Class Phone
+Class Module
Class Tag
+Class Gender
Class I #FFFFFF
}
@@ -39,16 +39,14 @@ AddressBook *--> "1" UniquePersonList
UniquePersonList --> "~* all" Person
Person *--> Name
Person *--> Phone
-Person *--> Email
-Person *--> Address
+Person *--> Module
+Person *--> Gender
Person *--> "*" Tag
Person -[hidden]up--> I
UniquePersonList -[hidden]right-> I
Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
ModelManager --> "~* filtered" Person
@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..75da465df91 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -6,6 +6,15 @@ skinparam classBackgroundColor STORAGE_COLOR
package Storage as StoragePackage {
+package "AddressBook Storage" #F4F6F6{
+Class "<
>\nAddressBookStorage" as AddressBookStorage
+Class JsonAddressBookStorage
+Class JsonSerializableAddressBook
+Class JsonAdaptedPerson
+Class JsonAdaptedTag
+Class JsonAdaptedModule
+}
+
package "UserPrefs Storage" #F4F6F6{
Class "<>\nUserPrefsStorage" as UserPrefsStorage
Class JsonUserPrefsStorage
@@ -14,13 +23,7 @@ Class JsonUserPrefsStorage
Class "<>\nStorage" as Storage
Class StorageManager
-package "AddressBook Storage" #F4F6F6{
-Class "<>\nAddressBookStorage" as AddressBookStorage
-Class JsonAddressBookStorage
-Class JsonSerializableAddressBook
-Class JsonAdaptedPerson
-Class JsonAdaptedTag
-}
+
}
@@ -39,5 +42,5 @@ JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
-
+JsonAdaptedPerson --> "*" JsonAdaptedModule
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..bc53e21f812 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -15,6 +15,9 @@ Class PersonListPanel
Class PersonCard
Class StatusBarFooter
Class CommandBox
+Class DialogBox
+Class HBox
+Class AutocompleteParser
}
package Model <> {
@@ -31,6 +34,7 @@ HiddenOutside ..> Ui
UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
+MainWindow *-down-> "1" DialogBox
MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
MainWindow *-down-> "1" StatusBarFooter
@@ -42,6 +46,8 @@ MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
+CommandBox --down-> "1" AutocompleteParser
+DialogBox --|> HBox
PersonListPanel --|> UiPart
PersonCard --|> UiPart
StatusBarFooter --|> UiPart
diff --git a/docs/images/ArchiveAndLoadInitialState.png b/docs/images/ArchiveAndLoadInitialState.png
new file mode 100644
index 00000000000..9e6b4ebe1a1
Binary files /dev/null and b/docs/images/ArchiveAndLoadInitialState.png differ
diff --git a/docs/images/ArchiveSequenceDiagram-Logic.png b/docs/images/ArchiveSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..d325b47eb03
Binary files /dev/null and b/docs/images/ArchiveSequenceDiagram-Logic.png differ
diff --git a/docs/images/ArchiveToExistingFile.png b/docs/images/ArchiveToExistingFile.png
new file mode 100644
index 00000000000..11508945a78
Binary files /dev/null and b/docs/images/ArchiveToExistingFile.png differ
diff --git a/docs/images/ArchiveToNewFile.png b/docs/images/ArchiveToNewFile.png
new file mode 100644
index 00000000000..25a0a3fac11
Binary files /dev/null and b/docs/images/ArchiveToNewFile.png differ
diff --git a/docs/images/AutocompleteExample.png b/docs/images/AutocompleteExample.png
new file mode 100644
index 00000000000..24ef9c311e3
Binary files /dev/null and b/docs/images/AutocompleteExample.png differ
diff --git a/docs/images/Load.png b/docs/images/Load.png
new file mode 100644
index 00000000000..acf4de4e619
Binary files /dev/null and b/docs/images/Load.png differ
diff --git a/docs/images/LoadSequenceDiagram-Logic.png b/docs/images/LoadSequenceDiagram-Logic.png
new file mode 100644
index 00000000000..cd15379f869
Binary files /dev/null and b/docs/images/LoadSequenceDiagram-Logic.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..b4fe43c91ea 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/StoreClass-Logo.png b/docs/images/StoreClass-Logo.png
new file mode 100644
index 00000000000..4c04941c824
Binary files /dev/null and b/docs/images/StoreClass-Logo.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..b28160cdb27 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 11f06d68671..abe0f956510 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoRedoExample1.png b/docs/images/UndoRedoExample1.png
new file mode 100644
index 00000000000..3af1f3dc635
Binary files /dev/null and b/docs/images/UndoRedoExample1.png differ
diff --git a/docs/images/UndoRedoExample2.png b/docs/images/UndoRedoExample2.png
new file mode 100644
index 00000000000..18b6311196f
Binary files /dev/null and b/docs/images/UndoRedoExample2.png differ
diff --git a/docs/images/UndoRedoExample3.png b/docs/images/UndoRedoExample3.png
new file mode 100644
index 00000000000..757d1d2e8d9
Binary files /dev/null and b/docs/images/UndoRedoExample3.png differ
diff --git a/docs/images/UndoRedoExample4.png b/docs/images/UndoRedoExample4.png
new file mode 100644
index 00000000000..006dfc17335
Binary files /dev/null and b/docs/images/UndoRedoExample4.png differ
diff --git a/docs/images/findResult.png b/docs/images/findResult.png
new file mode 100644
index 00000000000..8c692b206a2
Binary files /dev/null and b/docs/images/findResult.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
index b1f70470137..6e75c706e43 100644
Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ
diff --git a/docs/images/isaactodo.png b/docs/images/isaactodo.png
new file mode 100644
index 00000000000..4693c2c61b9
Binary files /dev/null and b/docs/images/isaactodo.png differ
diff --git a/docs/images/jianwen0451.png b/docs/images/jianwen0451.png
new file mode 100644
index 00000000000..f24ce6c23b7
Binary files /dev/null and b/docs/images/jianwen0451.png differ
diff --git a/docs/images/prabhu.png b/docs/images/prabhu.png
new file mode 100644
index 00000000000..bff5177be60
Binary files /dev/null and b/docs/images/prabhu.png differ
diff --git a/docs/images/radieonajax.png b/docs/images/radieonajax.png
new file mode 100644
index 00000000000..ecd7842e3e9
Binary files /dev/null and b/docs/images/radieonajax.png differ
diff --git a/docs/images/storeclass.png b/docs/images/storeclass.png
new file mode 100644
index 00000000000..2b326ac4fcf
Binary files /dev/null and b/docs/images/storeclass.png differ
diff --git a/docs/images/tanyqcollin.png b/docs/images/tanyqcollin.png
new file mode 100644
index 00000000000..816565d4525
Binary files /dev/null and b/docs/images/tanyqcollin.png differ
diff --git a/docs/images/yongqqqq.png b/docs/images/yongqqqq.png
new file mode 100644
index 00000000000..baaf8dd98bc
Binary files /dev/null and b/docs/images/yongqqqq.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..bb4b59411ce 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,18 @@
---
layout: page
-title: AddressBook Level-3
+title: StoreClass
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![Java CI](https://github.com/AY2425S1-CS2103T-W10-4/tp/actions/workflows/gradle.yml/badge.svg?branch=master)](https://github.com/AY2425S1-CS2103T-W10-4/tp/actions/workflows/gradle.yml)
+[![codecov](https://codecov.io/gh/AY2425S1-CS2103T-W10-4/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2425S1-CS2103T-W10-4/tp)
![Ui](images/Ui.png)
+**StoreClass is an application designed to be used by educators in private educational institutions such as tuition
+centers.** StoreClass allows educators to quickly access student information such as contact details, class details and
+other necessary information.
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+* If you are interested in using StoreClass, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing StoreClass, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
diff --git a/docs/team/johndoe.md b/docs/team/RadieonAjax.md
similarity index 100%
rename from docs/team/johndoe.md
rename to docs/team/RadieonAjax.md
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 678ddc8c218..4ee111ee9e4 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -36,7 +36,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, 3, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..c5c19d5e57b 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -6,6 +6,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
+import java.util.regex.Pattern;
/**
* Helper functions for handling strings.
@@ -38,6 +39,38 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
.anyMatch(preppedWord::equalsIgnoreCase);
}
+ /**
+ * Returns true if the {@code phoneNumber} contains the {@code number}.
+ * It requires at least one number to match the phoneNumber.
+ * examples:
+ * containsNumber("123456", "123") == true
+ * containsNumber("123456", "789") == false
+ * containsNumber("123456", "124") == false
+ *
+ * @param phoneNumber cannot be null, must be a single number
+ * @param inputNumber cannot be null, cannot be empty, must be a single number
+ */
+ public static boolean containsNumber(String phoneNumber, String inputNumber) {
+ requireNonNull(phoneNumber);
+ requireNonNull(inputNumber);
+
+ String preppedNumber = inputNumber.trim();
+ checkArgument(!preppedNumber.isEmpty(), "Number parameter cannot be empty");
+
+ String preppedPhoneNumber = phoneNumber.trim();
+ if (preppedPhoneNumber.isEmpty()) {
+ return false;
+ }
+
+ //phone number should not have any space
+ checkArgument(preppedNumber.split("\\s+").length == 1, "Number parameter should be a single word");
+ checkArgument(phoneNumber.split("\\s+").length == 1, "phoneNumber parameter should be a single word");
+
+ Pattern pattern = Pattern.compile("^\\d+$");
+
+ return pattern.matcher(phoneNumber).matches() && phoneNumber.contains(inputNumber);
+ }
+
/**
* Returns a detailed message of the t, including the stack trace.
*/
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 5aa3b91c7d0..d913c5c2f1b 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -8,8 +8,11 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.logic.commands.ArchiveCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.LoadCommand;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.AddressBookParser;
import seedu.address.logic.parser.exceptions.ParseException;
@@ -26,6 +29,7 @@ public class LogicManager implements Logic {
public static final String FILE_OPS_PERMISSION_ERROR_FORMAT =
"Could not save data to file %s due to insufficient permissions to write to the file or the folder.";
+ public static final String LOAD_ERROR_FORMAT = "Could not load date due to the following error: %s";
private final Logger logger = LogsCenter.getLogger(LogicManager.class);
@@ -46,11 +50,15 @@ public LogicManager(Model model, Storage storage) {
public CommandResult execute(String commandText) throws CommandException, ParseException {
logger.info("----------------[USER COMMAND][" + commandText + "]");
+ ReadOnlyAddressBook original = model.getAddressBook();
CommandResult commandResult;
Command command = addressBookParser.parseCommand(commandText);
+ archiveIfNeeded(command, storage, model);
+
commandResult = command.execute(model);
try {
+ updateModelWithStorage(command, storage, model);
storage.saveAddressBook(model.getAddressBook());
} catch (AccessDeniedException e) {
throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
@@ -85,4 +93,31 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ private void updateModelWithStorage(Command command, Storage storage, Model model) throws CommandException {
+ try {
+ if (command instanceof LoadCommand) {
+ ReadOnlyAddressBook readOnlyAddressBook = storage.readAddressBook(((LoadCommand) command)
+ .getLoadPath()).get();
+ model.setAddressBook(readOnlyAddressBook);
+ model.updateAddressBook(readOnlyAddressBook);
+ }
+ } catch (DataLoadingException e) {
+ throw new CommandException(String.format(LOAD_ERROR_FORMAT, e.getMessage()), e);
+ }
+
+ }
+
+ private void archiveIfNeeded(Command command, Storage storage, Model model) throws CommandException {
+ try {
+ ReadOnlyAddressBook original = model.getAddressBook();
+ if (command instanceof ArchiveCommand) {
+ storage.saveArchivedAddressBook(original, ((ArchiveCommand) command).getArchivePath());
+ }
+ } catch (AccessDeniedException e) {
+ throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
+ } catch (IOException ioe) {
+ throw new CommandException(String.format(FILE_OPS_ERROR_FORMAT, ioe.getMessage()), ioe);
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..9a984c9e883 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -39,11 +39,9 @@ public static String format(Person person) {
builder.append(person.getName())
.append("; Phone: ")
.append(person.getPhone())
- .append("; Email: ")
- .append(person.getEmail())
- .append("; Address: ")
- .append(person.getAddress())
- .append("; Tags: ");
+ .append("; Module: ");
+ person.getModules().forEach(builder::append);
+ builder.append("; Tags: ");
person.getTags().forEach(builder::append);
return builder.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 5d7185a9680..60c895bb2a4 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -1,8 +1,8 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-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_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -24,14 +24,15 @@ public class AddCommand extends Command {
+ "Parameters: "
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
+ + PREFIX_GENDER + "GENDER "
+ + PREFIX_MODULE + "MODULE... "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ + PREFIX_GENDER + "male "
+ + PREFIX_MODULE + "MA2103 "
+ + PREFIX_MODULE + "CS2103T "
+ PREFIX_TAG + "friends "
+ PREFIX_TAG + "owesMoney";
@@ -57,6 +58,7 @@ public CommandResult execute(Model model) throws CommandException {
}
model.addPerson(toAdd);
+ model.saveAddressBook();
return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
}
diff --git a/src/main/java/seedu/address/logic/commands/ArchiveCommand.java b/src/main/java/seedu/address/logic/commands/ArchiveCommand.java
new file mode 100644
index 00000000000..9c43ec45109
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ArchiveCommand.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+
+import java.nio.file.Path;
+
+import seedu.address.model.Model;
+/**
+ * Archives the address book.
+ */
+public class ArchiveCommand extends Command {
+
+ public static final String COMMAND_WORD = "archive";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " " + PREFIX_PATH + "FileName\n"
+ + "The file name should be a path of an json file "
+ + "and not contain any slash `/` \n"
+ + "There should be only one path provided.\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_PATH + "mybook.json";
+ public static final String MESSAGE_SUCCESS = "Archive the file to: %1$s";
+
+ private Path archivePath;
+
+ public ArchiveCommand(Path archivePath) {
+ this.archivePath = archivePath;
+ }
+
+ public Path getArchivePath() {
+ return archivePath;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.clearAddressBook();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, archivePath));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ArchiveCommand)) {
+ return false;
+ }
+
+ ArchiveCommand otherLoadCommand = (ArchiveCommand) other;
+ return archivePath.equals(otherLoadCommand.archivePath);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..a234ec93b02 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -18,6 +18,7 @@ public class ClearCommand extends Command {
public CommandResult execute(Model model) {
requireNonNull(model);
model.setAddressBook(new AddressBook());
+ model.saveAddressBook();
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..0b0658c151a 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -42,6 +42,7 @@ public CommandResult execute(Model model) throws CommandException {
Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
model.deletePerson(personToDelete);
+ model.saveAddressBook();
return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..ae63826fbc0 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,8 +1,8 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
-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_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -21,8 +21,8 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -41,12 +41,11 @@ public class EditCommand extends Command {
+ "Parameters: INDEX (must be a positive integer) "
+ "[" + PREFIX_NAME + "NAME] "
+ "[" + PREFIX_PHONE + "PHONE] "
- + "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_GENDER + "GENDER] "
+ + "[" + PREFIX_MODULE + "MODULE] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
+ + PREFIX_PHONE + "91234567 ";
public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
@@ -85,6 +84,7 @@ public CommandResult execute(Model model) throws CommandException {
model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.saveAddressBook();
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
}
@@ -97,11 +97,12 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
- Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender());
+ Set updatedModules = editPersonDescriptor.getModules().orElse(personToEdit.getModules());
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedPhone, updatedGender,
+ updatedModules, updatedTags);
}
@Override
@@ -135,8 +136,8 @@ public String toString() {
public static class EditPersonDescriptor {
private Name name;
private Phone phone;
- private Email email;
- private Address address;
+ private Gender gender;
+ private Set modules;
private Set tags;
public EditPersonDescriptor() {}
@@ -148,8 +149,8 @@ public EditPersonDescriptor() {}
public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setName(toCopy.name);
setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
+ setGender(toCopy.gender);
+ setModules(toCopy.modules);
setTags(toCopy.tags);
}
@@ -157,7 +158,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, gender, modules, tags);
}
public void setName(Name name) {
@@ -176,20 +177,20 @@ public Optional getPhone() {
return Optional.ofNullable(phone);
}
- public void setEmail(Email email) {
- this.email = email;
+ public void setModules(Set modules) {
+ this.modules = (modules != null) ? new HashSet<>(modules) : null;
}
- public Optional getEmail() {
- return Optional.ofNullable(email);
+ public Optional> getModules() {
+ return (modules != null) ? Optional.of(Collections.unmodifiableSet(modules)) : Optional.empty();
}
- public void setAddress(Address address) {
- this.address = address;
+ public void setGender(Gender gender) {
+ this.gender = gender;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ public Optional getGender() {
+ return Optional.ofNullable(gender);
}
/**
@@ -223,8 +224,8 @@ public boolean equals(Object other) {
EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other;
return Objects.equals(name, otherEditPersonDescriptor.name)
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
- && Objects.equals(email, otherEditPersonDescriptor.email)
- && Objects.equals(address, otherEditPersonDescriptor.address)
+ && Objects.equals(gender, otherEditPersonDescriptor.gender)
+ && Objects.equals(modules, otherEditPersonDescriptor.modules)
&& Objects.equals(tags, otherEditPersonDescriptor.tags);
}
@@ -233,8 +234,8 @@ public String toString() {
return new ToStringBuilder(this)
.add("name", name)
.add("phone", phone)
- .add("email", email)
- .add("address", address)
+ .add("gender", gender)
+ .add("modules", modules)
.add("tags", tags)
.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java
new file mode 100644
index 00000000000..e2a3f884d78
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java
@@ -0,0 +1,190 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.person.FilterPredicate;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Filters and list out all person in address book whose details match multiple specified conditions.
+ * Matching process is case-insensitive and utilizes the provided keywords to filer result.
+ */
+public class FilterCommand extends Command {
+
+ public static final String COMMAND_WORD = "filter";
+ public static final String MESSAGE_NOT_FILTERED = "At least one field to filter must be provided.";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filter all persons whose details contain all "
+ + "the specified keywords (case-insensitive) as condition and display them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " n/joe m/MA1521";
+
+ private final FilterPersonDescriptor filterPersonDescriptor;
+ private final FilterPredicate predicate;
+
+ /**
+ * @param filterPersonDescriptor details to filter thr person with
+ */
+ public FilterCommand(FilterPersonDescriptor filterPersonDescriptor) {
+ requireNonNull(filterPersonDescriptor);
+
+ this.filterPersonDescriptor = filterPersonDescriptor;
+ this.predicate = new FilterPredicate(filterPersonDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredPersonList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FilterCommand)) {
+ return false;
+ }
+
+ FilterCommand otherFilterCommand = (FilterCommand) other;
+ return filterPersonDescriptor.equals(otherFilterCommand.filterPersonDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("filterPersonDescriptor", filterPersonDescriptor)
+ .toString();
+ }
+
+ /**
+ * Stores the details to filter the person with. Each non-empty field value will replace the
+ * corresponding field value of the person.
+ */
+ public static class FilterPersonDescriptor {
+ private Name name;
+ private Phone phone;
+ private Gender gender;
+ private Set modules;
+ private Set tags;
+
+ public FilterPersonDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public FilterPersonDescriptor(FilterPersonDescriptor toCopy) {
+ setName(toCopy.name);
+ setPhone(toCopy.phone);
+ setGender(toCopy.gender);
+ setModules(toCopy.modules);
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldFiltered() {
+ return CollectionUtil.isAnyNonNull(name, phone, gender, modules, tags);
+ }
+
+ public void setName(Name name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setPhone(Phone phone) {
+ this.phone = phone;
+ }
+
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
+ }
+
+ public void setModules(Set modules) {
+ this.modules = (modules != null) ? new HashSet<>(modules) : null;
+ }
+
+ public Optional> getModules() {
+ return (modules != null) ? Optional.of(Collections.unmodifiableSet(modules)) : Optional.empty();
+ }
+
+ public void setGender(Gender gender) {
+ this.gender = gender;
+ }
+
+ public Optional getGender() {
+ return Optional.ofNullable(gender);
+ }
+
+ /**
+ * Sets {@code tags} to this object's {@code tags}.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public void setTags(Set tags) {
+ this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ }
+
+ /**
+ * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FilterPersonDescriptor)) {
+ return false;
+ }
+
+ FilterPersonDescriptor otherFilterPersonDescriptor = (FilterPersonDescriptor) other;
+ return Objects.equals(name, otherFilterPersonDescriptor.name)
+ && Objects.equals(phone, otherFilterPersonDescriptor.phone)
+ && Objects.equals(gender, otherFilterPersonDescriptor.gender)
+ && Objects.equals(modules, otherFilterPersonDescriptor.modules)
+ && Objects.equals(tags, otherFilterPersonDescriptor.tags);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("phone", phone)
+ .add("gender", gender)
+ .add("modules", modules)
+ .add("tags", tags)
+ .toString();
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index 72b9eddd3a7..a6db289701f 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -5,24 +5,24 @@
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
+ * Finds and lists all persons in address book whose details contains any of the argument keywords.
* Keyword matching is case insensitive.
*/
public class FindCommand extends Command {
public static final String COMMAND_WORD = "find";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose name/tag contain any of "
+ "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ "Example: " + COMMAND_WORD + " alice bob charlie";
- private final NameContainsKeywordsPredicate predicate;
+ private final DetailContainsKeywordsPredicate predicate;
- public FindCommand(NameContainsKeywordsPredicate predicate) {
+ public FindCommand(DetailContainsKeywordsPredicate predicate) {
this.predicate = predicate;
}
diff --git a/src/main/java/seedu/address/logic/commands/GradeCommand.java b/src/main/java/seedu/address/logic/commands/GradeCommand.java
new file mode 100644
index 00000000000..27ab11761f3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/GradeCommand.java
@@ -0,0 +1,114 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Person;
+
+/**
+ * Grades a student in the address book.
+ */
+public class GradeCommand extends Command {
+ public static final String COMMAND_WORD = "grade";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Grades a student identified by the index number "
+ + "used in the displayed person list with a numerical score.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_MODULE + "MODULE "
+ + PREFIX_GRADE + "GRADE\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_MODULE + "MA2103 "
+ + PREFIX_GRADE + "80";
+ public static final String MESSAGE_GRADE_SUCCESS = "Graded Module: %1$s for %2$s";
+ public static final String MESSAGE_INVALID_GRADE = "The grade provided is invalid.";
+ public static final String MESSAGE_INVALID_MODULE = "The module specified is not found.";
+ public static final String MESSAGE_INVALID_PERSON = "The person index provided is invalid.";
+ public static final String MESSAGE_MISMATCH_MODULE_GRADE = "The number of modules and grades"
+ + "provided do not match.";
+
+ private final Index targetIndex;
+ private final Map moduleGrades;
+
+ /**
+ * Creates a GradeCommand to grade the specified {@code Person}
+ */
+ public GradeCommand(Index targetIndex, Map moduleGrades) {
+ requireNonNull(targetIndex, "Target index cannot be null");
+ requireNonNull(moduleGrades, "Module cannot be null");
+ this.targetIndex = targetIndex;
+ this.moduleGrades = new LinkedHashMap<>(moduleGrades);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+ Person personToGrade = lastShownList.get(targetIndex.getZeroBased());
+ Set personModules = new HashSet<>();
+ for (Module mod : personToGrade.getModules()) {
+ // Make a copy of each Module to avoid modifying the original
+ personModules.add(new Module(mod.getModule(), mod.getGrade()));
+ }
+
+ List gradeResults = new ArrayList<>();
+
+ for (Map.Entry entry : moduleGrades.entrySet()) {
+ String moduleName = entry.getKey();
+ Integer grade = entry.getValue();
+
+ if (grade < 0 || grade > 100) {
+ throw new CommandException(MESSAGE_INVALID_GRADE);
+ }
+
+ Optional moduleToGrade = personModules.stream()
+ .filter(m -> m.module.equals(moduleName))
+ .findFirst();
+
+ if (moduleToGrade.isEmpty()) {
+ throw new CommandException(MESSAGE_INVALID_MODULE + " (" + moduleName + ")");
+ }
+
+ // Replace the module with a new instance containing the grade
+ Module updatedModule = new Module(moduleName, grade.toString());
+ personModules.remove(moduleToGrade.get());
+ personModules.add(updatedModule);
+
+ gradeResults.add(String.format("%s: %d", moduleName, grade));
+ }
+
+ Person updatedPerson = new Person(personToGrade.getName(), personToGrade.getPhone(),
+ personToGrade.getGender(), personModules, personToGrade.getTags());
+ model.setPerson(personToGrade, updatedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.saveAddressBook();
+
+ String gradeResultsString = String.join("\n", gradeResults);
+ return new CommandResult(String.format(MESSAGE_GRADE_SUCCESS, personToGrade.getName(), gradeResultsString));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this || (other instanceof GradeCommand
+ && targetIndex.equals(((GradeCommand) other).targetIndex)
+ && moduleGrades.equals(((GradeCommand) other).moduleGrades));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/LoadCommand.java b/src/main/java/seedu/address/logic/commands/LoadCommand.java
new file mode 100644
index 00000000000..cb28b401c7b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LoadCommand.java
@@ -0,0 +1,53 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+
+import java.nio.file.Path;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * This command is to load the model with a file at the path
+ * */
+public class LoadCommand extends Command {
+
+ public static final String COMMAND_WORD = "load";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " " + PREFIX_PATH + "FileName\n"
+ + "The file name should be a path of an existing json file containing an address book "
+ + "and not contain any slash `/` \n"
+ + "There should be only one path provided.\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_PATH + "mybook.json";
+ public static final String MESSAGE_SUCCESS = "Loading file from: %1$s";
+
+ private Path loadPath;
+
+ public LoadCommand(Path loadPath) {
+ this.loadPath = loadPath;
+ }
+
+ public Path getLoadPath() {
+ return loadPath;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ model.clearAddressBook();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, loadPath));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof LoadCommand)) {
+ return false;
+ }
+
+ LoadCommand otherLoadCommand = (LoadCommand) other;
+ return loadPath.equals(otherLoadCommand.loadPath);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java
new file mode 100644
index 00000000000..7ddf12378d7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java
@@ -0,0 +1,39 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Represents a command that redoes the last undone action in the address book.
+ */
+public class RedoCommand extends Command {
+ public static final String COMMAND_WORD = "redo";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Reapplies the last undone change to the address book.\n"
+ + "Example: " + COMMAND_WORD;
+ public static final String MESSAGE_REDO_SUCCESS = "Successfully redone the last undone action!";
+ public static final String MESSAGE_REDO_FAILURE = "There are no actions to redo.";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.canRedoAddressBook()) {
+ throw new CommandException(MESSAGE_REDO_FAILURE);
+ }
+
+ redoLastAction(model);
+ return new CommandResult(MESSAGE_REDO_SUCCESS);
+ }
+
+ /**
+ * Redoes the last action in the address book model.
+ *
+ * @param model The model of the application.
+ */
+ private void redoLastAction(Model model) {
+ model.redoAddressBook(); // Calls the model to redo the action
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..d2d847da0df
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Represents a command that undoes the last action in the address book.
+ */
+public class UndoCommand extends Command {
+ public static final String COMMAND_WORD = "undo";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Reverts the address book to its previous state.\n"
+ + "Example: " + COMMAND_WORD;
+ public static final String MESSAGE_UNDO_SUCCESS = "Successfully undone the last action!";
+ public static final String MESSAGE_UNDO_FAILURE = "There are no actions to undo.";
+
+ /**
+ * Executes the undo command to revert the last action.
+ *
+ * @param model The model of the application, which contains the address book.
+ * @return The result of executing the undo command.
+ * @throws CommandException If there are no actions to undo.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.canUndoAddressBook()) {
+ throw new CommandException(MESSAGE_UNDO_FAILURE);
+ }
+
+ undoLastAction(model);
+ return new CommandResult(MESSAGE_UNDO_SUCCESS);
+ }
+
+ /**
+ * Undoes the last action in the address book model.
+ *
+ * @param model The model of the application.
+ */
+ private void undoLastAction(Model model) {
+ model.undoAddressBook(); // Calls the model to undo the action
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..6ff8f53ac61 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,8 +1,8 @@
package seedu.address.logic.parser;
import static seedu.address.logic.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_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -12,8 +12,8 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -31,21 +31,23 @@ public class AddCommandParser implements Parser {
*/
public AddCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_MODULE,
+ PREFIX_GENDER, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_GENDER,
+ PREFIX_PHONE) || !argMultimap.getPreamble().isEmpty()
+ || argMultimap.getAllValues(PREFIX_MODULE).isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_GENDER);
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
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());
+ Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get());
+ Set moduleList = ParserUtil.parseModules(argMultimap.getAllValues(PREFIX_MODULE));
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, gender, moduleList, 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..2cf079988ff 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -9,14 +9,20 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.ArchiveCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.DeleteCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FilterCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.GradeCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -37,6 +43,7 @@ public class AddressBookParser {
* @return the command based on the user input
* @throws ParseException if the user input does not conform the expected format
*/
+ @SuppressWarnings("checkstyle:Regexp")
public Command parseCommand(String userInput) throws ParseException {
final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
if (!matcher.matches()) {
@@ -56,6 +63,9 @@ public Command parseCommand(String userInput) throws ParseException {
case AddCommand.COMMAND_WORD:
return new AddCommandParser().parse(arguments);
+ case ArchiveCommand.COMMAND_WORD:
+ return new ArchiveCommandParser().parse(arguments);
+
case EditCommand.COMMAND_WORD:
return new EditCommandParser().parse(arguments);
@@ -71,12 +81,27 @@ public Command parseCommand(String userInput) throws ParseException {
case ListCommand.COMMAND_WORD:
return new ListCommand();
+ case LoadCommand.COMMAND_WORD:
+ return new LoadCommandParser().parse(arguments);
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
+ case FilterCommand.COMMAND_WORD:
+ return new FilterCommandParser().parse(arguments);
+
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case GradeCommand.COMMAND_WORD:
+ return new GradeCommandParser().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/ArchiveCommandParser.java b/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java
new file mode 100644
index 00000000000..9bba3b78e3e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.ArchiveCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ArchiveCommand object
+ */
+public class ArchiveCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ArchiveCommand
+ * and returns an ArchiveCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ArchiveCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_PATH);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_PATH) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ArchiveCommand.MESSAGE_USAGE));
+ }
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_PATH);
+ Path path = ParserUtil.parsePathWithoutCheck(argMultimap.getValue(PREFIX_PATH).get());
+ return new ArchiveCommand(path);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AutocompleteParser.java b/src/main/java/seedu/address/logic/parser/AutocompleteParser.java
new file mode 100644
index 00000000000..b6dedce0fff
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AutocompleteParser.java
@@ -0,0 +1,380 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.ArchiveCommand;
+import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.GradeCommand;
+import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses user input for autocomplete features.
+ */
+public class AutocompleteParser {
+ /**
+ * List of commands to match for when autocompleting.
+ */
+ private static final String[] commands = {
+ AddCommand.COMMAND_WORD,
+ ArchiveCommand.COMMAND_WORD,
+ EditCommand.COMMAND_WORD,
+ DeleteCommand.COMMAND_WORD,
+ ClearCommand.COMMAND_WORD,
+ FindCommand.COMMAND_WORD,
+ ListCommand.COMMAND_WORD,
+ ExitCommand.COMMAND_WORD,
+ HelpCommand.COMMAND_WORD,
+ GradeCommand.COMMAND_WORD,
+ UndoCommand.COMMAND_WORD,
+ RedoCommand.COMMAND_WORD,
+ LoadCommand.COMMAND_WORD,
+ FilterCommand.COMMAND_WORD,
+ };
+
+ /**
+ * List of prefixes to match for when autocompleting.
+ */
+ private static final Prefix[] prefixes = {
+ PREFIX_MODULE,
+ PREFIX_TAG,
+ PREFIX_GENDER,
+ PREFIX_PATH
+ };
+
+ /**
+ * List of genders to match for when autocompleting.
+ */
+ private static final String[] validGenders = {
+ "male",
+ "female"
+ };
+
+ private String filePath = "archived";
+
+ /**
+ * Overloaded constructor for AutocompleteParser. Only used for testing purposes.
+ *
+ * @param filePath File directory to use when matching for file paths.
+ */
+ public AutocompleteParser(String filePath) {
+ if (!filePath.isEmpty()) {
+ this.filePath = filePath;
+ }
+ }
+
+ /**
+ * Constructor for AutocompleteParser.
+ */
+ public AutocompleteParser() {
+ }
+
+ /**
+ * Parses user input for a list of suggestions for autocompletion.
+ *
+ * @param userInput Full input from user.
+ * @param ab AddressBook to base the suggestions off of.
+ * @param caretPosition Current caret position in the text field.
+ * @return HashMap of items consisting of (item, full input from user with word under caret substituted with item).
+ */
+ public HashMap parseCommand(String userInput, ReadOnlyAddressBook ab, int caretPosition) {
+ String wordUnderCaret = getWordFromCaretPosition(userInput, caretPosition);
+
+ if (shouldReturnEmptyList(wordUnderCaret)) {
+ return new HashMap<>();
+ }
+ assert(!wordUnderCaret.isEmpty());
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(" " + wordUnderCaret.trim(), prefixes);
+ int startIndex = getPreviousWhitespaceIndex(userInput, caretPosition);
+ int endIndex = getNextWhitespaceIndex(userInput, caretPosition);
+
+ if (isCaretOnFirstWord(userInput, caretPosition)) {
+ return getCommandSuggestions(userInput, wordUnderCaret, startIndex, endIndex);
+ }
+
+ if (argMultimap.getValue(PREFIX_MODULE).isPresent()) {
+ return getModuleSuggestions(userInput, wordUnderCaret, ab, startIndex, endIndex, argMultimap);
+ }
+
+ if (argMultimap.getValue(PREFIX_TAG).isPresent()) {
+ return getTagSuggestions(userInput, wordUnderCaret, ab, startIndex, endIndex, argMultimap);
+ }
+
+ if (argMultimap.getValue(PREFIX_GENDER).isPresent()) {
+ return getGenderSuggestions(userInput, wordUnderCaret, startIndex, endIndex, argMultimap);
+ }
+
+ if (argMultimap.getValue(PREFIX_PATH).isPresent()) {
+ return getPathSuggestions(userInput, wordUnderCaret, startIndex, endIndex, argMultimap);
+ }
+
+ return new HashMap<>();
+ }
+
+ /**
+ * Checks if the conditions for returning an empty list of suggestions are met.
+ *
+ * @param wordUnderCaret The word that is currently being typed under the caret.
+ * @return true if word under caret is empty.
+ */
+ private boolean shouldReturnEmptyList(String wordUnderCaret) {
+ return wordUnderCaret.isEmpty();
+ }
+
+ /**
+ * Generates suggestions for modules based on the current user input.
+ *
+ * @param userInput The full user input string.
+ * @param wordUnderCaret The word that is currently being typed under the caret.
+ * @param ab The AddressBook that contains persons with modules to search for suggestions.
+ * @param startIndex The index of the start of the word under the caret.
+ * @param endIndex The index of the end of the word under the caret.
+ * @param argMultimap The tokenized argument multimap containing the parsed user input.
+ * @return A HashMap of module suggestions.
+ */
+ private HashMap getModuleSuggestions(String userInput, String wordUnderCaret,
+ ReadOnlyAddressBook ab, int startIndex, int endIndex,
+ ArgumentMultimap argMultimap) {
+ HashMap suggestionList = new HashMap<>();
+ for (Person person : ab.getPersonList()) {
+ for (Module module : person.getModules()) {
+ String moduleString = module.module;
+ if (isStringMatching(moduleString, argMultimap.getValue(PREFIX_MODULE).get())
+ && !suggestionList.containsKey(moduleString)) {
+ suggestionList.put(moduleString, getCompleteStringWithReplacement(userInput, wordUnderCaret,
+ moduleString, startIndex, endIndex));
+ }
+ }
+ }
+ return suggestionList;
+ }
+
+ /**
+ * Generates suggestions for tags based on the current user input.
+ *
+ * @param userInput The full user input string.
+ * @param wordUnderCaret The word that is currently being typed under the caret.
+ * @param ab The AddressBook that contains persons with tags to search for suggestions.
+ * @param startIndex The index of the start of the word under the caret.
+ * @param endIndex The index of the end of the word under the caret.
+ * @param argMultimap The tokenized argument multimap containing the parsed user input.
+ * @return A HashMap of tag suggestions.
+ */
+ private HashMap getTagSuggestions(String userInput, String wordUnderCaret, ReadOnlyAddressBook ab,
+ int startIndex, int endIndex, ArgumentMultimap argMultimap) {
+ HashMap suggestionList = new HashMap<>();
+ for (Person person : ab.getPersonList()) {
+ for (Tag tag : person.getTags()) {
+ if (isStringMatching(tag.tagName, argMultimap.getValue(PREFIX_TAG).get())
+ && !suggestionList.containsKey(tag.tagName)) {
+ suggestionList.put(tag.tagName, getCompleteStringWithReplacement(userInput, wordUnderCaret,
+ tag.tagName, startIndex, endIndex));
+ }
+ }
+ }
+ return suggestionList;
+ }
+
+ /**
+ * Generates suggestions for genders based on the current user input.
+ *
+ * @param userInput The full user input string.
+ * @param wordUnderCaret The word that is currently being typed under the caret.
+ * @param startIndex The index of the start of the word under the caret.
+ * @param endIndex The index of the end of the word under the caret.
+ * @param argMultimap The tokenized argument multimap containing the parsed user input.
+ * @return A HashMap of gender suggestions.
+ */
+ private HashMap getGenderSuggestions(String userInput, String wordUnderCaret,
+ int startIndex, int endIndex, ArgumentMultimap argMultimap) {
+ HashMap suggestionList = new HashMap<>();
+ for (String gender : validGenders) {
+ if (isStringMatching(gender, argMultimap.getValue(PREFIX_GENDER).get())) {
+ suggestionList.put(gender, getCompleteStringWithReplacement(userInput, wordUnderCaret, gender,
+ startIndex, endIndex));
+ }
+ }
+ return suggestionList;
+ }
+
+ /**
+ * Generates suggestions for file paths based on the current user input.
+ *
+ * @param userInput The full user input string.
+ * @param wordUnderCaret The word that is currently being typed under the caret.
+ * @param startIndex The index of the start of the word under the caret.
+ * @param endIndex The index of the end of the word under the caret.
+ * @param argMultimap The tokenized argument multimap containing the parsed user input.
+ * @return A HashMap of file path suggestions.
+ */
+ private HashMap getPathSuggestions(String userInput, String wordUnderCaret,
+ int startIndex, int endIndex, ArgumentMultimap argMultimap) {
+ HashMap suggestionList = new HashMap<>();
+ for (String file : getAllFilesInArchiveDirectory()) {
+ if (isStringMatching(file, argMultimap.getValue(PREFIX_PATH).get())) {
+ suggestionList.put(file, getCompleteStringWithReplacement(userInput, wordUnderCaret, file,
+ startIndex, endIndex));
+ }
+ }
+ return suggestionList;
+ }
+
+ /**
+ * Generates suggestions for commands based on the current user input.
+ *
+ * @param userInput The full user input string.
+ * @param wordUnderCaret The word that is currently being typed under the caret.
+ * @param startIndex The index of the start of the word under the caret.
+ * @param endIndex The index of the end of the word under the caret.
+ * @return A HashMap of command suggestions.
+ */
+ private HashMap getCommandSuggestions(String userInput, String wordUnderCaret,
+ int startIndex, int endIndex) {
+ HashMap suggestionList = new HashMap<>();
+ for (String command : commands) {
+ // If full command has already been typed out, do not show suggestions.
+ if (command.equals(wordUnderCaret)) {
+ return new HashMap<>();
+ }
+ if (isStringMatching(command, wordUnderCaret)) {
+ suggestionList.put(command, getCompleteStringWithReplacement(userInput, wordUnderCaret, command,
+ startIndex, endIndex));
+ }
+ }
+ return suggestionList;
+ }
+
+ private String getWordFromCaretPosition(String fullInput, int caretPosition) {
+ String text = fullInput.substring(0, caretPosition);
+ int startIndex = getPreviousWhitespaceIndex(text, caretPosition);
+
+ return text.substring(startIndex + 1);
+ }
+
+ private boolean isCaretOnFirstWord(String fullInput, int caretPosition) {
+ int startIndex = getPreviousWhitespaceIndex(fullInput, 0);
+ int endIndex = getNextWhitespaceIndex(fullInput, 0);
+
+ return caretPosition >= startIndex && caretPosition <= endIndex;
+ }
+
+ private int getPreviousWhitespaceIndex(String text, int caretPosition) {
+ int index = caretPosition - 1;
+ while (index >= 0 && !Character.isWhitespace(text.charAt(index))) {
+ index--;
+ }
+
+ return index;
+ }
+
+ private int getNextWhitespaceIndex(String text, int caretPosition) {
+ int endIndex = caretPosition;
+ while (endIndex < text.length()
+ && !Character.isWhitespace(text.charAt(endIndex))) {
+ endIndex++;
+ }
+
+ return endIndex;
+ }
+
+ /**
+ * Gets the full user input string with the suggestion slotted in
+ *
+ * @param fullUserInput The full user input string.
+ * @param word The word that is currently being typed under the caret.
+ * @param command The word to replace the word under the caret.
+ * @param startIndex The index of the start of the word under the caret.
+ * @param endIndex The index of the end of the word under the caret.
+ * @return Full user input string with the suggestion slotted in
+ */
+ private String getCompleteStringWithReplacement(String fullUserInput, String word, String command,
+ int startIndex, int endIndex) {
+ String stringToSlotIn;
+ stringToSlotIn = getStringWithPrefix(word, command);
+
+ StringBuffer buf = new StringBuffer(fullUserInput);
+ buf.replace(startIndex + 1, endIndex, stringToSlotIn);
+ return buf.toString();
+ }
+
+ /**
+ * Gets all the file names in the directory "archived".
+ * Will return an empty list if the directory does not exist.
+ * @return An ArrayList with all the name of json file
+ */
+
+ private ArrayList getAllFilesInArchiveDirectory() {
+ File directory = Paths.get(filePath).toFile();
+
+ // Check if the directory exists and is indeed a directory
+ if (directory.exists() && directory.isDirectory()) {
+ // Get all files and directories in the specified path
+ return getAllJsonFiles(directory);
+ } else {
+ // Return an empty list if the directory does not exist
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * Gets all the json file name from a directory of given Path
+ * @param directory The file path to the directory
+ * @return An array list of all json file name
+ * */
+ private ArrayList getAllJsonFiles(File directory) {
+ File[] files = directory.listFiles();
+ assert (files != null);
+ ArrayList result = new ArrayList<>();
+ for (File file : files) {
+ // add file name if it is a json file
+ if (isJson(file)) {
+ result.add(file.getName());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the given file is a json file.
+ * @param file the file to check
+ * */
+ private boolean isJson(File file) {
+ return file.isFile() && file.getName().toLowerCase().endsWith(".json");
+ }
+
+ private String getStringWithPrefix(String word, String command) {
+ for (Prefix prefix : prefixes) {
+ if (word.startsWith(prefix.getPrefix())) {
+ return prefix.getPrefix() + command;
+ }
+ }
+ return command;
+ }
+
+ private boolean isStringMatching(String targetString, String input) {
+ return targetString.toLowerCase().startsWith(input.toLowerCase());
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..7ce9c3d75f3 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -8,8 +8,10 @@ public class CliSyntax {
/* Prefix definitions */
public static final Prefix PREFIX_NAME = new Prefix("n/");
public static final Prefix PREFIX_PHONE = new Prefix("p/");
- public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
+ public static final Prefix PREFIX_GENDER = new Prefix("g/");
+ public static final Prefix PREFIX_MODULE = new Prefix("m/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_GRADE = new Prefix("s/");
+ public static final Prefix PREFIX_PATH = new Prefix("pa/");
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..6ec9ac2dd23 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -2,8 +2,8 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.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_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -17,6 +17,7 @@
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Module;
import seedu.address.model.tag.Tag;
/**
@@ -32,7 +33,8 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_GENDER,
+ PREFIX_MODULE, PREFIX_TAG);
Index index;
@@ -42,7 +44,7 @@ public EditCommand parse(String args) throws ParseException {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_GENDER);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
@@ -52,12 +54,10 @@ public EditCommand parse(String args) throws ParseException {
if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
}
- if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
- editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
- }
- if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
- editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
+ if (argMultimap.getValue(PREFIX_GENDER).isPresent()) {
+ editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()));
}
+ parseModulesForEdit(argMultimap.getAllValues(PREFIX_MODULE)).ifPresent(editPersonDescriptor::setModules);
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
@@ -82,4 +82,19 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars
return Optional.of(ParserUtil.parseTags(tagSet));
}
+ /**
+ * Parses {@code Collection modules} into a {@code Set} if {@code modules} is non-empty.
+ * If {@code modules} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero tags.
+ */
+ private Optional> parseModulesForEdit(Collection modules) throws ParseException {
+ assert modules != null;
+
+ if (modules.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection moduleSet = modules.size() == 1 && modules.contains("") ? Collections.emptySet() : modules;
+ return Optional.of(ParserUtil.parseModules(moduleSet));
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
new file mode 100644
index 00000000000..f64250dfd52
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
@@ -0,0 +1,114 @@
+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.logic.parser.CliSyntax.PREFIX_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Name;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new FilterCommandParser
+ */
+public class FilterCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of FilterCommand
+ * and return a FilterCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FilterCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_GENDER,
+ PREFIX_MODULE, PREFIX_TAG);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_GENDER, PREFIX_PHONE, PREFIX_MODULE, PREFIX_TAG)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_GENDER);
+
+ FilterPersonDescriptor filterPersonDescriptor = new FilterPersonDescriptor();
+
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ filterPersonDescriptor.setName(isNameValid(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())));
+ }
+ if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ filterPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_GENDER).isPresent()) {
+ filterPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()));
+ }
+ parseModulesForFilter(argMultimap.getAllValues(PREFIX_MODULE)).ifPresent(filterPersonDescriptor::setModules);
+ parseTagsForFilter(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(filterPersonDescriptor::setTags);
+
+ if (!filterPersonDescriptor.isAnyFieldFiltered()) {
+ throw new ParseException(FilterCommand.MESSAGE_NOT_FILTERED);
+ }
+ return new FilterCommand(filterPersonDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
+ * If {@code tags} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero tags.
+ */
+ private Optional> parseTagsForFilter(Collection tags) throws ParseException {
+ assert tags != null;
+
+ if (tags.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
+ return Optional.of(ParserUtil.parseTags(tagSet));
+ }
+
+ /**
+ * Parses {@code Collection modules} into a {@code Set} if {@code modules} is non-empty.
+ * If {@code modules} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero tags.
+ */
+ private Optional> parseModulesForFilter(Collection modules) throws ParseException {
+ assert modules != null;
+
+ if (modules.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection moduleSet = modules.size() == 1 && modules.contains("") ? Collections.emptySet() : modules;
+ return Optional.of(ParserUtil.parseModules(moduleSet));
+ }
+
+ /**
+ * Returns true if contains a valid prefix.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Returns name if the name contains one word.
+ * @throws ParseException If the name contains more than one word.
+ */
+ private Name isNameValid(Name name) throws ParseException {
+ String[] words = name.fullName.split("\\s+");
+ if (words.length != 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+ return 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..676e691b5a3 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -6,7 +6,7 @@
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
/**
* Parses input arguments and creates a new FindCommand object
@@ -25,9 +25,9 @@ public FindCommand parse(String args) throws ParseException {
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
- String[] nameKeywords = trimmedArgs.split("\\s+");
+ String[] keywords = trimmedArgs.split("\\s+");
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ return new FindCommand(new DetailContainsKeywordsPredicate(Arrays.asList(keywords)));
}
}
diff --git a/src/main/java/seedu/address/logic/parser/GradeCommandParser.java b/src/main/java/seedu/address/logic/parser/GradeCommandParser.java
new file mode 100644
index 00000000000..5dae0ebead6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/GradeCommandParser.java
@@ -0,0 +1,58 @@
+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.logic.commands.GradeCommand.MESSAGE_MISMATCH_MODULE_GRADE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.GradeCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+
+
+/**
+ * Parses input arguments and creates a new GradeCommand object
+ */
+public class GradeCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the GradeCommand
+ * and returns an GradeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public GradeCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_MODULE, PREFIX_GRADE);
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GradeCommand.MESSAGE_USAGE), pe);
+ }
+
+ List moduleNames = argMultimap.getAllValues(PREFIX_MODULE);
+ List gradeStrings = argMultimap.getAllValues(PREFIX_GRADE);
+
+ if (moduleNames.isEmpty() || gradeStrings.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GradeCommand.MESSAGE_USAGE));
+ }
+
+ if (moduleNames.size() != gradeStrings.size()) {
+ throw new ParseException(String.format(MESSAGE_MISMATCH_MODULE_GRADE));
+ }
+
+ Map moduleGrades = new LinkedHashMap<>();
+ for (int i = 0; i < moduleNames.size(); i++) {
+ String moduleName = moduleNames.get(i);
+ int grade = ParserUtil.parseGrade(gradeStrings.get(i));
+ moduleGrades.put(moduleName, grade);
+ }
+
+ return new GradeCommand(index, moduleGrades);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/LoadCommandParser.java b/src/main/java/seedu/address/logic/parser/LoadCommandParser.java
new file mode 100644
index 00000000000..ef918c2f817
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/LoadCommandParser.java
@@ -0,0 +1,42 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new LoadCommand object
+ */
+public class LoadCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LoadCommand
+ * and returns an LoadCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LoadCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_PATH);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_PATH) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoadCommand.MESSAGE_USAGE));
+ }
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_PATH);
+ Path path = ParserUtil.parsePathWithCheck(argMultimap.getValue(PREFIX_PATH).get());
+ return new LoadCommand(path);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..61b845e23e4 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -2,15 +2,20 @@
import static java.util.Objects.requireNonNull;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
+import seedu.address.logic.commands.ArchiveCommand;
+import seedu.address.logic.commands.LoadCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -21,6 +26,7 @@
public class ParserUtil {
public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+ public static final String MESSAGE_FILE_NOT_EXIST = "The target file is not a file or does not exits!";
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
@@ -66,35 +72,45 @@ public static Phone parsePhone(String phone) throws ParseException {
}
/**
- * Parses a {@code String address} into an {@code Address}.
+ * Parses a {@code String gender} into a {@code Gender}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code address} is invalid.
+ * @throws ParseException if the given {@code Gender} is invalid.
*/
- public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
- String trimmedAddress = address.trim();
- if (!Address.isValidAddress(trimmedAddress)) {
- throw new ParseException(Address.MESSAGE_CONSTRAINTS);
+ public static Gender parseGender(String gender) throws ParseException {
+ requireNonNull(gender);
+ String trimmedGender = gender.trim();
+ if (!Gender.isValidGender(trimmedGender)) {
+ throw new ParseException(Gender.MESSAGE_CONSTRAINTS);
}
- return new Address(trimmedAddress);
+ return new Gender(trimmedGender);
}
/**
- * Parses a {@code String email} into an {@code Email}.
+ * Parses a {@code String module} into a {@code Module}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code email} is invalid.
+ * @throws ParseException if the given {@code module} is invalid.
*/
- public static Email parseEmail(String email) throws ParseException {
- requireNonNull(email);
- String trimmedEmail = email.trim();
- if (!Email.isValidEmail(trimmedEmail)) {
- throw new ParseException(Email.MESSAGE_CONSTRAINTS);
+ public static Module parseModule(String module) throws ParseException {
+ requireNonNull(module);
+ String trimmedModuleName = module.trim();
+ if (!Module.isValidModule(trimmedModuleName) || !Module.isValidLength(trimmedModuleName)) {
+ throw new ParseException(Module.MESSAGE_CONSTRAINTS);
}
- return new Email(trimmedEmail);
+ return new Module(trimmedModuleName);
+ }
+ /**
+ * Parses {@code Collection modules} into a {@code Set}.
+ */
+ public static Set parseModules(Collection modules) throws ParseException {
+ requireNonNull(modules);
+ final Set moduleSet = new HashSet<>();
+ for (String module : modules) {
+ moduleSet.add(parseModule(module));
+ }
+ return moduleSet;
}
-
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -121,4 +137,55 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+ /**
+ * Parses a {@code String grade} into a {@code Grade}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code grade} is invalid.
+ */
+ public static int parseGrade(String grade) throws ParseException {
+ requireNonNull(grade);
+ String trimmedGrade = grade.trim();
+ try {
+ int numericGrade = Integer.parseInt(trimmedGrade);
+ if (!Module.isValidGrade(numericGrade)) {
+ throw new ParseException(Module.GRADE_CONSTRAINTS);
+ }
+ return numericGrade;
+ } catch (NumberFormatException e) {
+ throw new ParseException(Module.GRADE_CONSTRAINTS);
+ }
+ }
+
+ /**
+ * Parses a {@code String path} into a {@code Path}.
+ * Leading and trailing whitespaces will be trimmed.
+ * */
+
+ public static Path parsePathWithCheck(String path) throws ParseException {
+ requireNonNull(path);
+ path = path.trim();
+ final Path parsedPath = Paths.get("archived", path);
+ if (!path.endsWith(".json") || path.contains("/")) {
+ throw new ParseException(LoadCommand.MESSAGE_USAGE);
+ } else if (!Files.exists(parsedPath) || !Files.isRegularFile(parsedPath)) {
+ throw new ParseException(MESSAGE_FILE_NOT_EXIST);
+ }
+ return parsedPath;
+ }
+
+ /**
+ * Parses a {@code String path} into a {@code Path}.
+ * Leading and trailing whitespaces will be trimmed.
+ * */
+
+ public static Path parsePathWithoutCheck(String path) throws ParseException {
+ requireNonNull(path);
+ path = path.trim();
+ final Path parsedPath = Paths.get("archived", path);
+ if (!path.endsWith(".json") || path.contains("/")) {
+ throw new ParseException(ArchiveCommand.MESSAGE_USAGE);
+ }
+ return parsedPath;
+ }
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..489610b4033 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -84,4 +84,53 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+
+ /**
+ * Saves the current state of the address book.
+ * This allows the current state to be restored via undo or redo operations.
+ */
+ void saveAddressBook();
+
+ /**
+ * Clears the current state of the address book.
+ * This clears all state in the undo/redo stack.
+ */
+ void clearAddressBook();
+
+ /**
+ * Restores the previous state of the address book.
+ * This allows the user to undo the last change made to the address book.
+ *
+ * @throws seedu.address.model.VersionedAddressBook.InvalidUndoException if there is no previous state to undo to.
+ */
+ void undoAddressBook();
+
+ /**
+ * Restores the next state of the address book that was undone.
+ * This allows the user to redo the last undone change.
+ *
+ * @throws seedu.address.model.VersionedAddressBook.InvalidRedoException if there is no state to redo to.
+ */
+ void redoAddressBook();
+
+ /**
+ * Update and initializes the address book with a given address book.
+ * This clears and updates the entire undo/redo stack.
+ */
+ void updateAddressBook(ReadOnlyAddressBook addressBook);
+
+ /**
+ * Returns {@code true} if there are states to undo in the address book.
+ *
+ * @return {@code true} if undo is possible, {@code false} otherwise.
+ */
+ boolean canUndoAddressBook();
+
+ /**
+ * Returns {@code true} if there are states to redo in the address book.
+ *
+ * @return {@code true} if redo is possible, {@code false} otherwise.
+ */
+ boolean canRedoAddressBook();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..04bc949758a 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -15,25 +15,29 @@
/**
* Represents the in-memory model of the address book data.
+ * This class manages the address book data and handles user preferences.
*/
public class ModelManager implements Model {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
- private final AddressBook addressBook;
+ private final VersionedAddressBook versionedAddressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
/**
- * Initializes a ModelManager with the given addressBook and userPrefs.
+ * Initializes a ModelManager with the given addressBook and user preferences.
+ *
+ * @param addressBook The address book to be managed.
+ * @param userPrefs The user preferences for the application.
*/
public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
requireAllNonNull(addressBook, userPrefs);
logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
- this.addressBook = new AddressBook(addressBook);
+ this.versionedAddressBook = new VersionedAddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredPersons = new FilteredList<>(this.versionedAddressBook.getPersonList());
}
public ModelManager() {
@@ -79,28 +83,28 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
+ versionedAddressBook.resetData(addressBook);
}
@Override
public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
+ return versionedAddressBook;
}
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
- return addressBook.hasPerson(person);
+ return versionedAddressBook.hasPerson(person);
}
@Override
public void deletePerson(Person target) {
- addressBook.removePerson(target);
+ versionedAddressBook.removePerson(target);
}
@Override
public void addPerson(Person person) {
- addressBook.addPerson(person);
+ versionedAddressBook.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}
@@ -108,9 +112,10 @@ public void addPerson(Person person) {
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
- addressBook.setPerson(target, editedPerson);
+ versionedAddressBook.setPerson(target, editedPerson);
}
+
//=========== Filtered Person List Accessors =============================================================
/**
@@ -128,6 +133,51 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ //=========== Undo and Redo =============================================================
+
+ @Override
+ public void undoAddressBook() {
+ versionedAddressBook.undo();
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ logger.info("Undid last action in address book.");
+ }
+
+ @Override
+ public void updateAddressBook(ReadOnlyAddressBook addressBook) {
+ versionedAddressBook.resetData(addressBook);
+ versionedAddressBook.update(addressBook);
+ }
+
+ @Override
+ public void redoAddressBook() {
+ versionedAddressBook.redo();
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ logger.info("Redid last action in address book.");
+ }
+
+ @Override
+ public void saveAddressBook() {
+ versionedAddressBook.save();
+ logger.info("Saved the current state of the address book.");
+ }
+
+ @Override
+ public void clearAddressBook() {
+ versionedAddressBook.clear();
+ this.setAddressBook(new AddressBook());
+ logger.info("Cleared the address book.");
+ }
+
+ @Override
+ public boolean canUndoAddressBook() {
+ return versionedAddressBook.canUndo();
+ }
+
+ @Override
+ public boolean canRedoAddressBook() {
+ return versionedAddressBook.canRedo();
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -140,7 +190,7 @@ public boolean equals(Object other) {
}
ModelManager otherModelManager = (ModelManager) other;
- return addressBook.equals(otherModelManager.addressBook)
+ return versionedAddressBook.equals(otherModelManager.versionedAddressBook)
&& userPrefs.equals(otherModelManager.userPrefs)
&& filteredPersons.equals(otherModelManager.filteredPersons);
}
diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java
new file mode 100644
index 00000000000..dff355b6b2c
--- /dev/null
+++ b/src/main/java/seedu/address/model/VersionedAddressBook.java
@@ -0,0 +1,162 @@
+package seedu.address.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@code VersionedAddressBook} stores multiple versions of {@code AddressBook} state,
+ * enabling undo and redo functionality.
+ */
+public class VersionedAddressBook extends AddressBook {
+
+ private List addressBookStateList;
+ private int currentStatePointer;
+
+ /**
+ * Initializes a new {@code VersionedAddressBook} with the initial state.
+ *
+ * @param initialState The initial state of the address book.
+ */
+ public VersionedAddressBook(ReadOnlyAddressBook initialState) {
+ super(initialState);
+
+ addressBookStateList = new ArrayList<>();
+ AddressBook newState = new AddressBook(initialState);
+ addressBookStateList.add(newState);
+ currentStatePointer = 0;
+ }
+
+ /**
+ * Saves the current state of the address book.
+ * Clears any redo history when called after undo.
+ */
+ public void save() {
+ // Clear all states after the current pointer to prevent redo states
+ clearRedoStack();
+ saveCurrentState();
+ }
+
+ /**
+ * Saves the current state as a new entry in the address book state list.
+ */
+ private void saveCurrentState() {
+ AddressBook newState = new AddressBook(this); // Create a new state
+ addressBookStateList.add(newState); // Add the new state to the list
+ currentStatePointer++; // Move the pointer to the new current state
+ }
+
+ /**
+ * Restores the {@code AddressBook} to the previous state.
+ *
+ * @throws InvalidUndoException if there is no state to undo to.
+ */
+ public void undo() {
+ if (canUndo()) {
+ currentStatePointer--;
+ ReadOnlyAddressBook previousState = addressBookStateList.get(currentStatePointer);
+ resetData(previousState);
+ return;
+ }
+ throw new InvalidUndoException();
+ }
+
+ /**
+ * Restores the {@code AddressBook} to the next undone state.
+ *
+ * @throws InvalidRedoException if there is no state to redo to.
+ */
+ public void redo() {
+ if (canRedo()) {
+ currentStatePointer++;
+ ReadOnlyAddressBook nextState = addressBookStateList.get(currentStatePointer);
+ resetData(nextState);
+ return;
+ }
+ throw new InvalidRedoException();
+ }
+
+ /**
+ * Clears all states from the Versioned Address Book.
+ */
+ public void clear() {
+ this.addressBookStateList = new ArrayList<>();
+ this.currentStatePointer = 0;
+ AddressBook newState = new AddressBook();
+ addressBookStateList.add(newState);
+ }
+
+ /**
+ * Updates the addressBookStateList with a new state - used exclusively by LoadCommand
+ * @param readOnlyAddressBook new state to be saved
+ */
+ public void update(ReadOnlyAddressBook readOnlyAddressBook) {
+ this.addressBookStateList = new ArrayList<>();
+ this.currentStatePointer = 0;
+ AddressBook newState = new AddressBook(readOnlyAddressBook);
+ addressBookStateList.add(newState);
+ }
+
+ /**
+ * Clears all states after the current state to prevent redo.
+ * This is called when a new state is saved after an undo.
+ */
+ private void clearRedoStack() {
+ // Remove all states beyond the current pointer
+ addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear();
+ }
+
+ /**
+ * Returns {@code true} if there are states to undo.
+ *
+ * @return {@code true} if undo is possible, {@code false} otherwise.
+ */
+ public boolean canUndo() {
+ return currentStatePointer > 0;
+ }
+
+ /**
+ * Returns {@code true} if there are states to redo.
+ *
+ * @return {@code true} if redo is possible, {@code false} otherwise.
+ */
+ public boolean canRedo() {
+ int sizeOfStateList = addressBookStateList.size() - 1;
+ return currentStatePointer < sizeOfStateList;
+ }
+
+ /**
+ * Returns the current state of the address book.
+ *
+ * @return the current {@code ReadOnlyAddressBook}.
+ */
+ public ReadOnlyAddressBook getCurrentState() {
+ return addressBookStateList.get(currentStatePointer); // Return the current state
+ }
+
+ /**
+ * Returns the total number of saved states.
+ *
+ * @return total count of saved states.
+ */
+ public int getTotalStates() {
+ return addressBookStateList.size(); // Return the size of the state list
+ }
+
+ /**
+ * Exception thrown when there is no state to undo to.
+ */
+ public static class InvalidUndoException extends RuntimeException {
+ public InvalidUndoException() {
+ super("No available states to undo to.");
+ }
+ }
+
+ /**
+ * Exception thrown when there is no state to redo to.
+ */
+ public static class InvalidRedoException extends RuntimeException {
+ public InvalidRedoException() {
+ super("No available states to redo to.");
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
deleted file mode 100644
index 469a2cc9a1e..00000000000
--- a/src/main/java/seedu/address/model/person/Address.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's address in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
- */
-public class Address {
-
- public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank";
-
- /*
- * 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 = "[^\\s].*";
-
- public final String value;
-
- /**
- * Constructs an {@code Address}.
- *
- * @param address A valid address.
- */
- public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
- }
-
- /**
- * Returns true if a given string is a valid email.
- */
- public static boolean isValidAddress(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Address)) {
- return false;
- }
-
- Address otherAddress = (Address) other;
- return value.equals(otherAddress.value);
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/DetailContainsKeywordsPredicate.java
similarity index 60%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/seedu/address/model/person/DetailContainsKeywordsPredicate.java
index 62d19be2977..4dcc701dc18 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/person/DetailContainsKeywordsPredicate.java
@@ -9,17 +9,22 @@
/**
* Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
*/
-public class NameContainsKeywordsPredicate implements Predicate {
+public class DetailContainsKeywordsPredicate implements Predicate {
private final List keywords;
- public NameContainsKeywordsPredicate(List keywords) {
+ public DetailContainsKeywordsPredicate(List keywords) {
this.keywords = keywords;
}
@Override
public boolean test(Person person) {
- return keywords.stream()
+ Boolean nameMatched = keywords.stream()
.anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ Boolean tagMatched = person.getTags().stream()
+ .anyMatch(tag -> keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword)));
+
+ return nameMatched || tagMatched;
}
@Override
@@ -29,12 +34,11 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof NameContainsKeywordsPredicate)) {
+ if (!(other instanceof DetailContainsKeywordsPredicate otherPredicate)) {
return false;
}
- NameContainsKeywordsPredicate otherNameContainsKeywordsPredicate = (NameContainsKeywordsPredicate) other;
- return keywords.equals(otherNameContainsKeywordsPredicate.keywords);
+ return keywords.equals(otherPredicate.keywords);
}
@Override
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
deleted file mode 100644
index c62e512bc29..00000000000
--- a/src/main/java/seedu/address/model/person/Email.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's email in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
- */
-public class Email {
-
- private static final String SPECIAL_CHARACTERS = "+_.-";
- public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain "
- + "and adhere to the following constraints:\n"
- + "1. The local-part should only contain alphanumeric characters and these special characters, excluding "
- + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special "
- + "characters.\n"
- + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels "
- + "separated by periods.\n"
- + "The domain name must:\n"
- + " - end with a domain label at least 2 characters long\n"
- + " - have each domain label start and end with alphanumeric characters\n"
- + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
- // alphanumeric and special characters
- private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore
- private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
- + ALPHANUMERIC_NO_UNDERSCORE + ")*";
- private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE
- + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*";
- private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars
- private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX;
- public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
-
- public final String value;
-
- /**
- * Constructs an {@code Email}.
- *
- * @param email A valid email address.
- */
- public Email(String email) {
- requireNonNull(email);
- checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
- value = email;
- }
-
- /**
- * Returns if a given string is a valid email.
- */
- public static boolean isValidEmail(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Email)) {
- return false;
- }
-
- Email otherEmail = (Email) other;
- return value.equals(otherEmail.value);
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/FilterPredicate.java b/src/main/java/seedu/address/model/person/FilterPredicate.java
new file mode 100644
index 00000000000..a6b8294e200
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/FilterPredicate.java
@@ -0,0 +1,111 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor;
+
+/**
+ * Tests that a {@code Person}'s details matches any of the keywords given.
+ */
+public class FilterPredicate implements Predicate {
+ private final FilterPersonDescriptor filterPersonDescriptor;
+
+ public FilterPredicate(FilterPersonDescriptor filterPersonDescriptor) {
+ this.filterPersonDescriptor = filterPersonDescriptor;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ boolean nameMatched = filterName(person);
+ boolean phoneMatched = filterPhone(person);
+ boolean genderMatched = filterGender(person);
+ boolean modulesMatched = filterModules(person);
+ boolean tagsMatched = filterTags(person);
+
+ return nameMatched && phoneMatched && genderMatched && modulesMatched && tagsMatched;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FilterPredicate otherPredicate)) {
+ return false;
+ }
+
+ return filterPersonDescriptor.equals(otherPredicate.filterPersonDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("conditions", filterPersonDescriptor)
+ .toString();
+ }
+
+ /**
+ * Filters a person based on the name specified in the filerPersonDescriptor.
+ * If the name in the filterPersonDescriptor is not present,
+ * this method will return true, indicating that the person passes the filter.
+ */
+ private boolean filterName(Person person) {
+ return filterPersonDescriptor.getName()
+ .map(name -> StringUtil.containsWordIgnoreCase(person.getName().fullName, name.fullName))
+ .orElse(true);
+ }
+
+ /**
+ * Filters a person based on the phone specified in the filerPersonDescriptor.
+ * If the phone in the filterPersonDescriptor is not present,
+ * this method will return true, indicating that the person passes the filter.
+ */
+ private boolean filterPhone(Person person) {
+ return filterPersonDescriptor.getPhone()
+ .map(phone -> StringUtil.containsNumber(person.getPhone().value, phone.value))
+ .orElse(true);
+ }
+
+ /**
+ * Filters a person based on the gender specified in the filerPersonDescriptor.
+ * If the gender in the filterPersonDescriptor is not present,
+ * this method will return true, indicating that the person passes the filter.
+ */
+ private boolean filterGender(Person person) {
+ return filterPersonDescriptor.getGender()
+ .map(gender -> StringUtil.containsWordIgnoreCase(person.getGender().gender, gender.gender))
+ .orElse(true);
+ }
+
+ /**
+ * Filters a person based on the modules specified in the filerPersonDescriptor.
+ * If the modules in the filterPersonDescriptor is not present,
+ * this method will return true, indicating that the person passes the filter.
+ */
+ private boolean filterModules(Person person) {
+ return filterPersonDescriptor.getModules()
+ .map(modules -> modules.stream()
+ .allMatch(module -> person.getModules().stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(module.module, keyword.module)))
+ )
+ .orElse(true);
+ }
+
+ /**
+ * Filters a person based on the tags specified in the filerPersonDescriptor.
+ * If the tags in the filterPersonDescriptor is not present,
+ * this method will return true, indicating that the person passes the filter.
+ */
+ private boolean filterTags(Person person) {
+ return filterPersonDescriptor.getTags()
+ .map(tags -> tags.stream()
+ .allMatch(tag -> person.getTags().stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword.tagName)))
+ )
+ .orElse(true);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Gender.java b/src/main/java/seedu/address/model/person/Gender.java
new file mode 100644
index 00000000000..ee2e3eb6dd0
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Gender.java
@@ -0,0 +1,75 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Student's gender in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidGender(String)}
+ */
+public class Gender {
+
+ public static final String MALE_SYMBOL = "♂";
+ public static final String FEMALE_SYMBOL = "♀";
+ public static final String MESSAGE_CONSTRAINTS =
+ "Gender should be either 'male' or 'female', and it should not be empty.";
+
+ /*
+ * The first character of the gender should only be "male" or "female"
+ */
+ public static final String VALIDATION_REGEX = "^(male|female)$";
+
+ public final String gender;
+
+ /**
+ * Constructs a {@code Gender}.
+ *
+ * @param gender A valid gender.
+ */
+ public Gender(String gender) {
+ requireNonNull(gender);
+ checkArgument(isValidGender(gender), MESSAGE_CONSTRAINTS);
+ this.gender = gender;
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidGender(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Returns gender of person concatenated with its corresponding symbol.
+ */
+ public String getGenderWithSymbol() {
+ return gender.equals("male") ? MALE_SYMBOL : FEMALE_SYMBOL;
+ }
+
+
+ @Override
+ public String toString() {
+ return gender;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Gender)) {
+ return false;
+ }
+
+ Gender otherGender = (Gender) other;
+ return gender.equals(otherGender.gender);
+ }
+
+ @Override
+ public int hashCode() {
+ return gender.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Module.java b/src/main/java/seedu/address/model/person/Module.java
new file mode 100644
index 00000000000..d6e9d64e6e3
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Module.java
@@ -0,0 +1,123 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Person's Module in the address book
+ * Guarantees: immutable; is valid as declared in {@link #isValidModule(String)}
+ */
+public class Module {
+ public static final String MESSAGE_CONSTRAINTS = "Modules should consist of alphanumeric characters and "
+ + "spaces only, and it should be between 1 and 30 characters.";
+ public static final String GRADE_CONSTRAINTS = "Grade should be a number between 0 and 100.";
+ public static final String VALIDATION_REGEX = "^[\\p{Alnum} ]+$";
+
+ private static final int MIN_MODULE_LENGTH = 1;
+ private static final int MAX_MODULE_LENGTH = 30;
+ private static final int MIN_GRADE = 0;
+ private static final int MAX_GRADE = 100;
+ private static final int UNGRADED = -1;
+ public final String module;
+ private int grade;
+ private boolean isGraded;
+ /**
+ * Constructs an {@code Module}.
+ *
+ * @param module a valid Module.
+ */
+ public Module(String module) {
+ requireNonNull(module);
+ checkArgument(isValidModule(module), MESSAGE_CONSTRAINTS);
+ checkArgument(isValidLength(module), MESSAGE_CONSTRAINTS);
+ this.module = module;
+ this.grade = UNGRADED;
+ this.isGraded = false;
+ }
+
+ /**
+ * Constructs an {@code Module}. Only used in grading.
+ *
+ * @param module a valid Module.
+ */
+ public Module(String module, String grade) {
+ requireNonNull(module);
+ checkArgument(isValidModule(module), MESSAGE_CONSTRAINTS);
+ checkArgument(isValidLength(module), MESSAGE_CONSTRAINTS);
+ this.module = module;
+ this.grade = grade.equals("Ungraded") ? UNGRADED : Integer.parseInt(grade);
+ this.isGraded = this.grade >= 0;
+ }
+
+ /**
+ * Constructs an {@code Module} with grade.
+ * @param grade a valid grade (0 - 100).
+ */
+ public void assignGrade(int grade) {
+ checkArgument(isValidGrade(grade), GRADE_CONSTRAINTS);
+ this.grade = grade;
+ this.isGraded = true;
+ }
+ /**
+ * Returns true if the module is graded.
+ */
+ public boolean isGraded() {
+ return isGraded;
+ }
+ /**
+ * Returns true if a given integer is a valid grade.
+ * @param grade an integer.
+ * @return true if grade is a valid value, between (0 - 100).
+ */
+ public static boolean isValidGrade(int grade) {
+ return (grade >= MIN_GRADE && grade <= MAX_GRADE) || grade == UNGRADED;
+ }
+ public String getGrade() {
+ return grade == UNGRADED ? "Ungraded" : String.valueOf(grade);
+ }
+ public String getModule() {
+ return module;
+ }
+ /**
+ * Returns true if a given string is a valid module.
+ */
+ public static boolean isValidModule(String test) {
+ return test.trim().matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Checks if the input value is of valid length.
+ * @param test the module value to be tested.
+ * @return the result of the test.
+ */
+ public static boolean isValidLength(String test) {
+ boolean isValidMinLength = test.length() >= MIN_MODULE_LENGTH;
+ boolean isValidMaxLength = test.length() <= MAX_MODULE_LENGTH;
+ return isValidMinLength && isValidMaxLength;
+ }
+
+ @Override
+ public String toString() {
+ return '[' + module + " | Grade: " + (grade == UNGRADED ? "Ungraded" : grade) + ']';
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Module)) {
+ return false;
+ }
+
+ Module otherModule = (Module) other;
+ return module.equals(otherModule.module);
+ }
+
+ @Override
+ public int hashCode() {
+ return module.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..82e32d112c1 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -10,13 +10,16 @@
public class Name {
public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
+ "Names should only contain alphabets, hyphens, dots, commas, forward slash"
+ + "and spaces, and be between 1 and 100 characters.";
/*
* 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 = "[\\p{Alnum}][\\p{Alnum} ]*";
+ public static final String VALIDATION_REGEX = "^(?=.*[\\p{L}\\p{N}])[\\p{L}\\-\\., /]{1,100}$";
+ private static final int MIN_LENGTH = 1;
+ private static final int MAX_LENGTH = 100;
public final String fullName;
@@ -28,6 +31,7 @@ public class Name {
public Name(String name) {
requireNonNull(name);
checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
+ checkArgument(isValidLength(name), MESSAGE_CONSTRAINTS);
fullName = name;
}
@@ -38,12 +42,28 @@ public static boolean isValidName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Checks the length of the name is valid.
+ * @param test the name to be tested.
+ * @return the result of the test.
+ */
+ private boolean isValidLength(String test) {
+ if (test.trim().isEmpty()) {
+ return false;
+ } else {
+ return test.length() <= MAX_LENGTH;
+ }
+ }
@Override
public String toString() {
return fullName;
}
+ public boolean isSameName(Name otherName) {
+ return this.fullName.equalsIgnoreCase(otherName.fullName);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..4c2efa032a5 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -19,22 +19,22 @@ public class Person {
// Identity fields
private final Name name;
private final Phone phone;
- private final Email email;
+ private final Gender gender;
// Data fields
- private final Address address;
+ private final Set modules = new HashSet<>();
private final Set tags = new HashSet<>();
/**
* 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);
+ public Person(Name name, Phone phone, Gender gender, Set modules, Set tags) {
+ requireAllNonNull(name, phone, gender, modules, tags);
this.name = name;
this.phone = phone;
- this.email = email;
- this.address = address;
+ this.modules.addAll(modules);
this.tags.addAll(tags);
+ this.gender = gender;
}
public Name getName() {
@@ -45,12 +45,12 @@ public Phone getPhone() {
return phone;
}
- public Email getEmail() {
- return email;
+ public Gender getGender() {
+ return gender;
}
- public Address getAddress() {
- return address;
+ public Set getModules() {
+ return Collections.unmodifiableSet(modules);
}
/**
@@ -71,7 +71,7 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.getName().isSameName(getName());
}
/**
@@ -92,15 +92,14 @@ public boolean equals(Object other) {
Person otherPerson = (Person) other;
return name.equals(otherPerson.name)
&& phone.equals(otherPerson.phone)
- && email.equals(otherPerson.email)
- && address.equals(otherPerson.address)
+ && modules.equals(otherPerson.modules)
&& tags.equals(otherPerson.tags);
}
@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, modules, tags);
}
@Override
@@ -108,8 +107,8 @@ public String toString() {
return new ToStringBuilder(this)
.add("name", name)
.add("phone", phone)
- .add("email", email)
- .add("address", address)
+ .add("gender", gender)
+ .add("modules", modules)
.add("tags", tags)
.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index d733f63d739..7ba399af9fd 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -9,10 +9,11 @@
*/
public class Phone {
-
public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
+ "Phone numbers should only contain numbers and be exactly 8 digits long.";
+ public static final String VALIDATION_REGEX = "\\d{8}$";
+
+ private static final int PHONE_LENGTH = 8;
public final String value;
/**
@@ -23,6 +24,7 @@ public class Phone {
public Phone(String phone) {
requireNonNull(phone);
checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
+ checkArgument(isLengthValid(phone), MESSAGE_CONSTRAINTS);
value = phone;
}
@@ -33,6 +35,15 @@ public static boolean isValidPhone(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Returns true if a given string has a valid length of 8.
+ * @param test the phone string to be tested.
+ * @return result of the check.
+ */
+ public boolean isLengthValid(String test) {
+ return test.length() == PHONE_LENGTH;
+ }
+
@Override
public String toString() {
return value;
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index f1a0d4e233b..fe3a317d037 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -9,8 +9,10 @@
*/
public class Tag {
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
+ public static final String MESSAGE_CONSTRAINTS =
+ "Tags names should be alphanumeric, and be between 1 and 30 characters.";
public static final String VALIDATION_REGEX = "\\p{Alnum}+";
+ private static final int MAX_TAG_LENGTH = 30;
public final String tagName;
@@ -22,6 +24,7 @@ public class Tag {
public Tag(String tagName) {
requireNonNull(tagName);
checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
+ checkArgument(isValidLength(tagName), MESSAGE_CONSTRAINTS);
this.tagName = tagName;
}
@@ -32,6 +35,15 @@ public static boolean isValidTagName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Checks if the test tag is valid.
+ * @param test the tag to be tested.
+ * @return the result of the test
+ */
+ private boolean isValidLength(String test) {
+ return test.length() <= MAX_TAG_LENGTH;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..f8eda048a9b 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -6,8 +6,8 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -19,24 +19,26 @@
public class SampleDataUtil {
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"),
- 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"),
- 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"),
- 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"),
- 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"),
- 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"),
- getTagSet("colleagues"))
+ new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Gender("male"),
+ getModuleSet("Chemistry"), getTagSet("new", "IB", "smart")),
+ new Person(new Name("Bernice Yu"), new Phone("99272758"), new Gender("female"),
+ getModuleSet("History"), getTagSet("smart")),
+ new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Gender("female"),
+ getModuleSet("English"), getTagSet("OLevels")),
+ new Person(new Name("David Li"), new Phone("91031282"), new Gender("male"),
+ getModuleSet("English"), getTagSet("OLevels", "smart", "new")),
+ new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Gender("male"),
+ getModuleSet("Mathematics"), getTagSet("ALevels", "smart")),
+ new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Gender("male"),
+ getModuleSet("Tamil"), getTagSet("new", "smart")),
+ new Person(new Name("Evelyn Tan"), new Phone("81234567"), new Gender("female"),
+ getModuleSet("Chinese"), getTagSet("IB", "smart")),
+ new Person(new Name("Farah Ahmed"), new Phone("87987654"), new Gender("male"),
+ getModuleSet("Malay"), getTagSet("NLevels")),
+ new Person(new Name("Gabriel Lim"), new Phone("89876543"), new Gender("male"),
+ getModuleSet("Higher Chinese"), getTagSet("help")),
+ new Person(new Name("Hannah Koh"), new Phone("89765432"), new Gender("female"),
+ getModuleSet("Literature"), getTagSet("new"))
};
}
@@ -57,4 +59,13 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ /**
+ * Returns a module set containing the list of strings given.
+ */
+ public static Set getModuleSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(Module::new)
+ .collect(Collectors.toSet());
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java
index f2e015105ae..d616bb89022 100644
--- a/src/main/java/seedu/address/storage/AddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/AddressBookStorage.java
@@ -42,4 +42,13 @@ public interface AddressBookStorage {
*/
void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
+ /**
+ * Archive the given {@link ReadOnlyAddressBook} to the storage.
+ * @param addressBook cannot be null.
+ * @param filePath the path to archive, can not be null
+ * @throws IOException if there was any problem writing to the file.
+ */
+
+ void saveArchivedAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
+
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedModule.java b/src/main/java/seedu/address/storage/JsonAdaptedModule.java
new file mode 100644
index 00000000000..877ddf86551
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedModule.java
@@ -0,0 +1,70 @@
+package seedu.address.storage;
+
+import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Module;
+
+
+/**
+ * Jackson-friendly version of {@link Module}.
+ */
+class JsonAdaptedModule {
+
+ private final String module;
+ private Integer grade;
+
+ /**
+ * Constructs a {@code JsonAdaptedModule} with the given {@code module}.
+ */
+ @JsonCreator
+ public JsonAdaptedModule(@JsonProperty("module") String module, @JsonProperty("grade") Integer grade) {
+ this.module = module;
+ this.grade = grade;
+ }
+
+ /**
+ * Converts a given {@code Module} into this class for Jackson use.
+ */
+ public JsonAdaptedModule(Module source) {
+ module = source.module;
+ if ("Ungraded".equals(source.getGrade())) {
+ grade = -1;
+ } else {
+ grade = Integer.parseInt(source.getGrade());
+ }
+ }
+ public String getModule() {
+ return module;
+ }
+ public int getGrade() {
+ return grade;
+ }
+ /**
+ * Converts this Jackson-friendly adapted module object into the model's {@code Module} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted module.
+ */
+ public Module toModelType() throws IllegalValueException {
+ if (module == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Module.class.getSimpleName()));
+ }
+
+ if (grade == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Grade"));
+ }
+
+ if (!Module.isValidModule(module)) {
+ throw new IllegalValueException(Module.MESSAGE_CONSTRAINTS);
+ }
+ if (!Module.isValidGrade(grade)) {
+ throw new IllegalValueException(Module.GRADE_CONSTRAINTS);
+ }
+ Module moduleObject = new Module(module);
+ moduleObject.assignGrade(grade);
+ return moduleObject;
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..92957eecece 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,8 +10,8 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -26,8 +26,8 @@ class JsonAdaptedPerson {
private final String name;
private final String phone;
- private final String email;
- private final String address;
+ private final String gender;
+ private final List modules = new ArrayList<>();
private final List tags = new ArrayList<>();
/**
@@ -35,12 +35,15 @@ class JsonAdaptedPerson {
*/
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
+ @JsonProperty("gender") String gender,
+ @JsonProperty("modules") List modules,
+ @JsonProperty("tags") List tags) {
this.name = name;
this.phone = phone;
- this.email = email;
- this.address = address;
+ this.gender = gender;
+ if (modules != null) {
+ this.modules.addAll(modules);
+ }
if (tags != null) {
this.tags.addAll(tags);
}
@@ -52,8 +55,12 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone
public JsonAdaptedPerson(Person source) {
name = source.getName().fullName;
phone = source.getPhone().value;
- email = source.getEmail().value;
- address = source.getAddress().value;
+ gender = source.getGender().gender;
+ modules.addAll(source.getModules().stream()
+ .map(module -> new JsonAdaptedModule(
+ module.getModule(),
+ "Ungraded".equals(module.getGrade()) ? -1 : Integer.parseInt(module.getGrade())))
+ .collect(Collectors.toList()));
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
@@ -70,6 +77,14 @@ public Person toModelType() throws IllegalValueException {
personTags.add(tag.toModelType());
}
+ final List personModules = new ArrayList<>();
+ for (JsonAdaptedModule module : modules) {
+ personModules.add(module.toModelType());
+ }
+
+ if (personModules.isEmpty()) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Module.class.getSimpleName()));
+ }
if (name == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
}
@@ -86,24 +101,15 @@ public Person toModelType() throws IllegalValueException {
}
final Phone modelPhone = new Phone(phone);
- if (email == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()));
+ if (gender == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName()));
}
- if (!Email.isValidEmail(email)) {
- throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
+ if (!Gender.isValidGender(gender)) {
+ throw new IllegalValueException(Gender.MESSAGE_CONSTRAINTS);
}
- final Email modelEmail = new Email(email);
-
- if (address == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
- }
- if (!Address.isValidAddress(address)) {
- throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
- }
- final Address modelAddress = new Address(address);
-
+ final Gender modelGender = new Gender(gender);
+ final Set modelModules = new HashSet<>(personModules);
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ return new Person(modelName, modelPhone, modelGender, modelModules, modelTags);
}
-
}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
index 41e06f264e1..a9cd6147261 100644
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
@@ -23,6 +23,10 @@ public class JsonAddressBookStorage implements AddressBookStorage {
private Path filePath;
+ /**
+ * Create a JsonAddressBookStorage with file Path and default archive path
+ * @param filePath the file path.
+ * */
public JsonAddressBookStorage(Path filePath) {
this.filePath = filePath;
}
@@ -77,4 +81,14 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath);
}
+
+ @Override
+ public void saveArchivedAddressBook(ReadOnlyAddressBook addressBook, Path archivePath) throws IOException {
+ requireNonNull(addressBook);
+ requireNonNull(archivePath);
+
+ FileUtil.createIfMissing(archivePath);
+ JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), archivePath);
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
index 48a9754807d..c820e4f61ef 100644
--- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
+++ b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
@@ -20,6 +20,7 @@ public JsonUserPrefsStorage(Path filePath) {
this.filePath = filePath;
}
+
@Override
public Path getUserPrefsFilePath() {
return filePath;
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 8b84a9024d5..7443b9240a7 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -75,4 +75,18 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
addressBookStorage.saveAddressBook(addressBook, filePath);
}
+ @Override
+ public void saveArchivedAddressBook(ReadOnlyAddressBook addressBook, Path archivedPath) throws IOException {
+ logger.fine("Attempting to write to data file: " + archivedPath);
+ addressBookStorage.saveArchivedAddressBook(addressBook, archivedPath);
+ }
+
+ /**
+ * read from the archived file
+ * */
+ public Optional readArchivedAddressBook(Path archivePath) throws DataLoadingException {
+ logger.fine("Attempting to read data from file: " + archivePath);
+ return addressBookStorage.readAddressBook(archivePath);
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java
index e94ca422ea8..6940aef275d 100644
--- a/src/main/java/seedu/address/storage/UserPrefsStorage.java
+++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java
@@ -18,6 +18,7 @@ public interface UserPrefsStorage {
*/
Path getUserPrefsFilePath();
+
/**
* Returns UserPrefs data from storage.
* Returns {@code Optional.empty()} if storage file is not found.
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
index 9e75478664b..18f34576f8a 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/seedu/address/ui/CommandBox.java
@@ -1,36 +1,110 @@
package seedu.address.ui;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.util.HashMap;
+
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
+import javafx.geometry.Bounds;
+import javafx.geometry.Side;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
+import seedu.address.logic.Logic;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.AutocompleteParser;
import seedu.address.logic.parser.exceptions.ParseException;
/**
* The UI component that is responsible for receiving user command inputs.
*/
public class CommandBox extends UiPart {
-
public static final String ERROR_STYLE_CLASS = "error";
private static final String FXML = "CommandBox.fxml";
+ private static final int BORDER_SIZE = 10;
+ private static final int ITEM_SIZE = 30;
+ private static final double ITEM_PADDING = 1.5;
private final CommandExecutor commandExecutor;
+ private final AutocompleteParser autocompleteParser;
@FXML
private TextField commandTextField;
+ @FXML
+ private ContextMenu autoComplete;
/**
* Creates a {@code CommandBox} with the given {@code CommandExecutor}.
*/
- public CommandBox(CommandExecutor commandExecutor) {
+ public CommandBox(CommandExecutor commandExecutor, Logic logic) {
super(FXML);
this.commandExecutor = commandExecutor;
+ this.autocompleteParser = new AutocompleteParser();
// calls #setStyleToDefault() whenever there is a change to the text of the command box.
commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
+
+ commandTextField.caretPositionProperty().addListener((obs, oldPosition, newPosition) -> {
+ // Call method to update and show the autocomplete suggestions
+ updateAutoComplete(autocompleteParser.parseCommand(commandTextField.getText(), logic.getAddressBook(),
+ newPosition.intValue()));
+ });
}
+ /**
+ * Updates the autocomplete context menu with suggestions based on the current input.
+ */
+ private void updateAutoComplete(HashMap suggestions) {
+ autoComplete.getItems().clear();
+
+ if (suggestions.isEmpty()) {
+ autoComplete.hide();
+ return;
+ }
+
+ for (HashMap.Entry suggestion : suggestions.entrySet()) {
+ MenuItem item = new MenuItem(suggestion.getKey());
+ item.setOnAction(e -> {
+ commandTextField.setText(suggestion.getValue());
+ commandTextField.positionCaret(suggestion.getValue().length());
+ autoComplete.hide();
+ });
+ autoComplete.getItems().add(item);
+
+ autoComplete.setMaxHeight(150);
+ autoComplete.setMaxWidth(400);
+ autoComplete.setAutoFix(false);
+
+ autoComplete.show(commandTextField, Side.BOTTOM, 0, 0);
+
+ // To solve weird JavaFX behavior where scrolling position is not updated after items have changed.
+ autoComplete.getSkin().getNode().requestFocus();
+ commandTextField.fireEvent(new KeyEvent(
+ KeyEvent.KEY_PRESSED, "", "",
+ KeyCode.DOWN, false, false, false, false));
+ }
+ Bounds boundsInScreen = commandTextField.localToScreen(commandTextField.getBoundsInLocal());
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ double height = screenSize.getHeight();
+ double autocompleteHeight = Math.min(suggestions.size(), 5) * ITEM_SIZE
+ + BORDER_SIZE
+ - Math.min(suggestions.size() - 1, 5) * ITEM_PADDING;
+
+ // Position suggestion box above text field if there is insufficient space below the text box
+ if (height - boundsInScreen.getMaxY() < autocompleteHeight) {
+ autoComplete.show(commandTextField, Side.BOTTOM, 0, -autocompleteHeight - commandTextField.getHeight());
+ autoComplete.setOpacity(0.8);
+ } else {
+ autoComplete.show(commandTextField, Side.BOTTOM, 0, 0);
+ autoComplete.setOpacity(1);
+ }
+ }
+
+
/**
* Handles the Enter button pressed event.
*/
diff --git a/src/main/java/seedu/address/ui/DialogBox.java b/src/main/java/seedu/address/ui/DialogBox.java
new file mode 100644
index 00000000000..8d9e55bcf8c
--- /dev/null
+++ b/src/main/java/seedu/address/ui/DialogBox.java
@@ -0,0 +1,91 @@
+package seedu.address.ui;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+import javafx.scene.shape.Rectangle;
+
+/**
+ * Represents a dialog box consisting of an ImageView to represent the speaker's face
+ * and a label containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ Rectangle rectangle = new Rectangle(0, 0, 150, 150);
+ dialog.setText(text);
+ displayPicture.setImage(img);
+ displayPicture.setClip(rectangle);
+ }
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ dialog.getStyleClass().add("reply-label");
+ }
+
+ /**
+ * Turns the dialog box into an error box message.
+ */
+ private void makeErrorMessage() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ getChildren().setAll(tmp);
+ dialog.getStyleClass().add("error-label");
+ }
+
+ /**
+ * Returns a user dialog box message.
+ *
+ * @param text Command text inputted by user.
+ * @param img Image to be used by the dialog box.
+ * @param isError Whether the command resulted in an error.
+ */
+ public static DialogBox getUserDialog(String text, Image img, boolean isError) {
+ var db = new DialogBox(text, img);
+ if (isError) {
+ db.makeErrorMessage();
+ }
+ return db;
+ }
+
+ /**
+ * Returns a chatbot dialog box message.
+ *
+ * @param text Command text inputted by user.
+ * @param img Image to be used by the dialog box.
+ */
+ public static DialogBox getChatBotDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+
+ return db;
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..daae77c2c9d 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://ay2425s1-cs2103t-w10-4.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/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..d403ca3f9b2 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -115,11 +115,12 @@ void fillInnerParts() {
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
+ resultDisplay.greet("Welcome to StoreClass!");
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
- CommandBox commandBox = new CommandBox(this::executeCommand);
+ CommandBox commandBox = new CommandBox(this::executeCommand, this.logic);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
}
@@ -176,7 +177,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
try {
CommandResult commandResult = logic.execute(commandText);
logger.info("Result: " + commandResult.getFeedbackToUser());
- resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
+ resultDisplay.setFeedbackToUser(commandText, commandResult.getFeedbackToUser(), false);
if (commandResult.isShowHelp()) {
handleHelp();
@@ -189,7 +190,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("An error occurred while executing command: " + commandText);
- resultDisplay.setFeedbackToUser(e.getMessage());
+ resultDisplay.setFeedbackToUser(commandText, e.getMessage(), true);
throw e;
}
}
diff --git a/src/main/java/seedu/address/ui/MaxSizedContextMenu.java b/src/main/java/seedu/address/ui/MaxSizedContextMenu.java
new file mode 100644
index 00000000000..6bcd7f736cc
--- /dev/null
+++ b/src/main/java/seedu/address/ui/MaxSizedContextMenu.java
@@ -0,0 +1,36 @@
+package seedu.address.ui;
+
+import javafx.scene.Node;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Menu;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Region;
+
+/**
+ * Modified context menu component that has scrolling mechanism if max height is exceeded
+ */
+public class MaxSizedContextMenu extends ContextMenu {
+ public static final int CONTEXT_MENU_MAX_HEIGHT = 150;
+ public static final int CONTEXT_MENU_MAX_WIDTH = 400;
+
+ /**
+ * Constructor for max sized context menu
+ */
+ public MaxSizedContextMenu() {
+ addEventHandler(Menu.ON_SHOWING, e -> {
+ Node content = getSkin().getNode();
+ if (content instanceof Region regionContent) {
+ regionContent.setMaxHeight(CONTEXT_MENU_MAX_HEIGHT);
+ regionContent.setMaxWidth(CONTEXT_MENU_MAX_WIDTH);
+
+ // To solve weird JavaFX behavior where scrolling position is not updated after items have
+ // changed.
+ content.requestFocus();
+ this.fireEvent(new KeyEvent(
+ KeyEvent.KEY_PRESSED, "", "",
+ KeyCode.UP, false, false, false, false));
+ }
+ });
+ }
+}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..2c271e2f9d1 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -2,11 +2,14 @@
import java.util.Comparator;
+import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
+import javafx.scene.control.Tooltip;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
import seedu.address.model.person.Person;
/**
@@ -33,13 +36,15 @@ public class PersonCard extends UiPart {
@FXML
private Label id;
@FXML
+ private Label gender;
+ @FXML
private Label phone;
@FXML
- private Label address;
+ private FlowPane tags;
@FXML
- private Label email;
+ private FlowPane modules;
@FXML
- private FlowPane tags;
+ private FlowPane grades;
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
@@ -49,11 +54,53 @@ public PersonCard(Person person, int displayedIndex) {
this.person = person;
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
+ gender.setText(person.getGender().getGenderWithSymbol());
phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
- email.setText(person.getEmail().value);
person.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
.forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ person.getModules().stream()
+ .sorted(Comparator.comparing(module -> module.module))
+ .forEach(moduleCode -> {
+ Label moduleLabel = new Label(moduleCode.module);
+
+ // Color-coding based on grade
+ String gradeString = moduleCode.getGrade();
+ int gradeValue = 0; // Default to 0 if parsing is needed
+
+ if (gradeString.equalsIgnoreCase("Ungraded")) {
+ // Set style for ungraded modules
+ moduleLabel.setStyle("-fx-background-color: #B0BEC5; -fx-background-radius: 5; "
+ + "-fx-text-fill: black; -fx-padding: 5 10; -fx-font-weight: bold;");
+ moduleLabel.setTooltip(new Tooltip(moduleCode.module + " (Ungraded)"));
+ } else {
+ try {
+ gradeValue = Integer.parseInt(gradeString);
+ // Set color based on grade range
+ if (gradeValue >= 50) {
+ moduleLabel.setStyle("-fx-background-color: #4CAF50; -fx-background-radius: 5; "
+ + "-fx-text-fill: white; -fx-padding: 5 10; -fx-font-weight: bold;");
+ } else {
+ moduleLabel.setStyle("-fx-background-color: #F44336; -fx-background-radius: 5; "
+ + "-fx-text-fill: white; -fx-padding: 5 10; -fx-font-weight: bold;");
+ }
+ // Tooltip for graded modules
+ moduleLabel.setTooltip(new Tooltip("Module: " + moduleCode.module + "\nGrade: "
+ + gradeValue));
+ } catch (NumberFormatException e) {
+ // Handle unexpected non-numeric grades gracefully
+ moduleLabel.setStyle("-fx-background-color: #B0BEC5; -fx-background-radius: 5; "
+ + "-fx-text-fill: black; -fx-padding: 5 10; -fx-font-weight: bold;");
+ moduleLabel.setTooltip(new Tooltip(moduleCode.module + " (Invalid grade)"));
+ }
+ }
+
+ // Add the moduleLabel directly to the modules FlowPane
+ modules.getChildren().add(moduleLabel);
+ });
+ gender.textFillProperty().bind(
+ Bindings.when(gender.textProperty().isEqualTo("♂"))
+ .then(Color.LIGHTBLUE)
+ .otherwise(Color.PINK));
}
}
diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java
index 7d98e84eedf..a8af1b527f0 100644
--- a/src/main/java/seedu/address/ui/ResultDisplay.java
+++ b/src/main/java/seedu/address/ui/ResultDisplay.java
@@ -1,10 +1,11 @@
package seedu.address.ui;
-import static java.util.Objects.requireNonNull;
-
import javafx.fxml.FXML;
+import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
/**
* A ui for the status bar that is displayed at the header of the application.
@@ -13,16 +14,51 @@ public class ResultDisplay extends UiPart {
private static final String FXML = "ResultDisplay.fxml";
+ private final Image userImage = new Image(this.getClass().getResourceAsStream("/images/userimage.png"));
+ private final Image chatBotImage = new Image(this.getClass().getResourceAsStream("/images/storeclass.png"));
+
@FXML
private TextArea resultDisplay;
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
public ResultDisplay() {
super(FXML);
}
- public void setFeedbackToUser(String feedbackToUser) {
- requireNonNull(feedbackToUser);
- resultDisplay.setText(feedbackToUser);
+ /**
+ * Bind vvalue property of scroll pane so that it scrolls to the bottom after each command.
+ */
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
}
+ /**
+ * Outputs a feedback message in the chat panel in response to a user command.
+ *
+ * @param commandText Command sent by user.
+ * @param feedbackToUser Response of chatbot to user.
+ * @param isError Whether the command resulted in an error.
+ */
+ public void setFeedbackToUser(String commandText, String feedbackToUser, boolean isError) {
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(commandText, userImage, isError),
+ DialogBox.getChatBotDialog(feedbackToUser, chatBotImage)
+ );
+ }
+
+ /**
+ * Outputs a greeting message from chatbot.
+ *
+ * @param message Message for chatbot to greet with.
+ */
+ @FXML
+ public void greet(String message) {
+ dialogContainer.getChildren().addAll(
+ DialogBox.getChatBotDialog(message, chatBotImage)
+ );
+ }
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..1f2055b0565 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/storeclass.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/storeclass.png b/src/main/resources/images/storeclass.png
new file mode 100644
index 00000000000..2b326ac4fcf
Binary files /dev/null and b/src/main/resources/images/storeclass.png differ
diff --git a/src/main/resources/images/userimage.png b/src/main/resources/images/userimage.png
new file mode 100644
index 00000000000..d60aea7d62f
Binary files /dev/null and b/src/main/resources/images/userimage.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 124283a392e..f161f257d76 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -2,8 +2,13 @@
+
-
+
+
+
+
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..a757b353d91 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -76,15 +76,18 @@
-fx-background-color: -fx-focus-color;
}
-.split-pane:horizontal .split-pane-divider {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: transparent transparent transparent #4d4d4d;
+.split-pane > .split-pane-divider {
+ -fx-background-color: #18181B;
+ -fx-border-color: #18181B;
+ -fx-border-width: 2;
+ -fx-padding: 0;
}
.split-pane {
- -fx-border-radius: 1;
- -fx-border-width: 1;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-radius: 0;
+ -fx-border-width: 0;
+ -fx-padding: 0;
+ -fx-background-color: #18181B;
}
.list-view {
@@ -107,6 +110,11 @@
-fx-background-color: #515658;
}
+.list-cell:filled:first {
+ -fx-border-radius: 5;
+ -fx-background-radius: 5;
+}
+
.list-cell:filled:selected {
-fx-background-color: #424d5f;
}
@@ -122,8 +130,9 @@
.cell_big_label {
-fx-font-family: "Segoe UI Semibold";
- -fx-font-size: 16px;
+ -fx-font-size: 20px;
-fx-text-fill: #010504;
+ -fx-font-weight: bold;
}
.cell_small_label {
@@ -133,17 +142,23 @@
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #27272a;
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-background-color: #27272a;
+ -fx-border-color: #27272a;
+ -fx-border-top-width: 1px;
+}
+
+.pane-with-border .scrollPane {
+ -fx-background-color: #27272a;
+ -fx-border-color: #27272a;
-fx-border-top-width: 1px;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #18181B;
}
.result-display {
@@ -159,9 +174,9 @@
.status-bar .label {
-fx-font-family: "Segoe UI Light";
- -fx-text-fill: white;
+ -fx-text-fill: #E0F2FE;
-fx-padding: 4px;
- -fx-pref-height: 30px;
+ -fx-pref-height: 20px;
}
.status-bar-with-border {
@@ -189,17 +204,16 @@
}
.context-menu .label {
- -fx-text-fill: white;
+ -fx-text-fill: #E0F2FE;
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #18181B;
}
.menu-bar .label {
- -fx-font-size: 14pt;
- -fx-font-family: "Segoe UI Light";
- -fx-text-fill: white;
+ -fx-font: bold 12pt "Inter";
+ -fx-text-fill: #E0F2FE;
-fx-opacity: 0.9;
}
@@ -282,11 +296,11 @@
}
.scroll-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: transparent;
}
.scroll-bar .thumb {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: #0F172A;
-fx-background-insets: 3;
}
@@ -308,33 +322,41 @@
}
#cardPane {
- -fx-background-color: transparent;
- -fx-border-width: 0;
+ -fx-background-color: #334155;
+ -fx-border-width: 0.5;
+ -fx-border-color: black;
}
#commandTypeLabel {
-fx-font-size: 11px;
- -fx-text-fill: #F70D1A;
+ -fx-text-fill: #E0F2FE;
}
#commandTextField {
- -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-color: #1E293B;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-color: transparent;
-fx-border-insets: 0;
- -fx-border-width: 1;
+ -fx-border-width: 0;
-fx-font-family: "Segoe UI Light";
- -fx-font-size: 13pt;
- -fx-text-fill: white;
+ -fx-font-size: 14pt;
+ -fx-prompt-text-fill: #E0F2FE;
+ -fx-text-fill: #E0F2FE;
}
#filterField, #personListPanel, #personWebpage {
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
-#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
+.scroll-pane {
+ -fx-background-color: #475569;
-fx-background-radius: 0;
+ -fx-border-radius: 0;
+}
+
+.scroll-pane .viewport {
+ -fx-background-color: #475569;
+ -fx-border-radius: 0;
}
#tags {
@@ -350,3 +372,32 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#modules .label {
+ -fx-text-fill: black;
+ -fx-font-weight: bold;
+ -fx-font-family: "Noto Sans";
+ -fx-background-color: #ea580c;
+ -fx-padding: 7;
+ -fx-border-insets: 3;
+ -fx-background-insets: 3;
+ -fx-border-radius: 20;
+ -fx-background-radius: 20;
+ -fx-font-size: 16;
+}
+
+#personListLabel {
+ -fx-background-color: #0F172A;
+ -fx-padding: 20;
+}
+
+#personListLabel .label {
+ -fx-text-fill: #E0F2FE;
+ -fx-font-size: 20px;
+ -fx-font-weight: bold;
+}
+
+#autoComplete {
+ -fx-background-color: derive(#1d1d1d, 35%);
+ -fx-border-color: #27272a;
+}
diff --git a/src/main/resources/view/DialogBox.css b/src/main/resources/view/DialogBox.css
new file mode 100644
index 00000000000..63fcc52cad2
--- /dev/null
+++ b/src/main/resources/view/DialogBox.css
@@ -0,0 +1,41 @@
+.label {
+ -fx-background-color: #1E293B;
+ -fx-border-color: #0F172A;
+ -fx-border-width: 1px;
+ -fx-background-radius: 1em;
+ -fx-border-radius: 1em;
+ -fx-font: medium 1.25em "Inter";
+ -fx-text-fill: #16a34a;
+}
+
+.reply-label {
+ -fx-text-fill: #E0F2FE;
+ -fx-background-color: #1E293B;
+ -fx-border-color: #0F172A;
+ -fx-background-radius: 1em 1em 1em 0;
+ -fx-border-radius: 1em 1em 1em 0;
+}
+
+.error-label {
+ -fx-background-color: #1E293B;
+ -fx-border-color: #0F172A;
+ -fx-border-width: 1px;
+ -fx-background-radius: 1em;
+ -fx-border-radius: 1em;
+ -fx-font: medium 1.25em "Inter";
+ -fx-text-fill: #dc2626;
+}
+
+#displayPicture {
+ /* Shadow effect on image. */
+ -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 10, 0.5, 5, 5);
+
+ /* Change size of image. */
+ -fx-scale-x: 1;
+ -fx-scale-y: 1;
+
+ /* Rotate image clockwise by degrees. */
+ -fx-rotate: 0;
+ -fx-border-radius: 100%;
+ -fx-background-radius: 50px;
+}
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 00000000000..f075c81a4db
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..35d6aefd1ae 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -1,7 +1,6 @@
-
@@ -12,9 +11,9 @@
+ title="StoreClass" minWidth="800" minHeight="700" onCloseRequest="#handleExit">
-
+
@@ -33,26 +32,15 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index 84e09833a87..bbc67f1e812 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -7,30 +7,47 @@
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml
index a1bb6bbace8..0744dbfb882 100644
--- a/src/main/resources/view/PersonListPanel.fxml
+++ b/src/main/resources/view/PersonListPanel.fxml
@@ -2,7 +2,12 @@
+
+
+
+
+
diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml
index 01b691792a9..37d2da70416 100644
--- a/src/main/resources/view/ResultDisplay.fxml
+++ b/src/main/resources/view/ResultDisplay.fxml
@@ -1,9 +1,11 @@
-
-
+
+
-
-
-
+
+
+
+
+
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
index 6a4d2b7181c..1959aebab68 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
@@ -1,13 +1,9 @@
{
"persons": [ {
"name": "Valid Person",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
+ "phone": "94824992"
}, {
"name": "Person With Invalid Phone Field",
- "phone": "948asdf2424",
- "email": "hans@example.com",
- "address": "4th street"
+ "phone": "948asdf2424"
} ]
}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
index ccd21f7d1a9..987459edf7d 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
@@ -1,8 +1,6 @@
{
"persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
+ "name": "Person with invalid name field Ha!ns Mu@ster",
+ "phone": "94824249"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..3b57d410022 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -2,13 +2,19 @@
"persons": [ {
"name": "Alice Pauline",
"phone": "94351253",
- "email": "alice@example.com",
- "address": "123, Jurong West Ave 6, #08-111",
+ "gender": "female",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 20
+ } ],
"tags": [ "friends" ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
- "email": "pauline@example.com",
- "address": "4th street"
+ "gender": "female",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 20
+ } ]
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..ed4e28d576e 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -1,8 +1,11 @@
{
"persons": [ {
"name": "Hans Muster",
- "phone": "9482424",
- "email": "invalid@email!3e",
- "address": "4th street"
+ "phone": "#9482424",
+ "gender": "male",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 20
+ } ]
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..9816e28c41b 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -3,44 +3,65 @@
"persons" : [ {
"name" : "Alice Pauline",
"phone" : "94351253",
- "email" : "alice@example.com",
- "address" : "123, Jurong West Ave 6, #08-111",
+ "gender": "female",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 0
+ } ],
"tags" : [ "friends" ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
- "email" : "johnd@example.com",
- "address" : "311, Clementi Ave 2, #02-25",
+ "gender": "male",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 0
+ } ],
"tags" : [ "owesMoney", "friends" ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
- "email" : "heinz@example.com",
- "address" : "wall street",
- "tags" : [ ]
+ "gender": "male",
+ "modules" : [ {
+ "module" : "EL1101",
+ "grade" : 0
+ } ],
+ "tags" : [ "runner" ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
- "email" : "cornelia@example.com",
- "address" : "10th street",
+ "gender": "male",
+ "modules" : [ {
+ "module" : "CS1101",
+ "grade" : 0
+ } ],
"tags" : [ "friends" ]
}, {
"name" : "Elle Meyer",
- "phone" : "9482224",
- "email" : "werner@example.com",
- "address" : "michegan ave",
+ "phone" : "94820224",
+ "gender": "female",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 0
+ } ],
"tags" : [ ]
}, {
"name" : "Fiona Kunz",
- "phone" : "9482427",
- "email" : "lydia@example.com",
- "address" : "little tokyo",
+ "phone" : "94824207",
+ "gender": "female",
+ "modules" : [ {
+ "module" : "MA1522",
+ "grade" : 0
+ } ],
"tags" : [ ]
}, {
"name" : "George Best",
- "phone" : "9482442",
- "email" : "anna@example.com",
- "address" : "4th street",
+ "phone" : "94824402",
+ "gender": "male",
+ "modules" : [ {
+ "module" : "EL1101",
+ "grade" : 0
+ } ],
"tags" : [ ]
} ]
}
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java
index c56d407bf3f..b34d5cfbb1e 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java
@@ -140,4 +140,61 @@ public void getDetails_nullGiven_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> StringUtil.getDetails(null));
}
+ @Test
+ public void containsNumber_validInput_correctResult() {
+ // Empty phoneNumber
+ assertFalse(StringUtil.containsNumber("", "123"));
+ assertFalse(StringUtil.containsNumber(" ", "123"));
+
+ // Matches a partial number only
+ assertTrue(StringUtil.containsNumber("12345678", "12"));
+ assertTrue(StringUtil.containsNumber("12345678", "45678"));
+
+ // Matches full number
+ assertTrue(StringUtil.containsNumber("12345678", "12345678"));
+
+ // Does not match partial number
+ assertFalse(StringUtil.containsNumber("12345678", "13"));
+ assertFalse(StringUtil.containsNumber("12345678", "123456789"));
+ assertFalse(StringUtil.containsNumber("12345678", "12347678"));
+ }
+
+ @Test
+ public void containsNumber_validInput_returnFalse() {
+ // inputNumber does not contain in phoneNumber
+ assertFalse(StringUtil.containsNumber("123456", "789"));
+ assertFalse(StringUtil.containsNumber("234567", "0189"));
+ }
+
+ @Test
+ public void containsNumber_invalidPhoneNumber_returnsFalse() {
+ assertFalse(StringUtil.containsNumber("1234a567", "3456"));
+ assertFalse(StringUtil.containsNumber("1234a567", "890"));
+ }
+
+ @Test
+ public void containsNumber_nullWord_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, ()
+ -> StringUtil.containsNumber("12345678", null));
+ }
+
+ @Test
+ public void containsNumber_emptyNumber_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, "Number parameter cannot be empty", ()
+ -> StringUtil.containsNumber("12345678", " "));
+ }
+
+ @Test
+ public void containsNumbers_multipleNumbers_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, "Number parameter should be a single word", ()
+ -> StringUtil.containsNumber("12345678", "12 34"));
+
+ assertThrows(IllegalArgumentException.class, "phoneNumber parameter should be a single word", ()
+ -> StringUtil.containsNumber("1234 5678", "1234"));
+ }
+
+ @Test
+ public void containsNumbers_nullPhoneNumber_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> StringUtil.containsNumber(null, "123"));
+ }
}
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..8a3f97ba962 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -3,8 +3,8 @@
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.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.GENDER_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
@@ -12,13 +12,16 @@
import java.io.IOException;
import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.ArchiveCommand;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.exceptions.CommandException;
@@ -70,23 +73,57 @@ public void execute_validCommand_success() throws Exception {
assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model);
}
+ @Test
+ public void execute_validArchiveCommand_success() throws Exception {
+ String archiveCommand = ArchiveCommand.COMMAND_WORD + " pa/mybook.json";
+ assertCommandSuccess(archiveCommand, String.format(ArchiveCommand.MESSAGE_SUCCESS,
+ Paths.get("archived", "mybook.json")), model);
+ }
+
@Test
public void execute_storageThrowsIoException_throwsCommandException() {
assertCommandFailureForExceptionFromStorage(DUMMY_IO_EXCEPTION, String.format(
LogicManager.FILE_OPS_ERROR_FORMAT, DUMMY_IO_EXCEPTION.getMessage()));
}
+ @Test
+ public void execute_storageThrowsIoException_throwsCommandExceptionArchive() {
+ assertCommandFailureForExceptionFromArchiveStorage(DUMMY_IO_EXCEPTION, String.format(
+ LogicManager.FILE_OPS_ERROR_FORMAT, DUMMY_IO_EXCEPTION.getMessage()));
+ }
+
@Test
public void execute_storageThrowsAdException_throwsCommandException() {
assertCommandFailureForExceptionFromStorage(DUMMY_AD_EXCEPTION, String.format(
LogicManager.FILE_OPS_PERMISSION_ERROR_FORMAT, DUMMY_AD_EXCEPTION.getMessage()));
}
+ @Test
+ public void execute_storageThrowsAdException_throwsCommandExceptionArchive() {
+ assertCommandFailureForExceptionFromArchiveStorage(DUMMY_AD_EXCEPTION, String.format(
+ LogicManager.FILE_OPS_PERMISSION_ERROR_FORMAT, DUMMY_AD_EXCEPTION.getMessage()));
+ }
+
@Test
public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0));
}
+ @Test
+ public void execute_loadCommand() throws Exception {
+
+ String input = "load pa/TestingLogicManager.json";
+ Path tempDir = Paths.get("archived");
+ if (!Files.exists(tempDir)) {
+ tempDir = Files.createDirectory(tempDir);
+ }
+ Path tempFile = tempDir.resolve("TestingLogicManager.json");
+ Path typical = Paths.get("src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json");
+ Files.copy(typical, tempFile);
+ logic.execute(input);
+ Files.deleteIfExists(tempFile);
+ }
+
/**
* Executes the command and confirms that
* - no exceptions are thrown
@@ -165,11 +202,43 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
logic = new LogicManager(model, storage);
// Triggers the saveAddressBook method by executing an add command
- String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY;
+ String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + GENDER_DESC_AMY
+ + MODULE_DESC_AMY;
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
+
+
}
+ /**
+ * Tests the Logic component's handling of an {@code IOException} thrown by the Storage component.
+ *
+ * @param e the exception to be thrown by the Storage component
+ * @param expectedMessage the message expected inside exception thrown by the Logic component
+ */
+ private void assertCommandFailureForExceptionFromArchiveStorage(IOException e, String expectedMessage) {
+ Path prefPath = temporaryFolder.resolve("ExceptionUserPrefs.json");
+
+ // Inject LogicManager with an AddressBookStorage that throws the IOException e when saving
+ JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(prefPath) {
+ @Override
+ public void saveArchivedAddressBook(ReadOnlyAddressBook addressBook, Path archivePath) throws IOException {
+ throw e;
+ }
+ };
+
+ JsonUserPrefsStorage userPrefsStorage =
+ new JsonUserPrefsStorage(temporaryFolder.resolve("ExceptionUserPrefs.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+
+ logic = new LogicManager(model, storage);
+
+ // Trigger the saveArchivedAddressBook method with an archive command
+ String archiveCommand = ArchiveCommand.COMMAND_WORD + " pa/mybook.json";
+ ModelManager expectedModel = new ModelManager();
+ assertCommandFailure(archiveCommand, CommandException.class, expectedMessage, expectedModel);
+ }
+
+
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index 162a0c86031..2d8ba6938a5 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -32,6 +32,7 @@ public void execute_newPerson_success() {
Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
expectedModel.addPerson(validPerson);
+ expectedModel.saveAddressBook();
assertCommandSuccess(new AddCommand(validPerson), model,
String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)),
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..234e7fd942e 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -157,6 +157,41 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void undoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void redoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateAddressBook(ReadOnlyAddressBook addressBook) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void saveAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void clearAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean canUndoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean canRedoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
}
/**
@@ -199,6 +234,11 @@ public void addPerson(Person person) {
public ReadOnlyAddressBook getAddressBook() {
return new AddressBook();
}
+
+ @Override
+ public void saveAddressBook() {
+ // do nothing, but needed to avoid calling the default stub
+ }
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ArchiveCommandTest.java b/src/test/java/seedu/address/logic/commands/ArchiveCommandTest.java
new file mode 100644
index 00000000000..7578af289d8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ArchiveCommandTest.java
@@ -0,0 +1,53 @@
+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.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+public class ArchiveCommandTest {
+
+ private static final Path archivePath = Paths.get("archiveTest", "archiveTest.json");
+ private static final UserPrefs prefs = new UserPrefs();
+
+ @Test
+ public void execute_emptyAddressBook_success() {
+ Model model = new ModelManager(new AddressBook(), prefs);
+ Model expectedModel = new ModelManager();
+ assertCommandSuccess(new ArchiveCommand(archivePath), model,
+ String.format(ArchiveCommand.MESSAGE_SUCCESS, archivePath), expectedModel);
+ }
+
+ @Test
+ public void execute_nonEmptyAddressBook_success() {
+ Model model = new ModelManager(getTypicalAddressBook(), prefs);
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), prefs);
+ expectedModel.setAddressBook(new AddressBook());
+ assertCommandSuccess(new ArchiveCommand(archivePath), model,
+ String.format(ArchiveCommand.MESSAGE_SUCCESS, archivePath), expectedModel);
+ }
+
+ @Test
+ public void equal() {
+ Path path1 = Paths.get("mybook.json");
+ Path path2 = Paths.get("yourbook.json");
+ ArchiveCommand command1 = new ArchiveCommand(path1);
+ ArchiveCommand command2 = new ArchiveCommand(path1);
+ ArchiveCommand command3 = new ArchiveCommand(path2);
+ assertTrue(command1.equals(command1));
+ assertTrue(command1.equals(command2));
+ assertFalse(command1.equals(command3));
+ assertFalse(command1.equals(1));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..938df6c5f17 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -16,6 +16,7 @@ public class ClearCommandTest {
public void execute_emptyAddressBook_success() {
Model model = new ModelManager();
Model expectedModel = new ModelManager();
+ expectedModel.saveAddressBook();
assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
}
@@ -25,6 +26,7 @@ public void execute_nonEmptyAddressBook_success() {
Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
expectedModel.setAddressBook(new AddressBook());
+ expectedModel.saveAddressBook();
assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..fc64c9a19b2 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -2,8 +2,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-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_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -17,7 +17,7 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -30,28 +30,30 @@ public class CommandTestUtil {
public static final String VALID_NAME_BOB = "Bob Choo";
public static final String VALID_PHONE_AMY = "11111111";
public static final String VALID_PHONE_BOB = "22222222";
- public static final String VALID_EMAIL_AMY = "amy@example.com";
- public static final String VALID_EMAIL_BOB = "bob@example.com";
- public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1";
- public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
+ public static final String VALID_GENDER_AMY = "female";
+ public static final String VALID_GENDER_BOB = "male";
+ public static final String VALID_MODULE_AMY = "MA1522";
+ public static final String VALID_MODULE_BOB = "EL1101";
public static final String VALID_TAG_HUSBAND = "husband";
public static final String VALID_TAG_FRIEND = "friend";
+ public static final int VALID_GRADE_AMY = 100;
+ public static final int VALID_GRADE_BOB = 50;
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY;
public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB;
- public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY;
- public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
- public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
- public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
+ public static final String GENDER_DESC_AMY = " " + PREFIX_GENDER + VALID_GENDER_AMY;
+ public static final String GENDER_DESC_BOB = " " + PREFIX_GENDER + VALID_GENDER_BOB;
+ public static final String MODULE_DESC_AMY = " " + PREFIX_MODULE + VALID_MODULE_AMY;
+ public static final String MODULE_DESC_BOB = " " + PREFIX_MODULE + VALID_MODULE_BOB;
public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
- public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
- public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
+ public static final String INVALID_GENDER_DESC = " " + PREFIX_GENDER + "not a gender"; // not a valid gender
+ public static final String INVALID_MODULE_DESC = " " + PREFIX_MODULE + "#sciene";
public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
@@ -62,11 +64,11 @@ public class CommandTestUtil {
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_FRIEND).build();
+ .withPhone(VALID_PHONE_AMY).withGender(VALID_GENDER_AMY)
+ .withModules(VALID_MODULE_AMY).withTags(VALID_TAG_FRIEND).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ .withPhone(VALID_PHONE_BOB).withGender(VALID_GENDER_BOB)
+ .withModules(VALID_MODULE_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
}
/**
@@ -120,7 +122,7 @@ public static void showPersonAtIndex(Model model, Index targetIndex) {
Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased());
final String[] splitName = person.getName().fullName.split("\\s+");
- model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0])));
+ model.updateFilteredPersonList(new DetailContainsKeywordsPredicate(Arrays.asList(splitName[0])));
assertEquals(1, model.getFilteredPersonList().size());
}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..b65c020ca49 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -10,6 +10,8 @@
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import java.util.ArrayList;
+
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.index.Index;
@@ -37,6 +39,7 @@ public void execute_validIndexUnfilteredList_success() {
ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
expectedModel.deletePerson(personToDelete);
+ expectedModel.saveAddressBook();
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
}
@@ -61,6 +64,7 @@ public void execute_validIndexFilteredList_success() {
Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
expectedModel.deletePerson(personToDelete);
+ expectedModel.saveAddressBook();
showNoPerson(expectedModel);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -79,6 +83,83 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_undoRedoSingleValidIndex_success() throws Exception {
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Index lastPersonIndex = Index.fromOneBased(model.getFilteredPersonList().size());
+ Person personToDelete = model.getFilteredPersonList().get(lastPersonIndex.getZeroBased());
+
+ DeleteCommand deleteCommand = new DeleteCommand(lastPersonIndex);
+
+ // Update expected model
+ expectedModel.deletePerson(personToDelete);
+ expectedModel.saveAddressBook();
+
+ // Execute delete command on test model
+ deleteCommand.execute(model);
+ assertFalse(model.getFilteredPersonList().contains(personToDelete));
+
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), model, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ assertTrue(model.getFilteredPersonList().contains(personToDelete));
+
+ expectedModel.redoAddressBook();
+ assertCommandSuccess(new RedoCommand(), model, RedoCommand.MESSAGE_REDO_SUCCESS, expectedModel);
+ assertFalse(model.getFilteredPersonList().contains(personToDelete));
+ }
+
+ @Test
+ public void execute_undoRedoMultipleValidIndex_success() throws Exception {
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ArrayList deletedPersons = new ArrayList<>();
+
+ //Perform multiple deletions, saving deleted persons and updating expected model
+ for (int i = 0; i < 3; i++) {
+ Index lastPersonIndex = Index.fromOneBased(model.getFilteredPersonList().size());
+ Person personToDelete = model.getFilteredPersonList().get(lastPersonIndex.getZeroBased());
+
+ DeleteCommand deleteCommand = new DeleteCommand(lastPersonIndex);
+
+ // Update expected model
+ expectedModel.deletePerson(personToDelete);
+ expectedModel.saveAddressBook();
+
+ // Execute delete command on test model
+ deleteCommand.execute(model);
+
+ // Add deleted person to list and verify they are not in the test model list
+ deletedPersons.add(personToDelete);
+ assertFalse(model.getFilteredPersonList().contains(personToDelete));
+ }
+
+ for (int i = deletedPersons.size() - 1; i >= 0; i--) {
+ Person lastDeletedPerson = deletedPersons.get(i);
+
+ // Undo deletion
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), model, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ assertTrue(model.getFilteredPersonList().contains(lastDeletedPerson));
+ }
+
+ for (Person personToRedoDelete : deletedPersons) {
+ // Redo deletion
+ expectedModel.redoAddressBook();
+ assertCommandSuccess(new RedoCommand(), model, RedoCommand.MESSAGE_REDO_SUCCESS, expectedModel);
+ assertFalse(model.getFilteredPersonList().contains(personToRedoDelete));
+ }
+ }
+
+ @Test
+ public void execute_undoRedoInvalidIndex_failure() {
+ Index invalidIndex = Index.fromOneBased(Integer.MAX_VALUE);
+ DeleteCommand deleteCommand = new DeleteCommand(invalidIndex);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+
+ // no changes made, so undo and redo should fail
+ assertCommandFailure(new UndoCommand(), model, UndoCommand.MESSAGE_UNDO_FAILURE);
+ assertCommandFailure(new RedoCommand(), model, RedoCommand.MESSAGE_REDO_FAILURE);
+ }
+
@Test
public void equals() {
DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON);
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..2d21f2fef8b 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -15,6 +16,8 @@
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import java.util.ArrayList;
+
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.index.Index;
@@ -45,6 +48,7 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() {
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+ expectedModel.saveAddressBook();
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -56,16 +60,17 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
PersonBuilder personInList = new PersonBuilder(lastPerson);
Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withTags(VALID_TAG_HUSBAND).build();
+ .withModules(VALID_MODULE_BOB).withTags(VALID_TAG_HUSBAND).build();
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
+ .withPhone(VALID_PHONE_BOB).withModules(VALID_MODULE_BOB).withTags(VALID_TAG_HUSBAND).build();
EditCommand editCommand = new EditCommand(indexLastPerson, descriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
expectedModel.setPerson(lastPerson, editedPerson);
+ expectedModel.saveAddressBook();
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -78,6 +83,7 @@ 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());
+ expectedModel.saveAddressBook();
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -95,6 +101,7 @@ public void execute_filteredList_success() {
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+ expectedModel.saveAddressBook();
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -146,6 +153,97 @@ public void execute_invalidPersonIndexFilteredList_failure() {
assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_undoRedoSingleValidIndex_success() throws Exception {
+ // Set up model, expected model, and edit descriptor
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Index lastPersonIndex = Index.fromOneBased(model.getFilteredPersonList().size());
+ Person personToEdit = model.getFilteredPersonList().get(lastPersonIndex.getZeroBased());
+ Person editedPerson = new PersonBuilder(personToEdit).withName("New Name").build();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
+ EditCommand editCommand = new EditCommand(lastPersonIndex, descriptor);
+
+ // Update expected model
+ expectedModel.setPerson(personToEdit, editedPerson);
+ expectedModel.saveAddressBook();
+
+ // Execute edit command on test model
+ editCommand.execute(model);
+ assertEquals(editedPerson, model.getFilteredPersonList().get(lastPersonIndex.getZeroBased()));
+
+ // Undo the edit
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), model, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ assertEquals(personToEdit, model.getFilteredPersonList().get(lastPersonIndex.getZeroBased()));
+
+ // Redo the edit
+ expectedModel.redoAddressBook();
+ assertCommandSuccess(new RedoCommand(), model, RedoCommand.MESSAGE_REDO_SUCCESS, expectedModel);
+ assertEquals(editedPerson, model.getFilteredPersonList().get(lastPersonIndex.getZeroBased()));
+ }
+
+ @Test
+ public void execute_undoRedoMultipleValidIndex_success() throws Exception {
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ArrayList originalPersons = new ArrayList<>();
+ ArrayList editedPersons = new ArrayList<>();
+
+ // Perform multiple edits and update the expected model
+ for (int i = 0; i < 3; i++) {
+ Index lastPersonIndex = Index.fromOneBased(model.getFilteredPersonList().size() - i);
+ Person personToEdit = model.getFilteredPersonList().get(lastPersonIndex.getZeroBased());
+ char uniqueNameChar = (char) ('A' + i);
+ Person editedPerson = new PersonBuilder(personToEdit).withName("Edited Name " + uniqueNameChar).build();
+
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
+ EditCommand editCommand = new EditCommand(lastPersonIndex, descriptor);
+
+ // Update expected model
+ expectedModel.setPerson(personToEdit, editedPerson);
+ expectedModel.saveAddressBook();
+
+ // Execute edit command on test model
+ editCommand.execute(model);
+
+ // Add edited person to the list and verify
+ originalPersons.add(personToEdit);
+ editedPersons.add(editedPerson);
+ assertEquals(editedPerson, model.getFilteredPersonList().get(lastPersonIndex.getZeroBased()));
+ }
+
+ // Loop to undo each edit in reverse order
+ for (int i = originalPersons.size(); i > 0; i--) {
+ Person lastEditedPerson = originalPersons.get(i - 1);
+
+ // Undo edit
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), model, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ assertEquals(model.getFilteredPersonList().get(model.getFilteredPersonList().size() - i).getName(),
+ lastEditedPerson.getName());
+ }
+
+ // Loop to redo each edit in original order
+ for (Person personToRedoEdit : editedPersons) {
+ // Redo edit
+ expectedModel.redoAddressBook();
+ assertCommandSuccess(new RedoCommand(), model, RedoCommand.MESSAGE_REDO_SUCCESS, expectedModel);
+ assertTrue(model.getFilteredPersonList().contains(personToRedoEdit));
+ }
+ }
+
+ @Test
+ public void execute_undoRedoInvalidIndex_failure() {
+ Index invalidIndex = Index.fromOneBased(Integer.MAX_VALUE);
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName("Invalid Edit").build();
+ EditCommand editCommand = new EditCommand(invalidIndex, descriptor);
+
+ assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+
+ // Since no changes were made, undo and redo should fail
+ assertCommandFailure(new UndoCommand(), model, UndoCommand.MESSAGE_UNDO_FAILURE);
+ assertCommandFailure(new RedoCommand(), model, RedoCommand.MESSAGE_REDO_FAILURE);
+ }
+
@Test
public void equals() {
final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY);
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..1f5b44d009c 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -5,8 +5,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GENDER_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -44,12 +44,12 @@ public void equals() {
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
- // different email -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build();
+ // different gender -> return false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withGender(VALID_GENDER_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
- // different address -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build();
+ // different module -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withModules(VALID_MODULE_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
// different tags -> returns false
@@ -62,9 +62,9 @@ public void toStringMethod() {
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
String expected = EditPersonDescriptor.class.getCanonicalName() + "{name="
+ editPersonDescriptor.getName().orElse(null) + ", phone="
- + editPersonDescriptor.getPhone().orElse(null) + ", email="
- + editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
+ + editPersonDescriptor.getPhone().orElse(null) + ", gender="
+ + editPersonDescriptor.getGender().orElse(null) + ", modules="
+ + editPersonDescriptor.getModules().orElse(null) + ", tags="
+ editPersonDescriptor.getTags().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
diff --git a/src/test/java/seedu/address/logic/commands/FilterCommandTest.java b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java
new file mode 100644
index 00000000000..b80998244cc
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java
@@ -0,0 +1,130 @@
+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.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BENSON;
+import static seedu.address.testutil.TypicalPersons.ELLE;
+import static seedu.address.testutil.TypicalPersons.FIONA;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.FilterPredicate;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Name;
+
+public class FilterCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void equals() {
+ FilterPersonDescriptor firstDescriptor = new FilterPersonDescriptor();
+ firstDescriptor.setName(new Name("Alice"));
+ FilterCommand firstFilterCommand = new FilterCommand(firstDescriptor);
+
+ FilterPersonDescriptor secondDescriptor = new FilterPersonDescriptor();
+ secondDescriptor.setName(new Name("Alice"));
+ secondDescriptor.setGender(new Gender("female"));
+ FilterCommand secondFilterCommand = new FilterCommand(secondDescriptor);
+
+ // same object -> returns true
+ assertTrue(firstFilterCommand.equals(firstFilterCommand));
+
+ // same values -> returns true
+ FilterCommand filterCommandCopy = new FilterCommand(firstDescriptor);
+ assertTrue(firstFilterCommand.equals(filterCommandCopy));
+
+ // different types -> returns false
+ assertFalse(firstFilterCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(firstFilterCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(firstFilterCommand.equals(secondFilterCommand));
+ }
+
+ @Test
+ public void execute_noConditionSatisfied_noPersonFound() {
+ // name that not in the data
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setName(new Name("hahaha"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ FilterCommand command = new FilterCommand(descriptor);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_oneConditionSatisfied_personFound() {
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setGender(new Gender("female"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
+ FilterCommand command = new FilterCommand(descriptor);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(ALICE, ELLE, FIONA), model.getFilteredPersonList());
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setModules(Set.of(new Module("MA1522")));
+ predicate = preparePredicate(descriptor);
+
+ expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 4);
+ command = new FilterCommand(descriptor);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(ALICE, BENSON, ELLE, FIONA), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_twoConditionSatisfied() {
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setModules(Set.of(new Module("MA1522")));
+ descriptor.setName(new Name("Alice"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
+ FilterCommand command = new FilterCommand(descriptor);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(ALICE), model.getFilteredPersonList());
+ }
+
+
+ @Test
+ public void toStringMethod() {
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setName(new Name("Alice"));
+ FilterCommand filterCommand = new FilterCommand(descriptor);
+ String expected = FilterCommand.class.getCanonicalName() + "{filterPersonDescriptor=" + descriptor + "}";
+ assertEquals(expected, filterCommand.toString());
+ }
+
+ /**
+ * Parses {@code FilterPersonDescription} into a {@code FilterPredicate}.
+ */
+ private FilterPredicate preparePredicate(FilterPersonDescriptor descriptor) {
+ return new FilterPredicate(descriptor);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..2a82b69f224 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -5,7 +5,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BENSON;
import static seedu.address.testutil.TypicalPersons.CARL;
+import static seedu.address.testutil.TypicalPersons.DANIEL;
import static seedu.address.testutil.TypicalPersons.ELLE;
import static seedu.address.testutil.TypicalPersons.FIONA;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
@@ -18,7 +21,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
/**
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
@@ -29,10 +32,10 @@ public class FindCommandTest {
@Test
public void equals() {
- NameContainsKeywordsPredicate firstPredicate =
- new NameContainsKeywordsPredicate(Collections.singletonList("first"));
- NameContainsKeywordsPredicate secondPredicate =
- new NameContainsKeywordsPredicate(Collections.singletonList("second"));
+ DetailContainsKeywordsPredicate firstPredicate =
+ new DetailContainsKeywordsPredicate(Collections.singletonList("first"));
+ DetailContainsKeywordsPredicate secondPredicate =
+ new DetailContainsKeywordsPredicate(Collections.singletonList("second"));
FindCommand findFirstCommand = new FindCommand(firstPredicate);
FindCommand findSecondCommand = new FindCommand(secondPredicate);
@@ -57,7 +60,7 @@ public void equals() {
@Test
public void execute_zeroKeywords_noPersonFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
- NameContainsKeywordsPredicate predicate = preparePredicate(" ");
+ DetailContainsKeywordsPredicate predicate = preparePredicate(" ");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
@@ -67,25 +70,95 @@ public void execute_zeroKeywords_noPersonFound() {
@Test
public void execute_multipleKeywords_multiplePersonsFound() {
String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
- NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz");
+ DetailContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
}
+ @Test
+ public void execute_singleKeyword_tagMatch() {
+ // Single keyword matches one person's tag
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
+ DetailContainsKeywordsPredicate predicate = preparePredicate("friends");
+ FindCommand command = new FindCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ // Assuming "David Li" has the "mentor" tag in the dataset
+ assertEquals(Arrays.asList(ALICE, BENSON, DANIEL), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_multipleKeywords_tagMatch() {
+ // Multiple keywords match multiple persons' tags
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2);
+ DetailContainsKeywordsPredicate predicate = preparePredicate("runner owesMoney");
+ FindCommand command = new FindCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ // Assuming "David Li" and "Irfan Ibrahim" have "mentor" and "tutor" tags respectively
+ assertEquals(Arrays.asList(BENSON, CARL), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_keywordMatchesBothNameAndTag() {
+ // A keyword matches both a person's name and another person's tag
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2);
+ DetailContainsKeywordsPredicate predicate = preparePredicate("owesMoney Carl");
+ FindCommand command = new FindCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ // Assuming "David Li" has the "mentor" tag and "Carl Kurz" has the name "Carl"
+ assertEquals(Arrays.asList(BENSON, CARL), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_partialTagMatch_noPersonFound() {
+ // Partial tag matches should not return any persons
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ DetailContainsKeywordsPredicate predicate = preparePredicate("fri");
+ FindCommand command = new FindCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_noTagMatches_noPersonFound() {
+ // No matching tag should return an empty list
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ DetailContainsKeywordsPredicate predicate = preparePredicate("randomTag");
+ FindCommand command = new FindCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_caseInsensitiveTagMatch() {
+ // Case-insensitive tag matching should work correctly
+ String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1);
+ DetailContainsKeywordsPredicate predicate = preparePredicate("OwEsMoneY");
+ FindCommand command = new FindCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ // Assuming "David Li" has the "mentor" tag in the dataset
+ assertEquals(Arrays.asList(BENSON), model.getFilteredPersonList());
+ }
+
@Test
public void toStringMethod() {
- NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
+ DetailContainsKeywordsPredicate predicate = new DetailContainsKeywordsPredicate(Arrays.asList("keyword"));
FindCommand findCommand = new FindCommand(predicate);
String expected = FindCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";
assertEquals(expected, findCommand.toString());
}
/**
- * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}.
+ * Parses {@code userInput} into a {@code DetailContainsKeywordsPredicate}.
*/
- private NameContainsKeywordsPredicate preparePredicate(String userInput) {
- return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ private DetailContainsKeywordsPredicate preparePredicate(String userInput) {
+ return new DetailContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
}
}
diff --git a/src/test/java/seedu/address/logic/commands/GradeCommandTest.java b/src/test/java/seedu/address/logic/commands/GradeCommandTest.java
new file mode 100644
index 00000000000..84f3402876b
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/GradeCommandTest.java
@@ -0,0 +1,268 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_AMY;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.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.ReadOnlyUserPrefs;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class GradeCommandTest {
+
+ @Test
+ public void constructor_nullFields_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new GradeCommand(Index.fromOneBased(1), null));
+ Map validModuleGrades = new LinkedHashMap<>();
+ validModuleGrades.put(VALID_MODULE_AMY, VALID_GRADE_AMY);
+ assertThrows(NullPointerException.class, () -> new GradeCommand(null, validModuleGrades));
+ }
+
+ @Test
+ public void execute_validGrade_success() throws Exception {
+ ModelStubAcceptingGradeAdded modelStub = new ModelStubAcceptingGradeAdded();
+ Person validPerson = new PersonBuilder().withModules(VALID_MODULE_AMY).build();
+ Map validModuleGrades = new LinkedHashMap<>();
+ validModuleGrades.put(VALID_MODULE_AMY, VALID_GRADE_AMY);
+
+ CommandResult commandResult = new GradeCommand(Index.fromOneBased(1),
+ validModuleGrades).execute(modelStub);
+
+ assertEquals(String.format(GradeCommand.MESSAGE_GRADE_SUCCESS, validPerson.getName(),
+ VALID_MODULE_AMY + ": " + VALID_GRADE_AMY),
+ commandResult.getFeedbackToUser());
+ assertEquals(Arrays.asList(validPerson), modelStub.personsUpdated);
+ }
+
+ @Test
+ public void execute_invalidPersonIndex_throwsCommandException() {
+ ModelStub modelStub = new ModelStubWithPerson(ALICE);
+ Map validModuleGrades = new LinkedHashMap<>();
+ validModuleGrades.put(VALID_MODULE_AMY, VALID_GRADE_AMY);
+
+ GradeCommand gradeCommand = new GradeCommand(Index.fromOneBased(99), validModuleGrades);
+
+ CommandException exception = assertThrows(CommandException.class, () -> gradeCommand.execute(modelStub));
+ assertEquals(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, exception.getMessage());
+ }
+
+ @Test
+ public void execute_invalidModule_throwsCommandException() {
+ ModelStub modelStub = new ModelStubWithPerson(ALICE);
+ String invalidModule = "INVALID_MODULE";
+ Map invalidModuleGrades = new LinkedHashMap<>();
+ invalidModuleGrades.put(invalidModule, VALID_GRADE_AMY);
+ GradeCommand gradeCommand = new GradeCommand(Index.fromOneBased(1), invalidModuleGrades);
+
+ CommandException exception = assertThrows(CommandException.class, () -> gradeCommand.execute(modelStub));
+ assertEquals(GradeCommand.MESSAGE_INVALID_MODULE + " (" + invalidModule + ")", exception.getMessage());
+ }
+ @Test
+ public void equals() {
+ Map moduleGrades = new LinkedHashMap<>();
+ moduleGrades.put(VALID_MODULE_AMY, VALID_GRADE_AMY);
+ GradeCommand gradeFirstCommand = new GradeCommand(Index.fromOneBased(1), moduleGrades);
+ GradeCommand gradeSecondCommand = new GradeCommand(Index.fromOneBased(2), moduleGrades);
+
+ // same object -> returns true
+ assertTrue(gradeFirstCommand.equals(gradeFirstCommand));
+
+ // same values -> returns true
+ GradeCommand gradeFirstCommandCopy = new GradeCommand(Index.fromOneBased(1), moduleGrades);
+ assertTrue(gradeFirstCommand.equals(gradeFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(gradeFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(gradeFirstCommand.equals(null));
+
+ // different index -> returns false
+ assertFalse(gradeFirstCommand.equals(gradeSecondCommand));
+ }
+
+ /**
+ * A Model stub that contains a single person with modules.
+ */
+ private class ModelStubWithPerson extends ModelStub {
+ private final Person person;
+
+ ModelStubWithPerson(Person person) {
+ requireNonNull(person);
+ this.person = person;
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ requireNonNull(person);
+ return this.person.isSamePerson(person);
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ return javafx.collections.FXCollections.observableArrayList(person);
+ }
+ }
+ /**
+ * A default model stub that has all the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getAddressBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBookFilePath(Path addressBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setAddressBook(ReadOnlyAddressBook newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasPerson(Person person) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public boolean canRedoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public boolean canUndoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public void redoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateAddressBook(ReadOnlyAddressBook addressBook) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void undoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public void saveAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public void clearAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ }
+
+ /**
+ * A Model stub that always accepts the grading.
+ */
+ private class ModelStubAcceptingGradeAdded extends ModelStub {
+ final ArrayList personsUpdated = new ArrayList<>();
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ return javafx.collections.FXCollections.observableArrayList(new PersonBuilder()
+ .withModules(VALID_MODULE_AMY).build());
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson) {
+ requireNonNull(editedPerson);
+ personsUpdated.add(editedPerson);
+ }
+
+ @Override
+ public ReadOnlyAddressBook getAddressBook() {
+ return new AddressBook();
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ //do nothing, but needed since it is called in the default stub.
+ }
+
+ @Override
+ public void saveAddressBook() {
+ //do nothing, but needed since it is called in the default stub.
+ }
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/LoadCommandTest.java b/src/test/java/seedu/address/logic/commands/LoadCommandTest.java
new file mode 100644
index 00000000000..b4b066705d6
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/LoadCommandTest.java
@@ -0,0 +1,46 @@
+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.assertCommandSuccess;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+
+public class LoadCommandTest {
+
+ @Test
+ public void execute_emptyAddressBook_success() {
+ Model model = new ModelManager();
+ Model expectedModel = new ModelManager();
+ assertCommandSuccess(new LoadCommand(Paths.get("my.json")), model,
+ String.format(LoadCommand.MESSAGE_SUCCESS, Paths.get("my.json")), expectedModel);
+ }
+ @Test
+ public void otherUtilityTest() {
+ Path path = Paths.get("Test.json");
+ LoadCommand command = new LoadCommand(path);
+ assertEquals(command.getLoadPath(), path);
+ }
+
+ @Test
+ public void equal() {
+ Path path1 = Paths.get("mybook.json");
+ Path path2 = Paths.get("yourbook.json");
+ LoadCommand command1 = new LoadCommand(path1);
+ LoadCommand command2 = new LoadCommand(path1);
+ LoadCommand command3 = new LoadCommand(path2);
+ assertTrue(command1.equals(command1));
+ assertTrue(command1.equals(command2));
+ assertFalse(command1.equals(command3));
+ assertFalse(command1.equals(1));
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
new file mode 100644
index 00000000000..149d068792d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+public class RedoCommandTest {
+ private static final Integer FIRST_INDEX = 1;
+
+ private Model testModel;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() throws CommandException {
+ testModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Index index = Index.fromOneBased(FIRST_INDEX);
+ DeleteCommand deleteCommand = new DeleteCommand(index);
+
+ deleteCommand.execute(testModel);
+ deleteCommand.execute(testModel);
+ testModel.undoAddressBook();
+ testModel.undoAddressBook();
+
+ deleteCommand.execute(expectedModel);
+ deleteCommand.execute(expectedModel);
+ expectedModel.undoAddressBook();
+ expectedModel.undoAddressBook();
+ }
+
+ @Test
+ public void execute_afterUndo_success() {
+ expectedModel.redoAddressBook();
+ assertCommandSuccess(new RedoCommand(), testModel, RedoCommand.MESSAGE_REDO_SUCCESS, expectedModel);
+ expectedModel.redoAddressBook();
+ assertCommandSuccess(new RedoCommand(), testModel, RedoCommand.MESSAGE_REDO_SUCCESS, expectedModel);
+ }
+
+ @Test
+ public void execute_noFurtherActions_fail() {
+ testModel.redoAddressBook();
+ testModel.redoAddressBook();
+ assertCommandFailure(new RedoCommand(), testModel, RedoCommand.MESSAGE_REDO_FAILURE);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
new file mode 100644
index 00000000000..dea2e28448d
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
@@ -0,0 +1,57 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+public class UndoCommandTest {
+ private static final Integer FIRST_INDEX = 1;
+
+ private Model testModel;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() throws CommandException {
+ testModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Index index = Index.fromOneBased(FIRST_INDEX);
+ DeleteCommand deleteCommand = new DeleteCommand(index);
+
+ deleteCommand.execute(testModel);
+ deleteCommand.execute(testModel);
+
+ deleteCommand.execute(expectedModel);
+ deleteCommand.execute(expectedModel);
+ }
+
+ @Test
+ public void execute_afterSingleCommand_success() {
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), testModel, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ }
+
+ @Test
+ public void execute_multipleCommands_success() throws CommandException {
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), testModel, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ expectedModel.undoAddressBook();
+ assertCommandSuccess(new UndoCommand(), testModel, UndoCommand.MESSAGE_UNDO_SUCCESS, expectedModel);
+ }
+
+ @Test
+ public void execute_noFurtherActions_fail() {
+ testModel.undoAddressBook();
+ testModel.undoAddressBook();
+ assertCommandFailure(new UndoCommand(), testModel, UndoCommand.MESSAGE_UNDO_FAILURE);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..2a1ba900434 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -1,15 +1,14 @@
package seedu.address.logic.parser;
import static seedu.address.logic.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;
-import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.GENDER_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_GENDER_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
@@ -18,14 +17,13 @@
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GENDER_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-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_GENDER;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
@@ -37,8 +35,8 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -53,22 +51,23 @@ public void parse_allFieldsPresent_success() {
Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build();
// whitespace only preamble
- assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB
+ + GENDER_DESC_BOB + MODULE_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
// multiple tags - all accepted
Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
.build();
assertParseSuccess(parser,
- NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ NAME_DESC_BOB + PHONE_DESC_BOB + GENDER_DESC_BOB + MODULE_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
new AddCommand(expectedPersonMultipleTags));
}
@Test
public void parse_repeatedNonTagValue_failure() {
- String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
+ String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + GENDER_DESC_BOB
+ + MODULE_DESC_BOB + TAG_DESC_FRIEND;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
@@ -77,20 +76,16 @@ public void parse_repeatedNonTagValue_failure() {
// multiple phones
assertParseFailure(parser, PHONE_DESC_AMY + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
-
- // multiple emails
- assertParseFailure(parser, EMAIL_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
-
- // multiple addresses
- assertParseFailure(parser, ADDRESS_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // multiple genders
+ assertParseFailure(parser, GENDER_DESC_BOB + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_GENDER));
// multiple fields repeated
assertParseFailure(parser,
- validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY
+ validExpectedPersonString + PHONE_DESC_AMY + NAME_DESC_AMY + GENDER_DESC_BOB
+ validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_GENDER,
+ PREFIX_PHONE));
// invalid value followed by valid value
@@ -98,17 +93,12 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, INVALID_NAME_DESC + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
- // invalid email
- assertParseFailure(parser, INVALID_EMAIL_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
-
// invalid phone
assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
-
- // invalid address
- assertParseFailure(parser, INVALID_ADDRESS_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // invalid gender
+ assertParseFailure(parser, INVALID_GENDER_DESC + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_GENDER));
// valid value followed by invalid value
@@ -116,24 +106,20 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, validExpectedPersonString + INVALID_NAME_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
- // invalid email
- assertParseFailure(parser, validExpectedPersonString + INVALID_EMAIL_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
-
// invalid phone
assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // invalid address
- assertParseFailure(parser, validExpectedPersonString + INVALID_ADDRESS_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // invalid gender
+ assertParseFailure(parser, validExpectedPersonString + INVALID_GENDER_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_GENDER));
}
@Test
public void parse_optionalFieldsMissing_success() {
// zero tags
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
- assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + GENDER_DESC_BOB + MODULE_DESC_AMY,
new AddCommand(expectedPerson));
}
@@ -142,55 +128,49 @@ public void parse_compulsoryFieldMissing_failure() {
String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE);
// missing name prefix
- assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
+ assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + GENDER_DESC_BOB + MODULE_DESC_BOB,
expectedMessage);
// missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
+ assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + GENDER_DESC_BOB + MODULE_DESC_BOB,
expectedMessage);
-
- // missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
+ // missing gender prefix
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_GENDER_BOB,
expectedMessage);
-
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
+ // missing module prefix
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + GENDER_DESC_BOB + VALID_MODULE_BOB,
expectedMessage);
-
// all prefixes missing
- assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
+ assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_GENDER_BOB + VALID_MODULE_BOB,
expectedMessage);
}
@Test
public void parse_invalidValue_failure() {
// invalid name
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB
+ + GENDER_DESC_BOB + MODULE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
// invalid phone
- assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
-
- // invalid email
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC
+ + GENDER_DESC_BOB + MODULE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
- // invalid address
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+ // invalid module
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB
+ + GENDER_DESC_BOB + INVALID_MODULE_DESC + TAG_DESC_FRIEND, Module.MESSAGE_CONSTRAINTS);
// invalid tag
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
-
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB
+ + GENDER_DESC_BOB + MODULE_DESC_BOB + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
+ // invalid gender
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_GENDER_DESC + MODULE_DESC_BOB
+ + VALID_TAG_FRIEND, Gender.MESSAGE_CONSTRAINTS);
// two invalid values, only first invalid value reported
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
- Name.MESSAGE_CONSTRAINTS);
-
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + GENDER_DESC_BOB
+ + INVALID_MODULE_DESC, Name.MESSAGE_CONSTRAINTS);
// non-empty preamble
- assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB
+ + GENDER_DESC_BOB + MODULE_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..c2b404c30e6 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -4,9 +4,13 @@
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.logic.commands.CommandTestUtil.GENDER_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -14,16 +18,23 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.ArchiveCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.DeleteCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FilterCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.GradeCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
+import seedu.address.model.person.Gender;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
@@ -46,6 +57,12 @@ public void parseCommand_clear() throws Exception {
assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand);
}
+ @Test
+ public void parseCommand_archive() throws Exception {
+ String archiveCommand = ArchiveCommand.COMMAND_WORD + " pa/mybook.json";
+ assertTrue(parser.parseCommand(archiveCommand) instanceof ArchiveCommand);
+ }
+
@Test
public void parseCommand_delete() throws Exception {
DeleteCommand command = (DeleteCommand) parser.parseCommand(
@@ -62,6 +79,15 @@ public void parseCommand_edit() throws Exception {
assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
}
+ @Test
+ public void parseCommand_filter() throws Exception {
+ FilterCommand.FilterPersonDescriptor descriptor = new FilterCommand.FilterPersonDescriptor();
+ descriptor.setGender(new Gender("female"));
+ FilterCommand command = (FilterCommand) parser.parseCommand(FilterCommand.COMMAND_WORD + " "
+ + GENDER_DESC_AMY);
+ assertEquals(new FilterCommand(descriptor), command);
+ }
+
@Test
public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand);
@@ -73,7 +99,7 @@ public void parseCommand_find() throws Exception {
List keywords = Arrays.asList("foo", "bar", "baz");
FindCommand command = (FindCommand) parser.parseCommand(
FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
- assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command);
+ assertEquals(new FindCommand(new DetailContainsKeywordsPredicate(keywords)), command);
}
@Test
@@ -87,6 +113,36 @@ public void parseCommand_list() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand);
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
}
+ @Test
+ public void parseCommand_grade() throws Exception {
+ assertTrue(parser.parseCommand(GradeCommand.COMMAND_WORD + " 1 m/MA1522 s/80") instanceof GradeCommand);
+ }
+
+ @Test
+ public void parseCommand_undo() throws Exception {
+ assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD) instanceof UndoCommand);
+ assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD + " 3") instanceof UndoCommand);
+
+ }
+ @Test
+ public void parseCommand_redo() throws Exception {
+ assertTrue(parser.parseCommand(RedoCommand.COMMAND_WORD) instanceof RedoCommand);
+ assertTrue(parser.parseCommand(RedoCommand.COMMAND_WORD + " 3") instanceof RedoCommand);
+
+ }
+
+ @Test
+ public void parseCommand_load() throws Exception {
+ Path tempDir = Paths.get("archived");
+ if (!Files.exists(tempDir)) {
+ tempDir = Files.createDirectory(tempDir);
+ }
+ Path tempFile = tempDir.resolve("TestLoading.json");
+ Files.createFile(tempFile);
+ assertTrue(parser.parseCommand(LoadCommand.COMMAND_WORD
+ + " pa/TestLoading.json") instanceof LoadCommand);
+ Files.deleteIfExists(tempFile);
+ }
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
diff --git a/src/test/java/seedu/address/logic/parser/ArchiveCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ArchiveCommandParserTest.java
new file mode 100644
index 00000000000..e104179248c
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ArchiveCommandParserTest.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.ArchiveCommand;
+
+public class ArchiveCommandParserTest {
+ private static final String INPUT_MISSING_PREFIX = " mybook.json";
+ private static final String INPUT_MULTIPLE_PATH = " pa/mybook.json pa/mybook2.json";
+ private static final String INPUT_NONEMPTY_PREAMBLE = " 1123454 pa/mybook.json";
+ private static final String VALID_INPUT = " pa/TestValidInput.json";
+ private static final ArchiveCommandParser PARSER = new ArchiveCommandParser();
+
+ @Test
+ void invalid_input_throwException() {
+ assertParseFailure(PARSER, INPUT_MISSING_PREFIX,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ArchiveCommand.MESSAGE_USAGE));
+ assertParseFailure(PARSER, INPUT_MULTIPLE_PATH,
+ String.format(Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PATH)));
+ assertParseFailure(PARSER, INPUT_NONEMPTY_PREAMBLE,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ArchiveCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ void valid_input() throws Exception {
+ assertParseSuccess(PARSER, VALID_INPUT,
+ new ArchiveCommand(Paths.get("archived", "TestValidInput.json")));
+ }
+}
+
diff --git a/src/test/java/seedu/address/logic/parser/AutocompleteParserTest.java b/src/test/java/seedu/address/logic/parser/AutocompleteParserTest.java
new file mode 100644
index 00000000000..97b9651c754
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AutocompleteParserTest.java
@@ -0,0 +1,334 @@
+package seedu.address.logic.parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.TypicalPersons.getStressTestAddressBook;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+public class AutocompleteParserTest {
+ private static final int PARSER_TIME_LIMIT = 300; // in milliseconds
+
+ @TempDir
+ public Path temporaryFolder;
+ private Model model;
+
+ @BeforeEach
+ public void setUp() throws IOException {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ }
+
+ @Test
+ public void parseCommand_emptyInput_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "";
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ 0);
+
+ assertEquals(suggestions, new HashMap<>());
+ }
+
+ @Test
+ public void parseCommand_blankInput_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = " ";
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, new HashMap<>());
+ }
+
+ @Test
+ public void parseCommand_caretOnBlankCharacter_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "m/MODULE ";
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, new HashMap<>());
+ }
+
+ @Test
+ public void parseCommand_invalidPrefix_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add ummm/";
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, new HashMap<>());
+ }
+
+ @Test
+ public void parseCommand_prefixOnFirstWord_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "m/";
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, new HashMap<>());
+ }
+
+ // Test for command parsing
+ @Test
+ public void parseCommand_commandNotOnFirstWord_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "m/MODULE ad";
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, new HashMap<>());
+ }
+
+ @Test
+ public void parseCommand_aInput_returnsAddArchiveCommandSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "a";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("add", "add");
+ expectedSuggestions.put("archive", "archive");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_completeCommandInput_returnsNoSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add";
+ HashMap expectedSuggestions = new HashMap<>();
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ // Test for module parsing
+ @Test
+ public void parseCommand_modulePrefixInput_returnsAllModuleSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add m/";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("MA1522", "add m/MA1522");
+ expectedSuggestions.put("CS1101", "add m/CS1101");
+ expectedSuggestions.put("EL1101", "add m/EL1101");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_modulePrefixInputStartWithM_returnsOneModuleSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "ed m/M";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("MA1522", "ed m/MA1522");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_modulePrefixInputCaretOnPrefix_returnsAllModuleSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add m/M";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("MA1522", "add m/MA1522");
+ expectedSuggestions.put("CS1101", "add m/CS1101");
+ expectedSuggestions.put("EL1101", "add m/EL1101");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length() - 1);
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ // Test for tag parsing
+ @Test
+ public void parseCommand_tagPrefixInput_returnsAllTagSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "edit t/";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("friends", "edit t/friends");
+ expectedSuggestions.put("owesMoney", "edit t/owesMoney");
+ expectedSuggestions.put("runner", "edit t/runner");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_tagPrefixInputStartWithF_returnsOneTagSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "edit t/f";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("friends", "edit t/friends");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_tagPrefixInputCaretOnPrefix_returnsAllTagSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "edit t/f";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("friends", "edit t/friends");
+ expectedSuggestions.put("owesMoney", "edit t/owesMoney");
+ expectedSuggestions.put("runner", "edit t/runner");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length() - 1);
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ // Test for gender parsing
+ @Test
+ public void parseCommand_genderPrefixInput_returnsAllGenderSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add g/";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("male", "add g/male");
+ expectedSuggestions.put("female", "add g/female");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_genderPrefixInputStartsWithM_returnsMaleGenderSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add g/m";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("male", "add g/male");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_genderPrefixInputCaretOnPrefix_returnsAllGenderSuggestions() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add g/m";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("male", "add g/male");
+ expectedSuggestions.put("female", "add g/female");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length() - 1);
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+ // Test file paths
+ @Test
+ public void parseCommand_existingFilePath_returnsFilePathSuggestion() throws IOException {
+ // Add test file
+ Path archiveTestFile = temporaryFolder.resolve("archiveTest.json");
+ Files.createFile(archiveTestFile);
+
+ AutocompleteParser autocompleteParser = new AutocompleteParser(temporaryFolder.toString());
+ String userInput = "archive pa/";
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertTrue(suggestions.containsKey("archiveTest.json"));
+ }
+
+ @Test
+ public void parseCommand_nonExistingFilePath_returnsNoFilePathSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser(temporaryFolder.toString());
+ String userInput = "archive pa/";
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertTrue(suggestions.isEmpty());
+ }
+
+ @Test
+ public void parseCommand_nonExistingDirectory_returnsNoFilePathSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser("X@!9393049118!!");
+ String userInput = "archive pa/";
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(),
+ userInput.length());
+
+ assertTrue(suggestions.isEmpty());
+ }
+
+
+
+ // Test caret positions
+ @Test
+ public void parseCommand_caretPositionAtSecondLetter_returnsEditCommandSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "ed 1 m/";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("edit", "edit 1 m/");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(), 2);
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ @Test
+ public void parseCommand_caretPositionAtModule_returnsEditCommandSuggestion() {
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add 1 m/CS m/1010";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("CS1101", "add 1 m/CS1101 m/1010");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, model.getAddressBook(), 9);
+
+ assertEquals(suggestions, expectedSuggestions);
+ }
+
+ // Stress tests
+ @Test
+ public void stressTestParseCommand_oneThousandEntries_executeWithinTime() {
+ AddressBook stressTestAB = getStressTestAddressBook();
+
+ long start = System.currentTimeMillis();
+ AutocompleteParser autocompleteParser = new AutocompleteParser();
+ String userInput = "add 1 m/CS m/1010";
+ HashMap expectedSuggestions = new HashMap<>();
+ expectedSuggestions.put("CS1101", "add 1 m/CS1101 m/1010");
+
+ HashMap suggestions = autocompleteParser.parseCommand(userInput, stressTestAB, 9);
+ long end = System.currentTimeMillis();
+
+ assertTrue((end - start) < PARSER_TIME_LIMIT);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..04d5c1a6ac3 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -1,29 +1,27 @@
package seedu.address.logic.parser;
import static seedu.address.logic.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;
-import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.GENDER_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.GENDER_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_GENDER_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GENDER_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-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_GENDER;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
@@ -38,8 +36,8 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -85,32 +83,37 @@ public void parse_invalidPreamble_failure() {
public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
- assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email
- assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address
+ assertParseFailure(parser, "1" + INVALID_GENDER_DESC, Gender.MESSAGE_CONSTRAINTS); // invalid gender
+ assertParseFailure(parser, "1" + INVALID_MODULE_DESC, Module.MESSAGE_CONSTRAINTS); // invalid module
+
assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
- // invalid phone followed by valid email
- assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
+ // invalid phone followed by valid module
+ assertParseFailure(parser, "1" + INVALID_PHONE_DESC + MODULE_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
// while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited,
// parsing it together with a valid tag results in error
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND
+ + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY
+ + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND
+ + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
// multiple invalid values, but only the first invalid value is captured
- assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY,
- Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_PHONE_DESC, Name.MESSAGE_CONSTRAINTS);
}
@Test
public void parse_allFieldsSpecified_success() {
Index targetIndex = INDEX_SECOND_PERSON;
String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND;
+ + GENDER_DESC_AMY + NAME_DESC_AMY + MODULE_DESC_AMY + TAG_DESC_FRIEND;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withPhone(VALID_PHONE_BOB)
+ .withGender(VALID_GENDER_AMY)
+ .withModules(VALID_MODULE_AMY)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
@@ -120,10 +123,10 @@ public void parse_allFieldsSpecified_success() {
@Test
public void parse_someFieldsSpecified_success() {
Index targetIndex = INDEX_FIRST_PERSON;
- String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY;
+ String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + MODULE_DESC_AMY;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_AMY).build();
+ .withModules(VALID_MODULE_AMY).build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
@@ -144,15 +147,15 @@ public void parse_oneFieldSpecified_success() {
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // email
- userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY;
- descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build();
+ // gender
+ userInput = targetIndex.getOneBased() + GENDER_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withGender(VALID_GENDER_AMY).build();
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // address
- userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY;
- descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build();
+ // module
+ userInput = targetIndex.getOneBased() + MODULE_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withModules(VALID_MODULE_AMY).build();
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
@@ -179,20 +182,21 @@ public void parse_multipleRepeatedFields_failure() {
assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // mulltiple valid fields repeated
- userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY
- + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND
- + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND;
+ // multiple valid fields repeated
+ userInput = targetIndex.getOneBased()
+ + PHONE_DESC_AMY + GENDER_DESC_AMY + TAG_DESC_FRIEND
+ + PHONE_DESC_AMY + GENDER_DESC_AMY + TAG_DESC_FRIEND
+ + PHONE_DESC_BOB + GENDER_DESC_BOB + TAG_DESC_HUSBAND;
assertParseFailure(parser, userInput,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_GENDER));
// multiple invalid values
- userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC
- + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC;
+ userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_GENDER_DESC
+ + INVALID_PHONE_DESC + INVALID_GENDER_DESC;
assertParseFailure(parser, userInput,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_GENDER));
}
@Test
diff --git a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
new file mode 100644
index 00000000000..eec142f7c86
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
@@ -0,0 +1,152 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.GENDER_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.GENDER_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_GENDER_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_MODULE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.MODULE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GENDER_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+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.Messages;
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.FilterPersonDescriptorBuilder;
+
+public class FilterCommandParserTest {
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE);
+
+ private FilterCommandParser parser = new FilterCommandParser();
+
+ @Test
+ public void parse_noFilterProvided_throwException() {
+ String userInput = "";
+
+ assertParseFailure(parser, userInput, MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_duplicatePrefixs_throwParseException() {
+ // valid followed by invalid
+ String userInput = INVALID_PHONE_DESC + PHONE_DESC_BOB;
+ assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+
+ // invalid followed by valid
+ userInput = PHONE_DESC_BOB + INVALID_PHONE_DESC;
+
+ assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+
+ // multiple valid fields repeated
+ userInput = PHONE_DESC_AMY + GENDER_DESC_AMY + TAG_DESC_FRIEND
+ + PHONE_DESC_AMY + GENDER_DESC_AMY + TAG_DESC_FRIEND
+ + PHONE_DESC_BOB + GENDER_DESC_BOB + TAG_DESC_HUSBAND;
+
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_GENDER));
+
+ // multiple invalid values
+ userInput = INVALID_PHONE_DESC + INVALID_GENDER_DESC
+ + INVALID_PHONE_DESC + INVALID_GENDER_DESC;
+
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_GENDER));
+ }
+
+ @Test
+ public void parse_oneFieldSpecified_success() {
+ // name
+ String userInput = " n/ Amy";
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptorBuilder().withName("Amy").build();
+ FilterCommand expectedCommand = new FilterCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // phone
+ userInput = PHONE_DESC_AMY;
+ descriptor = new FilterPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build();
+ expectedCommand = new FilterCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // gender
+ userInput = GENDER_DESC_AMY;
+ descriptor = new FilterPersonDescriptorBuilder().withGender(VALID_GENDER_AMY).build();
+ expectedCommand = new FilterCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // module
+ userInput = MODULE_DESC_AMY;
+ descriptor = new FilterPersonDescriptorBuilder().withModules(VALID_MODULE_AMY).build();
+ expectedCommand = new FilterCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // tags
+ userInput = TAG_DESC_FRIEND;
+ descriptor = new FilterPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build();
+ expectedCommand = new FilterCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_allFieldsSpecified_success() {
+ String userInput = PHONE_DESC_BOB + TAG_DESC_HUSBAND
+ + GENDER_DESC_AMY + " n/ Amy" + MODULE_DESC_AMY + TAG_DESC_FRIEND;
+
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptorBuilder().withName("Amy")
+ .withPhone(VALID_PHONE_BOB)
+ .withGender(VALID_GENDER_AMY)
+ .withModules(VALID_MODULE_AMY)
+ .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ FilterCommand expectedCommand = new FilterCommand(descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+ assertParseFailure(parser, INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
+ assertParseFailure(parser, INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
+ assertParseFailure(parser, INVALID_GENDER_DESC, Gender.MESSAGE_CONSTRAINTS); // invalid gender
+ assertParseFailure(parser, INVALID_MODULE_DESC, Module.MESSAGE_CONSTRAINTS); // invalid module
+
+ assertParseFailure(parser, INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
+
+ // invalid phone followed by valid module
+ assertParseFailure(parser, INVALID_PHONE_DESC + MODULE_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
+
+ // multiple invalid values, but only the first invalid value is captured
+ assertParseFailure(parser, INVALID_NAME_DESC + INVALID_PHONE_DESC, Name.MESSAGE_CONSTRAINTS);
+ }
+
+ @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);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
index d92e64d12f9..8a9cbc0e028 100644
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
@@ -9,7 +9,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.FindCommand;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
public class FindCommandParserTest {
@@ -24,7 +24,7 @@ public void parse_emptyArg_throwsParseException() {
public void parse_validArgs_returnsFindCommand() {
// no leading and trailing whitespaces
FindCommand expectedFindCommand =
- new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")));
+ new FindCommand(new DetailContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")));
assertParseSuccess(parser, "Alice Bob", expectedFindCommand);
// multiple whitespaces between keywords
diff --git a/src/test/java/seedu/address/logic/parser/GradeCommandParserTest.java b/src/test/java/seedu/address/logic/parser/GradeCommandParserTest.java
new file mode 100644
index 00000000000..1806569f75f
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/GradeCommandParserTest.java
@@ -0,0 +1,52 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_AMY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.GradeCommand;
+
+public class GradeCommandParserTest {
+
+ private static final String TAG_EMPTY = " " + PREFIX_TAG;
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, GradeCommand.MESSAGE_USAGE);
+
+ private GradeCommandParser parser = new GradeCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ String userinput = "1" + " " + PREFIX_MODULE + VALID_MODULE_AMY + " " + PREFIX_GRADE + VALID_GRADE_AMY;
+ Map moduleGrades = new LinkedHashMap<>();
+ moduleGrades.put(VALID_MODULE_AMY, VALID_GRADE_AMY);
+ GradeCommand expectedCommand = new GradeCommand(Index.fromOneBased(1), moduleGrades);
+ assertParseSuccess(parser, userinput, expectedCommand);
+ }
+ @Test
+ public void parse_missingModule_failure() {
+ assertParseFailure(parser, "1 s/" + VALID_GRADE_AMY, MESSAGE_INVALID_FORMAT);
+ }
+ @Test
+ public void parse_missingGrade_failure() {
+ assertParseFailure(parser, "1 " + PREFIX_MODULE + VALID_MODULE_AMY, MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "-1", MESSAGE_INVALID_FORMAT);
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/LoadCommandParserTest.java b/src/test/java/seedu/address/logic/parser/LoadCommandParserTest.java
new file mode 100644
index 00000000000..dfc3373804c
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/LoadCommandParserTest.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PATH;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.LoadCommand;
+
+
+public class LoadCommandParserTest {
+ private static final String INPUT_MISSING_PREFIX = " mybook.json";
+ private static final String INPUT_MULTIPLE_PATH = " pa/mybook.json, pa/mybook2.json";
+ private static final String INPUT_NONEMPTY_PREAMBLE = " 1123454 pa/mybook.json";
+ private static final String VALID_INPUT = " pa/TestValidInput.json";
+ private static final LoadCommandParser PARSER = new LoadCommandParser();
+
+ @Test
+ void invalid_input_throwException() {
+ assertParseFailure(PARSER, INPUT_MISSING_PREFIX,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoadCommand.MESSAGE_USAGE));
+ assertParseFailure(PARSER, INPUT_MULTIPLE_PATH,
+ String.format(Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PATH)));
+ assertParseFailure(PARSER, INPUT_NONEMPTY_PREAMBLE,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoadCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ void valid_input() throws Exception {
+ Path tempDir = Paths.get("archived");
+ if (!Files.exists(tempDir)) {
+ tempDir = Files.createDirectory(tempDir);
+ }
+ Path tempFile = tempDir.resolve("TestValidInput.json");
+ Files.createFile(tempFile);
+ assertParseSuccess(PARSER, VALID_INPUT, new LoadCommand(tempFile));
+ Files.deleteIfExists(tempFile);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..eeaabd911dc 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -3,9 +3,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX;
+import static seedu.address.logic.parser.ParserUtil.parsePathWithCheck;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -14,8 +18,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -23,16 +26,16 @@
public class ParserUtilTest {
private static final String INVALID_NAME = "R@chel";
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_MODULE = " ";
private static final String INVALID_TAG = "#friend";
-
private static final String VALID_NAME = "Rachel Walker";
- private static final String VALID_PHONE = "123456";
- private static final String VALID_ADDRESS = "123 Main Street #0505";
- private static final String VALID_EMAIL = "rachel@example.com";
+ private static final String VALID_PHONE = "12345678";
+ private static final String VALID_MODULE = "CS2103T";
private static final String VALID_TAG_1 = "friend";
private static final String VALID_TAG_2 = "neighbour";
+ private static final String INVALID_PATH_1 = "notjson";
+ private static final String INVALID_PATH_2 = "have/invalid.json";
+ private static final String VALID_PATH = "TestingParser.json";
private static final String WHITESPACE = " \t\r\n";
@@ -103,49 +106,49 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc
}
@Test
- public void parseAddress_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null));
+ public void parseModule_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseModule((String) null));
}
@Test
- public void parseAddress_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS));
+ public void parseModule_invalidValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseModule(INVALID_MODULE));
}
-
@Test
- public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception {
- Address expectedAddress = new Address(VALID_ADDRESS);
- assertEquals(expectedAddress, ParserUtil.parseAddress(VALID_ADDRESS));
+ public void parseModule_validValueWithoutWhitespace_returnsModule() throws Exception {
+ Module expectedModule = new Module(VALID_MODULE);
+ assertEquals(expectedModule, ParserUtil.parseModule(VALID_MODULE));
}
@Test
- public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws Exception {
- String addressWithWhitespace = WHITESPACE + VALID_ADDRESS + WHITESPACE;
- Address expectedAddress = new Address(VALID_ADDRESS);
- assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace));
+ public void parseModule_validValueWithWhitespace_returnsTrimmedModule() throws Exception {
+ String moduleWithWhitespace = WHITESPACE + VALID_MODULE + WHITESPACE;
+ Module expectedModule = new Module(VALID_MODULE);
+ assertEquals(expectedModule, ParserUtil.parseModule(moduleWithWhitespace));
}
@Test
- public void parseEmail_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail((String) null));
+ public void parseModules_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parseModules(null));
}
@Test
- public void parseEmail_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseEmail(INVALID_EMAIL));
+ public void parseModules_collectionWithInvalidModules_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseModules(Arrays.asList(VALID_MODULE, INVALID_MODULE)));
}
@Test
- public void parseEmail_validValueWithoutWhitespace_returnsEmail() throws Exception {
- Email expectedEmail = new Email(VALID_EMAIL);
- assertEquals(expectedEmail, ParserUtil.parseEmail(VALID_EMAIL));
+ public void parseModules_emptyCollection_returnsEmptySet() throws Exception {
+ assertTrue(ParserUtil.parseModules(Collections.emptyList()).isEmpty());
}
@Test
- public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exception {
- String emailWithWhitespace = WHITESPACE + VALID_EMAIL + WHITESPACE;
- Email expectedEmail = new Email(VALID_EMAIL);
- assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace));
+ public void parseModules_collectionWithValidModules_returnsModuleSet() throws Exception {
+ Set actualModuleSet = ParserUtil.parseModules(Arrays.asList(VALID_MODULE, VALID_MODULE));
+ Set expectedModuleSet = new HashSet<>(Arrays.asList(new Module(VALID_MODULE),
+ new Module(VALID_MODULE)));
+
+ assertEquals(expectedModuleSet, actualModuleSet);
}
@Test
@@ -193,4 +196,44 @@ public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
assertEquals(expectedTagSet, actualTagSet);
}
+ @Test
+ public void parseGrade_validInput_success() throws Exception {
+ assertEquals(100, ParserUtil.parseGrade("100"));
+ }
+
+ @Test
+ public void parseGrade_invalidInput_throwsParseException() {
+ assertThrows(ParseException.class, Module.GRADE_CONSTRAINTS, () -> ParserUtil.parseGrade("101"));
+ }
+ @Test
+ public void parsePathWithCheck_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parsePathWithCheck((String) null));
+ }
+
+ @Test
+ public void parsePathWithCheck_invalid_path() {
+ assertThrows(ParseException.class, () -> ParserUtil.parsePathWithCheck(INVALID_PATH_1));
+ assertThrows(ParseException.class, () -> ParserUtil.parsePathWithCheck(INVALID_PATH_2));
+ assertThrows(ParseException.class, () -> ParserUtil.parsePathWithCheck(VALID_PATH));
+ }
+
+ @Test
+ public void parsePathWithCheck_valid_path() throws Exception {
+ Path tempDir = Paths.get("archived");
+ if (!Files.exists(tempDir)) {
+ tempDir = Files.createDirectory(tempDir);
+ }
+ Path tempFile = tempDir.resolve("TestingParser.json");
+ Files.createFile(tempFile);
+ assertEquals(tempFile, parsePathWithCheck(VALID_PATH));
+ Files.deleteIfExists(tempFile);
+ }
+
+ @Test
+ public void parsePathWithoutCheck_invalid_path() {
+ assertThrows(ParseException.class, () -> ParserUtil.parsePathWithoutCheck(INVALID_PATH_1));
+ assertThrows(ParseException.class, () -> ParserUtil.parsePathWithoutCheck(INVALID_PATH_2));
+ }
+
+
}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..ab453dea80b 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -3,7 +3,6 @@
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.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
@@ -46,8 +45,7 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() {
@Test
public void resetData_withDuplicatePersons_throwsDuplicatePersonException() {
// Two persons with the same identity fields
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
- .build();
+ Person editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
List newPersons = Arrays.asList(ALICE, editedAlice);
AddressBookStub newData = new AddressBookStub(newPersons);
@@ -73,8 +71,7 @@ public void hasPerson_personInAddressBook_returnsTrue() {
@Test
public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() {
addressBook.addPerson(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
- .build();
+ Person editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
assertTrue(addressBook.hasPerson(editedAlice));
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..cc835a4b724 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -15,7 +15,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.DetailContainsKeywordsPredicate;
import seedu.address.testutil.AddressBookBuilder;
public class ModelManagerTest {
@@ -118,7 +118,7 @@ public void equals() {
// different filteredList -> returns false
String[] keywords = ALICE.getName().fullName.split("\\s+");
- modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords)));
+ modelManager.updateFilteredPersonList(new DetailContainsKeywordsPredicate(Arrays.asList(keywords)));
assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs)));
// resets modelManager to initial state for upcoming tests
diff --git a/src/test/java/seedu/address/model/VersionedAddressBookTest.java b/src/test/java/seedu/address/model/VersionedAddressBookTest.java
new file mode 100644
index 00000000000..e658aa43e10
--- /dev/null
+++ b/src/test/java/seedu/address/model/VersionedAddressBookTest.java
@@ -0,0 +1,98 @@
+package seedu.address.model;
+
+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.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class VersionedAddressBookTest {
+ private VersionedAddressBook versionedAddressBook;
+ private AddressBook initialState;
+
+ @BeforeEach
+ public void setUp() {
+ initialState = new AddressBook();
+ versionedAddressBook = new VersionedAddressBook(initialState);
+ }
+
+ @Test
+ public void save_savesNewState() {
+ AddressBook newState = new AddressBook();
+ versionedAddressBook.save();
+ assertEquals(2, versionedAddressBook.getTotalStates());
+ assertEquals(newState, versionedAddressBook.getCurrentState());
+ }
+
+ @Test
+ public void undo_canUndoPreviousState() {
+ versionedAddressBook.save();
+ versionedAddressBook.save();
+
+ versionedAddressBook.undo();
+
+ assertEquals(initialState, versionedAddressBook.getCurrentState());
+ assertTrue(versionedAddressBook.canRedo());
+ }
+
+ @Test
+ public void undo_noStatesToUndo_throwsException() {
+ assertThrows(VersionedAddressBook.InvalidUndoException.class, () -> versionedAddressBook.undo());
+ }
+
+ @Test
+ public void redo_canRedoUndoneState() {
+ versionedAddressBook.save();
+ versionedAddressBook.save();
+ ReadOnlyAddressBook expectedSecondState = versionedAddressBook.getCurrentState();
+
+ versionedAddressBook.undo();
+ assertEquals(initialState, versionedAddressBook.getCurrentState());
+
+ versionedAddressBook.redo();
+ assertEquals(expectedSecondState, versionedAddressBook.getCurrentState());
+ }
+
+ @Test
+ public void redo_noStatesToRedo_throwsException() {
+ versionedAddressBook.save();
+ versionedAddressBook.save();
+ versionedAddressBook.undo();
+
+ versionedAddressBook.redo();
+
+ assertThrows(VersionedAddressBook.InvalidRedoException.class, () -> versionedAddressBook.redo());
+ }
+
+ @Test
+ public void clearRedoStack_clearsRedoHistory() {
+ versionedAddressBook.save();
+ versionedAddressBook.save();
+ versionedAddressBook.undo();
+
+ versionedAddressBook.redo();
+ versionedAddressBook.save();
+
+ assertFalse(versionedAddressBook.canRedo());
+ }
+
+ @Test
+ public void getCurrentState_returnsCurrentState() {
+ versionedAddressBook.save();
+ versionedAddressBook.save();
+
+ ReadOnlyAddressBook currentState = versionedAddressBook.getCurrentState();
+
+ assertEquals(currentState, versionedAddressBook.getCurrentState());
+ }
+
+ @Test
+ public void getTotalStates_returnsCorrectCount() {
+ assertEquals(1, versionedAddressBook.getTotalStates());
+
+ versionedAddressBook.save();
+ assertEquals(2, versionedAddressBook.getTotalStates());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java
deleted file mode 100644
index 314885eca26..00000000000
--- a/src/test/java/seedu/address/model/person/AddressTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-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 AddressTest {
-
- @Test
- public void constructor_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> new Address(null));
- }
-
- @Test
- public void constructor_invalidAddress_throwsIllegalArgumentException() {
- String invalidAddress = "";
- assertThrows(IllegalArgumentException.class, () -> new Address(invalidAddress));
- }
-
- @Test
- public void isValidAddress() {
- // null address
- assertThrows(NullPointerException.class, () -> Address.isValidAddress(null));
-
- // invalid addresses
- assertFalse(Address.isValidAddress("")); // empty string
- assertFalse(Address.isValidAddress(" ")); // spaces only
-
- // valid addresses
- assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355"));
- assertTrue(Address.isValidAddress("-")); // one character
- assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address
- }
-
- @Test
- public void equals() {
- Address address = new Address("Valid Address");
-
- // same values -> returns true
- assertTrue(address.equals(new Address("Valid Address")));
-
- // same object -> returns true
- assertTrue(address.equals(address));
-
- // null -> returns false
- assertFalse(address.equals(null));
-
- // different types -> returns false
- assertFalse(address.equals(5.0f));
-
- // different values -> returns false
- assertFalse(address.equals(new Address("Other Valid Address")));
- }
-}
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/DetailContainsKeywordsPredicateTest.java
similarity index 58%
rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
rename to src/test/java/seedu/address/model/person/DetailContainsKeywordsPredicateTest.java
index 6b3fd90ade7..3de14130f2f 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/person/DetailContainsKeywordsPredicateTest.java
@@ -12,21 +12,24 @@
import seedu.address.testutil.PersonBuilder;
-public class NameContainsKeywordsPredicateTest {
+public class DetailContainsKeywordsPredicateTest {
@Test
public void equals() {
List firstPredicateKeywordList = Collections.singletonList("first");
List secondPredicateKeywordList = Arrays.asList("first", "second");
- NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList);
- NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList);
+ DetailContainsKeywordsPredicate firstPredicate =
+ new DetailContainsKeywordsPredicate(firstPredicateKeywordList);
+ DetailContainsKeywordsPredicate secondPredicate =
+ new DetailContainsKeywordsPredicate(secondPredicateKeywordList);
// same object -> returns true
assertTrue(firstPredicate.equals(firstPredicate));
// same values -> returns true
- NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList);
+ DetailContainsKeywordsPredicate firstPredicateCopy =
+ new DetailContainsKeywordsPredicate(firstPredicateKeywordList);
assertTrue(firstPredicate.equals(firstPredicateCopy));
// different types -> returns false
@@ -42,44 +45,45 @@ public void equals() {
@Test
public void test_nameContainsKeywords_returnsTrue() {
// One keyword
- NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice"));
+ DetailContainsKeywordsPredicate predicate =
+ new DetailContainsKeywordsPredicate(Collections.singletonList("Alice"));
assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
// Multiple keywords
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"));
+ predicate = new DetailContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"));
assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
// Only one matching keyword
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol"));
+ predicate = new DetailContainsKeywordsPredicate(Arrays.asList("Bob", "Carol"));
assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build()));
// Mixed-case keywords
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB"));
+ predicate = new DetailContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB"));
assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
}
@Test
public void test_nameDoesNotContainKeywords_returnsFalse() {
// Zero keywords
- NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList());
+ DetailContainsKeywordsPredicate predicate = new DetailContainsKeywordsPredicate(Collections.emptyList());
assertFalse(predicate.test(new PersonBuilder().withName("Alice").build()));
// Non-matching keyword
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol"));
+ predicate = new DetailContainsKeywordsPredicate(Arrays.asList("Carol"));
assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
- // Keywords match phone, email and address, but does not match name
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street"));
- assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345")
- .withEmail("alice@email.com").withAddress("Main Street").build()));
+ // Keywords match phone and gender, but does not match name
+ predicate = new DetailContainsKeywordsPredicate(Arrays.asList("12345", "female"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345678")
+ .withGender("female").build()));
}
@Test
public void toStringMethod() {
List keywords = List.of("keyword1", "keyword2");
- NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(keywords);
+ DetailContainsKeywordsPredicate predicate = new DetailContainsKeywordsPredicate(keywords);
- String expected = NameContainsKeywordsPredicate.class.getCanonicalName() + "{keywords=" + keywords + "}";
+ String expected = DetailContainsKeywordsPredicate.class.getCanonicalName() + "{keywords=" + keywords + "}";
assertEquals(expected, predicate.toString());
}
}
diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/person/EmailTest.java
deleted file mode 100644
index f08cdff0a64..00000000000
--- a/src/test/java/seedu/address/model/person/EmailTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-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 EmailTest {
-
- @Test
- public void constructor_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> new Email(null));
- }
-
- @Test
- public void constructor_invalidEmail_throwsIllegalArgumentException() {
- String invalidEmail = "";
- assertThrows(IllegalArgumentException.class, () -> new Email(invalidEmail));
- }
-
- @Test
- public void isValidEmail() {
- // null email
- assertThrows(NullPointerException.class, () -> Email.isValidEmail(null));
-
- // blank email
- assertFalse(Email.isValidEmail("")); // empty string
- assertFalse(Email.isValidEmail(" ")); // spaces only
-
- // missing parts
- assertFalse(Email.isValidEmail("@example.com")); // missing local part
- assertFalse(Email.isValidEmail("peterjackexample.com")); // missing '@' symbol
- assertFalse(Email.isValidEmail("peterjack@")); // missing domain name
-
- // invalid parts
- assertFalse(Email.isValidEmail("peterjack@-")); // invalid domain name
- assertFalse(Email.isValidEmail("peterjack@exam_ple.com")); // underscore in domain name
- assertFalse(Email.isValidEmail("peter jack@example.com")); // spaces in local part
- assertFalse(Email.isValidEmail("peterjack@exam ple.com")); // spaces in domain name
- assertFalse(Email.isValidEmail(" peterjack@example.com")); // leading space
- assertFalse(Email.isValidEmail("peterjack@example.com ")); // trailing space
- assertFalse(Email.isValidEmail("peterjack@@example.com")); // double '@' symbol
- assertFalse(Email.isValidEmail("peter@jack@example.com")); // '@' symbol in local part
- assertFalse(Email.isValidEmail("-peterjack@example.com")); // local part starts with a hyphen
- assertFalse(Email.isValidEmail("peterjack-@example.com")); // local part ends with a hyphen
- assertFalse(Email.isValidEmail("peter..jack@example.com")); // local part has two consecutive periods
- assertFalse(Email.isValidEmail("peterjack@example@com")); // '@' symbol in domain name
- assertFalse(Email.isValidEmail("peterjack@.example.com")); // domain name starts with a period
- assertFalse(Email.isValidEmail("peterjack@example.com.")); // domain name ends with a period
- assertFalse(Email.isValidEmail("peterjack@-example.com")); // domain name starts with a hyphen
- assertFalse(Email.isValidEmail("peterjack@example.com-")); // domain name ends with a hyphen
- assertFalse(Email.isValidEmail("peterjack@example.c")); // top level domain has less than two chars
-
- // valid email
- assertTrue(Email.isValidEmail("PeterJack_1190@example.com")); // underscore in local part
- assertTrue(Email.isValidEmail("PeterJack.1190@example.com")); // period in local part
- assertTrue(Email.isValidEmail("PeterJack+1190@example.com")); // '+' symbol in local part
- assertTrue(Email.isValidEmail("PeterJack-1190@example.com")); // hyphen in local part
- assertTrue(Email.isValidEmail("a@bc")); // minimal
- assertTrue(Email.isValidEmail("test@localhost")); // alphabets only
- assertTrue(Email.isValidEmail("123@145")); // numeric local part and domain name
- assertTrue(Email.isValidEmail("a1+be.d@example1.com")); // mixture of alphanumeric and special characters
- assertTrue(Email.isValidEmail("peter_jack@very-very-very-long-example.com")); // long domain name
- assertTrue(Email.isValidEmail("if.you.dream.it_you.can.do.it@example.com")); // long local part
- assertTrue(Email.isValidEmail("e1234567@u.nus.edu")); // more than one period in domain
- }
-
- @Test
- public void equals() {
- Email email = new Email("valid@email");
-
- // same values -> returns true
- assertTrue(email.equals(new Email("valid@email")));
-
- // same object -> returns true
- assertTrue(email.equals(email));
-
- // null -> returns false
- assertFalse(email.equals(null));
-
- // different types -> returns false
- assertFalse(email.equals(5.0f));
-
- // different values -> returns false
- assertFalse(email.equals(new Email("other.valid@email")));
- }
-}
diff --git a/src/test/java/seedu/address/model/person/FilterPredicateTest.java b/src/test/java/seedu/address/model/person/FilterPredicateTest.java
new file mode 100644
index 00000000000..87fde898c69
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/FilterPredicateTest.java
@@ -0,0 +1,135 @@
+package seedu.address.model.person;
+
+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 java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.PersonBuilder;
+
+public class FilterPredicateTest {
+
+ @Test
+ public void equals() {
+ FilterPersonDescriptor firstDescriptor = new FilterPersonDescriptor();
+ firstDescriptor.setName(new Name("Alice"));
+ FilterPredicate firstPredicate = preparePredicate(firstDescriptor);
+
+ FilterPersonDescriptor secondDescriptor = new FilterPersonDescriptor();
+ secondDescriptor.setName(new Name("Alice"));
+ secondDescriptor.setGender(new Gender("female"));
+ FilterPredicate secondPredicate = preparePredicate(secondDescriptor);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ FilterPredicate firstPredicateCopy = new FilterPredicate(firstDescriptor);
+ assertTrue(firstPredicate.equals(firstPredicateCopy));
+
+ // different types -> returns false
+ assertFalse(firstPredicate.equals(1));
+
+ // null -> returns false
+ assertFalse(firstPredicate.equals(null));
+
+ // different person -> returns false
+ assertFalse(firstPredicate.equals(secondPredicate));
+ }
+
+ @Test
+ public void test_oneSpecifiedCondition_returnTrue() {
+
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setName(new Name("Alice"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setGender(new Gender("male"));
+ predicate = preparePredicate(descriptor);
+ assertTrue(predicate.test(new PersonBuilder().withGender("male").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setPhone(new Phone("12345678"));
+ predicate = preparePredicate(descriptor);
+ assertTrue(predicate.test(new PersonBuilder().withPhone("12345678").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setModules(Set.of(new Module("CS1101S")));
+ predicate = preparePredicate(descriptor);
+ assertTrue(predicate.test(new PersonBuilder().withModules("CS1101S").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setTags(Set.of(new Tag("student")));
+ predicate = preparePredicate(descriptor);
+ assertTrue(predicate.test(new PersonBuilder().withTags("student").build()));
+ }
+
+ @Test
+ public void test_moreThanOneConditionSatisfied_returnTrue() {
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setName(new Name("Alice"));
+ descriptor.setGender(new Gender("female"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+ PersonBuilder filterPerson = new PersonBuilder().withName("Alice Bob").withGender("female");
+ assertTrue(predicate.test(filterPerson.build()));
+
+ descriptor.setTags(Set.of(new Tag("student")));
+ predicate = preparePredicate(descriptor);
+ filterPerson.withTags("student");
+ assertTrue(predicate.test(filterPerson.build()));
+ }
+
+ @Test
+ public void test_noConditionSatisfied_returnFalse() {
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setName(new Name("Alice"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+ assertFalse(predicate.test(new PersonBuilder().withName("Gan").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setGender(new Gender("male"));
+ predicate = preparePredicate(descriptor);
+ assertFalse(predicate.test(new PersonBuilder().withGender("female").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setPhone(new Phone("12345678"));
+ predicate = preparePredicate(descriptor);
+ assertFalse(predicate.test(new PersonBuilder().withPhone("34125678").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setModules(Set.of(new Module("CS1101S")));
+ predicate = preparePredicate(descriptor);
+ assertFalse(predicate.test(new PersonBuilder().withModules("MA1511").build()));
+
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setTags(Set.of(new Tag("student")));
+ predicate = preparePredicate(descriptor);
+ assertFalse(predicate.test(new PersonBuilder().withTags("colleague").build()));
+ }
+
+ @Test
+ public void toStringMethod() {
+ FilterPersonDescriptor descriptor = new FilterPersonDescriptor();
+ descriptor.setName(new Name("Alice"));
+ FilterPredicate predicate = preparePredicate(descriptor);
+
+ String expected = FilterPredicate.class.getCanonicalName()
+ + "{conditions=seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor"
+ + "{name=Alice, phone=null, gender=null, modules=null, tags=null}}";
+ assertEquals(expected, predicate.toString());
+ }
+
+ /**
+ * Parses {@code FilterPersonDescription} into a {@code FilterPredicate}.
+ */
+ private FilterPredicate preparePredicate(FilterPersonDescriptor descriptor) {
+ return new FilterPredicate(descriptor);
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/GenderTest.java b/src/test/java/seedu/address/model/person/GenderTest.java
new file mode 100644
index 00000000000..c12bbe9fb37
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/GenderTest.java
@@ -0,0 +1,72 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.model.person.Gender.FEMALE_SYMBOL;
+import static seedu.address.model.person.Gender.MALE_SYMBOL;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class GenderTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Gender(null));
+ }
+
+ @Test
+ public void constructor_invalidPhone_throwsIllegalArgumentException() {
+ String invalidGender = "mmm";
+ assertThrows(IllegalArgumentException.class, () -> new Gender(invalidGender));
+ }
+
+ @Test
+ public void isValidGender() {
+ // null phone number
+ assertThrows(NullPointerException.class, () -> Gender.isValidGender(null));
+
+ // invalid genders
+ assertFalse(Gender.isValidGender("")); // empty string
+ assertFalse(Gender.isValidGender(" ")); // spaces only
+ assertFalse(Gender.isValidGender("91")); // numbers used
+ assertFalse(Gender.isValidGender("MMM")); // not male or female
+ // valid phone numbers
+ assertTrue(Gender.isValidGender("male")); // male gender
+ assertTrue(Gender.isValidGender("female")); // female gender
+ }
+
+ @Test
+ public void getGenderWithSymbol() {
+ Gender gender = new Gender("male");
+ assertEquals(MALE_SYMBOL, gender.getGenderWithSymbol());
+ assertNotEquals(FEMALE_SYMBOL, gender.getGenderWithSymbol());
+ assertNotEquals("male", gender.getGenderWithSymbol());
+ gender = new Gender("female");
+ assertEquals(FEMALE_SYMBOL, gender.getGenderWithSymbol());
+ assertNotEquals(MALE_SYMBOL, gender.getGenderWithSymbol());
+ assertNotEquals("female", gender.getGenderWithSymbol());
+ }
+
+ @Test
+ public void equals() {
+ Gender gender = new Gender("male");
+
+ // same values -> returns true
+ assertTrue(gender.equals(new Gender("male")));
+
+ // same object -> returns true
+ assertTrue(gender.equals(gender));
+
+ // null -> returns false
+ assertFalse(gender.equals(null));
+
+ // different types -> returns false
+ assertFalse(gender.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(gender.equals(new Gender("female")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/ModuleTest.java b/src/test/java/seedu/address/model/person/ModuleTest.java
new file mode 100644
index 00000000000..d23246980ce
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/ModuleTest.java
@@ -0,0 +1,85 @@
+package seedu.address.model.person;
+
+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;
+
+public class ModuleTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Module(null));
+ }
+
+ @Test
+ public void constructor_invalidModuleName_throwsIllegalArgumentException() {
+ String invalidModuleName = "";
+ assertThrows(IllegalArgumentException.class, () -> new Module(invalidModuleName));
+ }
+
+ @Test
+ public void isValidModule() {
+ // null module name
+ assertThrows(NullPointerException.class, () -> Module.isValidModule(null));
+
+ // invalid module names
+ assertFalse(Module.isValidModule(""));
+ assertFalse(Module.isValidModule(" "));
+ assertFalse(Module.isValidModule("CS2103T@"));
+
+ // valid module names
+ assertTrue(Module.isValidModule("CS2103T"));
+ assertTrue(Module.isValidModule("MA2103"));
+ assertTrue(Module.isValidModule("CS12345"));
+ }
+
+ @Test
+ public void equals() {
+ Module module = new Module("CS2103T");
+
+ // same values -> returns true
+ assertTrue(module.equals(new Module("CS2103T")));
+
+ // same object -> returns true
+ assertTrue(module.equals(module));
+
+ // null -> returns false
+ assertFalse(module.equals(null));
+
+ // different types -> returns false
+ assertFalse(module.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(module.equals(new Module("CS2100")));
+ }
+
+ @Test
+ public void hashCode_equalsSameModule_returnsSameHashCode() {
+ Module module = new Module("CS2103T");
+ Module sameModule = new Module("CS2103T");
+ assertTrue(module.hashCode() == sameModule.hashCode());
+ }
+
+ @Test
+ public void toString_returnsCorrectString() {
+ Module module = new Module("CS2103T");
+ assertEquals("[CS2103T | Grade: Ungraded]", module.toString());
+ }
+
+ @Test
+ public void assignGrade_invalidGrade_throwsIllegalArgumentException() {
+ Module module = new Module("CS2103T");
+ assertThrows(IllegalArgumentException.class, () -> module.assignGrade(-2)); // Negative grade
+ assertThrows(IllegalArgumentException.class, () -> module.assignGrade(101)); // Grade above 100
+ }
+
+ @Test
+ public void assignGrade_validGrade_success() {
+ Module module = new Module("CS2103T");
+ module.assignGrade(85);
+ assertEquals("85", module.getGrade());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/person/NameTest.java
index 94e3dd726bd..9e984b95436 100644
--- a/src/test/java/seedu/address/model/person/NameTest.java
+++ b/src/test/java/seedu/address/model/person/NameTest.java
@@ -29,13 +29,13 @@ public void isValidName() {
assertFalse(Name.isValidName(" ")); // spaces only
assertFalse(Name.isValidName("^")); // only non-alphanumeric characters
assertFalse(Name.isValidName("peter*")); // contains non-alphanumeric characters
+ assertFalse(Name.isValidName("12345")); // numbers only
+ assertFalse(Name.isValidName("peter the 2nd")); // alphanumeric characters
// valid name
assertTrue(Name.isValidName("peter jack")); // alphabets only
- assertTrue(Name.isValidName("12345")); // numbers only
- assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters
assertTrue(Name.isValidName("Capital Tan")); // with capital letters
- assertTrue(Name.isValidName("David Roger Jackson Ray Jr 2nd")); // long names
+ assertTrue(Name.isValidName("David Roger Jackson Ray Jr")); // long names
}
@Test
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..a54b6fe29b9 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -3,8 +3,6 @@
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.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -14,6 +12,7 @@
import org.junit.jupiter.api.Test;
+import seedu.address.model.util.SampleDataUtil;
import seedu.address.testutil.PersonBuilder;
public class PersonTest {
@@ -33,17 +32,16 @@ public void isSamePerson() {
assertFalse(ALICE.isSamePerson(null));
// same name, all other attributes different -> returns true
- Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
- .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build();
+ Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
assertTrue(ALICE.isSamePerson(editedAlice));
// different name, all other attributes same -> returns false
editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
assertFalse(ALICE.isSamePerson(editedAlice));
- // name differs in case, all other attributes same -> returns false
+ // name differs in case, all other attributes same -> returns true
Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build();
- assertFalse(BOB.isSamePerson(editedBob));
+ assertTrue(BOB.isSamePerson(editedBob));
// name has trailing spaces, all other attributes same -> returns false
String nameWithTrailingSpaces = VALID_NAME_BOB + " ";
@@ -77,23 +75,23 @@ public void equals() {
editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build();
assertFalse(ALICE.equals(editedAlice));
- // different email -> returns false
- editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build();
- assertFalse(ALICE.equals(editedAlice));
-
- // different address -> returns false
- editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
- assertFalse(ALICE.equals(editedAlice));
-
// different tags -> returns false
editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
assertFalse(ALICE.equals(editedAlice));
}
+ @Test
+ public void testSamplePersonData() {
+ int expectedSize = 10;
+ Person[] expectedPersons = SampleDataUtil.getSamplePersons();
+ assertEquals(expectedSize, expectedPersons.length);
+ }
+
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", gender=" + ALICE.getGender() + ", modules=" + ALICE.getModules()
+ + ", tags=" + ALICE.getTags() + "}";
assertEquals(expected, ALICE.toString());
}
}
diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/person/PhoneTest.java
index deaaa5ba190..af69465147f 100644
--- a/src/test/java/seedu/address/model/person/PhoneTest.java
+++ b/src/test/java/seedu/address/model/person/PhoneTest.java
@@ -31,19 +31,22 @@ public void isValidPhone() {
assertFalse(Phone.isValidPhone("phone")); // non-numeric
assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits
assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits
+ assertFalse(Phone.isValidPhone("124293842033123")); // long phone numbers
+
// valid phone numbers
- assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers
- assertTrue(Phone.isValidPhone("93121534"));
- assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers
+ assertTrue(Phone.isValidPhone("91199999"));
+ assertTrue(Phone.isValidPhone("87801237"));
+ assertTrue(Phone.isValidPhone("09812392"));
+
}
@Test
public void equals() {
- Phone phone = new Phone("999");
+ Phone phone = new Phone("99999999");
// same values -> returns true
- assertTrue(phone.equals(new Phone("999")));
+ assertTrue(phone.equals(new Phone("99999999")));
// same object -> returns true
assertTrue(phone.equals(phone));
@@ -55,6 +58,6 @@ public void equals() {
assertFalse(phone.equals(5.0f));
// different values -> returns false
- assertFalse(phone.equals(new Phone("995")));
+ assertFalse(phone.equals(new Phone("99999995")));
}
}
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..4fa0e866529 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -3,7 +3,6 @@
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.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
@@ -42,7 +41,7 @@ public void contains_personInList_returnsTrue() {
@Test
public void contains_personWithSameIdentityFieldsInList_returnsTrue() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Person editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND)
.build();
assertTrue(uniquePersonList.contains(editedAlice));
}
@@ -85,8 +84,7 @@ public void setPerson_editedPersonIsSamePerson_success() {
@Test
public void setPerson_editedPersonHasSameIdentity_success() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
- .build();
+ Person editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
uniquePersonList.setPerson(ALICE, editedAlice);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
expectedUniquePersonList.add(editedAlice);
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..bcf6f940c22 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -12,22 +12,26 @@
import org.junit.jupiter.api.Test;
import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
public class JsonAdaptedPersonTest {
private static final String INVALID_NAME = "R@chel";
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_GENDER = "invalid gender";
+ private static final String INVALID_MODULE = "#MATH";
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_GENDER = BENSON.getGender().toString();
+ private static final String VALID_MODULE = BENSON.getModules().stream().toList().get(0).getModule();
+ private static final Integer VALID_GRADE = 75;
+ private static final List VALID_MODULES = BENSON.getModules().stream()
+ .map(JsonAdaptedModule::new)
+ .collect(Collectors.toList());
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
@@ -41,14 +45,15 @@ 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_GENDER, VALID_MODULES, 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_GENDER, VALID_MODULES, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,46 +61,55 @@ 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_GENDER, VALID_MODULES, 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_GENDER, VALID_MODULES, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_invalidEmail_throwsIllegalValueException() {
+ public void toModelType_invalidGender_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
- String expectedMessage = Email.MESSAGE_CONSTRAINTS;
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_GENDER, VALID_MODULES, VALID_TAGS);
+ String expectedMessage = Gender.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);
- String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
+ public void toModelType_nullGender_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_MODULES, VALID_TAGS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_invalidAddress_throwsIllegalValueException() {
+ public void toModelType_invalidModule_throwsIllegalValueException() {
+ List invalidModules = new ArrayList<>(VALID_MODULES);
+ invalidModules.add(new JsonAdaptedModule(INVALID_MODULE, 100));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
- String expectedMessage = Address.MESSAGE_CONSTRAINTS;
- assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_GENDER, invalidModules, VALID_TAGS);
+ assertThrows(IllegalValueException.class, person::toModelType);
}
@Test
- public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
- String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
- assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ public void toModelType_nullModule_throwsIllegalValueException() {
+ JsonAdaptedModule module = new JsonAdaptedModule(null, VALID_GRADE);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Module.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, module::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullGrade_throwsIllegalValueException() {
+ JsonAdaptedModule module = new JsonAdaptedModule(VALID_MODULE, null);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Grade");
+ assertThrows(IllegalValueException.class, expectedMessage, module::toModelType);
}
@Test
@@ -103,7 +117,7 @@ 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_GENDER, VALID_MODULES, invalidTags);
assertThrows(IllegalValueException.class, person::toModelType);
}
diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
index 4e5ce9200c8..a7aa569e5c1 100644
--- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
+++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
@@ -61,8 +61,9 @@ public void readAddressBook_invalidAndValidPersonAddressBook_throwDataLoadingExc
}
@Test
- public void readAndSaveAddressBook_allInOrder_success() throws Exception {
+ public void readAndSaveAndArchiveAddressBook_allInOrder_success() throws Exception {
Path filePath = testFolder.resolve("TempAddressBook.json");
+ Path archivePath = testFolder.resolve("ArchiveAddressBook.json");
AddressBook original = getTypicalAddressBook();
JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath);
@@ -84,6 +85,11 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception {
readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified
assertEquals(original, new AddressBook(readBack));
+ // Archive the file and read back to compare
+ jsonAddressBookStorage.saveAddressBook(original, filePath);
+ jsonAddressBookStorage.saveArchivedAddressBook(original, archivePath);
+ ReadOnlyAddressBook readBackArchive = jsonAddressBookStorage.readAddressBook(archivePath).get();
+ assertEquals(original, new AddressBook(readBackArchive));
}
@Test
@@ -107,4 +113,26 @@ private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) {
public void saveAddressBook_nullFilePath_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null));
}
+
+ /**
+ * Archive {@code addressBook} at the specified {@code filePath}.
+ */
+ private void archiveAddressBook(ReadOnlyAddressBook addressBook, String archivePath) {
+ try {
+ new JsonAddressBookStorage(Paths.get(archivePath))
+ .saveArchivedAddressBook(addressBook, addToTestDataPathIfNotNull(archivePath));
+ } catch (IOException ioe) {
+ throw new AssertionError("There should not be an error writing to the file.", ioe);
+ }
+ }
+
+ @Test
+ public void saveArchivedAddressBook_nullAddressBook_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> archiveAddressBook(null, "SomeFile.json"));
+ }
+
+ @Test
+ public void saveArchivedAddressBook_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> archiveAddressBook(new AddressBook(), null));
+ }
}
diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java
index 99a16548970..6dc5bc9a721 100644
--- a/src/test/java/seedu/address/storage/StorageManagerTest.java
+++ b/src/test/java/seedu/address/storage/StorageManagerTest.java
@@ -48,21 +48,25 @@ public void prefsReadSave() throws Exception {
}
@Test
- public void addressBookReadSave() throws Exception {
+ public void addressBookReadSaveArchive() throws Exception {
/*
* Note: This is an integration test that verifies the StorageManager is properly wired to the
* {@link JsonAddressBookStorage} class.
* More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class.
*/
AddressBook original = getTypicalAddressBook();
+
storageManager.saveAddressBook(original);
+ storageManager.saveArchivedAddressBook(original, getTempFilePath("TestArchive"));
ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get();
+ ReadOnlyAddressBook retrievedArchive =
+ storageManager.readArchivedAddressBook(getTempFilePath("TestArchive")).get();
assertEquals(original, new AddressBook(retrieved));
+ assertEquals(original, new AddressBook(retrievedArchive));
}
@Test
public void getAddressBookFilePath() {
assertNotNull(storageManager.getAddressBookFilePath());
}
-
}
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..98c1ed8e84a 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -5,8 +5,8 @@
import java.util.stream.Stream;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -34,8 +34,8 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor = new EditPersonDescriptor();
descriptor.setName(person.getName());
descriptor.setPhone(person.getPhone());
- descriptor.setEmail(person.getEmail());
- descriptor.setAddress(person.getAddress());
+ descriptor.setGender(person.getGender());
+ descriptor.setModules(person.getModules());
descriptor.setTags(person.getTags());
}
@@ -56,18 +56,20 @@ public EditPersonDescriptorBuilder withPhone(String phone) {
}
/**
- * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building.
+ * Parses the {@code modules} into a {@code Set} and set it to the {@code EditPersonDescriptor}
+ * that we are building.
*/
- public EditPersonDescriptorBuilder withEmail(String email) {
- descriptor.setEmail(new Email(email));
+ public EditPersonDescriptorBuilder withModules(String... modules) {
+ Set moduleSet = Stream.of(modules).map(Module::new).collect(Collectors.toSet());
+ descriptor.setModules(moduleSet);
return this;
}
/**
- * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building.
+ * Sets the {@code Gender} of the {@code EditPersonDescriptor} that we are building.
*/
- public EditPersonDescriptorBuilder withAddress(String address) {
- descriptor.setAddress(new Address(address));
+ public EditPersonDescriptorBuilder withGender(String gender) {
+ descriptor.setGender(new Gender(gender));
return this;
}
diff --git a/src/test/java/seedu/address/testutil/FilterPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/FilterPersonDescriptorBuilder.java
new file mode 100644
index 00000000000..152942f4439
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/FilterPersonDescriptorBuilder.java
@@ -0,0 +1,90 @@
+package seedu.address.testutil;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.logic.commands.FilterCommand.FilterPersonDescriptor;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * A utility class to help with building FilterPersonDescriptor objects.
+ */
+public class FilterPersonDescriptorBuilder {
+
+ private FilterCommand.FilterPersonDescriptor descriptor;
+
+ public FilterPersonDescriptorBuilder() {
+ this.descriptor = new FilterPersonDescriptor();
+ }
+
+ public FilterPersonDescriptorBuilder(FilterPersonDescriptor descriptor) {
+ this.descriptor = new FilterPersonDescriptor(descriptor);
+ }
+
+ /**
+ * Returns an {@code FilterPersonDescriptor} with fields containing {@code person}'s details
+ */
+ public FilterPersonDescriptorBuilder(Person person) {
+ descriptor = new FilterPersonDescriptor();
+ descriptor.setName(person.getName());
+ descriptor.setPhone(person.getPhone());
+ descriptor.setGender(person.getGender());
+ descriptor.setModules(person.getModules());
+ descriptor.setTags(person.getTags());
+ }
+
+ /**
+ * Sets the {@code Name} of the {@code FilterPersonDescriptor} that we are building.
+ */
+ public FilterPersonDescriptorBuilder withName(String name) {
+ descriptor.setName(new Name(name));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Phone} of the {@code FilterPersonDescriptor} that we are building.
+ */
+ public FilterPersonDescriptorBuilder withPhone(String phone) {
+ descriptor.setPhone(new Phone(phone));
+ return this;
+ }
+
+ /**
+ * Parses the {@code modules} into a {@code Set} and set it to the {@code FilterPersonDescriptor}
+ * that we are building.
+ */
+ public FilterPersonDescriptorBuilder withModules(String... modules) {
+ Set moduleSet = Stream.of(modules).map(Module::new).collect(Collectors.toSet());
+ descriptor.setModules(moduleSet);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Gender} of the {@code FilterPersonDescriptor} that we are building.
+ */
+ public FilterPersonDescriptorBuilder withGender(String gender) {
+ descriptor.setGender(new Gender(gender));
+ return this;
+ }
+
+ /**
+ * Parses the {@code tags} into a {@code Set} and set it to the {@code FilterPersonDescriptor}
+ * that we are building.
+ */
+ public FilterPersonDescriptorBuilder withTags(String... tags) {
+ Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet());
+ descriptor.setTags(tagSet);
+ return this;
+ }
+
+ public FilterPersonDescriptor build() {
+ return descriptor;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..afb0e31db08 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -3,8 +3,8 @@
import java.util.HashSet;
import java.util.Set;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
+import seedu.address.model.person.Gender;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
@@ -18,13 +18,13 @@ public class PersonBuilder {
public static final String DEFAULT_NAME = "Amy Bee";
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_GENDER = "female";
+ public static final String DEFAULT_MODULE = "CS2103T";
private Name name;
private Phone phone;
- private Email email;
- private Address address;
+ private Gender gender;
+ private Set modules;
private Set tags;
/**
@@ -33,8 +33,9 @@ public class PersonBuilder {
public PersonBuilder() {
name = new Name(DEFAULT_NAME);
phone = new Phone(DEFAULT_PHONE);
- email = new Email(DEFAULT_EMAIL);
- address = new Address(DEFAULT_ADDRESS);
+ gender = new Gender(DEFAULT_GENDER);
+ modules = new HashSet<>();
+ modules.add(new Module(DEFAULT_MODULE));
tags = new HashSet<>();
}
@@ -44,8 +45,8 @@ public PersonBuilder() {
public PersonBuilder(Person personToCopy) {
name = personToCopy.getName();
phone = personToCopy.getPhone();
- email = personToCopy.getEmail();
- address = personToCopy.getAddress();
+ gender = personToCopy.getGender();
+ modules = new HashSet<>(personToCopy.getModules());
tags = new HashSet<>(personToCopy.getTags());
}
@@ -66,31 +67,31 @@ public PersonBuilder withTags(String ... tags) {
}
/**
- * Sets the {@code Address} of the {@code Person} that we are building.
+ * Sets the {@code Phone} of the {@code Person} that we are building.
*/
- public PersonBuilder withAddress(String address) {
- this.address = new Address(address);
+ public PersonBuilder withPhone(String phone) {
+ this.phone = new Phone(phone);
return this;
}
/**
- * Sets the {@code Phone} of the {@code Person} that we are building.
+ * Sets the {@code Module} of the {@code Person} that we are building.
*/
- public PersonBuilder withPhone(String phone) {
- this.phone = new Phone(phone);
+ public PersonBuilder withModules(String modules) {
+ this.modules = SampleDataUtil.getModuleSet(modules);
return this;
}
/**
- * Sets the {@code Email} of the {@code Person} that we are building.
- */
- public PersonBuilder withEmail(String email) {
- this.email = new Email(email);
+ * Sets the {@code Gender} of the {@code Person} that we are building.
+ * */
+ public PersonBuilder withGender(String gender) {
+ this.gender = new Gender(gender);
return this;
}
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, gender, modules, tags);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..14dbebcbd6c 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,7 +1,7 @@
package seedu.address.testutil;
-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_GENDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -10,6 +10,7 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.model.person.Module;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;
@@ -32,8 +33,10 @@ public static String getPersonDetails(Person person) {
StringBuilder sb = new StringBuilder();
sb.append(PREFIX_NAME + person.getName().fullName + " ");
sb.append(PREFIX_PHONE + person.getPhone().value + " ");
- sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
- sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
+ sb.append(PREFIX_GENDER + person.getGender().gender + " ");
+ person.getModules().stream().forEach(
+ s -> sb.append(PREFIX_MODULE + s.module + " ")
+ );
person.getTags().stream().forEach(
s -> sb.append(PREFIX_TAG + s.tagName + " ")
);
@@ -47,8 +50,15 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
StringBuilder sb = new StringBuilder();
descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" "));
descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" "));
- descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" "));
- descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" "));
+ descriptor.getGender().ifPresent(gender -> sb.append(PREFIX_GENDER).append(gender.gender).append(" "));
+ if (descriptor.getModules().isPresent()) {
+ Set modules = descriptor.getModules().get();
+ if (modules.isEmpty()) {
+ sb.append(PREFIX_MODULE);
+ } else {
+ modules.forEach(s -> sb.append(PREFIX_MODULE).append(s.module).append(" "));
+ }
+ }
if (descriptor.getTags().isPresent()) {
Set tags = descriptor.getTags().get();
if (tags.isEmpty()) {
diff --git a/src/test/java/seedu/address/testutil/RandomPersonGenerator.java b/src/test/java/seedu/address/testutil/RandomPersonGenerator.java
new file mode 100644
index 00000000000..3d31ba61441
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/RandomPersonGenerator.java
@@ -0,0 +1,136 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import seedu.address.model.person.Person;
+
+/**
+ * A random {@code Person} object generator to be used in stress tests.
+ */
+public class RandomPersonGenerator {
+ // Pools of possible values for random generation
+ private static final String[] BASE_NAMES = {"Alice", "Benson", "Carl", "Daniel", "Elle", "Fiona",
+ "George", "Hoon", "Ida", "Amy", "Bob"};
+ private static final String[] GENDERS = {"male", "female"};
+ private static final String[] MODULES = {"MA1522", "CS1101", "EL1101", "CS2103T"};
+ private static final String[] TAGS = {"friends", "owesMoney", "runner", "husband"};
+
+ private static final Random RANDOM = new Random();
+ private static final Set USED_NAMES = new HashSet<>(); // To ensure unique names
+
+ /**
+ * Generates a list of random Person objects with unique names.
+ *
+ * @param count The number of persons to generate.
+ * @return List of randomly generated Person objects with unique names.
+ */
+ public static List generateRandomPersons(int count) {
+ List randomPersons = new ArrayList<>();
+
+ for (int i = 0; i < count; i++) {
+ randomPersons.add(generateRandomPerson());
+ }
+
+ return randomPersons;
+ }
+
+ /**
+ * Generates a single random Person object with a unique name.
+ *
+ * @return Randomly generated Person with a unique name.
+ */
+ private static Person generateRandomPerson() {
+ String uniqueName = generateUniqueName();
+ String phone = generateRandomPhone();
+ String gender = getRandomElement(GENDERS);
+ List personTags = getRandomTags();
+
+ // Build the Person object
+ return new PersonBuilder()
+ .withName(uniqueName)
+ .withPhone(phone)
+ .withGender(gender)
+ .withTags(personTags.toArray(new String[0]))
+ .withModules(getRandomElement(MODULES))
+ .build();
+ }
+
+ /**
+ * Generates a unique name by combining a base name with a unique number suffix.
+ *
+ * @return A unique name.
+ */
+ private static String generateUniqueName() {
+ String baseName = getRandomElement(BASE_NAMES);
+ String uniqueName;
+
+ // Ensure the name is unique by appending a 4-letter random suffix
+ do {
+ StringBuilder suffix = new StringBuilder();
+ for (int i = 0; i < 4; i++) {
+ // Generate a random letter (A-Z)
+ char randomChar = (char) ('A' + RANDOM.nextInt(26));
+ suffix.append(randomChar);
+ }
+ uniqueName = baseName + suffix;
+ } while (USED_NAMES.contains(uniqueName));
+
+ USED_NAMES.add(uniqueName); // Mark the name as used
+ return uniqueName;
+ }
+
+ /**
+ * Selects a random element from an array.
+ *
+ * @param array Array of possible values.
+ * @return Random element from the array.
+ */
+ private static String getRandomElement(String[] array) {
+ return array[RANDOM.nextInt(array.length)];
+ }
+
+ /**
+ * Generates a random phone number.
+ *
+ * @return Randomly generated phone number.
+ */
+ private static String generateRandomPhone() {
+ return String.valueOf(80000000 + RANDOM.nextInt(10000000));
+ }
+
+ /**
+ * Generates a random set of tags (1-2 random tags).
+ *
+ * @return List of random tags.
+ */
+ private static List getRandomTags() {
+ int tagCount = RANDOM.nextInt(2) + 1; // Randomly select 1 or 2 tags
+ List tags = new ArrayList<>();
+
+ for (int i = 0; i < tagCount; i++) {
+ tags.add(getRandomElement(TAGS));
+ }
+
+ return tags;
+ }
+
+ /**
+ * Generates a random set of modules (1-2 random modules).
+ *
+ * @return List of random modules.
+ */
+ private static List getRandomModules() {
+ int moduleCount = RANDOM.nextInt(2) + 1; // Randomly select 1 or 2 modules
+ List modules = new ArrayList<>();
+
+ for (int i = 0; i < moduleCount; i++) {
+ modules.add(getRandomElement(MODULES));
+ }
+
+ return modules;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..2d0b29e3b48 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -1,15 +1,16 @@
package seedu.address.testutil;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GENDER_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GENDER_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_MODULE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.testutil.RandomPersonGenerator.generateRandomPersons;
import java.util.ArrayList;
import java.util.Arrays;
@@ -24,36 +25,33 @@
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")
- .withTags("friends").build();
+ .withPhone("94351253").withGender("female").withTags("friends").withModules("MA1522").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
- .withAddress("311, Clementi Ave 2, #02-25")
- .withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
- public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
- .withEmail("heinz@example.com").withAddress("wall street").build();
- public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
- .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build();
- public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
- .withEmail("werner@example.com").withAddress("michegan ave").build();
- public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
- .withEmail("lydia@example.com").withAddress("little tokyo").build();
- public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
- .withEmail("anna@example.com").withAddress("4th street").build();
+ .withPhone("98765432").withGender("male").withTags("owesMoney", "friends").withModules("MA1522").build();
+ public static final Person CARL = new PersonBuilder().withName("Carl Kurz")
+ .withPhone("95352563").withGender("male").withModules("EL1101").withTags("runner").build();
+ public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier")
+ .withPhone("87652533").withGender("male").withTags("friends").withModules("CS1101").build();
+ public static final Person ELLE = new PersonBuilder().withName("Elle Meyer")
+ .withPhone("94820224").withGender("female").withModules("MA1522").build();
+ public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz")
+ .withPhone("94824207").withGender("female").withModules("MA1522").build();
+ public static final Person GEORGE = new PersonBuilder().withName("George Best")
+ .withPhone("94824402").withGender("male").withModules("EL1101").build();
// Manually added
- public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424")
- .withEmail("stefan@example.com").withAddress("little india").build();
- public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
- .withEmail("hans@example.com").withAddress("chicago ave").build();
+ public static final Person HOON = new PersonBuilder().withName("Hoon Meier")
+ .withPhone("84082424").withGender("male").withModules("MA1522").build();
+ public static final Person IDA = new PersonBuilder().withName("Ida Mueller")
+ .withPhone("84821301").withGender("female").withModules("CS2103T").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)
- .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build();
+ .withGender(VALID_GENDER_AMY).withTags(VALID_TAG_FRIEND).withModules(VALID_MODULE_AMY).build();
public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
- .build();
+ .withGender(VALID_GENDER_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ .withModules(VALID_MODULE_BOB).build();
+
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER
@@ -70,6 +68,17 @@ public static AddressBook getTypicalAddressBook() {
return ab;
}
+ /**
+ * Returns an {@code AddressBook} with 1000 random generated persons for stress testing.
+ */
+ public static AddressBook getStressTestAddressBook() {
+ AddressBook ab = new AddressBook();
+ for (Person person : generateRandomPersons(1000)) {
+ ab.addPerson(person);
+ }
+ return ab;
+ }
+
public static List getTypicalPersons() {
return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE));
}