diff --git a/.gitignore b/.gitignore
index 284c4ca7cd9..f3586cef993 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ src/main/resources/docs/
/data/
/config.json
/preferences.json
+/addressbook.json
/*.log.*
hs_err_pid[0-9]*.log
@@ -21,3 +22,5 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+
+bin
diff --git a/README.md b/README.md
index 16208adb9b6..82c75ae2c5f 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,18 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![CI Status](https://github.com/AY2425S1-CS2103T-W08-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103T-W08-4/tp/actions)
+[![codecov](https://codecov.io/github/AY2425S1-CS2103T-W08-2/tp/graph/badge.svg?token=1LWE987C3X)](https://codecov.io/github/AY2425S1-CS2103T-W08-4/tp)
![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.
- * 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.
+# GoonBook
+
+Goon Book is specialised to help educators with keeping track of their students. It can be used to record their students
+with their details, and access relevant information easily and conveniently
+
+![Ui](docs/images/basic_command_flowchart.png)
+
+## Quick Links
+
+- [Documentation](https://ay2425s1-cs2103t-w08-4.github.io/tp/)
+- [Developers Guide](https://ay2425s1-cs2103t-w08-4.github.io/tp/DeveloperGuide.html)
+
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
diff --git a/addressbook.json b/addressbook.json
new file mode 100644
index 00000000000..0f7f2f1a9eb
--- /dev/null
+++ b/addressbook.json
@@ -0,0 +1,38 @@
+{
+ "persons" : [ {
+ "name" : "Alice Pauline",
+ "studentClass" : "4A",
+ "phone" : "94351253",
+ "tags" : [ "friends" ]
+ }, {
+ "name" : "Benson Meier",
+ "studentClass" : "6B",
+ "phone" : "98765432",
+ "tags" : [ "owesMoney", "friends" ]
+ }, {
+ "name" : "Carl Kurz",
+ "studentClass" : "4A",
+ "phone" : "95352563",
+ "tags" : [ ]
+ }, {
+ "name" : "Daniel Meier",
+ "studentClass" : "7H",
+ "phone" : "87652533",
+ "tags" : [ "friends" ]
+ }, {
+ "name" : "Elle Meyer",
+ "studentClass" : "3U",
+ "phone" : "9482224",
+ "tags" : [ ]
+ }, {
+ "name" : "Fiona Kunz",
+ "studentClass" : "3B",
+ "phone" : "9482427",
+ "tags" : [ ]
+ }, {
+ "name" : "George Best",
+ "studentClass" : "5H",
+ "phone" : "9482442",
+ "tags" : [ ]
+ } ]
+}
diff --git a/build.gradle b/build.gradle
index 0db3743584e..e83d2eb391c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -59,14 +59,23 @@ dependencies {
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0'
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4'
-
+ implementation 'org.json:json:20211205'
+ implementation 'com.opencsv:opencsv:5.5.2'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'goonbook.jar'
+}
+
+run {
+ enableAssertions = true
}
defaultTasks 'clean', 'test'
+
+run {
+ enableAssertions = true
+}
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000000..3b8cd63a6de
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,23 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+_markbind/logs/
+
+# Dependency directories
+node_modules/
+
+# Production
+_site/
+
+# Env
+.env
+.env.local
+
+# IDE configs
+.vscode/
+.idea/*
+*.iml
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index ff3f04abd02..c1c6d789b63 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,51 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Beh Wen Jie
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
+[[github](https://github.com/wenjebs)]
[[portfolio](team/johndoe.md)]
* Role: Project Advisor
+* Responsibilities: Mewing
-### Jane Doe
+### Yim Jian Bing
-
+
-[[github](http://github.com/johndoe)]
+[[github](http://github.com/yimjianbing)]
[[portfolio](team/johndoe.md)]
-* Role: Team Lead
+* Role: Frontend designer
* Responsibilities: UI
-### Johnny Doe
+### Martin NGGGGGG
-
+
[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
* Role: Developer
* Responsibilities: Data
-### Jean Doe
+### Annie Song Si Hyun
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/hyxnnii)]
+[[portfolio](team/hyxnnii.md)]
* Role: Developer
* Responsibilities: Dev Ops + Threading
-### James Doe
+### Shaun Lee
-
+
-[[github](http://github.com/johndoe)]
+[[github](http://github.com/hoodini231)]
[[portfolio](team/johndoe.md)]
-* Role: Developer
-* Responsibilities: UI
+* Role: Back-end Developer
+* Responsibilities: Database
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 743c65a49d2..f48479b5456 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,65 +2,121 @@
layout: page
title: Developer Guide
---
-* Table of Contents
-{:toc}
---------------------------------------------------------------------------------------------------------------------
+- Table of Contents
+
+1. [Acknowledgements](#acknowledgements)
+2. [Setting up, getting started](#setting-up-getting-started)
+3. [Design](#design)
+ 1. [Architecture](#architecture)
+ 2. [UI component](#ui-component)
+ 3. [Logic component](#logic-component)
+ 4. [Model component](#model-component)
+ 5. [Storage component](#storage-component)
+ 6. [Common classes](#common-classes)
+4. [Implementation](#implementation)
+ 1. [Group feature](#group-feature)
+ 2. [DeleteGroup feature](#delete-group-feature)
+ 3. [Tag feature](#tag-feature)
+ 4. [Proposed Undo/redo feature](#proposed-undoredo-feature)
+ 1. [Proposed Implementation](#proposed-implementation)
+ 2. [Design considerations](#design-considerations)
+5. [Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops)
+6. [Appendix: Requirements](#appendix-requirements)
+ 1. [Product scope](#product-scope)
+ 2. [User Stories](#user-stories)
+ 3. [Use cases](#use-cases)
+ 4. [Non-Functional Requirements](#non-functional-requirements)
+ 5. [Glossary](#glossary)
+7. [Appendix: Instructions for manual testing](#appendix-instructions-for-manual-testing)
+ 1. [Launch and shutdown](#launch-and-shutdown)
+ 2. [Deleting a person](#deleting-a-person)
+8. [Appendix: Planned Enhancements](#appendix-planned-enhancements)
+ 1. [Ability to export specific groups to CSV files](#enhancement-1-export-specific-groups-to-csv-files)
+ 2. [Ability to change export filename or file path](#enhancement-2-custom-export-filename-and-file-path)
+ 3. [Ability to import groups](#enhancement-3-import-groups-from-csv-files)
+ 4. [Better student duplication handling](#enhancement-4-improved-handling-of-duplicate-student-names)
+ 5. [Support for special characters in name and class fields](#enhancement-5-support-special-characters-in-names-and-class-fields)
+ 6. [Increasing support to host more student information](#enhancement-6-support-additional-student-information)
+ 7. [Increased filter options for students](#enhancement-7-advanced-filtering-options-for-students)
+ 8. [Support for precise student name searching](#enhancement-8-precise-student-name-searching)
+
+---
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+GoonBook is a brownfield software project based off [AddressBook Level-3](https://se-education.org/addressbook-level3/) ([UG](https://se-education.org/addressbook-level3/UserGuide.html), [DG](https://se-education.org/addressbook-level3/DeveloperGuide.html)), taken under the CS2103T Software Engineering module held by the School of Computing at the National University of Singapore.
+
+Java dependencies:
+
+- JavaFX for GUI
+- JUnit5 for testing
+
+Documentation dependencies:
---------------------------------------------------------------------------------------------------------------------
+- Jekyll for rendering the website
+- PlantUML for creating UML diagrams
## **Setting up, getting started**
Refer to the guide [_Setting up and getting started_](SettingUp.md).
---------------------------------------------------------------------------------------------------------------------
+---
## **Design**
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
+:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [
+_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create
+and edit diagrams.
+
### Architecture
-The ***Architecture Diagram*** given above explains the high-level design of the App.
+The **_Architecture Diagram_** given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
**Main components of the architecture**
-**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
-* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
-* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
+**`Main`** (consisting of
+classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java)
+and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is
+in charge of the app launch and shut down.
+
+- At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
+- At shut down, it shuts down the other components and invokes cleanup methods where necessary.
The bulk of the app's work is done by the following four components:
-* [**`UI`**](#ui-component): The UI of the App.
-* [**`Logic`**](#logic-component): The command executor.
-* [**`Model`**](#model-component): Holds the data of the App in memory.
-* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
+- [**`UI`**](#ui-component): The UI of the App.
+- [**`Logic`**](#logic-component): The command executor.
+- [**`Model`**](#model-component): Holds the data of the App in memory.
+- [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
**How the architecture components interact with each other**
-The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
+The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues
+the command `delete 1`.
Each of the four main components (also shown in the diagram above),
-* defines its *API* in an `interface` with the same name as the Component.
-* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point.
+- defines its _API_ in an `interface` with the same name as the Component.
+- implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding
+ API `interface` mentioned in the previous point.
-For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
+For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using
+the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component
+through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the
+implementation of a component), as illustrated in the (partial) class diagram below.
@@ -68,20 +124,29 @@ The sections below give more details of each component.
### UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified
+in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
![Structure of the UI Component](images/UiClassDiagram.png)
-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+The UI consists of a `MainWindow` that is made up of parts
+e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`,
+inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the
+visible GUI.
-The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that
+are in the `src/main/resources/view` folder. For example, the layout of
+the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java)
+is specified
+in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
-* executes user commands using the `Logic` component.
-* listens for changes to `Model` data so that the UI can be updated with the modified data.
-* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
-* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+- executes user commands using the `Logic` component.
+- listens for changes to `Model` data so that the UI can be updated with the modified data.
+- keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
+- depends on some classes in the `Model` component, as it displays `Person` object and `Group` object residing in the
+ `Model`.
### Logic component
@@ -91,7 +156,8 @@ Here's a (partial) class diagram of the `Logic` component:
-The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example.
+The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API
+call as an example.
![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
@@ -100,32 +166,48 @@ The sequence diagram below illustrates the interactions within the `Logic` compo
How the `Logic` component works:
-1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command.
-1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`.
-1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
- Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve.
-1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
+1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates
+ a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command.
+2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which
+ is executed by the `LogicManager`.
+3. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
+ Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take
+ several interactions (between the command object and the `Model`) to achieve.
+4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
-* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
-* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
+
+- When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a
+ placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse
+ the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as
+ a `Command` object.
+- All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser`
+ interface so that they can be treated similarly where possible e.g, during testing.
### Model component
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
-
+**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
-* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
-* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
-* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
+- stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
+- stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which
+ is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to
+ this list so that the UI automatically updates when the data in the list change.
+- stores the currently 'selected' `Group` objects (e.g., results of a search query) as a separate _filtered_ list which
+ is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to
+ this list so that the UI automatically updates when the data in the list change.
+
+- stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as
+ a `ReadOnlyUserPref` objects.
+- does not depend on any of the other three components (as the `Model` represents data entities of the domain, they
+ should make sense on their own without depending on other components)
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
@@ -133,7 +215,6 @@ The `Model` component,
-
### Storage component
**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
@@ -141,43 +222,226 @@ The `Model` component,
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`)
+
+- 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`)
### Common classes
Classes used by multiple components are in the `seedu.address.commons` package.
---------------------------------------------------------------------------------------------------------------------
+---
## **Implementation**
This section describes some noteworthy details on how certain features are implemented.
+### Group feature
+
+The `GroupCommand` allows educators to group students together within the application. This is particularly useful for managing classes, group-based activities, assignments, and projects efficiently.
+
+#### Implementation Details
+
+The `GroupCommand` is implemented by extending the base `Command` class. It uses prefixes such as `/g`, `/s`, specifying
+required data fields `groupName`, `students`, respectively. Once the data fields are filled,
+a new Group is added. It implements the following operations:
+
+* `execute(Model)` — Checks the current address book state by calling the following methods as part of our defensive programming:
+ 1. `model.hasGroupName(new Group(groupName, List.of()))`, and throws a `CommandException` if a duplicate Group is found
+ 2. `groupName.isEmpty()`, and throws a `CommandException` if a the group name field is empty is found
+ 3. `model.getFilteredPersonList()`, to obtain a `List` to iterate over to search for the inputted student names and adding them to a new `List` to be passed into the constrcutor for a new `Group`
+* `addGroup(group)` — Adds the Group to the group list. This operation is exposed in the `Model` interface as `Model#addGroup(Group)`.
+
+The Group command is initiated by firstly checking the filtered group list to ensure no duplicate is found, after which `Model#addGroup(Group)` is called to complete the actual addition.
+
+Given below is an example usage scenario of how the addition mechanism behaves when the user tries to add a group to the group list.
+
+Step 1. The user launches the application, with some students and groups added to the address book already.
+The `AddressBook` will be initialized with the previously saved address book state.
+
+Step 2. The user executes `group` command with the specific data at each prefix to specify the person to be added.
+The `GroupCommand` will then call `excecute()`, which checks whether there is a duplicate Group in the group list before calling `addGroup(Group)`.
+
+
+
+:information_source: **Note:** If the `groupName` and `students` provided is invalid, a `CommandException` will be thrown.
+
+
+
+#### Sequence Diagram
+
+
+
+#### Design considerations:
+
+**Aspect: How Group executes:**
+
+- **Alternative 1 (current choice):** Create a new `Group` Object containing a list of `Person`, and storing it in the `model` component.
+
+ - Pros: Easier to implement and more intuitive.
+ - Cons: May have performance issues in terms of memory usage, and deleting a student will require iterating over all `Group`s to ensure it is deleted from all groups
+
+- **Alternative 2:** Each `Person` Object has a field of a List of all the groups it is in.
+ - Pros: Deleting a person is alot easier (Simply by deleting the `Person` Object will remove it from all the `Group`s it is in)).
+ - Cons: Very hard to implement, and not a good representation of the has-a relationship between `Group` and `Person`.
+
+### Delete Group feature
+
+The `DeleteGroupCommand` allows educators to delete Groups within the application. This is particularly useful
+for deleting Groups that the user no longer need, assisting the management of Groups.
+
+#### Implementation Details
+
+The `DeleteGroupCommand` is implemented by extending the base `Command` class. It does not use any prefixes, rather
+using the `groupName` to identify which group to delete. Once the data fields are filled, it will look for the specified group containing that `groupName` and delete it.
+It implements the following operations:
+
+* `execute(Model)` — Checks the current address book state by calling the `model.getFilteredGroupList()` to obtain an `ObservableList` and iterating over it to find a matching `groupName`, throwing a `CommandException` if
+ no `Group` is found.
+* `deleteGroup(groupToDelete)` — Deletes the Group from the group list. This operation is exposed in the `Model`
+ interface as `Model#deleteGroup(Group)`.
+
+Given below is an example usage scenario of how the delete group mechanism behaves when the user tries to delete a group from the group list.
+
+Step 1. The user launches the application, with some students and groups added to the address book already. The
+`AddressBook` will be initialized with the previously saved address book state.
+
+Step 2. The user executes `deleteGroup` command with the specified Group Name to specify the `Group` to be added.
+The `deleteGroupCommand` will then call `excecute()`, which checks whether there is a Group in the group list that
+matches the name before calling `deleteGroup(Group)`.
+
+#### Sequence Diagram
+
+
+
+#### Design considerations:
+
+**Aspect: How deleteGroup executes:**
+
+- **Alternative 1 (current choice):** Iterating over all `Group` in model and searching for a match and proceeding to delete that group
+
+ - Pros: Easier to implement, and deleting a Group will not cause bugs in other logic components
+ - Cons: Can become very slow when there are many groups in `Logic`
+
+- **Alternative 2:** Using a Hash to map every groupName to its respective Group
+ - Pros: Deleting a Group is much faster, since there is no longer a need to iterate over all groups within model
+ - Cons: The logic will be much more complicated, and showing a Hash through JavaFx is much more difficult compared to using the already provided `ObservableList`
+
+### Export feature
+
+The `ExportCommand` allows educators to export the data in their current GoonBook storage out as a csv file for usage in other spreadsheets such as Google spreadsheets, giving GoonBook seamless integration with existing spreadsheet editors.
+
+#### Implementation Details
+
+The `ExportCommand` is implemented by extending the base `Command` class. It does not use any prefixes, and is called as is. Once the data fields, like `projectRootPath` are filled, it will proceed to convert the data in model into a json file, then into a csv file for the user to freely use. It implements the following operations:
+
+* `execute(Model)` — Obtain the `Path` Objects that represent both the import directory, and the export directory, by calling the `Path#resolve` method.
+* `saveJsonfile` — Takes in the `model`, `importPath` and `exportPath` as parameters to then convert the data in model to a csv file by further calling the methods `translateJsonToCsv` and `getPersonTags` which will perform the necessary actions to convert the json file into a csv file
+
+Given below is an example usage scenario of how the export mechanism behaves when the user tries to export data
+from GoonBook.
+
+Step 1. The user launches the application, with some students and groups added to the address book already. The
+`AddressBook` will be initialized with the previously saved address book state.
+
+Step 2. The user executes `export` command. The `ExportCommand` will then call `excecute()`, which proceeds to take the current data stored in the address book and converting it into a csv file, and saving it in the same directory as root for the user to access
+
+#### Sequence Diagram
+
+
+
+#### Design considerations:
+
+**Aspect: How export executes:**
+
+- **Alternative 1 (current choice):** Using the existing Storage interface and addressBook.json to directly convert into a csv file
+
+ - Pros: Has already laid the foundation for our csv conversion method to build off from
+ - Cons: Has an added dependency on the json file, so if any issues were to occur to the json coverter, it would mean that the export function would no longer function
+
+- **Alternative 2:** Directly exporting the data of the address book and converting it into a csv file without needing the dependency on the json file
+ - Pros: Will ensure that any bugs with the jsonAdaptablePersons and etc will not affect the functionality of the export function, since they would no longer be associated
+ - Cons: Extremely hard to implement, and would not be worth the hassle
+
+### Tag feature
+
+The `TagCommand` allows educators to add custom tags to their students for quality of life benefits, such as reminders, or to categorise them accordingly.
+
+#### Implementation Details
+
+The `TagCommand` is implemented by extending the base `Command` class. It uses only the prefix `/t` taking in a index as the other part of the command syntax, specifying required data fields `newTags` and `targetIndex`, respectively. Once the data fields are filled, new tags are added to a specified Person. It implements the following operations:
+
+* `execute(Model)` — Obtain a `List` using the `model.getFilteredPersonList`, and using the `targetIndex` to get the corresponding `Person` object to add the `newTags` to, by calling `model.addTag`
+* `targetIndex.getZeroBased` — Used to check if the given index is larger than the size of the filteredPersonList, and throws a `CommandException` if the index is larger than the index in filteredPersonList
+* `model.tagExists(person, newTags)` — Used to check if the person object already has the given tag name already,
+ throws a `CommandException` if the person is found to already have an existing tag of the same name
+
+Given below is an example usage scenario of how the tag mechanism behaves when the user tries to add a tag to a student
+
+Step 1. The user launches the application, with some students and groups added to the address book already.
+The `AddressBook` will be initialized with the previously saved address book state.
+
+Step 2. The user executes `tag` command. The `TagCommand` will then call `excecute()`, which proceeds to take
+the list of filtered person, and using the index provided by the user, choose that specific person to add the given
+`tags` to
+
+#### Sequence Diagram
+
+
+
+#### Design considerations:
+
+**Aspect: How tag executes:**
+
+- **Alternative 1 (current choice):** Using a Set to implement a collection of tags per student
+
+ - Pros: Can easily check to see if there are any duplicate tags due to the nature of the data structure, saving
+ time that would have otherwise been spent iterating through it
+ - Cons: If we ever decided to allow multiple tags with the same name, we will not be able to do so
+
+- **Alternative 2:** Using a List to implement a collection of Tags
+-
+ - Pros: Much simpler to implement, and familiar to use
+ - Cons: Can become very inefficient, when there are many tags, it could take some time to iterate through every
+ tag one by one to ensure that the same tag doesn't exist twice
+
+
### \[Proposed\] Undo/redo feature
#### Proposed Implementation
-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:
+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:
-* `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.
+- `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.
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()`
+and `Model#redoAddressBook()` respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+Step 1. The 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.
![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. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command
+calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes
+to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book
+state.
![UndoRedoState1](images/UndoRedoState1.png)
-Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+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`.
![UndoRedoState2](images/UndoRedoState2.png)
@@ -185,7 +449,9 @@ Step 3. The user executes `add n/David …` to add a new person. The `add` co
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing
+the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer`
+once to the left, pointing it to the previous address book state, and restores the address book to that state.
![UndoRedoState3](images/UndoRedoState3.png)
@@ -206,17 +472,23 @@ Similarly, how an undo operation goes through the `Model` component is shown bel
![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png)
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once
+to the right, pointing to the previously undone state, and restores the address book to that state.
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such
+as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`.
+Thus, the `addressBookStateList` remains unchanged.
![UndoRedoState4](images/UndoRedoState4.png)
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not
+pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be
+purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern
+desktop applications follow.
![UndoRedoState5](images/UndoRedoState5.png)
@@ -228,33 +500,28 @@ The following activity diagram summarizes what happens when a user executes a ne
**Aspect: How undo & redo executes:**
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+- **Alternative 1 (current choice):** Saves the entire address book.
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
-
-_{more aspects and alternatives to be added}_
-
-### \[Proposed\] Data archiving
+ - Pros: Easy to implement.
+ - Cons: May have performance issues in terms of memory usage.
-_{Explain here how the data archiving feature will be implemented}_
+- **Alternative 2:** Individual command knows how to undo/redo by
+ itself.
+ - Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
+ - Cons: We must ensure that the implementation of each individual command are correct.
---------------------------------------------------------------------------------------------------------------------
+---
## **Documentation, logging, testing, configuration, dev-ops**
-* [Documentation guide](Documentation.md)
-* [Testing guide](Testing.md)
-* [Logging guide](Logging.md)
-* [Configuration guide](Configuration.md)
-* [DevOps guide](DevOps.md)
+- [Documentation guide](Documentation.md)
+- [Testing guide](Testing.md)
+- [Logging guide](Logging.md)
+- [Configuration guide](Configuration.md)
+- [DevOps guide](DevOps.md)
---------------------------------------------------------------------------------------------------------------------
+---
## **Appendix: Requirements**
@@ -262,73 +529,403 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+- educators who need to micromanage multiple students
+- prefer desktop apps over other types
+- can type fast
+- prefers typing to mouse interactions
+- is reasonably comfortable using CLI apps
+
+**Value proposition**: Goon Book is specialised to help educators with keeping track of their students. It can be used
+to record their students with their details, and access relevant information easily and conveniently
+
+### User Stories
+
+| Priority | As a … | I want to … | So that I can… |
+| -------- | -------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --- |
+| `* * *` | educator | add a new student | include all students I have currently in my app |
+| `* * *` | educator | delete a student | keep my database of students concise with only currently relevant students |
+| `* * *` | educator | search for students by name | find information about specific students |
+| `* * *` | educator | group students | efficiently manage classes, group-based activities, assignments, and projects |
+| `* * *` | educator | delete groups | correct mistakes by deleting a group |
+| `* * *` | educator | import and export student data from other systems | streamline data management and avoid manual entry, ensuring compatibility with school databases or grade books | |
+| `* *` | educator | search for groups by name | find information about specific groups of students |
+| `* *` | educator | store additional information about students on grades, attendance or notes | better access and organise student information |
+| `* *` | educator | edit a student’s details | correct mistakes or update new information about the student |
+| `* *` | educator | filter searched students by name | quickly find specific students |
+| `*` | educator | use security measures for student data | protect sensitive information and control access to parental data |
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+### Use cases
+(For all use cases below, the **System** is the `GoonBook` and the **Actor** is the `Educator`, unless specified
+otherwise)
-### User stories
+---
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+#### **Use Case: UC01 - Add a New Student**
-| 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 |
+**Main Success Scenario (MSS):**
-*{More to be added}*
+1. Educator chooses to add a new student.
+2. System prompts for the student's name, class, and contact information.
+3. Educator enters the student's name, class, and contact information.
+4. System validates the input.
+5. System adds the new student to the student list.
+6. System displays a confirmation message.
-### Use cases
+ Use case ends.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+**Extensions:**
-**Use case: Delete a person**
+- **2a.** Educator submits the form without entering the class or contact information.
-**MSS**
+ - **2a1.** System detects the missing class or contact information and returns an invalid command format message.
+ - **2a2.** Educator provides the missing information.
-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
+ Use case resumes from step 3.
+
+- **3a.** System detects that the entered student is a duplicate.
+
+ - **3a1.** System informs the educator that the student already exists and cancels the addition.
Use case ends.
-**Extensions**
+- **3b.** System detects invalid characters in the student's name (e.g., non-alphabetic characters).
-* 2a. The list is empty.
+ - **3b1.** System requests the educator to enter a valid name.
+ - **3b2.** Educator enters a valid name.
- Use case ends.
+ Steps 3b1-3b2 are repeated until the input is valid.
-* 3a. The given index is invalid.
+ Use case resumes from step 4.
- * 3a1. AddressBook shows an error message.
+- **3c.** System detects invalid input in the contact information (e.g., non-numeric characters).
- Use case resumes at step 2.
+ - **3c1.** System requests the educator to enter a valid contact number.
+ - **3c2.** Educator enters a valid contact number.
-*{More to be added}*
+ Steps 3c1-3c2 are repeated until the input is valid.
-### Non-Functional Requirements
+ Use case resumes from step 4.
+
+---
+
+#### **Use Case: UC02 - Search for a Student**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to search for a student.
+2. System prompts for the student's name or keywords.
+3. Educator enters the student's name or search keywords.
+4. System searches for matching students.
+5. System displays the list of matching students with their contact and class information.
+
+ Use case ends.
+
+**Extensions:**
+
+- **4a.** No students match the search criteria.
+
+ - **4a1.** System informs the educator that no matching students were found.
+
+ Use case ends.
+
+---
+
+#### **Use Case: UC03 - Delete a Student**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to delete a student.
+2. System prompts for the student's index in the list.
+3. Educator enters the student's index.
+4. System deletes the student from the student list.
+5. System displays a confirmation message.
+
+ Use case ends.
+
+**Extensions:**
+
+- **3a.** The specified index is invalid.
+
+ - **3a1.** System informs the educator of the invalid index.
+ - **3a2.** Educator enters a valid index.
+
+ Use case resumes from step 4.
+
+---
+
+#### **Use Case: UC04 - Edit a Student**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to edit a student.
+2. Educator enters the student's index and new details to update.
+3. System validates the new input.
+4. System updates the student's information.
+5. System displays a confirmation message.
+
+ Use case ends.
+
+**Extensions:**
+
+- **3a.** The specified index is invalid.
+
+ - **3a1.** System informs the educator of the invalid index.
+ - **3a2.** Educator enters a valid index.
+
+ Use case resumes from step 4.
+
+- **3b.** System detects invalid input in the new details.
+
+ - **3b1.** System requests the educator to correct the invalid input.
+ - **3b2.** Educator enters valid details.
+
+ Use case resumes from step 4.
+
+---
+
+#### **Use Case: UC05 - List All Students**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to list all students.
+2. System retrieves the list of all students.
+3. System displays the list with students' names, classes, and contact information.
+
+ Use case ends.
+
+---
+
+#### **Use Case: UC06 - List All Groups**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to list all groups.
+2. System retrieves the list of all groups.
+3. System displays the list with group names and member information.
+
+ Use case ends.
+
+---
+
+#### **Use Case: UC07 - Group Students Together**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to create a new group.
+2. System prompts for the group name.
+3. Educator enters the group name.
+4. System prompts to select students to add to the group.
+5. Educator selects students from the student list.
+6. System validates the group name and selected students.
+7. System creates the new group with the selected students.
+8. System displays a confirmation message.
-1. Should work on any _mainstream OS_ as long as it has Java `17` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+ Use case ends.
-*{More to be added}*
+**Extensions:**
+
+- **3a.** Educator enters a group name that already exists.
+
+ - **3a1.** System informs the educator of the duplicate group name.
+ - **3a2.** Educator enters a unique group name.
+
+ Use case resumes from step 4.
+
+- **5a.** Educator does not select any students.
+
+ - **5a1.** System informs the educator that at least one student must be added to the group.
+
+ Use case resumes from step 4.
+
+- **6a.** System detects invalid characters in the group name.
+
+ - **6a1.** System requests the educator to enter a valid group name.
+ - **6a2.** Educator enters a valid group name.
+
+ Use case resumes from step 6.
+
+---
+
+#### **Use Case: UC08 - Locate Groups by Name**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to search for groups.
+2. System prompts for the group name or keywords.
+3. Educator enters the group name or search keywords.
+4. System searches for matching groups.
+5. System displays the list of matching groups.
+
+ Use case ends.
+
+**Extensions:**
+
+- **4a.** No groups match the search criteria.
+
+ - **4a1.** System informs the educator that no matching groups were found.
+
+ Use case ends.
+
+---
+
+#### **Use Case: UC09 - Delete a Group**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to delete a group.
+2. System prompts for the group name.
+3. Educator enters the group name.
+4. System deletes the group from the group list.
+5. System displays a confirmation message.
+
+ Use case ends.
+
+**Extensions:**
+
+- **3a.** The specified group name does not exist.
+
+ - **3a1.** System informs the educator that the group was not found.
+ - **3a2.** Educator enters a valid group name.
+
+ Use case resumes from step 4.
+
+---
+
+#### **Use Case: UC10 - Add a Tag to a Student**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to add a tag to a student.
+2. System prompts for the student's index and the tag name.
+3. Educator enters the student's index and tag name.
+4. System validates the input.
+5. System adds the tag to the student.
+6. System displays a confirmation message.
+
+ Use case ends.
+
+**Extensions:**
+
+- **2a.** The specified student index is invalid.
+
+ - **2a1.** System informs the educator of the invalid index.
+ - **2a2.** Educator enters a valid index.
+
+ Use case resumes from step 3.
+
+- **3a.** The tag name is invalid or already exists for the student.
+
+ - **3a1.** System informs the educator of the invalid or duplicate tag.
+ - **3a2.** Educator enters a valid and unique tag name.
+
+ Use case resumes from step 4.
+
+---
+
+#### **Use Case: UC11 - Delete a Tag from a Student**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to delete a tag from a student.
+2. System prompts for the student's index and the tag name.
+3. Educator enters the student's index and tag name.
+4. System validates the input.
+5. System removes the tag from the student.
+6. System displays a confirmation message.
+
+ Use case ends.
+
+**Extensions:**
+
+- **2a.** The specified student index is invalid.
+
+ - **2a1.** System informs the educator of the invalid index.
+ - **2a2.** Educator enters a valid index.
+
+ Use case resumes from step 3.
+
+- **3a.** The tag does not exist for the student.
+
+ - **3a1.** System informs the educator that the tag was not found.
+ - **3a2.** Educator enters a valid tag name.
+
+ Use case resumes from step 4.
+
+---
+
+#### **Use Case: UC12 - Import Students**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to import students from a CSV file.
+2. System prompts for the CSV file location.
+3. Educator provides the file location.
+4. System validates the file location and format.
+5. System reads student data from the CSV file.
+6. System adds new, non-duplicate students to the student list.
+7. System displays a summary of the import process, including the number of students imported and any duplicates found.
+
+ Use case ends.
+
+**Extensions:**
+
+- **3a.** The file location is invalid or the file does not exist.
+
+ - **3a1.** System informs the educator that the file was not found.
+ - **3a2.** Educator provides a valid file location.
+
+ Use case resumes from step 4.
+
+- **4a.** The file format is invalid or corrupted.
+
+ - **4a1.** System informs the educator of the invalid file format.
+
+ Use case ends.
+
+---
+
+#### **Use Case: UC13 - Export Students**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to export students to a CSV file.
+2. System exports all student data to a CSV file at a default location.
+3. System displays a confirmation message with the file location.
+
+ Use case ends.
+
+---
+
+#### **Use Case: UC14 - Clear All Entries**
+
+**Main Success Scenario (MSS):**
+
+1. Educator chooses to clear all entries.
+2. System deletes all student and group data.
+3. System displays a confirmation message.
+
+ Use case ends.
+
+---
+
+### Non-Functional Requirements
+
+1. Should work on any _mainstream OS_ as long as it has Java `17` or above installed.
+2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
+3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be
+ able to accomplish most of the tasks faster using commands than using the mouse.
+4. Should be usable by an Educator who has never used a command line interface.
+5. Should not terminate unless exit command given.
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+- **Mainstream OS**: Windows, Linux, Unix, MacOS
+- **Educator**: Primary, secondary, JC, poly teacher
+- **Duplicate Student**: Student with the same name (case-insensitive)
+- **Duplicate Group**: Group with the same name (case-insensitive)
---------------------------------------------------------------------------------------------------------------------
+---
## **Appendix: Instructions for manual testing**
@@ -345,16 +942,15 @@ testers are expected to do more *exploratory* testing.
1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be
+ optimum.
1. Saving window preferences
1. Resize the window to an optimum size. Move the window to a different location. Close the window.
1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
-
-1. _{ more test cases … }_
+ Expected: The most recent window size and location is retained.
### Deleting a person
@@ -363,7 +959,8 @@ testers are expected to do more *exploratory* testing.
1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+ Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message.
+ Timestamp in the status bar is updated.
1. Test case: `delete 0`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
@@ -371,12 +968,145 @@ testers are expected to do more *exploratory* testing.
1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
-### Saving data
+## **Appendix: Planned Enhancements**
+
+This section outlines future enhancements planned for the application.
+
+---
+
+### **Enhancement 1: Export Specific Groups to CSV Files**
+
+**Current Issue:**
+The `export` command exports all GoonBook data without filtering options, lacking the ability to export specific groups.
+
+**Proposed Enhancement:**
+Allow users to export selected groups to CSV files, providing a choice based on user preference.
+
+**Justification:**
+Selective export enhances data portability, enabling users to share or back up specific groups as needed.
+
+**Updated Behavior:**
+- Introduce an `exportGroup` command for exporting chosen groups to a CSV file.
+
+---
+
+### **Enhancement 2: Custom Export Filename and File Path**
+
+**Current Issue:**
+The `export` command defaults to exporting data to the root path with an unchangeable filename, `exported_data.csv`.
+
+**Proposed Enhancement:**
+Allow users to specify a custom filename and file path when exporting data.
+
+**Justification:**
+Flexible file naming and location options improve user experience, allowing organization based on user preferences.
+
+**Updated Behavior:**
+- Modify the `export` command to accept optional filename and file path parameters.
+
+---
+
+### **Enhancement 3: Import Groups from CSV Files**
+
+**Current Issue:**
+The `import` command imports all data from a CSV file indiscriminately, with no way to distinguish group data.
+
+**Proposed Enhancement:**
+Add an `importGroup` command that creates groups based on CSV data, adding new students as needed or using existing ones.
+
+**Justification:**
+This simplifies data entry and supports easy migration of group data into the application.
+
+**Updated Behavior:**
+- Implement an `importGroup` command to import groups and members from CSV files.
+- Automatically add new students to the database when importing.
+
+---
+
+### **Enhancement 4: Improved Handling of Duplicate Student Names**
+
+**Current Issue:**
+The `addCommand` does not allow students with duplicate names, although real-life duplicates may exist.
+
+**Proposed Enhancement:**
+Implement a unique identifier (e.g., student ID) for each student to handle duplicates better.
+
+**Justification:**
+Using names as unique identifiers is unreliable; a unique ID ensures data integrity and accurate student identification.
+
+**Updated Behavior:**
+- Introduce a unique student identifier.
+- Update commands and data storage to use the new primary key.
+
+---
+
+### **Enhancement 5: Support Special Characters in Names and Class Fields**
+
+**Current Issue:**
+The `addCommand` cannot handle special characters in names, such as "s/o" due to character restrictions.
+
+**Proposed Enhancement:**
+Allow special characters in student names and class fields.
+
+**Justification:**
+Support for special characters improves data accuracy and reflects real-life names more accurately.
+
+**Updated Behavior:**
+- Adjust input validation to permit special characters in names and class fields.
+
+---
+
+### **Enhancement 6: Support Additional Student Information**
+
+**Current Issue:**
+The `Person` object has limited attributes, missing information such as grades, attendance, and guardian details.
+
+**Proposed Enhancement:**
+Extend the `Person` class to store additional information like grades, notes, and guardian contact details.
+
+**Justification:**
+Comprehensive profiles assist educators in managing and understanding their students better, reducing tutor workload.
+
+**Updated Behavior:**
+- Add new fields for grades, notes, and guardian information to the `Person` class.
+- Update the UI to display this additional information.
+
+---
+
+### **Enhancement 7: Advanced Filtering Options for Students**
+
+**Current Issue:**
+The `FindCommand` and `FindGroupCommand` are limited to name-based keyword searches, with no filtering by other criteria.
+
+**Proposed Enhancement:**
+Expand filtering to allow users to search based on grades, attendance, class, and other attributes.
+
+**Justification:**
+Enhanced filtering enables efficient student management, helping users find students that meet specific criteria.
+
+**Updated Behavior:**
+- Add a `filter` command to allow searching and sorting by various attributes like grades, attendance, and class.
+
+---
+
+
+
+### **Enhancement 8: Precise Student Name Searching**
+
+**Current Issue:**
+The `FindCommand` does not support keywords with whitespace, making it challenging to search for full names.
+
+**Proposed Enhancement:**
+Enable search functionality to handle keywords with spaces, allowing precise name searches.
+
+**Justification:**
+Allowing whitespace in searches improves accuracy, letting users find specific names like "Gong Yi" easily.
-1. Dealing with missing/corrupted data files
+**Updated Behavior:**
+- Update the `find` command to accept phrases enclosed in quotes for exact matches.
+- Enhance parsing to handle whitespace in keywords.
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+---
-1. _{ more test cases … }_
+This updated format uses consistent headings, clearer language, and cleaner formatting, improving readability and organization.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 84b4ddc4e40..c3e95b0beb4 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,41 +3,161 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+## About GoonBook
+
+GoonBook is a **modern desktop application designed for educators to efficiently manage their students**. It combines the speed and efficiency of a Command Line Interface (CLI) with an intuitive Graphical User Interface (GUI). Featuring robust student management capabilities including:
+
+- Quick student information lookup and editing
+- Smart grouping system for organizing students
+- Tag-based organization for tracking student attributes
+- Import/Export functionality for seamless data management
+- Automatic data saving and backup
+
+Perfect for educators who value efficiency and prefer keyboard-based interactions. With GoonBook, managing your student records becomes faster, simpler, and more organized than traditional GUI-only applications.
+
+
+
+## **Table of Contents**
+
+1. [Getting Started](#getting-started)
+2. [Learning GoonBook](#learning-goonbook)
+3. [Quick References](#quick-references)
+4. [Need Help](#need-help)
+5. [Quick Start](#quick-start)
+6. [Layout](#layout)
+7. [Getting Help](#getting-help)
+ 1. [Viewing help](#viewing-help--help)
+8. [Managing Students](#managing-students)
+ 1. [Adding a student](#adding-a-student-add)
+ 2. [Listing all students](#listing-all-students--list)
+ 3. [Editing a student](#editing-a-student--edit)
+ 4. [Deleting a student](#deleting-a-student--delete)
+ 5. [Finding a student](#locating-students-by-name-find)
+9. [Managing Groups](#managing-groups)
+ 1. [Listing all groups](#listing-all-groups--listgroups)
+ 2. [Grouping students together](#grouping-students-together-group)
+ 3. [Locating groups by name](#locating-groups-by-name-findgroup)
+ 4. [Deleting a group](#deleting-a-group--deletegroup)
+10. [Managing Tags](#managing-tags)
+ 1. [Adding a tag](#adding-a-tag--tag)
+ 2. [Deleting a tag](#deleting-a-tag--untag)
+11. [Importing and Exporting Data](#importing-and-exporting-data)
+ 1. [Import students](#import-students-import-csv_file_location)
+ 2. [Export students](#export-students-export)
+ 3. [Exported csv data file location](#exported-csv-data-file-location)
+12. [Data Management](#data-management)
+ 1. [Clearing all entries](#clearing-all-entries--clear)
+ 2. [Saving the data](#saving-the-data)
+ 3. [Editing the data file](#editing-the-data-file)
+ 4. [Archiving data files](#archiving-data-files-coming-in-v20)
+13. [Exiting the Program](#exiting-the-program)
+ 1. [Exiting the program](#exiting-the-program--exit)
+14. [FAQ](#faq)
+15. [Known Issues](#known-issues)
+16. [Command Summary](#command-summary)
+17. [Acknowledgements](#acknowledgements)
-* Table of Contents
-{:toc}
+---
+
+
+
+## How to Use This Guide
+
+### Getting Started
+
+If you haven't installed GoonBook yet, start with the [Quick Start](#quick-start) section which will guide you through:
+
+- Installing Java 17
+- Downloading GoonBook
+- Setting up your workspace
+- Running your first command
+
+
+
+
+### Learning GoonBook
+
+Once GoonBook is running, familiarize yourself with:
---------------------------------------------------------------------------------------------------------------------
+1. **Basic Interface**
+
+ - The command box for entering commands
+ - The student list panel
+ - The group management area
+ - The results display
+
+2. **Essential Features**
+
+ - [Adding students](#adding-a-student-add)
+ - [Creating groups](#grouping-students-together-group)
+ - [Managing tags](#adding-a-tag--tag)
+ - [Importing/Exporting data](#import-students-import-csv_file_location)
+
+### Quick References
+
+- For a complete list of commands, refer to the [Command Summary](#command-summary)
+- Experienced users can use this as a quick refresher
+- Each command includes examples of proper usage
+
+### Need Help?
+
+- Check the [FAQ](#faq) section for common questions
+- Review [Known Issues](#known-issues) if you encounter problems
+- Refer to the [Features](#features) section for detailed command usage
## Quick start
1. Ensure you have Java `17` or above installed in your Computer.
-1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases).
+ - If you are on MacOS do note you may need to download a specific JDK 17 version. More on this [here](https://nus-cs2103-ay2425s1.github.io/website/admin/programmingLanguages.html#programming-language).
+
+2. Download the latest `.jar` file from [here](https://github.com/AY2425S1-CS2103T-W08-4/tp/releases/tag/V1.6).
+
+3. Copy the file to the folder you want to use as the _home folder_ for your GoonBook.
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar goonbook.jar` command to run the application.
+ A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+
![Ui](images/Ui.png)
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+ Examples:
+
+ - cd /users/desktop/goonbook/goonbook.jar
+
+5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try:
- * `list` : Lists all contacts.
+ - `list` : Lists all students.
+
+ - `add n/Song Si Mew c/W08 p/10110011 t/Japanese` : Adds a student named `Song Si Mew` to the GoonBook.
+
+ - `delete 1` : Deletes the 1st student shown in the current list.
+
+ - `clear` : Deletes all students.
+
+ - `exit` : Exits the app.
+
+6. Refer to the [Features](#features) below for details of each command.
+
+---
+
+## Layout
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+When you launch GoonBook, GoonBook appears on your screen as a Graphical User Interface, or GUI. Let’s look at the layout of the different components of GoonBook.
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+GoonBook’s GUI consists of a single main window, as well as the Help Window. The main window consists of three components:
- * `clear` : Deletes all contacts.
+1. Student List Box
+2. Group List Box
+3. Command Input and Output Boxes
- * `exit` : Exits the app.
+The following picture of the main window shows the three components, numbered accordingly:
+![Layout](images/Layout.png)
-1. Refer to the [Features](#features) below for details of each command.
+Besides the main window, GoonBook also has the Help Window. It is not part of the main GUI and is only shown after a [Help Command](#viewing-help--help) is run.
---------------------------------------------------------------------------------------------------------------------
+---
## Features
@@ -45,155 +165,456 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
**:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+- Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
-* Items in square brackets are optional.
+- Items in square brackets are optional.
e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
-* Items with `…` after them can be used multiple times including zero times.
+- Items with `…` after them can be used multiple times including zero times.
e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
-* Parameters can be in any order.
+- Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+- Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
+- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-### Viewing help : `help`
+
-Shows a message explaning how to access the help page.
+## Getting Help
+
+### Viewing help : `help`
-![help message](images/helpMessage.png)
+Shows a message explaining how to access the help page.
Format: `help`
+![help message](images/Help.png)
-### Adding a person: `add`
+
-Adds a person to the address book.
+## Managing Students
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+### Adding a student: `add`
+
+Adds a student to the GoonBook.
+
+Format: `add n/NAME c/CLASS p/PHONE_NUMBER [t/TAG]…`
+
+- Students of same `NAME` is considered duplicate.
+- `NAME` is case-insensitive. It must be alphanumeric and allows whitespace.
+- `CLASS` must be alphanumeric.
+- `PHONE_NUMBER` must be at least 3 digits long and only contain numbers.
+- `PHONE_NUMBER` has support for `+` for the extension code.
+- `TAG` must be alphanumeric and allows whitespace. It must be within 30 characters.
:bulb: **Tip:**
A person can have any number of tags (including 0)
+![Add](images/Add.png)
+
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`
-### Listing all persons : `list`
+- `add n/Song Si Mew c/W08 p/10110011`
+ - Successfully adds new student `Song Si Mew` from class `W08` and phone number `10110011` successfully.
+- `add n/SONG SI MEW c/W08 p/10110011`
+ - Returns `This student already exists in the address book`
+- `add n/Aaron Tan c/G12 p/11110011 t/Trivial t/CS `
+ - Successfully adds new student `Aaron Tan` from class `G12`, phone number `11110011`, tags `Trivial` and `CS` successfully.
+- `add n/Beh Wen Jie c/ p/`
+ - Returns `Student's class names cannot be empty and should be alphanumeric`
+- `add n/Beh Wen Jie c/S09 p/###`
+ - Returns `Phone numbers should only contain numbers, and it should be at least 3 digits long`
-Shows a list of all persons in the address book.
+
+
+### Listing all students : `list`
+
+Shows a list of all students in the GoonBook.
Format: `list`
-### Editing a person : `edit`
+![List](images/List.png)
+
+### Editing a student : `edit`
+
+Edits an existing student in the GoonBook.
+
+Format: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [c/CLASS] [t/TAG]…`
-Edits an existing person in the address book.
+- Edits the student 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, …
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+- `INDEX` refers to the index number shown in the displayed person list.
+- `NAME` is case-insensitive. It must be alphanumeric and allows whitespace.
+- `CLASS` must be alphanumeric.
+- `PHONE_NUMBER` must be at least 3 digits long and only contain numbers.
+- `PHONE_NUMBER` has support for `+` for the extension code.
+- `TAG` must be alphanumeric and allows whitespace. It must be within 30 characters.
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+- At least one of the optional fields must be provided.
+- Existing values will be updated to the input values.
+- When editing tags, the existing tags of the student will be removed i.e adding of tags is not cumulative.
+- You can remove all the student’s tags by typing `t/` without
+ specifying any tags after it.
+- You can add multiple `t/` to add more than one tag.
+
+![Edit](images/Edit.png)
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.
-### Locating persons by name: `find`
+- `edit 1 c/A11 p/91234567`
+ - Edits the class and phone number of the 1st student to be `A11` and `91234567` respectively.
+- `edit 2`
+ - Returns `At least one field to edit must be provided.`
+- `edit 1 c/W-08`
+ - Returns `Student's class names cannot be empty and should be alphanumeric`
+
+### Deleting a student : `delete`
+
+Deletes the specified student from the GoonBook.
+
+Format: `delete INDEX`
-Finds persons whose names contain any of the given keywords.
+- Deletes the student at the specified `INDEX`.
+- `INDEX` refers to the index number shown in the displayed person list.
+- `INDEX` **must be a positive integer** 1, 2, 3, …
+
+![Delete](images/Delete.png)
+
+Examples:
+
+- `list` followed by `delete 2`
+ - Successfully deletes the 2nd person in the Goon book.
+- `find Betsy` followed by `delete 1`
+ - Successfully deletes the 1st person in the results of the `find` command.
+- `delete 100`
+ - If contains fewer than 100 students, returns `The index provided is greater than the max students`
+
+### Locating students by name: `find`
+
+Finds students whose names 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 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).
+- 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 full words will be matched e.g. `Han` will not match `Hans`
+- Students matching at least one keyword will be returned (i.e. `OR` search).
e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+![Find](images/Find.png)
+
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 alex david`
+ - Returns `Alex Yeoh`, `David Li`
+- `find alex dav`
+ - Returns `Alex Yeoh`
-Deletes the specified person from the address book.
+#### Tips for Effective Use
-Format: `delete INDEX`
+- Use the `list` command to display all students again.
+
+
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+## Managing groups
+
+## Listing all groups : `listGroups`
+
+Shows a list of all groups in the Goon Book.
+
+Format: `listGroups`
+
+![ListGroup](images/ListGroups.png)
+
+
+
+### Grouping students together: `group`
+
+Groups students together.
+
+Format: `group g/GROUPNAME s/STUDENTNAME [s/STUDENTNAME]…`
+
+- Each group must have a unique name.
+- `GROUPNAME` must be alphanumeric and allows whitespace (case-insensitive).
+- `STUDENTNAME` must match exactly (case-sensitive).
+- Group is successfully created only if all student names match.
+
+![Group](images/Group.png)
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.
+
+If the group contains `Alice Pauline` and `Benson Meier`,
+
+- `group g/Study Group 1 s/alice pauline s/benson meier`
+ - Successfully groups `Alice Pauline` and `Benson Meier` into `StudyGroup1`.
+- `group g/STUDY GROUP 1 s/alice pauline s/benson meier`
+ - Returns `Group name already taken!!`
+- `group g/Study Group 2 s/ali`
+ - Returns `The following students could not be found: ali`
+
+**Note:** The group command requires exact, case-sensitive matches for student names. Double-check the names before executing the command to ensure the group is created successfully.
+
+
+
+### Locating groups by name: `findGroup`
+
+Finds existing group(s) whose name contains the given keyword.
+
+Format: `findGroup KEYWORD`
+
+- The search is case-insensitive. e.g `class` will match `Class`
+- Only the group name is searched.
+- findGroup is a partial name serach where if group names contain the keyword they will be matched e.g. `findGroup cla` will match `class 9A`
+- Groups containing the keyword will be returned.
+ e.g. `findGroup s` will return `shaun's study group` or `house group` but not `Running Group`
+
+![FindGroup](images/FindGroup.png)
+
+Examples:
+
+- `findGroup study group`
+ - Returns `study group 1` and `study group 2`
+- `findGroup study group 1`
+ - Returns `study group 1`
+
+#### Tips for Effective Use
+
+- Use the `listGroups` command to display all groups again.
+
+### Deleting a group : `deleteGroup`
+
+Deletes the specified Group from the GoonBook.
+
+Format: `deleteGroup GROUPNAME`
+
+- Deletes the group given the specific `GROUPNAME`.
+- The group name refers to the name shown in the group list.
+
+![DeleteGroup](images/DeleteGroup.png)
+Examples:
+
+- `groups` followed by `deleteGroup StudyGroup1` deletes StudyGroup1
+
+**Warning:** Deleting a group using deleteGroup will permanently remove the group. The students in the group will not be deleted.
+
+
+
+## Managing Tags
+
+### Adding a tag : `tag`
+
+Adds a tag to a specified student.
+
+Format: `tag INDEX t/TAG [t/TAG]…`
+
+- Ability to add more than one tag at once by doing another `t/TAG` after.
+- `INDEX` **must be a positive integer** 1, 2, 3, …
+- `INDEX` refers to the index number shown in the displayed person list.
+- `TAG` must be alphanumeric and allows whitespace. It must be within 30 characters.
+- `TAG` will be converted to all lowercase characters.
+
+![Tag](images/Tag.png)
+
+Examples:
+
+- `tag 1 t/needs consult t/quiet`
+ - Successfully tags 1st student to `needs consult` tag and `quiet` tag
+- `tag 1 t/QUIET`
+ - Returns `Tag(s) already exist`
+- `tag 100 t/hardworking`
+ - If contains fewer than 100 students, returns `The person index provided is invalid`
+
+### Deleting a tag : `untag`
+
+Deletes a tag of a specified student.
+
+Format: `untag INDEX t/TAG [t/TAG]…`
+
+- `INDEX` **must be a positive integer** 1, 2, 3, …
+- `INDEX` refers to the index number shown in the displayed person list.
+- `TAG` letters must match exactly (case-insensitive)
+
+![UnTag](images/Untag.png)
+
+Examples:
+- `untag 8 t/Silent`
+ - Successfully removes `silent` tag from 8th student
+- `untag 1 t/needs consult t/quiet`
+ - Successfully removes `needs consult` tag and `quiet` tag from 1st student
+- `untag 1 t/qquiet`
+ - Returns `The tag(s) does not exist`
+
+## Importing and Exporting Data
+
+### Import students: `import`
+
+Imports and adds new NON-DUPLICATE students from a .csv file into GoonBook.
+
+Format: `import CSV_FILE_LOCATION`
+
+- Only adds NON-DUPLICATE students (i.e. students with same name).
+- Does not update existing users with the new imported data.
+- Will notify user of all duplicate students found and not imported.
+- `CSV_FILE_LOCATION` must be absolute path and valid.
+- Csv files must be properly formatted to GoonBook style (see exported_data.csv).
+- Csv files are to have 4 columns: `[name, class, phone number, tags]`.
+- Tags in the csv file are to be seperated with a space.
+- Will show user data corrupted error if parse or data is not formatted right.
+- Will show user cannot find error if no or invalid file location is given.
+- Will show user invalid file format, must be .csv if a valid file which is not a .csv is entered.
+
+![Import](images/Import.png)
+
+Examples:
+
+- `import /Users/martin/CODE/tp/data/exported_data.csv`
+ - Successfully imports data
+- `import /invalid/path`
+ - Returns `Invalid path!`
+
+**Warning:** When importing students with the import command, ensure that the CSV file is correctly formatted and contains valid data. Improper formatting may cause the import to fail or lead to data corruption.
+
+
+
+### Export students: `export`
+
+Exports all students in GoonBook to a .csv file.
+
+Format: `export`
+
+- Exports all students to fixed location as exported_data.csv
+- Location can be found at `[JAR FILE LOCATION]/data/exported_data.csv`
+
+![Export](images/Export.png)
+
+
+### Exported csv data file location
+
+GoonBook csv data files are saved automatically as a .csv file at `[JAR file location/data/exported_data.csv]`.
+
+
+
+## Data Management
### Clearing all entries : `clear`
-Clears all entries from the address book.
+Clears all entries from the GoonBook.
Format: `clear`
+![Clear](images/Clear.png)
+
+**Warning:** The `clear` command has no confirmation prompt and will delete all data immediately. Use with caution.
+
+## Exiting the Program
+
### Exiting the program : `exit`
Exits the program.
Format: `exit`
+**Note:** Always exit the application using the `exit` command to ensure all data is saved properly.
+
### 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.
+GoonBook data are saved in the hard disk automatically after any command 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.
+GoonBook 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.
+
+**Warning:** Manually editing the data file may lead to data loss if the JSON format is not strictly followed. Always back up your data before making direct edits.
: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, GoonBook 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 GoonBook 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 ..._
+- Import and Export feature to include Group support.
---------------------------------------------------------------------------------------------------------------------
+---
## 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 GoonBook home folder.
+**Q**: Is there support for importing and exporting groups?
+**A**: It is a planned enhacement. Do check out our Developer Guide for more information.
+**Q**: I don't understand what caused the error in my command what can I do?
+**A**: Take note of the command you tried using and look at the command format and examples in the user guide.
---------------------------------------------------------------------------------------------------------------------
+---
## Known issues
1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window.
+3. **If you require use of special characters**, unfortunately we currently support alphanumeric representations hence, special characters cannot be used in our application. However we will be adding support for this in future implementations as we understand some students or classes may require use of special characters.
+4. **If you are trying to identify a specific student using find** but cannot explicity just return that single student due to them having a space in their name, we understand this can be a problem. Our team will be adding support for more specific searches to more accurately and preciesly locate students.
+5. **Index isn't referencing the correct number**, index for our commands reference the currently displayed list of students and not the entire student list. Thus, it will be the index or number you see on you screen.
---------------------------------------------------------------------------------------------------------------------
+## Before submitting issues
+Do read up on our known issues and planned enhacements to see if we have already planned and covered potentially submitted issues!
+
+---
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+| Action | Format, Examples |
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| **Add** | `add n/NAME c/CLASS p/PHONE_NUMBER [t/TAG]…` e.g., `add n/James Ho p/22224444 c/4B t/friend t/colleague` |
+| **Clear** | `clear` |
+| **Delete** | `delete INDEX` e.g., `delete 3` |
+| **Delete Group** | `deleteGroup g/GROUP_NAME` e.g., `deleteGroup g/studygroup1` |
+| **Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [c/CLASS] [t/TAG]…` e.g.,`edit 2 n/James Lee c/4L` |
+| **Export Students** | `export` |
+| **Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake` |
+| **Find Group** | `findGroup g/GROUP_NAME` e.g., `findGroup g/studygroup1` |
+| **Delete Group** | `deleteGroup g/GROUP_NAME` e.g., `deleteGroup g/studygroup1` |
+| **Create Group** | `group g/GROUP_NAME s/STUDENT_NAME [s/STUDENT_NAME]…` e.g., `group g/studygroup1 s/Annie s/Martin s/Jianbing s/Shaun s/Wenjie` |
+| **Import** | `import FILELOCATION` e.g., `import /users/shaun/desktop/tp/test.csv` |
+| **List Students** | `list` |
+| **List Groups** | `listGroups` |
+| **Tag** | `tag INDEX t/TAG [t/TAG]…` e.g., `tag 2 t/HighAchiever t/SecondTag` |
+| **Untag** | `untag INDEX t/TAG [t/TAG]…` e.g., `tag 2 t/HighAchiever t/SecondTag` |
+| **Help** | `help` |
+
+---
+
+## Glossary
+
+| Term | Definition |
+|-------------------------|--------------------------------------------------------------|
+| **CLASS (placeholder)** | The field for users to input student classes |
+| **CLI** | Command Line Interface, where you enter commands |
+| **INDEX (placeholder)** | Refers to the index number shown in the displayed person list |
+| **KEYWORD (placeholder)** | The text we use search for a group or student |
+|**NAME (placeholder)** | The field for users to input student's names |
+|**PHONE_NUMBER (placeholder)** | The field for users to input student's phone numbers |
+|**TAG (placeholder)** | The field for users to input student tags |
+
+---
+
+## Acknowledgements
+
+GoonBook is a brownfield software project based off [AddressBook Level-3](https://se-education.org/addressbook-level3/) ([UG](https://se-education.org/addressbook-level3/UserGuide.html), [DG](https://se-education.org/addressbook-level3/DeveloperGuide.html)), taken under the CS2103T Software Engineering module held by the School of Computing at the National University of Singapore.
+
+Java dependencies:
+
+- JavaFX for GUI
+- JUnit5 for testing
+
+Documentation dependencies:
+
+- Jekyll for rendering the website
+- PlantUML for creating UML diagrams
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..0d6eaa4a5eb 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "GoonBook"
theme: minima
header_pages:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..77a403197cc 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: "GoonBook";
font-size: 32px;
}
}
diff --git a/docs/contents/topic1.md b/docs/contents/topic1.md
new file mode 100644
index 00000000000..3f28f03594b
--- /dev/null
+++ b/docs/contents/topic1.md
@@ -0,0 +1,9 @@
+
+ title: Topic 1
+
+
+
+
+# Topic 1
+
+> This is a placeholder page - more content to be added.
diff --git a/docs/contents/topic2.md b/docs/contents/topic2.md
new file mode 100644
index 00000000000..f86f7be5686
--- /dev/null
+++ b/docs/contents/topic2.md
@@ -0,0 +1,9 @@
+
+ title: Topic 2
+
+
+
+
+# Topic 2
+
+> This is a placeholder page - more content to be added.
diff --git a/docs/contents/topic3a.md b/docs/contents/topic3a.md
new file mode 100644
index 00000000000..90194da60af
--- /dev/null
+++ b/docs/contents/topic3a.md
@@ -0,0 +1,9 @@
+
+ title: Topic 3a
+
+
+
+
+# Topic 3a
+
+> This is a placeholder page - more content to be added.
diff --git a/docs/contents/topic3b.md b/docs/contents/topic3b.md
new file mode 100644
index 00000000000..db7ba8e857f
--- /dev/null
+++ b/docs/contents/topic3b.md
@@ -0,0 +1,9 @@
+
+ title: Topic 3b
+
+
+
+
+# Topic 3b
+
+> This is a placeholder page - more content to be added.
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 598474a5c82..656b1d88f69 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -14,8 +14,9 @@ UniquePersonList -right-> Person
Person -up-> "*" Tag
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
+Person *--> "1" Name
+Person *--> "1" StudentClass
+Person *--> "1" Phone
+Person *--> "0..*" Tag
+Person *--> "0..*" Group
@enduml
diff --git a/docs/diagrams/DeleteGroupSequenceDiagram.puml b/docs/diagrams/DeleteGroupSequenceDiagram.puml
new file mode 100644
index 00000000000..b199be6bada
--- /dev/null
+++ b/docs/diagrams/DeleteGroupSequenceDiagram.puml
@@ -0,0 +1,77 @@
+@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 ":DeleteGroupCommandParser" as DeleteGroupCommandParser LOGIC_COLOR
+participant "d:DeleteGroupCommand" as DeleteGroupCommand LOGIC_COLOR
+participant "r:CommandResult" as CommandResult LOGIC_COLOR
+participant "groupName:GroupName" as GroupName LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("deleteGroup ClassA")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("deleteGroup ClassA")
+activate AddressBookParser
+
+create DeleteGroupCommandParser
+AddressBookParser -> DeleteGroupCommandParser
+activate DeleteGroupCommandParser
+
+DeleteGroupCommandParser -> AddressBookParser
+deactivate DeleteGroupCommandParser
+
+AddressBookParser -> DeleteGroupCommandParser : parse("ClassA")
+activate DeleteGroupCommandParser
+
+create GroupName
+DeleteGroupCommandParser --> GroupName
+activate GroupName
+
+GroupName --> DeleteGroupCommandParser : groupName
+deactivate GroupName
+
+create DeleteGroupCommand
+DeleteGroupCommandParser -> DeleteGroupCommand : new DeleteGroupCommand(groupName)
+activate DeleteGroupCommand
+DeleteGroupCommand --> DeleteGroupCommandParser : d
+deactivate DeleteGroupCommand
+
+DeleteGroupCommandParser --> AddressBookParser : d
+deactivate DeleteGroupCommandParser
+
+AddressBookParser --> LogicManager : d
+deactivate AddressBookParser
+
+LogicManager -> DeleteGroupCommand : execute(m:Model)
+activate DeleteGroupCommand
+
+DeleteGroupCommand -> Model : deleteGroup(groupName)
+activate Model
+
+Model --> DeleteGroupCommand : groupToDelete
+deactivate Model
+
+create CommandResult
+DeleteGroupCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> DeleteGroupCommand : r
+deactivate CommandResult
+
+DeleteGroupCommand --> LogicManager : r
+deactivate DeleteGroupCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+autonumber
+scale 2
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 5241e79d7da..cf932325773 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -67,4 +67,6 @@ deactivate DeleteCommand
[<--LogicManager
deactivate LogicManager
+scale 2
@enduml
+
diff --git a/docs/diagrams/ExportSequenceDiagram.puml b/docs/diagrams/ExportSequenceDiagram.puml
new file mode 100644
index 00000000000..27dfe448743
--- /dev/null
+++ b/docs/diagrams/ExportSequenceDiagram.puml
@@ -0,0 +1,88 @@
+@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 ":ExportCommandParser" as ExportCommandParser LOGIC_COLOR
+participant "d:ExportCommand" as ExportCommand LOGIC_COLOR
+participant "r:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Model" as Model MODEL_COLOR
+end box
+
+box Storage STORAGE_COLOR_T1
+participant "s:Storage" as Storage STORAGE_COLOR
+end box
+
+[-> LogicManager : execute("export")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("export")
+activate AddressBookParser
+
+
+
+create ExportCommandParser
+AddressBookParser -> ExportCommandParser
+activate ExportCommandParser
+
+ExportCommandParser --> AddressBookParser
+deactivate ExportCommandParser
+
+AddressBookParser -> ExportCommandParser : parse("g/ClassA s/Alice")
+activate ExportCommandParser
+
+create ExportCommand
+ExportCommandParser -> ExportCommand : new ExportCommand(...)
+activate ExportCommand
+
+ExportCommand --> ExportCommandParser : g
+deactivate ExportCommand
+
+ExportCommandParser --> AddressBookParser : g
+deactivate ExportCommandParser
+
+AddressBookParser --> LogicManager : g
+deactivate AddressBookParser
+
+
+AddressBookParser --> LogicManager : e
+deactivate AddressBookParser
+
+LogicManager -> ExportCommand : execute(m)
+activate ExportCommand
+
+ExportCommand -> ExportCommand : saveJsonFile(...)
+
+ExportCommand -> Model : getAddressBook()
+activate Model
+Model --> ExportCommand
+deactivate Model
+
+ExportCommand -> Storage : saveAddressBook()
+activate Storage
+Storage -> ExportCommand
+deactivate Storage
+
+
+ExportCommand -> ExportCommand : translateJsonToCsv(...)
+
+
+create CommandResult
+ExportCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ExportCommand
+deactivate CommandResult
+
+ExportCommand --> LogicManager : r
+deactivate ExportCommand
+
+[<--LogicManager
+deactivate LogicManager
+scale 3
+@enduml
diff --git a/docs/diagrams/FindCommandActivityDiagram.puml b/docs/diagrams/FindCommandActivityDiagram.puml
new file mode 100644
index 00000000000..10bdfbdd304
--- /dev/null
+++ b/docs/diagrams/FindCommandActivityDiagram.puml
@@ -0,0 +1,22 @@
+@startuml
+start
+
+:User inputs "find" command with keywords;
+:Create FindCommand with NameContainsKeywordsPredicate;
+
+:Execute FindCommand;
+:requireNonNull(model);
+
+if (model is null) then (no)
+ :Throw NullPointerException;
+else (yes)
+ :model.updateFilteredPersonList(predicate);
+ :Retrieve the size of model.getFilteredPersonList();
+ :Format message with number of persons listed;
+ :Return CommandResult with formatted message;
+endif
+
+:End of execute method;
+
+stop
+@enduml
diff --git a/docs/diagrams/FindGroupCommandActivityDiagram.puml b/docs/diagrams/FindGroupCommandActivityDiagram.puml
new file mode 100644
index 00000000000..41353793a82
--- /dev/null
+++ b/docs/diagrams/FindGroupCommandActivityDiagram.puml
@@ -0,0 +1,24 @@
+@startuml
+start
+
+:User inputs "findGroup" command with keywords;
+:Create FindGroupCommand with GroupContainsKeywordsPredicate;
+
+:Execute FindGroupCommand;
+:requireNonNull(model);
+
+if (model is null) then (no)
+ :Throw NullPointerException;
+else (yes)
+ if (groupPredicate is not null) then (yes)
+ :model.updateFilteredGroupList(groupPredicate);
+ endif
+ :Retrieve the size of model.getFilteredGroupList();
+ :Format message with the number of groups listed;
+ :Return CommandResult with formatted message;
+endif
+
+:End of execute method;
+
+stop
+@enduml
diff --git a/docs/diagrams/GroupSequenceDiagram.puml b/docs/diagrams/GroupSequenceDiagram.puml
new file mode 100644
index 00000000000..bc9640c92f1
--- /dev/null
+++ b/docs/diagrams/GroupSequenceDiagram.puml
@@ -0,0 +1,80 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":GroupCommandParser" as GroupCommandParser LOGIC_COLOR
+participant "g:GroupCommand" as GroupCommand LOGIC_COLOR
+participant "newGroup:Group" as Group LOGIC_COLOR
+participant "r:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("group g/ClassA s/Alice")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("group g/ClassA s/Alice")
+activate AddressBookParser
+
+create GroupCommandParser
+AddressBookParser -> GroupCommandParser
+activate GroupCommandParser
+
+GroupCommandParser --> AddressBookParser
+deactivate GroupCommandParser
+
+AddressBookParser -> GroupCommandParser : parse("g/ClassA s/Alice")
+activate GroupCommandParser
+
+create GroupCommand
+GroupCommandParser -> GroupCommand : new GroupCommand(...)
+activate GroupCommand
+
+GroupCommand --> GroupCommandParser : g
+deactivate GroupCommand
+
+
+GroupCommandParser --> AddressBookParser : g
+deactivate GroupCommandParser
+
+AddressBookParser --> LogicManager : g
+deactivate AddressBookParser
+
+LogicManager -> GroupCommand : execute(m:Model)
+activate GroupCommand
+
+create Group
+GroupCommand --> Group : new Group("ClassA", ["Alice"])
+activate Group
+
+Group --> GroupCommand : newGroup
+deactivate Group
+
+GroupCommand -> Model : addGroup(newGroup)
+activate Model
+
+Model --> GroupCommand
+deactivate Model
+
+create CommandResult
+GroupCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> GroupCommand : r
+deactivate CommandResult
+
+GroupCommand --> LogicManager : r
+deactivate GroupCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+autonumber
+scale 3
+@enduml
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index 58b4f602ce6..cee88569d3a 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -34,12 +34,12 @@ ParserClasses ..> XYZCommand : <>
XYZCommand -up-|> Command
LogicManager .left.> Command : <>
-LogicManager --> Model
-LogicManager --> Storage
+LogicManager ---> Model
+LogicManager ---> Storage
Storage --[hidden] Model
Command .[hidden]up.> Storage
Command .right.> Model
-note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
+note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, GroupCommand, etc
Logic ..> CommandResult
LogicManager .down.> CommandResult
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..511ab938f7b 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -13,12 +13,14 @@ Class ModelManager
Class UserPrefs
Class UniquePersonList
+Class UniqueGroupList
Class Person
-Class Address
-Class Email
Class Name
Class Phone
Class Tag
+Class Tags
+Class Group
+Class GroupName
Class I #FFFFFF
}
@@ -36,19 +38,24 @@ ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
AddressBook *--> "1" UniquePersonList
+AddressBook *--> "1" UniqueGroupList
UniquePersonList --> "~* all" Person
+UniqueGroupList --> "~* all" Group
Person *--> Name
Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
+Person *--> "1" Tags
+Tags *--> "*" Tag
+Group *--> GroupName
+Group *-left-> "1..*" Person
Person -[hidden]up--> I
UniquePersonList -[hidden]right-> I
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
ModelManager --> "~* filtered" Person
+ModelManager --> "~* filtered" Group
+
+note "When all Persons are removed from a Group, the Group is deleted" as GroupDeletionNote
+
+Group -right-> GroupDeletionNote
@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..4d5617899e0 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -19,6 +19,7 @@ Class "<>\nAddressBookStorage" as AddressBookStorage
Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
+Class JsonAdaptedGroup
Class JsonAdaptedTag
}
@@ -38,6 +39,9 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
+JsonSerializableAddressBook --> "*" JsonAdaptedGroup
+
+JsonAdaptedGroup --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
@enduml
diff --git a/docs/diagrams/TagSequenceDiagram.puml b/docs/diagrams/TagSequenceDiagram.puml
new file mode 100644
index 00000000000..9c7d8fceaa5
--- /dev/null
+++ b/docs/diagrams/TagSequenceDiagram.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 ":TagCommandParser" as TagCommandParser LOGIC_COLOR
+participant "t:TagCommand" as TagCommand LOGIC_COLOR
+participant "r:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("tag 1 t/friend t/colleague")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("tag 1 t/friend t/colleague")
+activate AddressBookParser
+
+create TagCommandParser
+AddressBookParser -> TagCommandParser
+activate TagCommandParser
+
+TagCommandParser --> AddressBookParser
+deactivate TagCommandParser
+
+AddressBookParser -> TagCommandParser : parse("1 t/friend t/colleague")
+activate TagCommandParser
+
+create TagCommand
+TagCommandParser -> TagCommand : new TagCommand(index, tags)
+activate TagCommand
+TagCommand --> TagCommandParser : t
+deactivate TagCommand
+
+TagCommandParser --> AddressBookParser
+deactivate TagCommandParser
+
+AddressBookParser --> LogicManager : t
+deactivate AddressBookParser
+
+LogicManager -> TagCommand : execute(m)
+activate TagCommand
+
+TagCommand -> Model : getFilteredPersonList()
+activate Model
+Model --> TagCommand : lastShownList
+deactivate Model
+
+TagCommand -> Model : addTag(...)
+activate Model
+Model --> TagCommand
+deactivate Model
+
+create CommandResult
+TagCommand -> CommandResult
+activate CommandResult
+CommandResult --> TagCommand : r
+deactivate CommandResult
+
+TagCommand --> LogicManager : r
+deactivate TagCommand
+
+ <-- LogicManager
+deactivate LogicManager
+
+scale 2
+@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..7953eb208a6 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -10,6 +10,8 @@ Class "{abstract}\nUiPart" as UiPart
Class UiManager
Class MainWindow
Class HelpWindow
+Class GroupListPanel
+Class GroupCard
Class ResultDisplay
Class PersonListPanel
Class PersonCard
@@ -33,10 +35,12 @@ UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "1" GroupListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
PersonListPanel -down-> "*" PersonCard
+GroupListPanel -down-> "*" GroupCard
MainWindow -left-|> UiPart
@@ -47,7 +51,8 @@ PersonCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
-PersonCard ..> Model
+PersonCard -down-..> Model
+GroupCard -down-..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
diff --git a/docs/images/Add.png b/docs/images/Add.png
new file mode 100644
index 00000000000..e408ce547e6
Binary files /dev/null and b/docs/images/Add.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index 02a42e35e76..1f0e61af1f7 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/Clear.png b/docs/images/Clear.png
new file mode 100644
index 00000000000..5c3835bb598
Binary files /dev/null and b/docs/images/Clear.png differ
diff --git a/docs/images/Delete.png b/docs/images/Delete.png
new file mode 100644
index 00000000000..df202cc8cd7
Binary files /dev/null and b/docs/images/Delete.png differ
diff --git a/docs/images/DeleteGroup.png b/docs/images/DeleteGroup.png
new file mode 100644
index 00000000000..4ef37c4ad15
Binary files /dev/null and b/docs/images/DeleteGroup.png differ
diff --git a/docs/images/DeleteGroupSequenceDiagram.png b/docs/images/DeleteGroupSequenceDiagram.png
new file mode 100644
index 00000000000..321ee7074ba
Binary files /dev/null and b/docs/images/DeleteGroupSequenceDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index ac2ae217c51..3683aa0b039 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/Edit.png b/docs/images/Edit.png
new file mode 100644
index 00000000000..69df8b08fc0
Binary files /dev/null and b/docs/images/Edit.png differ
diff --git a/docs/images/Export.png b/docs/images/Export.png
new file mode 100644
index 00000000000..1f70ed9a20e
Binary files /dev/null and b/docs/images/Export.png differ
diff --git a/docs/images/ExportSequenceDiagram.png b/docs/images/ExportSequenceDiagram.png
new file mode 100644
index 00000000000..4e43ec51d41
Binary files /dev/null and b/docs/images/ExportSequenceDiagram.png differ
diff --git a/docs/images/Find.png b/docs/images/Find.png
new file mode 100644
index 00000000000..ed80aa5e301
Binary files /dev/null and b/docs/images/Find.png differ
diff --git a/docs/images/FindGroup.png b/docs/images/FindGroup.png
new file mode 100644
index 00000000000..06c719852f8
Binary files /dev/null and b/docs/images/FindGroup.png differ
diff --git a/docs/images/Group.png b/docs/images/Group.png
new file mode 100644
index 00000000000..c1920df379b
Binary files /dev/null and b/docs/images/Group.png differ
diff --git a/docs/images/GroupSequenceDiagram.png b/docs/images/GroupSequenceDiagram.png
new file mode 100644
index 00000000000..04c0851c445
Binary files /dev/null and b/docs/images/GroupSequenceDiagram.png differ
diff --git a/docs/images/Help.png b/docs/images/Help.png
new file mode 100644
index 00000000000..f10ad2b67e4
Binary files /dev/null and b/docs/images/Help.png differ
diff --git a/docs/images/Import.png b/docs/images/Import.png
new file mode 100644
index 00000000000..87cfe6aebe9
Binary files /dev/null and b/docs/images/Import.png differ
diff --git a/docs/images/Layout.png b/docs/images/Layout.png
new file mode 100644
index 00000000000..0f44df67cab
Binary files /dev/null and b/docs/images/Layout.png differ
diff --git a/docs/images/List.png b/docs/images/List.png
new file mode 100644
index 00000000000..141017f4516
Binary files /dev/null and b/docs/images/List.png differ
diff --git a/docs/images/ListGroups.png b/docs/images/ListGroups.png
new file mode 100644
index 00000000000..08a66d4a5ea
Binary files /dev/null and b/docs/images/ListGroups.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index fe91c69efe7..133fd96a0c7 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..ef715d706f3 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..dd84b705c87 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Tag.png b/docs/images/Tag.png
new file mode 100644
index 00000000000..6b9d9bbb06a
Binary files /dev/null and b/docs/images/Tag.png differ
diff --git a/docs/images/TagSequenceDiagram.png b/docs/images/TagSequenceDiagram.png
new file mode 100644
index 00000000000..fbe73f002e1
Binary files /dev/null and b/docs/images/TagSequenceDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..d0e48a98b0f 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..7847b5a6199 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/Untag.png b/docs/images/Untag.png
new file mode 100644
index 00000000000..169dbb025ed
Binary files /dev/null and b/docs/images/Untag.png differ
diff --git a/docs/images/basic_command_flowchart.png b/docs/images/basic_command_flowchart.png
new file mode 100644
index 00000000000..2e4f19fc056
Binary files /dev/null and b/docs/images/basic_command_flowchart.png differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
deleted file mode 100644
index 235da1c273e..00000000000
Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/hoodini231.png b/docs/images/hoodini231.png
new file mode 100644
index 00000000000..f99ef944700
Binary files /dev/null and b/docs/images/hoodini231.png differ
diff --git a/docs/images/hyxnnii.png b/docs/images/hyxnnii.png
new file mode 100644
index 00000000000..8e4a17e0f8d
Binary files /dev/null and b/docs/images/hyxnnii.png differ
diff --git a/docs/images/martout2002.png b/docs/images/martout2002.png
new file mode 100644
index 00000000000..8f82f791583
Binary files /dev/null and b/docs/images/martout2002.png differ
diff --git a/docs/images/wenjebs.png b/docs/images/wenjebs.png
new file mode 100644
index 00000000000..adafa289f54
Binary files /dev/null and b/docs/images/wenjebs.png differ
diff --git a/docs/images/yimjianbing.png b/docs/images/yimjianbing.png
new file mode 100644
index 00000000000..2d27ba5add9
Binary files /dev/null and b/docs/images/yimjianbing.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..db7f1df3332 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,22 @@
---
layout: page
-title: AddressBook Level-3
+title: Goonbook
---
-[![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)
+[![CI Status](https://github.com/AY2425S1-CS2103T-W08-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103T-W08-4/tp/actions)
+[![codecov](https://codecov.io/github/AY2425S1-CS2103T-W08-2/tp/graph/badge.svg?token=1LWE987C3X)](https://codecov.io/github/AY2425S1-CS2103T-W08-4/tp)
![Ui](images/Ui.png)
-**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.
+**GoonBook is a desktop application for managing your students.** While it has a GUI, most of the user
+interactions happen using a CLI (Command Line Interface).
+* If you are interested in using GoonBook, 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.
**Acknowledgements**
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* Libraries
+ used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
diff --git a/docs/stylesheets/main.css b/docs/stylesheets/main.css
new file mode 100644
index 00000000000..f02b3a062dc
--- /dev/null
+++ b/docs/stylesheets/main.css
@@ -0,0 +1,135 @@
+mark {
+ background-color: #ff0;
+ border-radius: 5px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.indented {
+ padding-left: 20px;
+}
+
+.theme-card img {
+ width: 100%;
+}
+
+/* Scrollbar */
+
+.slim-scroll::-webkit-scrollbar {
+ width: 5px;
+}
+
+.slim-scroll::-webkit-scrollbar-thumb {
+ background: #808080;
+ border-radius: 20px;
+}
+
+.slim-scroll::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: 20px;
+}
+
+.slim-scroll-blue::-webkit-scrollbar {
+ width: 5px;
+}
+
+.slim-scroll-blue::-webkit-scrollbar-thumb {
+ background: #00b0ef;
+ border-radius: 20px;
+}
+
+.slim-scroll-blue::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: 20px;
+}
+
+/* Layout containers */
+
+#flex-body {
+ display: flex;
+ flex: 1;
+ align-items: start;
+}
+
+#content-wrapper {
+ flex: 1;
+ margin: 0 auto;
+ min-width: 0;
+ max-width: 1000px;
+ overflow-x: auto;
+ padding: 0.8rem 20px 0;
+ transition: 0.4s;
+}
+
+#site-nav,
+#page-nav {
+ display: flex;
+ flex-direction: column;
+ position: sticky;
+ top: var(--sticky-header-height);
+ flex: 0 0 auto;
+ max-width: 300px;
+ max-height: calc(100vh - var(--sticky-header-height));
+ width: 300px;
+}
+
+#site-nav {
+ border-right: 1px solid lightgrey;
+ padding-bottom: 20px;
+ z-index: 999;
+}
+
+.site-nav-top {
+ margin: 0.8rem 0;
+ padding: 0 12px 12px;
+}
+
+.nav-component {
+ overflow-y: scroll;
+}
+
+#page-nav {
+ border-left: 1px solid lightgrey;
+}
+
+@media screen and (width <= 1299.98px) {
+ #page-nav {
+ display: none;
+ }
+}
+
+/* Bootstrap medium(md) responsive breakpoint */
+@media screen and (width <= 991.98px) {
+ #site-nav {
+ display: none;
+ }
+}
+
+/* Bootstrap small(sm) responsive breakpoint */
+@media (width <= 767.98px) {
+ .indented {
+ padding-left: 10px;
+ }
+
+ #content-wrapper {
+ padding: 0 10px;
+ }
+}
+
+/* Bootstrap extra small(xs) responsive breakpoint */
+@media screen and (width <= 575.98px) {
+ #site-nav {
+ display: none;
+ }
+}
+
+/* Hide site navigation when printing */
+@media print {
+ #site-nav {
+ display: none;
+ }
+
+ #page-nav {
+ display: none;
+ }
+}
diff --git a/exported_data.csv b/exported_data.csv
new file mode 100644
index 00000000000..9f585548328
--- /dev/null
+++ b/exported_data.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+Alice Pauline,4A,94351253,friends
+Benson Meier,6B,98765432,owesMoney friends
+Carl Kurz,4A,95352563,
+Daniel Meier,7H,87652533,friends
+Elle Meyer,3U,9482224,
+Fiona Kunz,3B,9482427,
+George Best,5H,9482442,
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java
index 9461d6da769..e5d3f315377 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/seedu/address/Main.java
@@ -37,5 +37,6 @@ public static void main(String[] args) {
logger.warning("The warning about Unsupported JavaFX configuration below (if any) can be ignored.");
Application.launch(MainApp.class, args);
+
}
}
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 678ddc8c218..7e4e129f455 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, 5, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -147,6 +147,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
UserPrefs initializedPrefs;
try {
+
Optional prefsOptional = storage.readUserPrefs();
if (!prefsOptional.isPresent()) {
logger.info("Creating new preference file " + prefsFilePath);
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..8ca4b7d6088 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -8,7 +8,9 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
+import seedu.address.storage.Storage;
/**
* API of the Logic component
@@ -33,6 +35,9 @@ public interface Logic {
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered list of groups */
+ public ObservableList getFilteredGroupList();
+
/**
* Returns the user prefs' address book file path.
*/
@@ -47,4 +52,8 @@ public interface Logic {
* Set the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+ /**
+ * Returns the Storage object.
+ */
+ Storage getStorage();
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 5aa3b91c7d0..acdabd9576b 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -15,6 +15,7 @@
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
import seedu.address.storage.Storage;
@@ -71,6 +72,11 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getFilteredGroupList() {
+ return model.getFilteredGroupList();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
@@ -85,4 +91,8 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ public Storage getStorage() {
+ return storage;
+ }
}
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..d577a4ebb3b 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -1,5 +1,7 @@
package seedu.address.logic;
+import static seedu.address.logic.commands.DeleteCommand.MESSAGE_USAGE;
+
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -15,10 +17,38 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+
+ public static final String MESSAGE_INVALID_INDEX_OVER_SIZE =
+ "The index provided is greater than the max students \n"
+ + MESSAGE_USAGE;
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
+
+ public static final String MESSAGE_GROUPS_LISTED_OVERVIEW = "%1$d groups listed!";
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
+ /**
+ * Returns a message indicating the number of people listed.
+ */
+ public static String getMessagePersonsListedOverview(int numOfPeople) {
+ if (numOfPeople <= 1) {
+ return numOfPeople + " person listed!";
+ } else {
+ return numOfPeople + " people listed!";
+ }
+ }
+
+ /**
+ * Returns a message indicating the number of groups listed.
+ */
+ public static String getMessageGroupsListedOverview(int numOfGroup) {
+ if (numOfGroup < 2) {
+ return numOfGroup + " group listed!";
+ } else {
+ return numOfGroup + " groups listed!";
+ }
+ }
+
/**
* Returns an error message indicating the duplicate prefixes.
*/
@@ -37,14 +67,12 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref
public static String format(Person person) {
final StringBuilder builder = new StringBuilder();
builder.append(person.getName())
+ .append("; Class: ")
+ .append(person.getStudentClass())
.append("; Phone: ")
.append(person.getPhone())
- .append("; Email: ")
- .append(person.getEmail())
- .append("; Address: ")
- .append(person.getAddress())
.append("; Tags: ");
- person.getTags().forEach(builder::append);
+ person.getTagSet().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..4a73d6794ef 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -1,8 +1,7 @@
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_CLASS;
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;
@@ -23,20 +22,19 @@ public class AddCommand extends Command {
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ "Parameters: "
+ PREFIX_NAME + "NAME "
+ + PREFIX_CLASS + "STUDENT CLASS "
+ PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ + PREFIX_CLASS + "4H "
+ PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ PREFIX_TAG + "friends "
+ PREFIX_TAG + "owesMoney";
public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_DUPLICATE_PERSON =
+ "This person already exists in the address book (Same name is considered duplicate)";
private final Person toAdd;
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 249b6072d0d..35cf825ce7b 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -16,6 +16,7 @@ public class CommandResult {
/** Help information should be shown to the user. */
private final boolean showHelp;
+
/** The application should exit. */
private final boolean exit;
@@ -48,6 +49,7 @@ public boolean isExit() {
return exit;
}
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..d734090e99a 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -1,6 +1,7 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_INDEX_OVER_SIZE;
import java.util.List;
@@ -25,6 +26,7 @@ public class DeleteCommand extends Command {
public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_INDEX_UNDER_1 = "Use integers >= 1 for index \n%1$s";
private final Index targetIndex;
public DeleteCommand(Index targetIndex) {
@@ -37,7 +39,7 @@ public CommandResult execute(Model model) throws CommandException {
List lastShownList = model.getFilteredPersonList();
if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(MESSAGE_INVALID_INDEX_OVER_SIZE);
}
Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
diff --git a/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java
new file mode 100644
index 00000000000..0ed1311ffcf
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java
@@ -0,0 +1,77 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+import seedu.address.model.group.GroupName;
+
+/**
+ * Deletes a group identified by its name from the address book.
+ */
+public class DeleteGroupCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteGroup";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the group identified by its name.\n"
+ + "Parameters: GROUP_NAME (must be a valid group name)\n"
+ + "Example: " + COMMAND_WORD + " Study Group";
+
+ public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Deleted Group: %1$s";
+ public static final String MESSAGE_GROUP_NOT_FOUND = "Group not found: %1$s";
+
+ private final GroupName groupName;
+
+ public DeleteGroupCommand(GroupName groupName) {
+ this.groupName = groupName;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS);
+ ObservableList lastShownList = model.getFilteredGroupList();
+
+ GroupContainsKeywordsPredicate groupPredicate =
+ new GroupContainsKeywordsPredicate(List.of(groupName.toString()));
+
+ FilteredList matchingGroups = lastShownList.filtered(groupPredicate);
+
+ if (matchingGroups.isEmpty()) {
+ throw new CommandException(String.format(MESSAGE_GROUP_NOT_FOUND, groupName));
+ }
+
+ Group groupToDelete = matchingGroups.get(0);
+ model.deleteGroup(groupToDelete);
+
+ return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, groupToDelete.getGroupName()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteGroupCommand)) {
+ return false;
+ }
+
+ DeleteGroupCommand otherDeleteGroupCommand = (DeleteGroupCommand) other;
+ return groupName.equals(otherDeleteGroupCommand.groupName);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("DeleteGroupCommand[groupName=%s]", groupName);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..68fe44154d4 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,19 +1,15 @@
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_CLASS;
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 static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
@@ -21,12 +17,11 @@
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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.StudentClass;
+import seedu.address.model.tag.Tags;
/**
* Edits the details of an existing person in the address book.
@@ -41,22 +36,22 @@ 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_CLASS + "STUDENT CLASS] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
+ + PREFIX_CLASS + "4L";
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.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_PERSON =
+ "This person already exists in the address book (Same name is considered duplicate)";
+
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
/**
- * @param index of the person in the filtered person list to edit
+ * @param index of the person in the filtered person list to edit
* @param editPersonDescriptor details to edit the person with
*/
public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
@@ -96,12 +91,12 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
assert personToEdit != null;
Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
+ StudentClass updatedStudentClass = editPersonDescriptor.getStudentClass()
+ .orElse(personToEdit.getStudentClass());
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
- Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+ Tags updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedStudentClass, updatedPhone, updatedTags);
}
@Override
@@ -129,17 +124,18 @@ public String toString() {
}
/**
- * Stores the details to edit the person with. Each non-empty field value will replace the
+ * Stores the details to edit the person with. Each non-empty field value will
+ * replace the
* corresponding field value of the person.
*/
public static class EditPersonDescriptor {
private Name name;
+ private StudentClass studentClass;
private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
+ private Tags tags;
- public EditPersonDescriptor() {}
+ public EditPersonDescriptor() {
+ }
/**
* Copy constructor.
@@ -147,9 +143,8 @@ public EditPersonDescriptor() {}
*/
public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setName(toCopy.name);
+ setStudentClass(toCopy.studentClass);
setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
setTags(toCopy.tags);
}
@@ -157,7 +152,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, studentClass, phone, tags);
}
public void setName(Name name) {
@@ -176,37 +171,30 @@ public Optional getPhone() {
return Optional.ofNullable(phone);
}
- public void setEmail(Email email) {
- this.email = email;
- }
-
- public Optional getEmail() {
- return Optional.ofNullable(email);
- }
-
- public void setAddress(Address address) {
- this.address = address;
+ public void setStudentClass(StudentClass studentClass) {
+ this.studentClass = studentClass;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ public Optional getStudentClass() {
+ return Optional.ofNullable(studentClass);
}
/**
* 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;
+ public void setTags(Tags tags) {
+ this.tags = tags;
}
/**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * 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();
+ public Optional getTags() {
+ return (tags != null) ? Optional.of(tags) : Optional.empty();
}
@Override
@@ -222,9 +210,8 @@ public boolean equals(Object other) {
EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other;
return Objects.equals(name, otherEditPersonDescriptor.name)
+ && Objects.equals(studentClass, otherEditPersonDescriptor.studentClass)
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
- && Objects.equals(email, otherEditPersonDescriptor.email)
- && Objects.equals(address, otherEditPersonDescriptor.address)
&& Objects.equals(tags, otherEditPersonDescriptor.tags);
}
@@ -232,9 +219,8 @@ public boolean equals(Object other) {
public String toString() {
return new ToStringBuilder(this)
.add("name", name)
+ .add("studentClass", studentClass)
.add("phone", phone)
- .add("email", email)
- .add("address", address)
.add("tags", tags)
.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/ExportCommand.java b/src/main/java/seedu/address/logic/commands/ExportCommand.java
new file mode 100644
index 00000000000..b4446e8de8c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ExportCommand.java
@@ -0,0 +1,140 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.FileWriter;
+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.json.JSONArray;
+import org.json.JSONObject;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonUserPrefsStorage;
+import seedu.address.storage.Storage;
+import seedu.address.storage.StorageManager;
+
+/**
+ * Command that exports current list of persons to a csv file
+ */
+public class ExportCommand extends Command {
+ public static final String FILE_OPS_ERROR_FORMAT = "Could not save data due to the following error: %s";
+
+ 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 COMMAND_WORD = "export";
+
+ public static final String MESSAGE_SUCCESS = "Data saved. \nData exported to /data/exported_data.csv";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exports the data to a spreadsheet.\n"
+ + "Example: " + COMMAND_WORD;
+
+ private static final Path projectRootPath = Paths.get(System.getProperty("user.dir"));
+
+ private Storage storage;
+
+ /**
+ * Public constructor for ExportCommand
+ */
+ public ExportCommand() {
+ JsonAddressBookStorage jsonStorage =
+ new JsonAddressBookStorage(projectRootPath.resolve("data").resolve("addressbook.json"));
+ JsonUserPrefsStorage userPrefStorage =
+ new JsonUserPrefsStorage(projectRootPath.resolve("config.json"));
+ storage = new StorageManager(jsonStorage, userPrefStorage);
+ }
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ Path importPath = projectRootPath.resolve("data").resolve("addressbook.json");
+ Path exportPath = projectRootPath.resolve("data").resolve("exported_data.csv");
+ requireNonNull(model);
+ saveJsonFile(model, importPath, exportPath);
+
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ /**
+ * Command that exports current list of persons to a csv file
+ */
+ public CommandResult execute(Model model, Path importPath, Path exportPath) throws CommandException {
+ requireNonNull(model);
+ saveJsonFile(model, importPath, exportPath);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ /**
+ * Saves the current data to the json file without exiting
+ * @param model
+ * @throws CommandException
+ */
+ private void saveJsonFile(Model model, Path importPath, Path exportPath) throws CommandException {
+ try {
+ storage.saveAddressBook(model.getAddressBook());
+ translateJsonToCsv(importPath, exportPath);
+ } 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);
+ }
+ }
+
+ /**
+ * Translate the Jsonfile into a csv file
+ * @param jsonFilePath
+ */
+ private void translateJsonToCsv(Path jsonFilePath, Path exportPath) throws AccessDeniedException {
+ try {
+ // Read the JSON file
+ String jsonContent = Files.readString(jsonFilePath);
+ JSONObject jsonObject = new JSONObject(jsonContent);
+ JSONArray jsonArray = jsonObject.getJSONArray("persons");
+
+ FileWriter csvWriter = new FileWriter(exportPath.toFile());
+
+ // Write the CSV header
+ csvWriter.append("Name,Class,Phone number,Tags\n");
+
+ // Iterate through the JSON array and write each object as a CSV row
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject person = jsonArray.getJSONObject(i);
+ String name = person.getString("name");
+ String studentClass = person.getString("studentClass");
+ String phone = person.getString("phone");
+ String tags = getPersonTags(person.getJSONArray("tags"));
+ csvWriter.append(name).append(",").append(studentClass)
+ .append(",").append(phone).append(",").append(tags).append("\n");
+ }
+ // Close the CSV writer
+ csvWriter.flush();
+ csvWriter.close();
+
+ } catch (AccessDeniedException e) {
+ throw e;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Parses the json array of tags into a parsable string
+ * @param tags
+ * @return String
+ */
+ private String getPersonTags(JSONArray tags) {
+ StringBuilder tagsString = new StringBuilder();
+ for (int i = 0; i < tags.length(); i++) {
+ tagsString.append(tags.getString(i));
+ if (i != tags.length() - 1) {
+ tagsString.append(" ");
+ }
+ }
+ return tagsString.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..7b5bb459589 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -30,8 +30,9 @@ public FindCommand(NameContainsKeywordsPredicate predicate) {
public CommandResult execute(Model model) {
requireNonNull(model);
model.updateFilteredPersonList(predicate);
+ int numOfPeople = model.getFilteredPersonList().size();
return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ String.format(Messages.getMessagePersonsListedOverview(numOfPeople)));
}
@Override
diff --git a/src/main/java/seedu/address/logic/commands/FindGroupCommand.java b/src/main/java/seedu/address/logic/commands/FindGroupCommand.java
new file mode 100644
index 00000000000..ec7026ac008
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindGroupCommand.java
@@ -0,0 +1,81 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+
+/**
+ * Finds and lists all groups in address book whose name contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindGroupCommand extends Command {
+
+ public static final String COMMAND_WORD = "findGroup";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows all groups whose names 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 + " Class A \n";
+
+
+ private final GroupContainsKeywordsPredicate groupPredicate;
+
+
+ /**
+ * Constructs a {@code FindCommand} for searching persons by group.
+ * The command will filter the address book to find all persons who belong to groups
+ * whose names contain any of the keywords specified in the given {@code GroupContainsKeywordsPredicate}.
+ *
+ * @param groupPredicate The {@code GroupContainsKeywordsPredicate}
+ * that defines the search criteria based on group membership.
+ */
+
+ public FindGroupCommand(GroupContainsKeywordsPredicate groupPredicate) {
+ this.groupPredicate = groupPredicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ if (groupPredicate != null) {
+ model.updateFilteredGroupList(groupPredicate);
+ }
+ int numOfGroup = model.getFilteredGroupList().size();
+ return new CommandResult(
+ String.format(Messages.getMessageGroupsListedOverview(numOfGroup)),
+ false,
+ false);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FindGroupCommand)) {
+ return false;
+ }
+
+ FindGroupCommand otherCommand = (FindGroupCommand) other;
+
+ if (this.groupPredicate != null) {
+ return this.groupPredicate.equals(otherCommand.groupPredicate);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("groupPredicate", groupPredicate)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/GroupCommand.java b/src/main/java/seedu/address/logic/commands/GroupCommand.java
new file mode 100644
index 00000000000..474f05acecd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/GroupCommand.java
@@ -0,0 +1,125 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * Groups students together in the application.
+ */
+public class GroupCommand extends Command {
+
+ public static final String COMMAND_WORD = "group";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a new group with the specified students.\n"
+ + "Groups the students by the group name and the list of students provided.\n"
+ + "Existing groups with the same name will not be overwritten.\n"
+ + "Parameters: GROUPNAME (alphanumeric) "
+ + "STUDENT1 STUDENT2... (must be valid student names)\n"
+ + "Example: " + COMMAND_WORD + " g/StudyGroup1 s/Benjamin s/Candice\n"
+ + "Example: " + COMMAND_WORD + " g/TeamA s/Martin s/Candice";
+
+ public static final String MESSAGE_NO_STUDENTS_FOUND = "No matching students found.";
+
+ public static final String MESSAGE_SUCCESS = "Group %s created with %d student(s)";
+ public static final String MESSAGE_DUPLICATE_GROUP = "Group name already taken!!";
+ public static final String EMPTY_STUDENT = "Please do not enter an empty string for student name!";
+
+ public static final String EMPTY_GROUP_NAME = "Please enter a valid group name that is not blank!";
+ public static final String DUPLICATE_STUDENT_FOUND = "Duplicate student found in input: %s";
+ public static final String STUDENTS_NOT_FOUND = "The following students could not be found: %s";
+ private final String groupName;
+ private final List students;
+
+ /**
+ * Creates a GroupCommand to group the specified students under the given group
+ * name.
+ *
+ * @param groupName The name of the group.
+ * @param students The list of students to be grouped.
+ * @throws NullPointerException if {@code groupName} or {@code students} is
+ * null.
+ */
+ public GroupCommand(String groupName, List students) throws NullPointerException {
+ requireAllNonNull(groupName, students);
+ this.groupName = groupName;
+ this.students = students;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+
+ if (groupName.isEmpty()) {
+ throw new CommandException(EMPTY_GROUP_NAME);
+ }
+
+ if (model.hasGroupName(new Group(groupName, List.of()))) {
+ throw new CommandException(MESSAGE_DUPLICATE_GROUP);
+ }
+
+ model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ List allPersons = model.getFilteredPersonList();
+ List groupMembers = new ArrayList<>();
+ List notFoundStudents = new ArrayList<>();
+
+ Set uniqueStudents = new HashSet<>();
+ for (String studentName : students) {
+ if (studentName.equals("")) {
+ throw new CommandException(EMPTY_STUDENT);
+ }
+ if (!uniqueStudents.add(studentName)) {
+ throw new CommandException(String.format(DUPLICATE_STUDENT_FOUND, studentName));
+ }
+ }
+
+ for (String studentName : students) {
+ boolean found = false;
+ for (Person person : allPersons) {
+ if (person.getName().fullName.equalsIgnoreCase(studentName)) {
+ groupMembers.add(person);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ notFoundStudents.add(studentName);
+ }
+ }
+
+ if (!notFoundStudents.isEmpty()) {
+ throw new CommandException(String.format(STUDENTS_NOT_FOUND,
+ String.join(", ", notFoundStudents)));
+ }
+
+ if (groupMembers.isEmpty()) {
+ throw new CommandException(MESSAGE_NO_STUDENTS_FOUND);
+ }
+ Group group = new Group(groupName, groupMembers);
+
+ model.addGroup(group);
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, groupName, groupMembers.size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof GroupCommand e)) {
+ return false;
+ }
+
+ return groupName.equalsIgnoreCase(e.groupName)
+ && students.equals(e.students);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java
new file mode 100644
index 00000000000..d1f62863ace
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java
@@ -0,0 +1,200 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.opencsv.CSVReader;
+import com.opencsv.exceptions.CsvValidationException;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
+import seedu.address.model.tag.Tags;
+import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonUserPrefsStorage;
+import seedu.address.storage.Storage;
+import seedu.address.storage.StorageManager;
+
+/**
+ * Command that exports current list of persons to a csv file
+ */
+public class ImportCommand extends Command {
+ public static final String COMMAND_WORD = "import";
+ public static final String FILE_OPS_ERROR_FORMAT = "Could not import data due to the following error: %s";
+ public static final String FILE_OPS_PERMISSION_ERROR_FORMAT =
+ "Could not import data to file %s due to insufficient permissions to read to the file or the folder.";
+ public static final String MESSAGE_CSV_ERROR = "Error reading CSV file";
+ public static final String MESSAGE_FILE_CORRUPTED = "Data in file is corrupted or missing data";
+ public static final String MESSAGE_FILE_NOT_FOUND =
+ "File not found\nAre you using the correct absolute file path?"
+ + "\nExample: Users/username/Desktop/addressbook.csv";
+ public static final String MESSAGE_SUCCESS = "Data imported. \n%d students imported.";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Imports the data from a csv file.\n"
+ + "Parameters: import \n"
+ + "Example: " + COMMAND_WORD
+ + " /Users/username/Desktop/addressbook.csv";
+
+ private static final Path projectRootPath = Paths.get(System.getProperty("user.dir"));
+ private Path importCsvFilePath;
+ private Storage storage;
+
+ /**
+ * Public constructor for ExportCommand
+ */
+ public ImportCommand(Path importCsvFilePath) {
+ this.importCsvFilePath = importCsvFilePath;
+ JsonAddressBookStorage jsonStorage =
+ new JsonAddressBookStorage(projectRootPath.resolve("addressbook.json"));
+ JsonUserPrefsStorage userPrefStorage =
+ new JsonUserPrefsStorage(projectRootPath.resolve("config.json"));
+ this.storage = new StorageManager(jsonStorage, userPrefStorage);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ requireNonNull(importCsvFilePath);
+ requireNonNull(storage);
+ int numStudentsImported = addStudentsFromCsv(model);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, numStudentsImported));
+ }
+
+ /**
+ * Method that reads the csv file and adds students to the model
+ * Returns the number of students imported as well
+ * @param model
+ * @return int
+ * @throws CommandException
+ */
+ private int addStudentsFromCsv(Model model) throws CommandException {
+ List duplicatePersonsNames = new ArrayList<>();
+ try {
+ CSVReader reader = new CSVReader(new FileReader(importCsvFilePath.toFile()));
+ String[] nextLine;
+ reader.readNext(); // skip header
+ int[] importStudentNumbers = {0, 0}; // [0] = no. imported, [1] = no. duplicates
+ while ((nextLine = reader.readNext()) != null) {
+
+ Name name = ParserUtil.parseName(nextLine[0]);
+
+ StudentClass studentClass = ParserUtil.parseClass(cleanDataString(nextLine[1]));
+ Phone phone = ParserUtil.parsePhone((nextLine[2].trim() == "") ? "00000000" : nextLine[2]);
+ List tagList = Arrays.asList(nextLine[3].split(" "));
+
+ if (!"".equals(tagList.get(0))) {
+ Tags tags = ParserUtil.parseTags(tagList);
+
+ importStudentNumbers = handleAddStudent(model, new Person(name, studentClass, phone, tags),
+ importStudentNumbers, duplicatePersonsNames);
+ } else {
+ importStudentNumbers = handleAddStudent(model, new Person(name, studentClass, phone, null),
+ importStudentNumbers, duplicatePersonsNames);
+ }
+ }
+
+ if (importStudentNumbers[0] == 0 || importStudentNumbers[1] > 0) {
+ String message = produceImportMessageToUser(importStudentNumbers, duplicatePersonsNames);
+ throw new CommandException(message);
+ }
+ reader.close();
+ return importStudentNumbers[0];
+ } catch (CsvValidationException e) {
+ throw new CommandException(MESSAGE_CSV_ERROR);
+ } catch (FileNotFoundException e) {
+ throw new CommandException(MESSAGE_FILE_NOT_FOUND);
+ } catch (ParseException e) {
+ throw new CommandException(MESSAGE_FILE_CORRUPTED);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return 0;
+ }
+
+ /**
+ * Method that produces a message to the user based on the number of students imported and duplicates found
+ * @param importStudentNumbers
+ * @return
+ */
+ private String produceImportMessageToUser(int[] importStudentNumbers, List duplicatePersonsNames) {
+ String message = "";
+ if (importStudentNumbers[0] == 0) {
+ message += "No students imported.";
+ } else {
+ message += String.format("Data imported with %d students added.", importStudentNumbers[0]);
+ }
+
+ if (importStudentNumbers[1] > 0) {
+ message += String.format("\n%d Duplicate person(s) found in file: %s",
+ importStudentNumbers[1],
+ duplicatePersonsNames.toString());
+ }
+ return message;
+ }
+
+ /**
+ * Method that checks if the person is a duplicate and adds non dups to the model
+ * @param model
+ * @param person
+ * @param importStudentNumbers
+ * @param duplicatePersonsNames
+ * @return
+ */
+ private int[] handleAddStudent(Model model, Person person,
+ int[] importStudentNumbers, List duplicatePersonsNames) {
+ if (model.hasPerson(person)) {
+ duplicatePersonsNames.add(person.getName().toString());
+ importStudentNumbers[1]++;
+ } else {
+ model.addPerson(person);
+ importStudentNumbers[0]++;
+ }
+ return importStudentNumbers;
+ }
+
+ /**
+ * Method that fills in missing data with default values
+ * @param data
+ * @return
+ */
+ private String cleanDataString(String data) {
+ return data.trim().isEmpty() ? "unknown" : data;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ImportCommand)) {
+ return false;
+ }
+
+ ImportCommand otherImportCommand = (ImportCommand) other;
+ return importCsvFilePath.toString().equals(otherImportCommand.importCsvFilePath.toString());
+
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("filepath: ", importCsvFilePath.toString())
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..61f7621df2b 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -12,13 +12,19 @@ public class ListCommand extends Command {
public static final String COMMAND_WORD = "list";
- public static final String MESSAGE_SUCCESS = "Listed all persons";
+ public static final String MESSAGE_EMPTY_LIST = "There are no persons in the address book.";
+ public static final String MESSAGE_SUCCESS = "Listed all %d person(s)";
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
+ int addressBookSize = model.getAddressBook().getPersonList().size();
+ if (addressBookSize == 0) {
+ return new CommandResult(MESSAGE_EMPTY_LIST);
+ } else {
+ return new CommandResult(String.format(MESSAGE_SUCCESS, addressBookSize));
+ }
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ListGroupsCommand.java b/src/main/java/seedu/address/logic/commands/ListGroupsCommand.java
new file mode 100644
index 00000000000..1440460035b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListGroupsCommand.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GROUPS;
+
+import seedu.address.model.Model;
+
+/**
+ * Lists all groups in the address book to the user.
+ */
+public class ListGroupsCommand extends Command {
+
+ public static final String COMMAND_WORD = "listGroups";
+
+ public static final String MESSAGE_SUCCESS = "Listed all groups";
+
+ public static final String MESSAGE_NOGROUPS = "no groups found";
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS);
+ if (model.groupsString().equals(MESSAGE_NOGROUPS)) {
+ return new CommandResult(MESSAGE_NOGROUPS, false, false);
+ } else {
+ return new CommandResult(MESSAGE_SUCCESS, false, false);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/TagCommand.java b/src/main/java/seedu/address/logic/commands/TagCommand.java
new file mode 100644
index 00000000000..9f64b0a7893
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/TagCommand.java
@@ -0,0 +1,87 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tags;
+
+/**
+ * Adds tags to a person.
+ */
+public class TagCommand extends Command {
+
+ public static final String COMMAND_WORD = "tag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tag(s) to a person. "
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_TAG + "TAG\n"
+ + "Example: " + COMMAND_WORD + " "
+ + "1 " + PREFIX_TAG + "needs consult";
+
+ public static final String MESSAGE_SUCCESS = "New tag(s) added";
+ public static final String MESSAGE_TAG_ALREADY_EXISTS = "Tag(s) already exist";
+ private final Index targetIndex;
+ private final Tags newTags;
+
+ /**
+ * Creates a TagCommand to add the specified {@code Set}
+ * to the person of specified {@code Index}
+ */
+ public TagCommand(Index targetIndex, Tags newTagSet) {
+ this.targetIndex = targetIndex;
+ this.newTags = newTagSet;
+ }
+
+ @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 person = lastShownList.get(targetIndex.getZeroBased());
+ Boolean tagExists = model.tagExists(person, newTags);
+
+ if (tagExists) {
+ throw new CommandException(MESSAGE_TAG_ALREADY_EXISTS);
+ }
+
+ model.addTag(person, newTags);
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(person)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TagCommand)) {
+ return false;
+ }
+
+ TagCommand otherTagCommand = (TagCommand) other;
+ return newTags.equals(otherTagCommand.newTags)
+ && targetIndex.equals(otherTagCommand.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .add("tags", newTags)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UntagCommand.java b/src/main/java/seedu/address/logic/commands/UntagCommand.java
new file mode 100644
index 00000000000..bfd16e91830
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UntagCommand.java
@@ -0,0 +1,85 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tags;
+
+/**
+ * Adds tags to a person.
+ */
+public class UntagCommand extends Command {
+
+ public static final String COMMAND_WORD = "untag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a tag(s) from a person. "
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_TAG + " \n"
+ + "Example: " + COMMAND_WORD + " "
+ + "1 " + PREFIX_TAG + "needs consult";
+
+ public static final String MESSAGE_SUCCESS = "Tag(s) deleted";
+ public static final String MESSAGE_TAG_DOES_NOT_EXIST = "The tag(s) does not exist";
+ private final Index targetIndex;
+ private final Tags tagsToDelete;
+
+ /**
+ * Creates a TagCommand to add the specified {@code Set}
+ * to the person of specified {@code Index}
+ */
+ public UntagCommand(Index targetIndex, Tags tagsToDelete) {
+ this.targetIndex = targetIndex;
+ this.tagsToDelete = tagsToDelete;
+ }
+
+ @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 person = lastShownList.get(targetIndex.getZeroBased());
+ Boolean tagExists = model.tagExists(person, tagsToDelete);
+
+ if (!tagExists) {
+ throw new CommandException(MESSAGE_TAG_DOES_NOT_EXIST);
+ }
+ model.deleteTag(person, tagsToDelete);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(person)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UntagCommand)) {
+ return false;
+ }
+
+ UntagCommand otherUntagCommand = (UntagCommand) other;
+ return tagsToDelete.equals(otherUntagCommand.tagsToDelete)
+ && targetIndex.equals(otherUntagCommand.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .add("tags", tagsToDelete)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..bc1388d694c 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,23 +1,20 @@
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_CLASS;
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.Set;
import java.util.stream.Stream;
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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.StudentClass;
+import seedu.address.model.tag.Tags;
/**
* Parses input arguments and creates a new AddCommand object
@@ -31,21 +28,20 @@ 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_CLASS, PREFIX_PHONE, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_CLASS, PREFIX_PHONE)
|| !argMultimap.getPreamble().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_CLASS, PREFIX_PHONE);
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ StudentClass studentClass = ParserUtil.parseClass(argMultimap.getValue(PREFIX_CLASS).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());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Tags tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, studentClass, phone, 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..9ca871dd4f2 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -12,11 +12,19 @@
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteGroupCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.ExportCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindGroupCommand;
+import seedu.address.logic.commands.GroupCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.ImportCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListGroupsCommand;
+import seedu.address.logic.commands.TagCommand;
+import seedu.address.logic.commands.UntagCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -59,27 +67,52 @@ public Command parseCommand(String userInput) throws ParseException {
case EditCommand.COMMAND_WORD:
return new EditCommandParser().parse(arguments);
+ case ExportCommand.COMMAND_WORD:
+ return new ExportCommand();
+
case DeleteCommand.COMMAND_WORD:
return new DeleteCommandParser().parse(arguments);
+ case DeleteGroupCommand.COMMAND_WORD:
+ return new DeleteGroupCommandParser().parse(arguments);
+
case ClearCommand.COMMAND_WORD:
return new ClearCommand();
+ case FindGroupCommand.COMMAND_WORD:
+ return new FindGroupCommandParser().parse(arguments);
+
case FindCommand.COMMAND_WORD:
return new FindCommandParser().parse(arguments);
+ case ImportCommand.COMMAND_WORD:
+ return new ImportCommandParser().parse(arguments);
+
case ListCommand.COMMAND_WORD:
return new ListCommand();
+ case ListGroupsCommand.COMMAND_WORD:
+ return new ListGroupsCommand();
+
+ case GroupCommand.COMMAND_WORD:
+ return new GroupCommandParser().parse(arguments);
+
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case TagCommand.COMMAND_WORD:
+ return new TagCommandParser().parse(arguments);
+
+ case UntagCommand.COMMAND_WORD:
+ return new UntagCommandParser().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/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..a5c04bfafb3 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -6,10 +6,11 @@
public class CliSyntax {
/* Prefix definitions */
+
public static final Prefix PREFIX_NAME = new Prefix("n/");
+ public static final Prefix PREFIX_CLASS = new Prefix("c/");
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_TAG = new Prefix("t/");
-
+ public static final Prefix PREFIX_GROUP = new Prefix("g/");
+ public static final Prefix PREFIX_STUDENTS = new Prefix("s/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 3527fe76a3e..3d04a180428 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.DeleteCommand.MESSAGE_INDEX_UNDER_1;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.DeleteCommand;
@@ -22,7 +22,7 @@ public DeleteCommand parse(String args) throws ParseException {
return new DeleteCommand(index);
} catch (ParseException pe) {
throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ String.format(MESSAGE_INDEX_UNDER_1, DeleteCommand.MESSAGE_USAGE), pe);
}
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java
new file mode 100644
index 00000000000..afc41b27317
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteGroupCommandParser.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.DeleteGroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.GroupName;
+
+/**
+ * Parses input arguments and creates a new DeleteCommand object
+ */
+public class DeleteGroupCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteCommand
+ * and returns a DeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteGroupCommand parse(String args) throws ParseException {
+ try {
+ GroupName groupName = ParserUtil.parseGroupName(args);
+ return new DeleteGroupCommand(groupName);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..4bef9c1e0be 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -2,8 +2,7 @@
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_CLASS;
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;
@@ -11,13 +10,12 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
-import java.util.Set;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* Parses input arguments and creates a new EditCommand object
@@ -32,7 +30,7 @@ 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_CLASS, PREFIX_PHONE, PREFIX_TAG);
Index index;
@@ -42,22 +40,20 @@ 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_CLASS, PREFIX_PHONE);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
+ if (argMultimap.getValue(PREFIX_CLASS).isPresent()) {
+ editPersonDescriptor.setStudentClass(ParserUtil.parseClass(argMultimap.getValue(PREFIX_CLASS).get()));
+ }
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()));
- }
+
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
@@ -72,7 +68,7 @@ public EditCommand parse(String args) throws ParseException {
* 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> parseTagsForEdit(Collection tags) throws ParseException {
+ private Optional parseTagsForEdit(Collection tags) throws ParseException {
assert tags != null;
if (tags.isEmpty()) {
diff --git a/src/main/java/seedu/address/logic/parser/FindGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/FindGroupCommandParser.java
new file mode 100644
index 00000000000..adfaf928294
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FindGroupCommandParser.java
@@ -0,0 +1,35 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+import java.util.List;
+
+import seedu.address.logic.commands.FindGroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindGroupCommand object
+ */
+public class FindGroupCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindGroupCommand
+ * and returns a FindGroupCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindGroupCommand parse(String arg) throws ParseException {
+
+ String trimmedArgs = arg.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindGroupCommand.MESSAGE_USAGE));
+ }
+
+ List keyword = Arrays.asList(trimmedArgs);
+ return new FindGroupCommand(new GroupContainsKeywordsPredicate(keyword));
+
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/GroupCommandParser.java b/src/main/java/seedu/address/logic/parser/GroupCommandParser.java
new file mode 100644
index 00000000000..78514cb48c9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/GroupCommandParser.java
@@ -0,0 +1,50 @@
+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_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENTS;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.GroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new {@code GroupCommand} object
+ */
+public class GroupCommandParser implements Parser {
+ /**
+ * 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());
+ }
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the
+ * {@code RemarkCommand}
+ * and returns a {@code RemarkCommand} object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public GroupCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ System.out.println(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_GROUP, PREFIX_STUDENTS);
+ if (!arePrefixesPresent(argMultimap, PREFIX_GROUP, PREFIX_STUDENTS)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GroupCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUP);
+ String name = argMultimap.getValue(PREFIX_GROUP).get();
+ List students = argMultimap.getAllValues(PREFIX_STUDENTS);
+
+ return new GroupCommand(name, students);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ImportCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java
new file mode 100644
index 00000000000..21218df91f6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java
@@ -0,0 +1,47 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import seedu.address.logic.commands.ImportCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ImportCommand object
+ */
+public class ImportCommandParser implements Parser {
+
+ private static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format!\n";
+ private static final String MESSAGE_INVALID_FILE_FORMAT = "Invalid file format, must be a .csv!\n";
+ private static final String MESSAGE_INVALID_PATH = "Invalid path!\n";
+
+ /**
+ * Parses the given import path as {@code String} and translates it into a Path object
+ * and returns an ImportCommand object for execution.
+ * @throws ParseException
+ */
+ public ImportCommand parse(String args) throws ParseException {
+ if (args.isBlank() || args.trim().isEmpty()) {
+ throw new ParseException(
+ String.format("%s\n%s", MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE));
+ }
+ requireNonNull(args);
+
+ Path path = Paths.get(args.trim());
+ if (!Files.exists(path)) {
+ throw new ParseException(
+ String.format("%s\n%s", MESSAGE_INVALID_PATH, ImportCommand.MESSAGE_USAGE));
+ }
+
+ if (!args.trim().endsWith(".csv")) {
+ throw new ParseException(
+ String.format("%s\n%s", MESSAGE_INVALID_FILE_FORMAT, ImportCommand.MESSAGE_USAGE));
+ }
+
+ return new ImportCommand(path);
+
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..604adabe9b4 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -3,17 +3,19 @@
import static java.util.Objects.requireNonNull;
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.parser.exceptions.ParseException;
+import seedu.address.model.group.GroupName;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -79,6 +81,34 @@ public static Address parseAddress(String address) throws ParseException {
}
return new Address(trimmedAddress);
}
+ /**
+ * Parses a {@code String studentClass} into an {@code StudentClass}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code studentClass} is invalid.
+ */
+ public static StudentClass parseClass(String studentClass) throws ParseException {
+ requireNonNull(studentClass);
+ String studentClassTrimmed = studentClass.trim();
+ if (!StudentClass.isValidClass(studentClassTrimmed)) {
+ throw new ParseException(StudentClass.MESSAGE_CONSTRAINTS);
+ }
+ return new StudentClass(studentClassTrimmed);
+ }
+ /**
+ * Parses a {@code String groupName} into an {@code GroupName}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code groupName} is invalid.
+ */
+ public static GroupName parseGroupName(String groupName) throws ParseException {
+ requireNonNull(groupName);
+ String groupNameTrimmed = groupName.trim();
+ if (!GroupName.isValidName(groupNameTrimmed)) {
+ throw new ParseException(GroupName.MESSAGE_CONSTRAINTS);
+ }
+ return new GroupName(groupNameTrimmed);
+ }
/**
* Parses a {@code String email} into an {@code Email}.
@@ -105,19 +135,24 @@ public static Tag parseTag(String tag) throws ParseException {
requireNonNull(tag);
String trimmedTag = tag.trim();
if (!Tag.isValidTagName(trimmedTag)) {
- throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
+ throw new ParseException(Tag.MESSAGE_CHAR_CONSTRAINTS);
+ }
+ if (!Tag.isWithinLengthLimit(trimmedTag)) {
+ throw new ParseException(Tag.MESSAGE_LENGTH_CONSTRAINTS);
}
- return new Tag(trimmedTag);
+ return new Tag(trimmedTag.toLowerCase());
}
/**
* Parses {@code Collection tags} into a {@code Set}.
*/
- public static Set parseTags(Collection tags) throws ParseException {
+ public static Tags parseTags(Collection tags) throws ParseException {
requireNonNull(tags);
- final Set tagSet = new HashSet<>();
+ final Tags tagSet = new Tags();
for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
+ Tag tag = parseTag(tagName.toLowerCase());
+ Tags tagsToAdd = new Tags(Set.of(tag));
+ tagSet.addAllTags(tagsToAdd);
}
return tagSet;
}
diff --git a/src/main/java/seedu/address/logic/parser/TagCommandParser.java b/src/main/java/seedu/address/logic/parser/TagCommandParser.java
new file mode 100644
index 00000000000..d1807c992e0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/TagCommandParser.java
@@ -0,0 +1,48 @@
+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_TAG;
+
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.TagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tags;
+
+/**
+ * Parses input arguments and creates a new TagCommand object
+ */
+public class TagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the TagCommand
+ * and returns a TagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public TagCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TAG);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE), pe);
+ }
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_TAG)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE));
+ }
+
+ Tags newTagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ return new TagCommand(index, newTagList);
+ }
+
+ 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/UntagCommandParser.java b/src/main/java/seedu/address/logic/parser/UntagCommandParser.java
new file mode 100644
index 00000000000..accae5686e3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UntagCommandParser.java
@@ -0,0 +1,48 @@
+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_TAG;
+
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UntagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tags;
+
+/**
+ * Parses input arguments and creates a new TagCommand object
+ */
+public class UntagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the TagCommand
+ * and returns a TagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UntagCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TAG);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE), pe);
+ }
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_TAG)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE));
+ }
+
+ Tags deleteTagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ return new UntagCommand(index, deleteTagList);
+ }
+
+ 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/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 73397161e84..10396db84d5 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -6,6 +6,8 @@
import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupList;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
@@ -16,19 +18,15 @@
public class AddressBook implements ReadOnlyAddressBook {
private final UniquePersonList persons;
+ private final GroupList groups;
- /*
- * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
- * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
- *
- * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
- * among constructors.
- */
{
persons = new UniquePersonList();
+ groups = new GroupList();
}
- public AddressBook() {}
+ public AddressBook() {
+ }
/**
* Creates an AddressBook using the Persons in the {@code toBeCopied}
@@ -48,6 +46,27 @@ public void setPersons(List persons) {
this.persons.setPersons(persons);
}
+ /**
+ * Replaces the contents of the groups list with {@code groups}.
+ * {@code groups} must not contain duplicate groups.
+ */
+ public void setGroups(List groups) {
+ this.groups.setGroups(groups);
+ }
+
+ /**
+ * Replaces the given group {@code target} with {@code editedGroup}.
+ * The group identity of {@code editedGroup} must not be the same as another
+ * existing group in the address book.
+ *
+ * @param target The group to be replaced.
+ * @param editedGroup The updated group.
+ */
+ public void setGroup(Group target, Group editedGroup) {
+ requireNonNull(editedGroup);
+ groups.setGroup(target, editedGroup);
+ }
+
/**
* Resets the existing data of this {@code AddressBook} with {@code newData}.
*/
@@ -55,18 +74,29 @@ public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
setPersons(newData.getPersonList());
+ setGroups(newData.getGroupList());
}
//// person-level operations
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Returns true if a person with the same identity as {@code person} exists in
+ * the address book.
*/
public boolean hasPerson(Person person) {
requireNonNull(person);
return persons.contains(person);
}
+ /**
+ * Returns true if a group with the same identity as {@code group} exists in the
+ * address book.
+ */
+ public boolean hasGroup(Group group) {
+ requireNonNull(group);
+ return groups.contains(group);
+ }
+
/**
* Adds a person to the address book.
* The person must not already exist in the address book.
@@ -76,9 +106,18 @@ public void addPerson(Person p) {
}
/**
- * Replaces the given person {@code target} in the list with {@code editedPerson}.
+ * Adds a Group to the address book.
+ */
+ public void addGroup(Group g) {
+ groups.add(g);
+ }
+
+ /**
+ * Replaces the given person {@code target} in the list with
+ * {@code editedPerson}.
* {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
+ * The person identity of {@code editedPerson} must not be the same as another
+ * existing person in the address book.
*/
public void setPerson(Person target, Person editedPerson) {
requireNonNull(editedPerson);
@@ -94,6 +133,14 @@ public void removePerson(Person key) {
persons.remove(key);
}
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeGroup(Group key) {
+ groups.remove(key);
+ }
+
//// util methods
@Override
@@ -108,6 +155,11 @@ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
+ @Override
+ public ObservableList getGroupList() {
+ return groups.asUnmodifiableObservableList();
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..69dc2e48f65 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,25 +5,30 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tags;
/**
* The API of the Model component.
*/
public interface Model {
- /** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
-
/**
- * Replaces user prefs data with the data in {@code userPrefs}.
+ * {@code Predicate} that always evaluate to true
*/
- void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_GROUPS = unused -> true;
/**
* Returns the user prefs.
*/
ReadOnlyUserPrefs getUserPrefs();
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
/**
* Returns the user prefs' GUI settings.
*/
@@ -44,14 +49,16 @@ public interface Model {
*/
void setAddressBookFilePath(Path addressBookFilePath);
+ /**
+ * Returns the AddressBook
+ */
+ ReadOnlyAddressBook getAddressBook();
+
/**
* Replaces address book data with the data in {@code addressBook}.
*/
void setAddressBook(ReadOnlyAddressBook addressBook);
- /** Returns the AddressBook */
- ReadOnlyAddressBook getAddressBook();
-
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
@@ -76,12 +83,54 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
- /** Returns an unmodifiable view of the filtered person list */
+ /**
+ * Check if any of the already exist for the person.
+ * {@code person} must already exist in the address book.
+ */
+ boolean tagExists(Person target, Tags tags);
+
+ /**
+ * Adds the tags to the specified person.
+ * {@code person} must already exist in the address book.
+ */
+ void addTag(Person target, Tags tags);
+
+ /**
+ * Deletes the tags of the specified person.
+ * {@code person} must already exist in the address book.
+ * {@code tags} must already exist for the person.
+ */
+ void deleteTag(Person target, Tags tags);
+
+ /**
+ * Returns an unmodifiable view of the filtered person list
+ */
ObservableList getFilteredPersonList();
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ *
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Adds the group to the model.
+ *
+ * @throws NullPointerException if {@code group} is null.
+ */
+ void addGroup(Group group);
+
+ void deleteGroup(Group groupToDelete);
+
+ String groupsString();
+
+ boolean hasGroupName(Group group);
+
+ void updateFilteredGroupList(Predicate groupName);
+
+ /**
+ * Returns an unmodifiable view of the groups list
+ */
+ ObservableList getFilteredGroupList();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..f71d2c5b867 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -4,6 +4,8 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Logger;
@@ -11,7 +13,9 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tags;
/**
* Represents the in-memory model of the address book data.
@@ -23,6 +27,8 @@ public class ModelManager implements Model {
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final FilteredList groups;
+
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
@@ -34,23 +40,25 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ this.groups = new FilteredList<>(this.addressBook.getGroupList());
}
public ModelManager() {
this(new AddressBook(), new UserPrefs());
}
- //=========== UserPrefs ==================================================================================
+ // =========== UserPrefs
+ // ==================================================================================
@Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
- requireNonNull(userPrefs);
- this.userPrefs.resetData(userPrefs);
+ public ReadOnlyUserPrefs getUserPrefs() {
+ return userPrefs;
}
@Override
- public ReadOnlyUserPrefs getUserPrefs() {
- return userPrefs;
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ requireNonNull(userPrefs);
+ this.userPrefs.resetData(userPrefs);
}
@Override
@@ -75,16 +83,17 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
userPrefs.setAddressBookFilePath(addressBookFilePath);
}
- //=========== AddressBook ================================================================================
+ // =========== AddressBook
+ // ================================================================================
@Override
- public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
+ public ReadOnlyAddressBook getAddressBook() {
+ return addressBook;
}
@Override
- public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
+ public void setAddressBook(ReadOnlyAddressBook addressBook) {
+ this.addressBook.resetData(addressBook);
}
@Override
@@ -95,7 +104,13 @@ public boolean hasPerson(Person person) {
@Override
public void deletePerson(Person target) {
+ requireNonNull(target);
+
+ // Remove the person from the address book
addressBook.removePerson(target);
+
+ // Remove the person from all groups
+ removePersonFromAllGroups(target);
}
@Override
@@ -107,14 +122,33 @@ public void addPerson(Person person) {
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
-
addressBook.setPerson(target, editedPerson);
+ editPersonFromAllGroups(target, editedPerson);
+ }
+
+ @Override
+ public boolean tagExists(Person target, Tags tags) {
+ return target.tagExists(tags);
}
- //=========== Filtered Person List Accessors =============================================================
+ @Override
+ public void addTag(Person target, Tags newTags) {
+ Person updatedPerson = target.addTags(newTags);
+ setPerson(target, updatedPerson);
+ }
+
+ @Override
+ public void deleteTag(Person target, Tags tagsToDelete) {
+ Person updatedPerson = target.deleteTags(tagsToDelete);
+ setPerson(target, updatedPerson);
+ }
+
+ // =========== Filtered Person List Accessors
+ // =============================================================
/**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
+ * Returns an unmodifiable view of the list of {@code Person} backed by the
+ * internal list of
* {@code versionedAddressBook}
*/
@Override
@@ -128,6 +162,140 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ // =========== Group Logic
+ // =============================================================
+
+ /**
+ * Adds a group to the Model
+ *
+ * @param group Group to be added
+ */
+ @Override
+ public void addGroup(Group group) {
+ addressBook.addGroup(group);
+ updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS);
+ }
+
+ /**
+ * Deletes the specified group from the list of groups.
+ *
+ * @param groupToDelete The group to be deleted.
+ * @throws NullPointerException if {@code groupToDelete} is null.
+ * @throws IllegalArgumentException if the group does not exist in the list.
+ */
+ @Override
+ public void deleteGroup(Group groupToDelete) {
+ requireNonNull(groupToDelete);
+ addressBook.removeGroup(groupToDelete);
+ }
+
+ /**
+ * Adds a group to the Model
+ */
+ @Override
+ public String groupsString() {
+ if (groups.isEmpty()) {
+ return "no groups found";
+ } else {
+ return groups.toString();
+ }
+
+ }
+
+ /**
+ * Checks if a group with the same name already exists in the Model
+ *
+ * @param group Group to be checked
+ * @return true if the group exists, false otherwise
+ */
+ @Override
+ public boolean hasGroupName(Group group) {
+ requireNonNull(group);
+ return addressBook.hasGroup(group);
+ }
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Group} backed by the
+ * internal list of
+ * {@code versionedAddressBook}
+ */
+ @Override
+ public ObservableList getFilteredGroupList() {
+ return groups;
+ }
+
+ /**
+ * Removes a person from all groups and deletes groups that become empty.
+ *
+ * @param person The person to remove from groups.
+ */
+ private void removePersonFromAllGroups(Person person) {
+ // Create a list to collect groups that become empty
+ List emptyGroups = new ArrayList<>();
+
+ // Iterate over a copy of the group list to avoid
+ // ConcurrentModificationException
+ for (Group group : new ArrayList<>(addressBook.getGroupList())) {
+ List members = new ArrayList<>(group.getMembers());
+
+ // Check if the group contains the person
+ if (members.contains(person)) {
+ // Remove the person from the group's member list
+ members.remove(person);
+
+ if (members.isEmpty()) {
+ // If the group is empty after removal, mark it for deletion
+ emptyGroups.add(group);
+ } else {
+ // Otherwise, update the group with the new member list
+ Group updatedGroup = new Group(group.getGroupName().toString(), members);
+ addressBook.setGroup(group, updatedGroup);
+ }
+ }
+ }
+
+ // Remove all empty groups from the address book
+ for (Group emptyGroup : emptyGroups) {
+ addressBook.removeGroup(emptyGroup);
+ }
+
+ // Update the filtered group list if necessary
+ updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS);
+ }
+
+ /**
+ * Updates the details of a person in all groups.
+ *
+ * @param oldPerson The person to be updated in the groups.
+ * @param newPerson The updated person with new details to replace the old person in the groups.
+ */
+ private void editPersonFromAllGroups(Person oldPerson, Person newPerson) {
+ // Iterate over a copy of the group list to avoid ConcurrentModificationException
+ for (Group group : new ArrayList<>(addressBook.getGroupList())) {
+ List members = new ArrayList<>(group.getMembers());
+ boolean updated = false;
+
+ // Check if the group contains the person with the old name
+ for (int i = 0; i < members.size(); i++) {
+ if (members.get(i).equals(oldPerson)) {
+ // Replace the old person with the new person
+ members.set(i, newPerson);
+ updated = true;
+ break;
+ }
+ }
+
+ if (updated) {
+ // Update the group with the modified member list
+ Group updatedGroup = new Group(group.getGroupName().toString(), members);
+ addressBook.setGroup(group, updatedGroup);
+ }
+ }
+
+ // Update the filtered group list if necessary
+ updateFilteredGroupList(PREDICATE_SHOW_ALL_GROUPS);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -145,4 +313,12 @@ public boolean equals(Object other) {
&& filteredPersons.equals(otherModelManager.filteredPersons);
}
+ @Override
+ public void updateFilteredGroupList(Predicate groupPredicate) {
+ requireNonNull(groupPredicate);
+
+ groups.setPredicate(groupPredicate);
+
+ }
+
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
index 6ddc2cd9a29..bdd54d8e5d0 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
@@ -1,6 +1,7 @@
package seedu.address.model;
import javafx.collections.ObservableList;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -13,5 +14,6 @@ public interface ReadOnlyAddressBook {
* This list will not contain any duplicate persons.
*/
ObservableList getPersonList();
+ ObservableList getGroupList();
}
diff --git a/src/main/java/seedu/address/model/group/Group.java b/src/main/java/seedu/address/model/group/Group.java
new file mode 100644
index 00000000000..65468644b4d
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/Group.java
@@ -0,0 +1,96 @@
+package seedu.address.model.group;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a group of persons.
+ */
+public class Group {
+ private final GroupName groupName;
+ private final List members;
+
+ /**
+ * Constructs a {@code Group} with the specified group name and members.
+ *
+ * @param groupName The name of the group.
+ * @param members The list of persons in the group.
+ * @throws NullPointerException if {@code groupName} or {@code members} is null.
+ */
+ public Group(String groupName, List members) {
+
+ requireNonNull(groupName);
+ requireNonNull(members);
+
+ this.groupName = new GroupName(groupName);
+ this.members = members;
+ }
+
+ /**
+ * Returns the name of the group.
+ *
+ * @return The group name.
+ */
+ public GroupName getGroupName() {
+ return groupName;
+ }
+
+ /**
+ * Returns the list of persons in the group.
+ *
+ * @return The list of group members.
+ */
+ public List getMembers() {
+ return members;
+ }
+
+ /**
+ * Returns a string representation of the group, including the group name and
+ * members.
+ *
+ * @return A string representation of the group.
+ */
+ @Override
+ public String toString() {
+ StringJoiner memberString = new StringJoiner(", ");
+ for (Person person : members) {
+ memberString.add(person.getName().fullName);
+ }
+ return String.format("[Group: %s, Members: %s]", groupName, memberString);
+ }
+
+ /**
+ * Returns true if both groups have the same name.
+ * This defines a weaker notion of equality between two groups.
+ */
+ public boolean isSameGroup(Group otherGroup) {
+ if (otherGroup == this) {
+ return true;
+ }
+ return otherGroup != null
+ && otherGroup.getGroupName().equals(getGroupName());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof Group)) {
+ return false;
+ }
+ Group otherGroup = (Group) other;
+ return groupName.equals(otherGroup.groupName)
+ && members.equals(otherGroup.members);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(groupName, members);
+ }
+}
diff --git a/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..4c4052b71d9
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/GroupContainsKeywordsPredicate.java
@@ -0,0 +1,48 @@
+package seedu.address.model.group;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+
+public class GroupContainsKeywordsPredicate implements Predicate {
+
+ private final List keywords;
+
+ public GroupContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+ @Override
+ public boolean test(Group group) {
+ String lowercaseGroupName = group.getGroupName().toString().toLowerCase();
+ return keywords.stream()
+ .anyMatch(keyword -> lowercaseGroupName.contains(keyword.toLowerCase()));
+ }
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof GroupContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ GroupContainsKeywordsPredicate otherGroupContainsKeywordsPredicate = (GroupContainsKeywordsPredicate) other;
+ return keywords.equals(otherGroupContainsKeywordsPredicate.keywords);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("keywords", keywords).toString();
+ }
+ public List getKeywords() {
+ return this.keywords;
+ }
+}
+
diff --git a/src/main/java/seedu/address/model/group/GroupList.java b/src/main/java/seedu/address/model/group/GroupList.java
new file mode 100644
index 00000000000..b4b06b02925
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/GroupList.java
@@ -0,0 +1,155 @@
+package seedu.address.model.group;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.group.exceptions.DuplicateGroupException;
+import seedu.address.model.group.exceptions.GroupNotFoundException;
+
+/**
+ * A list of groups that enforces uniqueness between its elements and does not
+ * allow nulls.
+ * A group is considered unique by comparing using
+ * {@code Group#isSameGroup(Group)}. As such, adding and updating of
+ * groups uses {@code Group#isSameGroup(Group)} for equality to ensure that the
+ * group being added or updated is
+ * unique in terms of identity within the `GroupList`. However, the removal of a
+ * group uses `Group#equals(Object)` to
+ * ensure that the group with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Group#isSameGroup(Group)
+ */
+public class GroupList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList = FXCollections
+ .unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent Group as the given argument.
+ */
+ public boolean contains(Group toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameGroup);
+ }
+
+ /**
+ * Adds a Group to the list.
+ * The Group must not already exist in the list.
+ */
+ public void add(Group toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateGroupException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the group {@code target} in the list with {@code editedGroup}.
+ * {@code target} must exist in the list.
+ * The group identity of {@code editedGroup} must not be the same as another
+ * existing group in the list.
+ *
+ * @param target The group to replace.
+ * @param editedGroup The group to replace with.
+ */
+ public void setGroup(Group target, Group editedGroup) {
+ requireAllNonNull(target, editedGroup);
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new GroupNotFoundException();
+ }
+ if (!target.isSameGroup(editedGroup) && contains(editedGroup)) {
+ throw new DuplicateGroupException();
+ }
+ internalList.set(index, editedGroup);
+ }
+
+ /**
+ * Removes the equivalent Group from the list.
+ * The Group must exist in the list.
+ */
+ public void remove(Group toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new GroupNotFoundException();
+ }
+ }
+
+ public void setGroups(GroupList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code Groups}.
+ * {@code Groups} must not contain duplicate Groups.
+ */
+ public void setGroups(List groups) {
+ requireAllNonNull(groups);
+ if (!groupsAreUnique(groups)) {
+ throw new DuplicateGroupException();
+ }
+
+ internalList.setAll(groups);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof GroupList)) {
+ return false;
+ }
+
+ GroupList otherUniqueGroupList = (GroupList) other;
+ return internalList.equals(otherUniqueGroupList.internalList);
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return internalList.toString();
+ }
+
+ /**
+ * Returns true if {@code Groups} contains only unique Groups.
+ */
+ private boolean groupsAreUnique(List groups) {
+ for (int i = 0; i < groups.size() - 1; i++) {
+ for (int j = i + 1; j < groups.size(); j++) {
+ if (groups.get(i).isSameGroup(groups.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/group/GroupName.java b/src/main/java/seedu/address/model/group/GroupName.java
new file mode 100644
index 00000000000..548ce0d9c34
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/GroupName.java
@@ -0,0 +1,68 @@
+package seedu.address.model.group;
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Person's name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
+ */
+public class GroupName {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "GroupNames should only contain alphanumeric characters and spaces, 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 = "[\\p{Alnum}][\\p{Alnum} ]*";
+
+ private final String groupName;
+
+ /**
+ * Constructs a {@code groupName}.
+ *
+ * @param groupName A valid group name.
+ */
+ public GroupName(String groupName) {
+ requireNonNull(groupName);
+ checkArgument(isValidName(groupName), MESSAGE_CONSTRAINTS);
+ this.groupName = groupName;
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidName(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+
+ @Override
+ public String toString() {
+ return groupName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof seedu.address.model.group.GroupName)) {
+ return false;
+ }
+
+ seedu.address.model.group.GroupName otherName = (seedu.address.model.group.GroupName) other;
+ return groupName.equalsIgnoreCase(otherName.groupName);
+ }
+
+ @Override
+ public int hashCode() {
+ return groupName.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java
new file mode 100644
index 00000000000..92a26c05bbb
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java
@@ -0,0 +1,12 @@
+package seedu.address.model.group.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate
+ * Groups (Groups are considered duplicates if they have the same
+ * identity).
+ */
+public class DuplicateGroupException extends RuntimeException {
+ public DuplicateGroupException() {
+ super("Operation would result in duplicate Groups");
+ }
+}
diff --git a/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java
new file mode 100644
index 00000000000..ea75a55b5f2
--- /dev/null
+++ b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.group.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified group.
+ */
+public class GroupNotFoundException extends RuntimeException { }
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..1e053834414 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -10,13 +10,14 @@
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 alphanumeric characters and spaces,"
+ + " it should not be blank and be maximum 50 characters long";
/*
* 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{Alnum}][\\p{Alnum} ]{1,50}";
public final String fullName;
@@ -56,7 +57,7 @@ public boolean equals(Object other) {
}
Name otherName = (Name) other;
- return fullName.equals(otherName.fullName);
+ return fullName.equalsIgnoreCase(otherName.fullName);
}
@Override
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..5b6cb094a56 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -1,5 +1,6 @@
package seedu.address.model.person;
+import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.util.Collections;
@@ -8,33 +9,39 @@
import java.util.Set;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.group.Group;
import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* Represents a Person in the address book.
- * Guarantees: details are present and not null, field values are validated, immutable.
+ * Guarantees: details are present and not null, field values are validated,
+ * immutable.
*/
public class Person {
// Identity fields
+
private final Name name;
private final Phone phone;
- private final Email email;
// Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
+ private final StudentClass studentClass;
+ private final Tags tags = new Tags();
+
+ private final Set groups = 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, StudentClass studentClass, Phone phone, Tags tags) {
+ requireAllNonNull(name, phone, studentClass);
this.name = name;
+ this.studentClass = studentClass;
this.phone = phone;
- this.email = email;
- this.address = address;
- this.tags.addAll(tags);
+ if (tags != null) {
+ this.tags.addAllTags(tags);
+ }
}
public Name getName() {
@@ -45,20 +52,46 @@ public Phone getPhone() {
return phone;
}
- public Email getEmail() {
- return email;
+ public StudentClass getStudentClass() {
+ return studentClass;
+ }
+
+ /**
+ * Returns an immutable tag set, which throws
+ * {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getTagSet() {
+ return Collections.unmodifiableSet(this.tags.getTags());
}
- public Address getAddress() {
- return address;
+ public Tags getTags() {
+ return this.tags;
}
/**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
+ * Returns a new Person object with a new set of tags
+ * with new tags added.
*/
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
+ public Person addTags(Tags newTags) {
+ this.tags.addAllTags(newTags);
+ return new Person(this.name, this.studentClass, this.phone, this.tags);
+ }
+
+ /**
+ * Returns a new Person object with a new set of tags
+ * with specified tags deleted.
+ */
+ public Person deleteTags(Tags tagsToBeDeleted) {
+ this.tags.removeAllTags(tagsToBeDeleted);
+ return new Person(this.name, this.studentClass, this.phone, this.tags);
+ }
+
+ /**
+ * Returns true if tags already exist in the existing tags.
+ */
+ public boolean tagExists(Tags tags) {
+ return tags.tagExists(this.tags);
}
/**
@@ -74,6 +107,25 @@ public boolean isSamePerson(Person otherPerson) {
&& otherPerson.getName().equals(getName());
}
+ /**
+ * Returns an immutable group set, which throws
+ * {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getGroups() {
+ return Collections.unmodifiableSet(groups);
+ }
+
+ /**
+ * Adds a group to the group field of the person.
+ *
+ * @param group
+ */
+ public void addGroups(Group group) {
+ requireNonNull(group);
+ groups.add(group);
+ }
+
/**
* Returns true if both persons have the same identity and data fields.
* This defines a stronger notion of equality between two persons.
@@ -83,35 +135,29 @@ public boolean equals(Object other) {
if (other == this) {
return true;
}
-
- // instanceof handles nulls
if (!(other instanceof Person)) {
return false;
}
-
Person otherPerson = (Person) other;
return name.equals(otherPerson.name)
+ && studentClass.equals(otherPerson.studentClass)
&& phone.equals(otherPerson.phone)
- && email.equals(otherPerson.email)
- && address.equals(otherPerson.address)
&& 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, studentClass, phone, tags);
}
@Override
public String toString() {
return new ToStringBuilder(this)
.add("name", name)
+ .add("studentClass", studentClass)
.add("phone", phone)
- .add("email", email)
- .add("address", address)
.add("tags", tags)
+ .add("groups", groups)
.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..e88e3ae4c74 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -11,8 +11,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 it should be at least 3 digits long and maximum 50 digits long";
+ public static final String VALIDATION_REGEX = "\\d{3,50}";
+
public final String value;
/**
diff --git a/src/main/java/seedu/address/model/person/StudentClass.java b/src/main/java/seedu/address/model/person/StudentClass.java
new file mode 100644
index 00000000000..2c055fc90f7
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/StudentClass.java
@@ -0,0 +1,62 @@
+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 #isValidClass(String)}
+ */
+public class StudentClass {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Student's class names should be alphanumeric and a maximum of 50 characters"; // alphanumeric
+ // and special characters
+ public static final String VALIDATION_REGEX = "\\p{Alnum}{1,50}";
+
+ public final String value;
+
+ /**
+ * Constructs an {@code studentClass}.
+ *
+ * @param studentClass A valid student class.
+ */
+ public StudentClass(String studentClass) {
+ requireNonNull(studentClass);
+ checkArgument(isValidClass(studentClass), MESSAGE_CONSTRAINTS);
+ value = studentClass;
+ }
+
+ /**
+ * Returns if a given string is a valid class.
+ */
+ public static boolean isValidClass(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 StudentClass)) {
+ return false;
+ }
+
+ StudentClass otherClass = (StudentClass) other;
+ return value.equals(otherClass.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
index fa764426ca7..72578fd7232 100644
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
@@ -3,4 +3,4 @@
/**
* Signals that the operation is unable to find the specified person.
*/
-public class PersonNotFoundException extends RuntimeException {}
+public class PersonNotFoundException extends RuntimeException { }
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index f1a0d4e233b..c4dc09de909 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -9,10 +9,10 @@
*/
public class Tag {
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
-
- public final String tagName;
+ public static final String MESSAGE_CHAR_CONSTRAINTS = "Tags should be alphanumeric";
+ public static final String MESSAGE_LENGTH_CONSTRAINTS = "Tags should be within 30 characters";
+ public static final String VALIDATION_REGEX = "^[a-zA-Z0-9\\s]+$";
+ private final String tagName;
/**
* Constructs a {@code Tag}.
@@ -21,10 +21,15 @@ public class Tag {
*/
public Tag(String tagName) {
requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
+ checkArgument(isValidTagName(tagName), MESSAGE_CHAR_CONSTRAINTS);
+ checkArgument(isWithinLengthLimit(tagName), MESSAGE_LENGTH_CONSTRAINTS);
this.tagName = tagName;
}
+ public String getTagName() {
+ return this.tagName;
+ }
+
/**
* Returns true if a given string is a valid tag name.
*/
@@ -32,6 +37,14 @@ public static boolean isValidTagName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Returns true if a given string is within 30 characters.
+ */
+ public static boolean isWithinLengthLimit(String test) {
+ int length = test.length();
+ return length <= 30;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -44,7 +57,7 @@ public boolean equals(Object other) {
}
Tag otherTag = (Tag) other;
- return tagName.equals(otherTag.tagName);
+ return tagName.equalsIgnoreCase(otherTag.tagName);
}
@Override
diff --git a/src/main/java/seedu/address/model/tag/Tags.java b/src/main/java/seedu/address/model/tag/Tags.java
new file mode 100644
index 00000000000..c2963adbef7
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/Tags.java
@@ -0,0 +1,86 @@
+package seedu.address.model.tag;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Represents a set of tags that belongs to a Person.
+ */
+public class Tags {
+ private final Set tags;
+ public Tags(Set tags) {
+ this.tags = tags;
+ }
+
+ public Tags() {
+ this.tags = new HashSet<>();
+ }
+
+ /**
+ * Returns true if any of otherTags already exist in the existing tag,
+ * regardless of upper or lower case.
+ */
+ public boolean tagExists(Tags otherTags) {
+
+ Set tagNamesLower = tags.stream()
+ .map(tag -> tag.getTagName().toLowerCase())
+ .collect(Collectors.toSet());
+
+ Set otherTagsLower = otherTags.tags.stream()
+ .map(tag -> tag.getTagName().toLowerCase())
+ .collect(Collectors.toSet());
+
+ return tagNamesLower.stream()
+ .anyMatch(otherTagsLower::contains);
+ }
+
+ public Set getTags() {
+ return this.tags;
+ }
+
+ /**
+ * Adds all tags to existing tags.
+ */
+ public void addAllTags(Tags tags) {
+ this.tags.addAll(tags.getTags());
+ }
+
+ /**
+ * Removes all specified tags previously in existing tags.
+ */
+ public void removeAllTags(Tags tags) {
+ this.tags.removeAll(tags.getTags());
+ }
+
+ /**
+ * Returns true if tags are empty.
+ */
+ public boolean isEmpty() {
+ return this.tags.isEmpty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Tags)) {
+ return false;
+ }
+
+ Tags otherTags = (Tags) other;
+
+ Set tagNamesLower = this.tags.stream()
+ .map(tag -> tag.getTagName().toLowerCase())
+ .collect(Collectors.toSet());
+
+ Set otherTagNamesLower = otherTags.tags.stream()
+ .map(tag -> tag.getTagName().toLowerCase())
+ .collect(Collectors.toSet());
+
+ return tagNamesLower.equals(otherTagNamesLower);
+ }
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..326fb950f78 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -6,12 +6,12 @@
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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
@@ -19,24 +19,18 @@
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 StudentClass("19S13"), new Phone("87438807"),
+ new Tags(getTagSet("friends"))),
+ new Person(new Name("Bernice Yu"), new StudentClass("6K"), new Phone("99272758"),
+ new Tags(getTagSet("colleagues", "friends"))),
+ new Person(new Name("Charlotte Oliveiro"), new StudentClass("4A"), new Phone("93210283"),
+ new Tags(getTagSet("neighbours"))),
+ new Person(new Name("David Li"), new StudentClass("14B"), new Phone("91031282"),
+ new Tags(getTagSet("family"))),
+ new Person(new Name("Irfan Ibrahim"), new StudentClass("19D"), new Phone("92492021"),
+ new Tags(getTagSet("classmates"))),
+ new Person(new Name("Roy Balakrishnan"), new StudentClass("5L"), new Phone("92624417"),
+ new Tags(getTagSet("colleagues")))
};
}
@@ -52,9 +46,9 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
* Returns a tag set containing the list of strings given.
*/
public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
+ Set tagSet = Arrays.stream(strings)
.map(Tag::new)
.collect(Collectors.toSet());
+ return tagSet;
}
-
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java
new file mode 100644
index 00000000000..052ca3c0485
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java
@@ -0,0 +1,63 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * Jackson-friendly version of {@link Group}.
+ */
+class JsonAdaptedGroup {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Group's %s field is missing!";
+
+ private final String groupName;
+ private final List members = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonAdaptedGroup} with the given group details.
+ */
+ @JsonCreator
+ public JsonAdaptedGroup(@JsonProperty("groupName") String groupName,
+ @JsonProperty("members") List members) {
+ this.groupName = groupName;
+ if (members != null) {
+ this.members.addAll(members);
+ }
+ }
+
+ /**
+ * Converts a given {@code Group} into this class for Jackson use.
+ */
+ public JsonAdaptedGroup(Group source) {
+ groupName = source.getGroupName().toString();
+ members.addAll(source.getMembers().stream()
+ .map(JsonAdaptedPerson::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted group object into the model's
+ * {@code Group} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in
+ * the adapted group.
+ */
+ public Group toModelType() throws IllegalValueException {
+ final List groupMembers = new ArrayList<>();
+ for (JsonAdaptedPerson person : members) {
+ groupMembers.add(person.toModelType());
+ }
+ if (groupName == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "GroupName"));
+ }
+ return new Group(groupName, groupMembers);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..e5deefb86d3 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,12 +10,12 @@
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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* Jackson-friendly version of {@link Person}.
@@ -25,22 +25,19 @@ class JsonAdaptedPerson {
public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
private final String name;
+ private final String studentClass;
private final String phone;
- private final String email;
- private final String address;
private final List tags = new ArrayList<>();
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
*/
@JsonCreator
- public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
+ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("studentClass") String studentClass,
+ @JsonProperty("phone") String phone, @JsonProperty("tags") List tags) {
this.name = name;
+ this.studentClass = studentClass;
this.phone = phone;
- this.email = email;
- this.address = address;
if (tags != null) {
this.tags.addAll(tags);
}
@@ -51,10 +48,9 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone
*/
public JsonAdaptedPerson(Person source) {
name = source.getName().fullName;
+ studentClass = source.getStudentClass().value;
phone = source.getPhone().value;
- email = source.getEmail().value;
- address = source.getAddress().value;
- tags.addAll(source.getTags().stream()
+ tags.addAll(source.getTagSet().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
}
@@ -78,6 +74,15 @@ public Person toModelType() throws IllegalValueException {
}
final Name modelName = new Name(name);
+ if (studentClass == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ StudentClass.class.getSimpleName()));
+ }
+ if (!StudentClass.isValidClass(studentClass)) {
+ throw new IllegalValueException(StudentClass.MESSAGE_CONSTRAINTS);
+ }
+ final StudentClass modelStudentClass = new StudentClass(studentClass);
+
if (phone == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
}
@@ -86,24 +91,9 @@ 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 (!Email.isValidEmail(email)) {
- throw new IllegalValueException(Email.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 Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ final Set modelTagSet = new HashSet<>(personTags);
+ final Tags modelTags = new Tags(modelTagSet);
+ return new Person(modelName, modelStudentClass, modelPhone, modelTags);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
index 0df22bdb754..a44598ec5c7 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
@@ -25,7 +25,7 @@ public JsonAdaptedTag(String tagName) {
* Converts a given {@code Tag} into this class for Jackson use.
*/
public JsonAdaptedTag(Tag source) {
- tagName = source.tagName;
+ tagName = source.getTagName();
}
@JsonValue
@@ -40,7 +40,10 @@ public String getTagName() {
*/
public Tag toModelType() throws IllegalValueException {
if (!Tag.isValidTagName(tagName)) {
- throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
+ throw new IllegalValueException(Tag.MESSAGE_CHAR_CONSTRAINTS);
+ }
+ if (!Tag.isWithinLengthLimit(tagName)) {
+ throw new IllegalValueException(Tag.MESSAGE_LENGTH_CONSTRAINTS);
}
return new Tag(tagName);
}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
index 41e06f264e1..6391599001a 100644
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
@@ -47,7 +47,7 @@ public Optional readAddressBook(Path filePath) throws DataL
Optional jsonAddressBook = JsonUtil.readJsonFile(
filePath, JsonSerializableAddressBook.class);
- if (!jsonAddressBook.isPresent()) {
+ if (jsonAddressBook.isEmpty()) {
return Optional.empty();
}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..fc77143c40d 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -11,6 +11,7 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
/**
@@ -20,24 +21,33 @@
class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_GROUP = "Groups list contains duplicate group(s).";
private final List persons = new ArrayList<>();
+ private final List groups = new ArrayList<>();
/**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
+ * Constructs a {@code JsonSerializableAddressBook} with the given persons and
+ * groups.
*/
@JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
+ public JsonSerializableAddressBook(@JsonProperty("persons") List persons,
+ @JsonProperty("groups") List groups) {
this.persons.addAll(persons);
+ if (groups != null) {
+ this.groups.addAll(groups);
+ }
}
/**
* Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
*
- * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}.
+ * @param source future changes to this will not affect the created
+ * {@code JsonSerializableAddressBook}.
*/
public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList()));
+ groups.addAll(source.getGroupList().stream().map(JsonAdaptedGroup::new).collect(Collectors.toList()));
}
/**
@@ -54,7 +64,13 @@ public AddressBook toModelType() throws IllegalValueException {
}
addressBook.addPerson(person);
}
+ for (JsonAdaptedGroup jsonAdaptedGroup : groups) {
+ Group group = jsonAdaptedGroup.toModelType();
+ if (addressBook.hasGroup(group)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_GROUP);
+ }
+ addressBook.addGroup(group);
+ }
return addressBook;
}
-
}
diff --git a/src/main/java/seedu/address/ui/GroupCard.java b/src/main/java/seedu/address/ui/GroupCard.java
new file mode 100644
index 00000000000..9fa4bd7dc3f
--- /dev/null
+++ b/src/main/java/seedu/address/ui/GroupCard.java
@@ -0,0 +1,50 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.group.Group;
+
+/**
+ * An UI component that displays information of a {@code Group}.
+ */
+public class GroupCard extends UiPart {
+
+ private static final String FXML = "GroupListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Group group;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private Label studentName;
+ @FXML
+ private FlowPane members;
+
+ /**
+ * Creates a {@code GroupCode} with the given {@code Group} and index to display.
+ */
+ public GroupCard(Group group, int displayedIndex) {
+ super(FXML);
+ this.group = group;
+ id.setText(displayedIndex + ". ");
+ name.setText(group.getGroupName().toString());
+ studentName.setText("Students:");
+ group.getMembers().stream()
+ .forEach(member -> members.getChildren().add(new Label(member.getName().fullName)));
+ }
+}
diff --git a/src/main/java/seedu/address/ui/GroupListPanel.java b/src/main/java/seedu/address/ui/GroupListPanel.java
new file mode 100644
index 00000000000..e68c90ad403
--- /dev/null
+++ b/src/main/java/seedu/address/ui/GroupListPanel.java
@@ -0,0 +1,53 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.group.Group;
+
+/**
+ * Panel containing the list of groups.
+ */
+public class GroupListPanel extends UiPart {
+ private static final String FXML = "GroupListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(GroupListPanel.class);
+
+ @FXML
+ private ListView groupListView;
+ @FXML
+ private Label groupListTitle;
+
+ /**
+ * Creates a {@code GroupListPanel} with the given {@code ObservableList}.
+ */
+ public GroupListPanel(ObservableList groupList) {
+ super(FXML);
+ this.groupListTitle.setText("Your Current Groups:");
+ groupListView.setItems(groupList);
+ groupListView.setCellFactory(listView -> new GroupListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Group} using a {@code GroupCard}.
+ */
+ class GroupListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Group group, boolean empty) {
+ super.updateItem(group, empty);
+
+ if (empty || group == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new GroupCard(group, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..883696a88e6 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,8 +15,26 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
- public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
+ public static final String USERGUIDE_URL = "https://ay2425s1-cs2103t-w08-4.github.io/tp/UserGuide.html#quick-start";
+ public static final String COMMANDS_DESCRIPTION = "Valid Commands:\n"
+ + "1. add\n"
+ + "2. clear\n"
+ + "3. delete\n"
+ + "4. deleteGroup\n"
+ + "5. edit\n"
+ + "6. exit\n"
+ + "7. export\n"
+ + "8. find\n"
+ + "9. findGroup\n"
+ + "10. group\n"
+ + "11. help\n"
+ + "12. import\n"
+ + "13. list\n"
+ + "14. listGroups\n"
+ + "15. tag\n"
+ + "16. untag\n";
+ public static final String GUIDE_REFERENCE_STRING = "Refer to the user guide for more details: " + USERGUIDE_URL;
+ public static final String HELP_MESSAGE = COMMANDS_DESCRIPTION + "\n" + GUIDE_REFERENCE_STRING;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
private static final String FXML = "HelpWindow.fxml";
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..b2acd562cff 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -32,6 +32,7 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private GroupListPanel groupListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
@@ -44,6 +45,9 @@ public class MainWindow extends UiPart {
@FXML
private StackPane personListPanelPlaceholder;
+ @FXML
+ private StackPane groupListPanelPlaceholder;
+
@FXML
private StackPane resultDisplayPlaceholder;
@@ -113,6 +117,9 @@ void fillInnerParts() {
personListPanel = new PersonListPanel(logic.getFilteredPersonList());
personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ groupListPanel = new GroupListPanel(logic.getFilteredGroupList());
+ groupListPanelPlaceholder.getChildren().add(groupListPanel.getRoot());
+
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
@@ -121,6 +128,7 @@ void fillInnerParts() {
CommandBox commandBox = new CommandBox(this::executeCommand);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+
}
/**
@@ -163,10 +171,6 @@ private void handleExit() {
primaryStage.hide();
}
- public PersonListPanel getPersonListPanel() {
- return personListPanel;
- }
-
/**
* Executes the command and returns the result.
*
@@ -186,6 +190,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleExit();
}
+
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("An error occurred while executing command: " + commandText);
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..ff6bc76fe3c 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -33,11 +33,9 @@ public class PersonCard extends UiPart {
@FXML
private Label id;
@FXML
- private Label phone;
- @FXML
- private Label address;
+ private Label studentClass;
@FXML
- private Label email;
+ private Label phone;
@FXML
private FlowPane tags;
@@ -49,11 +47,10 @@ public PersonCard(Person person, int displayedIndex) {
this.person = person;
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
+ studentClass.setText("Class: " + person.getStudentClass().value);
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.getTagSet().stream()
+ .sorted(Comparator.comparing(tag -> tag.getTagName()))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.getTagName())));
}
}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..4085fdc8010 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -4,6 +4,7 @@
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
+import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Region;
@@ -19,12 +20,15 @@ public class PersonListPanel extends UiPart {
@FXML
private ListView personListView;
+ @FXML
+ private Label personListTitle;
/**
* Creates a {@code PersonListPanel} with the given {@code ObservableList}.
*/
public PersonListPanel(ObservableList personList) {
super(FXML);
+ this.personListTitle.setText("List of Current Students:");
personListView.setItems(personList);
personListView.setCellFactory(listView -> new PersonListViewCell());
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..ad337990ccf 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/gb.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png
index 29810cf1fd9..4ee85230266 100644
Binary files a/src/main/resources/images/address_book_32.png and b/src/main/resources/images/address_book_32.png differ
diff --git a/src/main/resources/images/address_book_33.png b/src/main/resources/images/address_book_33.png
new file mode 100644
index 00000000000..29810cf1fd9
Binary files /dev/null and b/src/main/resources/images/address_book_33.png differ
diff --git a/src/main/resources/images/gb.png b/src/main/resources/images/gb.png
new file mode 100644
index 00000000000..89635f89948
Binary files /dev/null and b/src/main/resources/images/gb.png differ
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..6a854538156 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -1,6 +1,6 @@
.background {
- -fx-background-color: derive(#1d1d1d, 20%);
- background-color: #383838; /* Used in the default.html file */
+ -fx-background-color: derive(#4A628A, 20%);
+ background-color: #4A628A; /* Used in the default.html file */
}
.label {
@@ -40,9 +40,9 @@
}
.table-view {
- -fx-base: #1d1d1d;
- -fx-control-inner-background: #1d1d1d;
- -fx-background-color: #1d1d1d;
+ -fx-base: #4A628A;
+ -fx-control-inner-background: #4A628A;
+ -fx-background-color: #4A628A;
-fx-table-cell-border-color: transparent;
-fx-table-header-border-color: transparent;
-fx-padding: 5;
@@ -77,20 +77,34 @@
}
.split-pane:horizontal .split-pane-divider {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
-fx-border-color: transparent transparent transparent #4d4d4d;
}
.split-pane {
-fx-border-radius: 1;
-fx-border-width: 1;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
}
.list-view {
-fx-background-insets: 0;
-fx-padding: 0;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
+}
+
+#groupList .list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+#groupList .list-cell:filled:even {
+ -fx-background-color: #E8F6F8;
+}
+
+#groupList .list-cell:filled:odd {
+ -fx-background-color: #D8F1F2;
}
.list-cell {
@@ -100,15 +114,15 @@
}
.list-cell:filled:even {
- -fx-background-color: #3c3e3f;
+ -fx-background-color: #B9E5E8;
}
.list-cell:filled:odd {
- -fx-background-color: #515658;
+ -fx-background-color: #DFF2EB;
}
.list-cell:filled:selected {
- -fx-background-color: #424d5f;
+ -fx-background-color: #7AB2D3;
}
.list-cell:filled:selected #cardPane {
@@ -117,7 +131,7 @@
}
.list-cell .label {
- -fx-text-fill: white;
+ -fx-text-fill: black;
}
.cell_big_label {
@@ -133,24 +147,24 @@
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-background-color: derive(#4A628A, 20%);
+ -fx-border-color: derive(#4A628A, 10%);
-fx-border-top-width: 1px;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#4A628A, 30%);
}
.result-display {
- -fx-background-color: transparent;
+ -fx-background-color: derive(#4A628A, 20%);
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
- -fx-text-fill: white;
+ -fx-text-fill: black;
}
.result-display .label {
@@ -165,8 +179,8 @@
}
.status-bar-with-border {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-background-color: derive(#4A628A, 30%);
+ -fx-border-color: derive(#4A628A, 25%);
-fx-border-width: 1px;
}
@@ -175,17 +189,17 @@
}
.grid-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#4A628A, 30%);
+ -fx-border-color: derive(#4A628A, 30%);
-fx-border-width: 1px;
}
.grid-pane .stack-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#4A628A, 30%);
}
.context-menu {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: derive(#4A628A, 50%);
}
.context-menu .label {
@@ -193,7 +207,7 @@
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
}
.menu-bar .label {
@@ -217,7 +231,7 @@
-fx-border-color: #e2e2e2;
-fx-border-width: 2;
-fx-background-radius: 0;
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #4A628A;
-fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
-fx-font-size: 11pt;
-fx-text-fill: #d8d8d8;
@@ -230,7 +244,7 @@
.button:pressed, .button:default:hover:pressed {
-fx-background-color: white;
- -fx-text-fill: #1d1d1d;
+ -fx-text-fill: #4A628A;
}
.button:focused {
@@ -243,7 +257,7 @@
.button:disabled, .button:default:disabled {
-fx-opacity: 0.4;
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #4A628A;
-fx-text-fill: white;
}
@@ -257,11 +271,11 @@
}
.dialog-pane {
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #4A628A;
}
.dialog-pane > *.button-bar > *.container {
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #4A628A;
}
.dialog-pane > *.label.content {
@@ -271,7 +285,7 @@
}
.dialog-pane:header *.header-panel {
- -fx-background-color: derive(#1d1d1d, 25%);
+ -fx-background-color: derive(#4A628A, 25%);
}
.dialog-pane:header *.header-panel *.label {
@@ -282,11 +296,11 @@
}
.scroll-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
}
.scroll-bar .thumb {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: derive(#4A628A, 50%);
-fx-background-insets: 3;
}
@@ -328,13 +342,14 @@
-fx-text-fill: white;
}
-#filterField, #personListPanel, #personWebpage {
+#filterField, #personListPanel, #groupListPanel, #personWebpage {
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-color: derive(#4A628A, 80%);
-fx-background-radius: 0;
+ -fx-text-fill: black;
}
#tags {
@@ -350,3 +365,34 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#members {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#members .label {
+ -fx-text-fill: white;
+ -fx-background-color: #2E3A47;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 14;
+}
+
+#groupListTitle {
+ -fx-font-family: 'Segoe UI', sans-serif; /* Font family similar to UI design */
+ -fx-font-size: 18px; /* Font size matching the design */
+ -fx-text-fill: white; /* White text color */
+ -fx-padding: 5px 10px; /* Padding for better spacing */
+ -fx-font-weight: 200;
+}
+
+#personListTitle {
+ -fx-font-family: 'Segoe UI', sans-serif; /* Font family similar to UI design */
+ -fx-font-size: 18px; /* Font size matching the design */
+ -fx-text-fill: white; /* White text color */
+ -fx-padding: 5px 10px; /* Padding for better spacing */
+ -fx-font-weight: 200;
+}
+
diff --git a/src/main/resources/view/GroupListCard.fxml b/src/main/resources/view/GroupListCard.fxml
new file mode 100644
index 00000000000..651cfe397e3
--- /dev/null
+++ b/src/main/resources/view/GroupListCard.fxml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/GroupListPanel.fxml b/src/main/resources/view/GroupListPanel.fxml
new file mode 100644
index 00000000000..a4ebcb47353
--- /dev/null
+++ b/src/main/resources/view/GroupListPanel.fxml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css
index 17e8a8722cd..5c059071098 100644
--- a/src/main/resources/view/HelpWindow.css
+++ b/src/main/resources/view/HelpWindow.css
@@ -15,5 +15,5 @@
}
#helpMessageContainer {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#4A628A, 20%);
}
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..63d1bfed619 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -11,10 +11,11 @@
+
+ title="GoonBook App" minWidth="450" minHeight="600" onCloseRequest="#handleExit">
-
+
@@ -23,7 +24,7 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index 84e09833a87..9475e3741bb 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -7,30 +7,32 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml
index a1bb6bbace8..744ab883a11 100644
--- a/src/main/resources/view/PersonListPanel.fxml
+++ b/src/main/resources/view/PersonListPanel.fxml
@@ -3,6 +3,8 @@
+
+
diff --git a/src/test/data/ImportCommandTest/dups.csv b/src/test/data/ImportCommandTest/dups.csv
new file mode 100644
index 00000000000..00e2161dcac
--- /dev/null
+++ b/src/test/data/ImportCommandTest/dups.csv
@@ -0,0 +1,4 @@
+Name,Class,Phone number,Tags
+Shaun Lee,20D,88270342,friends
+Shaun Lee,20D,88270342,friends
+Shaun Lee,20D,88270342,friends
diff --git a/src/test/data/ImportCommandTest/empty.csv b/src/test/data/ImportCommandTest/empty.csv
new file mode 100644
index 00000000000..aacfa8f9f1c
--- /dev/null
+++ b/src/test/data/ImportCommandTest/empty.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+,10D,89019213,King
+Jun Yu,11D,19231014,Kitten
+Kingston,11D,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,343433,neighbours
+Meow,4C,90098008,CEO Invester
diff --git a/src/test/data/ImportCommandTest/hybrid_dups.csv b/src/test/data/ImportCommandTest/hybrid_dups.csv
new file mode 100644
index 00000000000..2a354d2d580
--- /dev/null
+++ b/src/test/data/ImportCommandTest/hybrid_dups.csv
@@ -0,0 +1,7 @@
+Name,Class,Phone number,Tags
+Jun Yu,11D,19231014,Kitten
+Kingston,,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,12315332,neighbours
+Shaun Lee,20D,88270342,friends
diff --git a/src/test/data/ImportCommandTest/invalid.csv b/src/test/data/ImportCommandTest/invalid.csv
new file mode 100644
index 00000000000..392abd802fd
--- /dev/null
+++ b/src/test/data/ImportCommandTest/invalid.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+,fas,d,King
+Jun Yu,11D,x,Kitten
+Kingston,,sdfadf,Bad
+Shaun Lee,20D,dfdadf,friends
+Martan Mickos,TechDisrupt,as0f2@2,colleagues father
+Josh Constine,7D,,neighbours
+,4C,782@,CEO Invester
diff --git a/src/test/data/ImportCommandTest/invalid_missing_name.csv b/src/test/data/ImportCommandTest/invalid_missing_name.csv
new file mode 100644
index 00000000000..aacfa8f9f1c
--- /dev/null
+++ b/src/test/data/ImportCommandTest/invalid_missing_name.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+,10D,89019213,King
+Jun Yu,11D,19231014,Kitten
+Kingston,11D,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,343433,neighbours
+Meow,4C,90098008,CEO Invester
diff --git a/src/test/data/ImportCommandTest/person_with_no_tags.csv b/src/test/data/ImportCommandTest/person_with_no_tags.csv
new file mode 100644
index 00000000000..3b430cc1262
--- /dev/null
+++ b/src/test/data/ImportCommandTest/person_with_no_tags.csv
@@ -0,0 +1,2 @@
+Name,Class,Phone number,Tags
+asdf,CS1101S,91234567,
diff --git a/src/test/data/ImportCommandTest/valid_missing_class.csv b/src/test/data/ImportCommandTest/valid_missing_class.csv
new file mode 100644
index 00000000000..fff817e4119
--- /dev/null
+++ b/src/test/data/ImportCommandTest/valid_missing_class.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+Meower,,89019213,King
+Jun Yu,11D,19231014,Kitten
+Kingston,10D,91028172,Bad
+Shaun Lee,,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,939393,neighbours
+Meow,4C,90098008,CEO Invester
diff --git a/src/test/data/ImportCommandTest/valid_missing_data.csv b/src/test/data/ImportCommandTest/valid_missing_data.csv
new file mode 100644
index 00000000000..43aba945462
--- /dev/null
+++ b/src/test/data/ImportCommandTest/valid_missing_data.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+JunBu,10D,89019213,King
+Jun Yu,11D,19231014,Kitten
+Kingston,,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,,neighbours
+Meow,4C,90098008,CEO Invester
diff --git a/src/test/data/ImportCommandTest/valid_missing_phone.csv b/src/test/data/ImportCommandTest/valid_missing_phone.csv
new file mode 100644
index 00000000000..4fb9c9eeab6
--- /dev/null
+++ b/src/test/data/ImportCommandTest/valid_missing_phone.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+Jin bu,10D,,King
+Jun Yu,11D,19231014,Kitten
+Kingston,10D,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,,neighbours
+Meow,4C,90098008,CEO Invester
diff --git a/src/test/data/ImportCommandTest/valid_missing_tags.csv b/src/test/data/ImportCommandTest/valid_missing_tags.csv
new file mode 100644
index 00000000000..7db16eb9585
--- /dev/null
+++ b/src/test/data/ImportCommandTest/valid_missing_tags.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+Jun Bug,10D,89019213,King
+Jun Yu,11D,19231014,Kitten
+Kingston,10D,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,
+Josh Constine,7D,882703,
+Meow,4C,90098008,CEO Invester
diff --git a/src/test/data/ImportCommandTest/valid_noDups_importFile.csv b/src/test/data/ImportCommandTest/valid_noDups_importFile.csv
new file mode 100644
index 00000000000..15a76f869cc
--- /dev/null
+++ b/src/test/data/ImportCommandTest/valid_noDups_importFile.csv
@@ -0,0 +1,8 @@
+Name,Class,Phone number,Tags
+Zuko,10D,89019213,King
+Jun Yu,11D,19231014,Kitten
+Kingston,1D,91028172,Bad
+Shaun Lee,20D,88270342,friends
+Martan Mickos,TechDisrupt,1231533,colleagues father
+Josh Constine,7D,90782341,neighbours
+Clayton Bryan,4C,90098008,CEO Invest
diff --git a/src/test/data/JsonExportTest/accessDenied.json b/src/test/data/JsonExportTest/accessDenied.json
new file mode 100644
index 00000000000..f9fac11fbb5
--- /dev/null
+++ b/src/test/data/JsonExportTest/accessDenied.json
@@ -0,0 +1,18 @@
+{
+ "persons" : [ {
+ "name" : "Alice Pauline",
+ "studentClass" : "4A",
+ "phone" : "94351253",
+ "tags" : [ "friends" ]
+ }, {
+ "name" : "Benson Meier",
+ "studentClass" : "6B",
+ "phone" : "98765432",
+ "tags" : [ "owesMoney", "friends" ]
+ }, {
+ "name" : "Carl Kurz",
+ "studentClass" : "4A",
+ "phone" : "95352563",
+ "tags" : [ ]
+ } ]
+}
diff --git a/src/test/data/JsonExportTest/typicalPersonsAddressBook.json b/src/test/data/JsonExportTest/typicalPersonsAddressBook.json
new file mode 100644
index 00000000000..f9fac11fbb5
--- /dev/null
+++ b/src/test/data/JsonExportTest/typicalPersonsAddressBook.json
@@ -0,0 +1,18 @@
+{
+ "persons" : [ {
+ "name" : "Alice Pauline",
+ "studentClass" : "4A",
+ "phone" : "94351253",
+ "tags" : [ "friends" ]
+ }, {
+ "name" : "Benson Meier",
+ "studentClass" : "6B",
+ "phone" : "98765432",
+ "tags" : [ "owesMoney", "friends" ]
+ }, {
+ "name" : "Carl Kurz",
+ "studentClass" : "4A",
+ "phone" : "95352563",
+ "tags" : [ ]
+ } ]
+}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..70a7d46d0f5 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -1,14 +1,13 @@
{
"persons": [ {
- "name": "Alice Pauline",
- "phone": "94351253",
- "email": "alice@example.com",
- "address": "123, Jurong West Ave 6, #08-111",
- "tags": [ "friends" ]
+ "name" : "Alice Pauline",
+ "studentClass" : "4A",
+ "phone" : "94351253",
+ "tags" : [ "friends" ]
}, {
- "name": "Alice Pauline",
- "phone": "94351253",
- "email": "pauline@example.com",
- "address": "4th street"
+ "name" : "Alice Pauline",
+ "studentClass" : "4A",
+ "phone" : "94351253",
+ "tags" : [ "friends" ]
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..60ccf0d8958 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -2,45 +2,38 @@
"_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()",
"persons" : [ {
"name" : "Alice Pauline",
+ "studentClass" : "4A",
"phone" : "94351253",
- "email" : "alice@example.com",
- "address" : "123, Jurong West Ave 6, #08-111",
"tags" : [ "friends" ]
}, {
"name" : "Benson Meier",
+ "studentClass" : "6B",
"phone" : "98765432",
- "email" : "johnd@example.com",
- "address" : "311, Clementi Ave 2, #02-25",
"tags" : [ "owesMoney", "friends" ]
}, {
"name" : "Carl Kurz",
+ "studentClass" : "4A",
"phone" : "95352563",
- "email" : "heinz@example.com",
- "address" : "wall street",
"tags" : [ ]
}, {
"name" : "Daniel Meier",
+ "studentClass" : "7H",
"phone" : "87652533",
- "email" : "cornelia@example.com",
- "address" : "10th street",
"tags" : [ "friends" ]
}, {
"name" : "Elle Meyer",
+ "studentClass" : "3U",
"phone" : "9482224",
- "email" : "werner@example.com",
- "address" : "michegan ave",
"tags" : [ ]
}, {
"name" : "Fiona Kunz",
+ "studentClass" : "3B",
"phone" : "9482427",
- "email" : "lydia@example.com",
- "address" : "little tokyo",
"tags" : [ ]
}, {
"name" : "George Best",
+ "studentClass" : "5H",
"phone" : "9482442",
- "email" : "anna@example.com",
- "address" : "4th street",
"tags" : [ ]
} ]
}
diff --git a/src/test/data/addressbook.json b/src/test/data/addressbook.json
new file mode 100644
index 00000000000..f9fac11fbb5
--- /dev/null
+++ b/src/test/data/addressbook.json
@@ -0,0 +1,18 @@
+{
+ "persons" : [ {
+ "name" : "Alice Pauline",
+ "studentClass" : "4A",
+ "phone" : "94351253",
+ "tags" : [ "friends" ]
+ }, {
+ "name" : "Benson Meier",
+ "studentClass" : "6B",
+ "phone" : "98765432",
+ "tags" : [ "owesMoney", "friends" ]
+ }, {
+ "name" : "Carl Kurz",
+ "studentClass" : "4A",
+ "phone" : "95352563",
+ "tags" : [ ]
+ } ]
+}
diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/address/commons/util/AppUtilTest.java
index 594de1e6365..b56326dba8d 100644
--- a/src/test/java/seedu/address/commons/util/AppUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/AppUtilTest.java
@@ -9,7 +9,7 @@ public class AppUtilTest {
@Test
public void getImage_exitingImage() {
- assertNotNull(AppUtil.getImage("/images/address_book_32.png"));
+ assertNotNull(AppUtil.getImage("/images/gb.png"));
}
@Test
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..99e2c84f0c1 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -1,10 +1,11 @@
package seedu.address.logic;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_INDEX_OVER_SIZE;
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.CLASS_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;
@@ -18,6 +19,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import seedu.address.commons.core.GuiSettings;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.ListCommand;
@@ -61,13 +63,13 @@ public void execute_invalidCommandFormat_throwsParseException() {
@Test
public void execute_commandExecutionError_throwsCommandException() {
String deleteCommand = "delete 9";
- assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandException(deleteCommand, MESSAGE_INVALID_INDEX_OVER_SIZE);
}
@Test
public void execute_validCommand_success() throws Exception {
String listCommand = ListCommand.COMMAND_WORD;
- assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model);
+ assertCommandSuccess(listCommand, ListCommand.MESSAGE_EMPTY_LIST, model);
}
@Test
@@ -87,6 +89,23 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException
assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0));
}
+ @Test
+ public void guiSettings_setAndRetrieveSuccessfully() {
+ // Set GUI settings in the model and verify they are retrieved correctly
+ GuiSettings expectedGuiSettings = new GuiSettings(800, 600, 0, 0);
+ logic.setGuiSettings(expectedGuiSettings);
+
+ // Verify that getGuiSettings returns the expected settings
+ assertEquals(expectedGuiSettings, logic.getGuiSettings());
+ }
+
+ @Test
+ public void getStorage_returnsNonNullStorage() {
+ // Ensure that the storage object is not null and is the one injected into LogicManager
+ assertNotNull(logic.getStorage());
+ assertSame(logic.getStorage(), logic.getStorage());
+ }
+
/**
* Executes the command and confirms that
* - no exceptions are thrown
@@ -165,8 +184,7 @@ 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 + CLASS_DESC_AMY + PHONE_DESC_AMY;
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
diff --git a/src/test/java/seedu/address/logic/MessagesTest.java b/src/test/java/seedu/address/logic/MessagesTest.java
new file mode 100644
index 00000000000..51375051fdd
--- /dev/null
+++ b/src/test/java/seedu/address/logic/MessagesTest.java
@@ -0,0 +1,16 @@
+package seedu.address.logic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class MessagesTest {
+
+ @Test
+ public void getMessageGroupsListedOverviewTest() {
+ String expectedSingular = "1 group listed!";
+ String expectedPlural = "3 groups listed!";
+ assertEquals(expectedSingular, Messages.getMessageGroupsListedOverview(1));
+ assertEquals(expectedPlural, Messages.getMessageGroupsListedOverview(3));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..1bd052081fa 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -22,7 +22,9 @@
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tags;
import seedu.address.testutil.PersonBuilder;
public class AddCommandTest {
@@ -89,7 +91,7 @@ public void toStringMethod() {
*/
private class ModelStub implements Model {
@Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ public String groupsString() {
throw new AssertionError("This method should not be called.");
}
@@ -98,6 +100,11 @@ public ReadOnlyUserPrefs getUserPrefs() {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public GuiSettings getGuiSettings() {
throw new AssertionError("This method should not be called.");
@@ -124,12 +131,12 @@ public void addPerson(Person person) {
}
@Override
- public void setAddressBook(ReadOnlyAddressBook newData) {
+ public ReadOnlyAddressBook getAddressBook() {
throw new AssertionError("This method should not be called.");
}
@Override
- public ReadOnlyAddressBook getAddressBook() {
+ public void setAddressBook(ReadOnlyAddressBook newData) {
throw new AssertionError("This method should not be called.");
}
@@ -148,6 +155,21 @@ public void setPerson(Person target, Person editedPerson) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public boolean tagExists(Person target, Tags tags) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addTag(Person target, Tags tags) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteTag(Person target, Tags tags) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public ObservableList getFilteredPersonList() {
throw new AssertionError("This method should not be called.");
@@ -157,6 +179,29 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void addGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteGroup(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public boolean hasGroupName(Group group) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredGroupList() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public void updateFilteredGroupList(Predicate groupPredicate) {
+ throw new AssertionError("This method should not be called");
+ }
}
/**
diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
index 7b8c7cd4546..0e859a4bd0e 100644
--- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java
+++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
@@ -56,7 +56,8 @@ public void hashcode() {
public void toStringMethod() {
CommandResult commandResult = new CommandResult("feedback");
String expected = CommandResult.class.getCanonicalName() + "{feedbackToUser="
- + commandResult.getFeedbackToUser() + ", showHelp=" + commandResult.isShowHelp()
+ + commandResult.getFeedbackToUser()
+ + ", showHelp=" + commandResult.isShowHelp()
+ ", exit=" + commandResult.isExit() + "}";
assertEquals(expected, commandResult.toString());
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..ebb81b22912 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -2,8 +2,7 @@
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_CLASS;
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;
@@ -25,33 +24,34 @@
* Contains helper methods for testing commands.
*/
public class CommandTestUtil {
-
+ public static final String VALID_GROUP_NAME = "Study Group";
+ public static final String OTHER_VALID_GROUP_NAME = "Meow";
+ public static final List VALID_STUDENTS = new ArrayList<>(List.of(new String[]{"Bob", "John", "Harry"}));
+ public static final List OTHER_VALID_STUDENTS = new ArrayList<>(
+ List.of(new String[]{"Bob", "John", "Dick"}));
public static final String VALID_NAME_AMY = "Amy Bee";
public static final String VALID_NAME_BOB = "Bob Choo";
+
+ public static final String VALID_CLASS_AMY = "4A";
+ public static final String VALID_CLASS_BOB = "19S13";
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_TAG_HUSBAND = "husband";
public static final String VALID_TAG_FRIEND = "friend";
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 CLASS_DESC_AMY = " " + PREFIX_CLASS + VALID_CLASS_AMY;
+ public static final String CLASS_DESC_BOB = " " + PREFIX_CLASS + VALID_CLASS_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 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_CLASS_DESC = " " + PREFIX_CLASS + "#19R"; // '#' not allowed in class
public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
@@ -62,10 +62,12 @@ public class CommandTestUtil {
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
- .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withStudentClass(VALID_CLASS_AMY)
+ .withPhone(VALID_PHONE_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)
+ .withStudentClass(VALID_CLASS_BOB)
+ .withPhone(VALID_PHONE_BOB)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
}
@@ -75,7 +77,7 @@ public class CommandTestUtil {
* - the {@code actualModel} matches {@code expectedModel}
*/
public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult,
- Model expectedModel) {
+ Model expectedModel) {
try {
CommandResult result = command.execute(actualModel);
assertEquals(expectedCommandResult, result);
@@ -90,7 +92,7 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm
* that takes a string {@code expectedMessage}.
*/
public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage,
- Model expectedModel) {
+ Model expectedModel) {
CommandResult expectedCommandResult = new CommandResult(expectedMessage);
assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel);
}
@@ -111,6 +113,7 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri
assertEquals(expectedAddressBook, actualModel.getAddressBook());
assertEquals(expectedFilteredList, actualModel.getFilteredPersonList());
}
+
/**
* Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the
* {@code model}'s address book.
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..ac258056f26 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -46,7 +46,7 @@ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
- assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_INDEX_OVER_SIZE);
}
@Test
@@ -76,7 +76,7 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
- assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_INDEX_OVER_SIZE);
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/DeleteGroupCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteGroupCommandTest.java
new file mode 100644
index 00000000000..d53e6554fd4
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteGroupCommandTest.java
@@ -0,0 +1,86 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalGroups.GOONERS;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupName;
+
+
+public class DeleteGroupCommandTest {
+
+ private Model model;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager();
+ }
+
+ @Test
+ public void execute_validGroupName_success() {
+ Group groupToDelete = GOONERS;
+ GroupName groupName = groupToDelete.getGroupName();
+ model.addGroup(groupToDelete); // Add the group to the model
+
+ DeleteGroupCommand deleteGroupCommand = new DeleteGroupCommand(groupName);
+
+ String expectedMessage = String.format(DeleteGroupCommand.MESSAGE_DELETE_GROUP_SUCCESS,
+ groupToDelete.getGroupName());
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ expectedModel.deleteGroup(groupToDelete);
+
+ assertCommandSuccess(deleteGroupCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_groupNotFound_throwsCommandException() {
+ GroupName nonExistentGroupName = new GroupName("Non Existent Group");
+ DeleteGroupCommand deleteGroupCommand = new DeleteGroupCommand(nonExistentGroupName);
+
+ assertCommandFailure(deleteGroupCommand, model,
+ String.format(DeleteGroupCommand.MESSAGE_GROUP_NOT_FOUND, nonExistentGroupName));
+ }
+
+ @Test
+ public void equals() {
+ GroupName groupName1 = new GroupName("Group 1");
+ GroupName groupName2 = new GroupName("Group 2");
+ DeleteGroupCommand deleteFirstCommand = new DeleteGroupCommand(groupName1);
+ DeleteGroupCommand deleteSecondCommand = new DeleteGroupCommand(groupName2);
+
+ // same object -> returns true
+ assertTrue(deleteFirstCommand.equals(deleteFirstCommand));
+
+ // same values -> returns true
+ DeleteGroupCommand deleteFirstCommandCopy = new DeleteGroupCommand(groupName1);
+ assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(deleteFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(deleteFirstCommand.equals(null));
+
+ // different group -> returns false
+ assertFalse(deleteFirstCommand.equals(deleteSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ GroupName groupName = new GroupName("Test Group");
+ DeleteGroupCommand deleteGroupCommand = new DeleteGroupCommand(groupName);
+ String expected = "DeleteGroupCommand[groupName=Test Group]";
+ assertEquals(expected, deleteGroupCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..06c722d51a8 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -33,7 +33,7 @@
*/
public class EditCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
@Test
public void execute_allFieldsSpecifiedUnfilteredList_success() {
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..8f0288bfcb8 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -5,8 +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_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_CLASS_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;
@@ -40,16 +39,12 @@ public void equals() {
EditPersonDescriptor editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
- // different phone -> returns false
- 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 class -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withStudentClass(VALID_CLASS_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
- // different address -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build();
+ // different phone -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
// different tags -> returns false
@@ -61,10 +56,9 @@ public void equals() {
public void toStringMethod() {
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
String expected = EditPersonDescriptor.class.getCanonicalName() + "{name="
+ + editPersonDescriptor.getStudentClass().orElse(null) + ", studentClass="
+ editPersonDescriptor.getName().orElse(null) + ", phone="
- + editPersonDescriptor.getPhone().orElse(null) + ", email="
- + editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
+ + editPersonDescriptor.getPhone().orElse(null) + ", tags="
+ editPersonDescriptor.getTags().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
diff --git a/src/test/java/seedu/address/logic/commands/ExportCommandTest.java b/src/test/java/seedu/address/logic/commands/ExportCommandTest.java
new file mode 100644
index 00000000000..80abe046920
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ExportCommandTest.java
@@ -0,0 +1,92 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.TypicalPaths.EXPORT_FILE_PATH;
+import static seedu.address.testutil.TypicalPaths.TYPICAL_PERSONS_ADDRESS_BOOK;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+
+/**
+ * Test class for export command
+ */
+public class ExportCommandTest {
+
+ private static final Path projectRootPath = Paths.get(System.getProperty("user.dir"));
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void execute_exportCommand_successfulExport() throws CommandException {
+ try {
+ ExportCommand exportCommand = new ExportCommand();
+
+ // Execute the export command
+ CommandResult commandResult = exportCommand.execute(model, TYPICAL_PERSONS_ADDRESS_BOOK , EXPORT_FILE_PATH);
+ // Verify the export is successful
+ assertEquals(String.format(ExportCommand.MESSAGE_SUCCESS),
+ commandResult.getFeedbackToUser());
+
+ // Verify the exported file exists
+ Path exportedFilePath = Paths.get(EXPORT_FILE_PATH.toString());
+ assertTrue(Files.exists(exportedFilePath));
+
+ Files.deleteIfExists(exportedFilePath);
+ } catch (IOException e) {
+ // Handle any IO exception
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void csvFormatTest() throws CommandException {
+ try {
+ Path importPath = projectRootPath.resolve("src").resolve("test").resolve("data")
+ .resolve("JsonExportTest").resolve("typicalPersonsAddressBook.json");
+ ExportCommand exportCommand = new ExportCommand();
+ CommandResult commandResult = exportCommand.execute(model, importPath , EXPORT_FILE_PATH);
+ assertEquals(String.format(ExportCommand.MESSAGE_SUCCESS),
+ commandResult.getFeedbackToUser());
+ List lines = Files.readAllLines(EXPORT_FILE_PATH);
+ final String[] header = {"Name", "Class", "Phone number", "Tags"};
+ final String[] alice = {"Alice Pauline", "4A", "94351253", "friends"};
+ final String[] benson = {"Benson Meier", "6B", "98765432", "owesMoney friends"};
+ final String[] carl = {"Carl Kurz", "4A", "95352563", ""};
+ final String[][] testStuds = {header, alice, benson, carl};
+ for (int i = 0; i < lines.size() - 1; i++) {
+ String[] columns = lines.get(i).split(",");
+ for (int j = 0; j < lines.size(); j++) {
+ System.out.println(testStuds[i][j]);
+ System.out.println(columns[j]);
+ assertEquals(testStuds[i][j], columns[j]);
+ }
+ }
+ // Clean up the exported file
+ Files.deleteIfExists(EXPORT_FILE_PATH);
+ } catch (IOException e) {
+ // Handle any IO exception
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ void executeBasic() throws CommandException {
+ ExportCommand exportCommand = new ExportCommand();
+ CommandResult commandResult = exportCommand.execute(model);
+ // Verify the export is successful
+ assertEquals(String.format(ExportCommand.MESSAGE_SUCCESS),
+ commandResult.getFeedbackToUser());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..468fe54a6fa 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.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.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.ELLE;
@@ -15,9 +14,11 @@
import org.junit.jupiter.api.Test;
+import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
/**
@@ -29,35 +30,40 @@ public class FindCommandTest {
@Test
public void equals() {
- NameContainsKeywordsPredicate firstPredicate =
+ NameContainsKeywordsPredicate firstNamePredicate =
new NameContainsKeywordsPredicate(Collections.singletonList("first"));
- NameContainsKeywordsPredicate secondPredicate =
+ NameContainsKeywordsPredicate secondNamePredicate =
new NameContainsKeywordsPredicate(Collections.singletonList("second"));
- FindCommand findFirstCommand = new FindCommand(firstPredicate);
- FindCommand findSecondCommand = new FindCommand(secondPredicate);
+ GroupContainsKeywordsPredicate firstGroupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("firstGroup"));
+ GroupContainsKeywordsPredicate secondGroupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("secondGroup"));
+
+ FindCommand findFirstNameCommand = new FindCommand(firstNamePredicate);
+ FindCommand findSecondNameCommand = new FindCommand(secondNamePredicate);
// same object -> returns true
- assertTrue(findFirstCommand.equals(findFirstCommand));
+ assertTrue(findFirstNameCommand.equals(findFirstNameCommand));
// same values -> returns true
- FindCommand findFirstCommandCopy = new FindCommand(firstPredicate);
- assertTrue(findFirstCommand.equals(findFirstCommandCopy));
+ FindCommand findFirstNameCommandCopy = new FindCommand(firstNamePredicate);
+ assertTrue(findFirstNameCommand.equals(findFirstNameCommandCopy));
// different types -> returns false
- assertFalse(findFirstCommand.equals(1));
+ assertFalse(findFirstNameCommand.equals(1));
// null -> returns false
- assertFalse(findFirstCommand.equals(null));
+ assertFalse(findFirstNameCommand.equals(null));
// different person -> returns false
- assertFalse(findFirstCommand.equals(findSecondCommand));
+ assertFalse(findFirstNameCommand.equals(findSecondNameCommand));
}
@Test
public void execute_zeroKeywords_noPersonFound() {
- String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
- NameContainsKeywordsPredicate predicate = preparePredicate(" ");
+ String expectedMessage = String.format(Messages.getMessagePersonsListedOverview(0));
+ NameContainsKeywordsPredicate predicate = prepareNamePredicate(" ");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
@@ -66,14 +72,15 @@ 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");
+ String expectedMessage = String.format(Messages.getMessagePersonsListedOverview(3));
+ NameContainsKeywordsPredicate predicate = prepareNamePredicate("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 toStringMethod() {
NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
@@ -85,7 +92,8 @@ public void toStringMethod() {
/**
* Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}.
*/
- private NameContainsKeywordsPredicate preparePredicate(String userInput) {
+ private NameContainsKeywordsPredicate prepareNamePredicate(String userInput) {
return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
}
+
}
diff --git a/src/test/java/seedu/address/logic/commands/FindGroupCommandTest.java b/src/test/java/seedu/address/logic/commands/FindGroupCommandTest.java
new file mode 100644
index 00000000000..472a59b4a81
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FindGroupCommandTest.java
@@ -0,0 +1,155 @@
+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 static seedu.address.testutil.TypicalGroups.GOONERS;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code FindCommand}.
+ */
+public class FindGroupCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void equals() {
+
+ GroupContainsKeywordsPredicate firstGroupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("firstGroup"));
+ GroupContainsKeywordsPredicate secondGroupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("secondGroup"));
+
+ FindGroupCommand findFirstGroupCommand = new FindGroupCommand(firstGroupPredicate);
+ FindGroupCommand findSecondGroupCommand = new FindGroupCommand(secondGroupPredicate);
+ FindGroupCommand findFirstGroupCommandCopy = new FindGroupCommand(firstGroupPredicate);
+
+ assertTrue(findFirstGroupCommand.equals(findFirstGroupCommand));
+
+ assertTrue(findFirstGroupCommand.equals(findFirstGroupCommandCopy));
+
+ assertFalse(findFirstGroupCommand.equals(findSecondGroupCommand));
+
+ assertFalse(findFirstGroupCommand.equals("notACommand"));
+
+ assertFalse(findFirstGroupCommand.equals(null));
+ }
+
+ @Test
+ public void execute_zeroKeywords_noGroupFound() {
+ String expectedMessage = String.format(Messages.getMessageGroupsListedOverview(0));
+ GroupContainsKeywordsPredicate predicate = prepareGroupPredicate(" ");
+ FindGroupCommand command = new FindGroupCommand(predicate);
+ expectedModel.updateFilteredGroupList(predicate);
+ CommandResult expectedCommandResult = new CommandResult(expectedMessage, false, false);
+ assertCommandSuccess(command, model, expectedCommandResult, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredGroupList());
+ }
+ @Test
+ public void execute_findGroup() {
+ model.addGroup(GOONERS);
+ expectedModel.addGroup(GOONERS);
+
+ GroupContainsKeywordsPredicate groupPredicate = prepareGroupPredicate("gooners");
+
+ FindGroupCommand command = new FindGroupCommand(groupPredicate);
+
+ model.updateFilteredGroupList(groupPredicate);
+
+ expectedModel.updateFilteredGroupList(groupPredicate);
+
+ int numOfGroup = expectedModel.getFilteredGroupList().size();
+ // Calculate the expected message based on the number of persons in the filtered list
+ String expectedMessage = String.format(
+ Messages.getMessageGroupsListedOverview(numOfGroup));
+
+ CommandResult expectedCommandResult = new CommandResult(expectedMessage, false, false);
+ assertCommandSuccess(command, model, expectedCommandResult, expectedModel);
+
+ }
+ @Test
+ public void equals_nonNullGroupPredicate_returnsCorrectComparison() {
+ GroupContainsKeywordsPredicate firstGroupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("keyword"));
+ GroupContainsKeywordsPredicate secondGroupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("differentKeyword"));
+
+ FindGroupCommand commandWithFirstPredicate = new FindGroupCommand(firstGroupPredicate);
+ FindGroupCommand commandWithSecondPredicate = new FindGroupCommand(secondGroupPredicate);
+
+ assertTrue(commandWithFirstPredicate.equals(new FindGroupCommand(firstGroupPredicate)));
+
+ assertFalse(commandWithFirstPredicate.equals(commandWithSecondPredicate));
+ }
+
+ @Test
+ public void execute_nonNullGroupPredicate_updatesFilteredGroupList() {
+ GroupContainsKeywordsPredicate predicate = prepareGroupPredicate("gooners");
+ FindGroupCommand command = new FindGroupCommand(predicate);
+
+ model.addGroup(GOONERS);
+ expectedModel.addGroup(GOONERS);
+
+ int numOfGroup = expectedModel.getFilteredGroupList().size();
+ String expectedMessage = String.format(
+ Messages.getMessageGroupsListedOverview(numOfGroup));
+ CommandResult expectedCommandResult = new CommandResult(expectedMessage, false, false);
+
+ assertCommandSuccess(command, model, expectedCommandResult, expectedModel);
+ assertEquals(expectedModel.getFilteredGroupList(), model.getFilteredGroupList());
+ }
+
+ @Test
+ public void toStringMethod() {
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(Arrays.asList("keyword"));
+ FindGroupCommand findGroupCommand = new FindGroupCommand(predicate);
+ String expected = FindGroupCommand.class.getCanonicalName() + "{groupPredicate=" + predicate + "}";
+ assertEquals(expected, findGroupCommand.toString());
+ }
+
+ /**
+ * Parses {@code userInput} into a {@code GroupContainsKeywordsPredicate}.
+ */
+ private GroupContainsKeywordsPredicate prepareGroupPredicate(String userInput) {
+ return new GroupContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ }
+
+ @Test
+ public void execute_emptyPredicate_noGroupFound() {
+ GroupContainsKeywordsPredicate predicate = prepareGroupPredicate("");
+ FindGroupCommand command = new FindGroupCommand(predicate);
+ expectedModel.updateFilteredGroupList(predicate);
+ String expectedMessage = String.format(Messages.getMessageGroupsListedOverview(0));
+ CommandResult expectedCommandResult = new CommandResult(expectedMessage, false, false);
+ assertCommandSuccess(command, model, expectedCommandResult, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredGroupList());
+ }
+
+ @Test
+ public void execute_validPredicate_success() {
+ model.addGroup(GOONERS);
+ expectedModel.addGroup(GOONERS);
+ GroupContainsKeywordsPredicate predicate = prepareGroupPredicate("gooners");
+ FindGroupCommand command = new FindGroupCommand(predicate);
+ expectedModel.updateFilteredGroupList(predicate);
+ int numOfGroup = expectedModel.getFilteredGroupList().size();
+ String expectedMessage = String.format(
+ Messages.getMessageGroupsListedOverview(numOfGroup));
+ CommandResult expectedCommandResult = new CommandResult(expectedMessage, false, false);
+ assertCommandSuccess(command, model, expectedCommandResult, expectedModel);
+ assertEquals(expectedModel.getFilteredGroupList(), model.getFilteredGroupList());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/GroupCommandTest.java b/src/test/java/seedu/address/logic/commands/GroupCommandTest.java
new file mode 100644
index 00000000000..0ba11b27b5a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/GroupCommandTest.java
@@ -0,0 +1,180 @@
+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.OTHER_VALID_GROUP_NAME;
+import static seedu.address.logic.commands.CommandTestUtil.OTHER_VALID_STUDENTS;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_GROUP_NAME;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_STUDENTS;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.Assert;
+
+public class GroupCommandTest {
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ }
+
+ @Test
+ public void execute_validGroup_success() {
+ List studentsNames = List
+ .of(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()).getName().fullName);
+ GroupCommand groupCommand = new GroupCommand("StudyGroup1", studentsNames);
+
+ String expectedMessage = String.format(GroupCommand.MESSAGE_SUCCESS, "StudyGroup1", studentsNames.size());
+
+ List students = List.of(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()));
+ expectedModel.addGroup(new Group("StudyGroup1", students));
+
+ assertCommandSuccess(groupCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_duplicateGroup_failure() {
+ List studentsNames = List
+ .of(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()).getName().fullName);
+ GroupCommand groupCommand = new GroupCommand("StudyGroup1", studentsNames);
+ List students = List.of(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()));
+ model.addGroup(new Group("StudyGroup1", students));
+
+ String expectedMessage = GroupCommand.MESSAGE_DUPLICATE_GROUP;
+
+ assertCommandFailure(groupCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_noMatchingStudents_failure() {
+ List students = List.of(); // Empty list of students
+ GroupCommand groupCommand = new GroupCommand("StudyGroup1", students);
+
+ String expectedMessage = GroupCommand.MESSAGE_NO_STUDENTS_FOUND;
+
+ assertCommandFailure(groupCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_duplicateStudentNamesInInput_throwsCommandException() {
+ // Prepare duplicate student names
+ String duplicateStudentName = model.getFilteredPersonList().get(0).getName().fullName;
+ List studentNames = List.of(duplicateStudentName, duplicateStudentName);
+ String groupName = "StudyGroup2";
+
+ GroupCommand groupCommand = new GroupCommand(groupName, studentNames);
+
+ String expectedMessage = String.format(GroupCommand.DUPLICATE_STUDENT_FOUND, duplicateStudentName);
+
+ assertCommandFailure(groupCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_emptyStudentName_throwsCommandException() {
+ // Prepare student names with an empty string
+ List studentNames = List.of("");
+ String groupName = "StudyGroup4";
+
+ GroupCommand groupCommand = new GroupCommand(groupName, studentNames);
+
+ assertCommandFailure(groupCommand, model, GroupCommand.EMPTY_STUDENT);
+ }
+
+ @Test
+ public void execute_noMatchingStudentsFound_throwsCommandException() {
+ // Prepare student names that do not match any existing person
+ List studentNames = List.of("NonExistentStudent");
+ String groupName = "StudyGroup5";
+
+ GroupCommand groupCommand = new GroupCommand(groupName, studentNames);
+
+ assertCommandFailure(groupCommand, model, String.format(GroupCommand.STUDENTS_NOT_FOUND,
+ "NonExistentStudent"));
+ }
+
+ @Test
+ public void execute_partialMatchingStudentsFound_throwsCommandException() {
+ // Prepare a mix of valid and invalid student names
+ String validStudentName = model.getFilteredPersonList().get(0).getName().fullName;
+ String invalidStudentName = "NonExistentStudent";
+ List studentNames = List.of(validStudentName, invalidStudentName);
+ String groupName = "StudyGroup6";
+
+ GroupCommand groupCommand = new GroupCommand(groupName, studentNames);
+
+ String expectedMessage = String.format(GroupCommand.STUDENTS_NOT_FOUND, invalidStudentName);
+
+ assertCommandFailure(groupCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void constructor_nullGroupName_throwsNullPointerException() {
+ List studentNames = new ArrayList<>();
+ Assert.assertThrows(NullPointerException.class, () -> new GroupCommand(null, studentNames));
+ }
+
+ @Test
+ public void constructor_nullStudentsList_throwsNullPointerException() {
+ Assert.assertThrows(NullPointerException.class, () -> new GroupCommand("GroupName", null));
+ }
+
+ @Test
+ public void execute_emptyStudentsList_throwsCommandException() {
+ List studentNames = new ArrayList<>();
+ String groupName = "StudyGroup7";
+
+ GroupCommand groupCommand = new GroupCommand(groupName, studentNames);
+
+ assertCommandFailure(groupCommand, model, GroupCommand.MESSAGE_NO_STUDENTS_FOUND);
+ }
+
+ @Test
+ public void execute_emptyGroupName_throwsCommandException() {
+ List studentNames = new ArrayList<>();
+ String groupName = "";
+
+ GroupCommand groupCommand = new GroupCommand(groupName, studentNames);
+
+ assertCommandFailure(groupCommand, model, GroupCommand.EMPTY_GROUP_NAME);
+ }
+
+ @Test
+ public void equals() {
+ final GroupCommand standardCommand = new GroupCommand(VALID_GROUP_NAME, VALID_STUDENTS);
+
+ // same values -> returns true
+ GroupCommand commandWithSameValues = new GroupCommand(VALID_GROUP_NAME, VALID_STUDENTS);
+ assertTrue(standardCommand.equals(commandWithSameValues));
+
+ // same object -> returns true
+ assertTrue(standardCommand.equals(standardCommand));
+
+ // null -> returns false
+ assertFalse(standardCommand.equals(null));
+
+ // different types -> returns false
+ assertFalse(standardCommand.equals(new ClearCommand()));
+
+ // different groupname -> returns false
+ assertFalse(standardCommand.equals(new GroupCommand(OTHER_VALID_GROUP_NAME, VALID_STUDENTS)));
+
+ // different students -> returns false
+ assertFalse(standardCommand.equals(new GroupCommand(VALID_GROUP_NAME, OTHER_VALID_STUDENTS)));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ImportCommandTest.java b/src/test/java/seedu/address/logic/commands/ImportCommandTest.java
new file mode 100644
index 00000000000..15f05d53677
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ImportCommandTest.java
@@ -0,0 +1,146 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalPaths.DUPLICATE_IMPORT_FILE;
+import static seedu.address.testutil.TypicalPaths.EMPTY_IMPORT_FILE;
+import static seedu.address.testutil.TypicalPaths.INVALID_IMPORT_FILE;
+import static seedu.address.testutil.TypicalPaths.INVALID_MISSING_NAME_FILE;
+import static seedu.address.testutil.TypicalPaths.VALID_MISSING_CLASS_FILE;
+import static seedu.address.testutil.TypicalPaths.VALID_MISSING_DATA_IMPORT_FILE;
+import static seedu.address.testutil.TypicalPaths.VALID_MISSING_PHONE_FILE;
+import static seedu.address.testutil.TypicalPaths.VALID_MISSING_TAGS_FILE;
+import static seedu.address.testutil.TypicalPaths.VALID_NO_DUPS_IMPORT_FILE;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.testutil.TypicalPaths;
+
+public class ImportCommandTest {
+ private Model model;
+
+ @Test
+ public void executeImportSuccessfulNoDefects() throws Exception {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ CommandResult commandResult = new ImportCommand(VALID_NO_DUPS_IMPORT_FILE).execute(model);
+ assertEquals(String.format(ImportCommand.MESSAGE_SUCCESS, 7), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void executeImportSuccessfulMissingData() throws Exception {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ CommandResult commandResult = new ImportCommand(VALID_MISSING_DATA_IMPORT_FILE).execute(model);
+
+ assertEquals(String.format(ImportCommand.MESSAGE_SUCCESS, 7), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void executeImportInvalidMissingName() throws Exception {
+ ImportCommand importCommand = new ImportCommand(INVALID_MISSING_NAME_FILE);
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ assertThrows(CommandException.class,
+ ImportCommand.MESSAGE_FILE_CORRUPTED, () -> importCommand.execute(model));
+ }
+
+ @Test
+ public void executeImportSuccessfulMissingClass() throws Exception {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ CommandResult commandResult = new ImportCommand(VALID_MISSING_CLASS_FILE).execute(model);
+ assertEquals(String.format(ImportCommand.MESSAGE_SUCCESS, 7), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void executeImportSuccessfulMissingPhone() throws Exception {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ CommandResult commandResult = new ImportCommand(VALID_MISSING_PHONE_FILE).execute(model);
+ assertEquals(String.format(ImportCommand.MESSAGE_SUCCESS, 7), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void executeImportSuccessfulMissingTags() throws Exception {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ CommandResult commandResult = new ImportCommand(VALID_MISSING_TAGS_FILE).execute(model);
+ assertEquals(String.format(ImportCommand.MESSAGE_SUCCESS, 7), commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void executeImportInvalidEmptyCsv() throws Exception {
+ ImportCommand importCommand = new ImportCommand(EMPTY_IMPORT_FILE);
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ assertThrows(CommandException.class,
+ ImportCommand.MESSAGE_FILE_CORRUPTED, () -> importCommand.execute(model));
+ }
+
+ @Test
+ public void executeFileNotFoundFailure() {
+ ImportCommand importCommand = new ImportCommand(TypicalPaths.getTypicalPath().resolve("adf.csv"));
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ assertThrows(CommandException.class,
+ ImportCommand.MESSAGE_FILE_NOT_FOUND, () -> importCommand.execute(model));
+ }
+
+ @Test
+ public void execute_parse_error() {
+ ImportCommand importCommand = new ImportCommand(INVALID_IMPORT_FILE);
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ assertThrows(CommandException.class,
+ ImportCommand.MESSAGE_FILE_CORRUPTED, () -> importCommand.execute(model));
+ }
+
+ @Test
+ public void execute_duplicatePersons_noAdditions() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ int addressBookBeforeTestSize = model.getAddressBook().getPersonList().size();
+ ImportCommand importCommand = new ImportCommand(DUPLICATE_IMPORT_FILE);
+ int addressBookAfterTestSize = model.getAddressBook().getPersonList().size();
+ assertEquals(addressBookBeforeTestSize, addressBookAfterTestSize);
+ String msg = "Data imported with 1 students added."
+ + "\n2 Duplicate person(s) found in file: [Shaun Lee, Shaun Lee]";
+ assertThrows(CommandException.class, msg, () -> importCommand.execute(model));
+ }
+
+ @Test
+ public void execute_csvError() {
+ Path hybridPath = Path.of("src/test/data/ImportCommandTest/invalid.csv");
+ ImportCommand importCommand = new ImportCommand(hybridPath);
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ assertThrows(CommandException.class,
+ ImportCommand.MESSAGE_FILE_CORRUPTED, () -> importCommand.execute(model));
+ }
+
+ @Test
+ public void execute_toString() {
+ ImportCommand importCommand = new ImportCommand(VALID_NO_DUPS_IMPORT_FILE);
+ assertEquals(ImportCommand.class.getCanonicalName() + "{filepath: =" + VALID_NO_DUPS_IMPORT_FILE + "}",
+ importCommand.toString());
+ }
+
+ @Test
+ public void equals() {
+ Path validPath = Path.of("src/test/data/ImportCommandTest/valid_noDups_importFile.csv");
+ ImportCommand importCommand1 = new ImportCommand(validPath);
+ ImportCommand importCommand2 = new ImportCommand(validPath);
+
+ // same object
+ assertEquals(importCommand1, importCommand1);
+
+ // different object, same path
+ assertEquals(importCommand1, importCommand2);
+
+ // null
+ assertNotEquals(importCommand1, null);
+
+ // different object, different path
+ Path differentPath = Path.of("src/test/data/ImportCommandTest/different.csv");
+ ImportCommand importCommand3 = new ImportCommand(differentPath);
+ assertNotEquals(importCommand1, importCommand3);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
index 435ff1f7275..3ced06d6e2d 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
@@ -2,6 +2,7 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.EmptyPersons.getEmptyAddressBook;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
@@ -28,12 +29,19 @@ public void setUp() {
@Test
public void execute_listIsNotFiltered_showsSameList() {
- assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel);
+ assertCommandSuccess(new ListCommand(), model, String.format(ListCommand.MESSAGE_SUCCESS, 7), expectedModel);
}
@Test
public void execute_listIsFiltered_showsEverything() {
showPersonAtIndex(model, INDEX_FIRST_PERSON);
- assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel);
+ assertCommandSuccess(new ListCommand(), model, String.format(ListCommand.MESSAGE_SUCCESS, 7), expectedModel);
+ }
+
+ @Test
+ public void execute_listIsEmpty_showsEmptyMessage() {
+ model = new ModelManager(getEmptyAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_EMPTY_LIST, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ListGroupsCommandTest.java b/src/test/java/seedu/address/logic/commands/ListGroupsCommandTest.java
new file mode 100644
index 00000000000..6d01a1e6928
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListGroupsCommandTest.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * GroupsCommand.
+ */
+public class ListGroupsCommandTest {
+
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ }
+
+ @Test
+ public void execute_groupsWhileEmpty_showsEmptyGroups() {
+ CommandResult expectedResult = new CommandResult(ListGroupsCommand.MESSAGE_NOGROUPS, false, false);
+ assertCommandSuccess(new ListGroupsCommand(), model, expectedResult, expectedModel);
+ }
+ // TODO MORE TESTS
+
+ @Test
+ public void execute_groupsWhileNotEmpty_showsNotEmptyGroups() {
+ CommandResult expectedResult = new CommandResult(ListGroupsCommand.MESSAGE_SUCCESS, false, false);
+ List people = model.getFilteredPersonList();
+ model.addGroup(new Group("abc", people));
+ assertCommandSuccess(new ListGroupsCommand(), model, expectedResult, expectedModel);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/TagCommandTest.java b/src/test/java/seedu/address/logic/commands/TagCommandTest.java
new file mode 100644
index 00000000000..a0a2a564f47
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/TagCommandTest.java
@@ -0,0 +1,120 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * {@code TagCommand}.
+ */
+public class TagCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ /*
+ // this test causes errors in another test class when run with gradlew clean build.
+ @Test
+ public void execute_validIndexAndTags_success() {
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Person personToTag = expectedModel.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Tag firstTag = new Tag("newTag1");
+ Tag secondTag = new Tag("newTag2");
+ Set tags = Set.of(firstTag, secondTag);
+ personToTag.addTags(tags);
+
+ TagCommand tagCommand = new TagCommand(INDEX_FIRST_PERSON, tags);
+ String expectedMessage = String.format(TagCommand.MESSAGE_SUCCESS);
+
+ assertCommandSuccess(tagCommand, model, expectedMessage, expectedModel);
+ }
+
+ */
+
+ @Test
+ public void execute_duplicateTag_throwsCommandException() {
+
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Tag tag = new Tag("friends");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ TagCommand tagCommand = new TagCommand(INDEX_FIRST_PERSON, tags);
+ assertCommandFailure(tagCommand, model, TagCommand.MESSAGE_TAG_ALREADY_EXISTS);
+ }
+
+ @Test
+ public void execute_invalidIndex_throwsCommandException() {
+
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ TagCommand tagCommand = new TagCommand(outOfBoundIndex, tags);
+
+ assertCommandFailure(tagCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ Tag firstTag = new Tag("firstTag");
+ Set firstTagSet = Set.of(firstTag);
+ Tags firstTags = new Tags(firstTagSet);
+ Tag secondTag = new Tag("secondTag");
+ Set secondTagSet = Set.of(secondTag);
+ Tags secondTags = new Tags(secondTagSet);
+ TagCommand tagFirstCommand = new TagCommand(INDEX_FIRST_PERSON, firstTags);
+ TagCommand tagSecondCommand = new TagCommand(INDEX_SECOND_PERSON, secondTags);
+
+ // same object -> returns true
+ assertTrue(tagFirstCommand.equals(tagFirstCommand));
+
+ // same values -> returns true
+ TagCommand tagFirstCommandCopy = new TagCommand(INDEX_FIRST_PERSON, firstTags);
+ assertTrue(tagFirstCommand.equals(tagFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(tagFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(tagFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(tagFirstCommand.equals(tagSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ TagCommand tagCommand = new TagCommand(targetIndex, tags);
+ String expected = TagCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex
+ + ", tags=" + tags + "}";
+ assertEquals(expected, tagCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/UntagCommandTest.java b/src/test/java/seedu/address/logic/commands/UntagCommandTest.java
new file mode 100644
index 00000000000..cee2357b037
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UntagCommandTest.java
@@ -0,0 +1,124 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * {@code TagCommand}.
+ */
+public class UntagCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ /*
+ // this test causes errors in another test class when run with gradlew clean build.
+ @Test
+ public void execute_validIndexAndTags_success() throws CommandException {
+
+ Person actualPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Tag tag = new Tag("friends");
+ Set tags = Set.of(tag);
+ UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tags);
+ CommandResult result = untagCommand.execute(model);
+ String expectedMessage = String.format(UntagCommand.MESSAGE_SUCCESS);
+
+ Person expectedPerson = new Person(actualPerson.getName(), actualPerson.getStudentClass(),
+ actualPerson.getPhone(), Set.of());
+
+ assertEquals(expectedPerson, actualPerson);
+ assertEquals(expectedMessage, result.getFeedbackToUser());
+
+ // add back the tag
+ actualPerson.addTags(tags);
+ }
+ */
+
+ @Test
+ public void execute_invalidIndex_throwsCommandException() {
+
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ TagCommand tagCommand = new TagCommand(outOfBoundIndex, tags);
+
+ assertCommandFailure(tagCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_tagNotExist_throwsCommandException() {
+
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Tag tag = new Tag("friendss");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ UntagCommand untagCommand = new UntagCommand(INDEX_FIRST_PERSON, tags);
+
+ assertCommandFailure(untagCommand, model, UntagCommand.MESSAGE_TAG_DOES_NOT_EXIST);
+ }
+
+ @Test
+ public void equals() {
+ Tag firstTag = new Tag("firstTag");
+ Set firstTagSet = Set.of(firstTag);
+ Tags firstTags = new Tags(firstTagSet);
+ Tag secondTag = new Tag("secondTag");
+ Set secondTagSet = Set.of(secondTag);
+ Tags secondTags = new Tags(secondTagSet);
+ UntagCommand untagFirstCommand = new UntagCommand(INDEX_FIRST_PERSON, firstTags);
+ UntagCommand untagSecondCommand = new UntagCommand(INDEX_SECOND_PERSON, secondTags);
+
+ // same object -> returns true
+ assertTrue(untagFirstCommand.equals(untagFirstCommand));
+
+ // same values -> returns true
+ UntagCommand untagFirstCommandCopy = new UntagCommand(INDEX_FIRST_PERSON, firstTags);
+
+ assertTrue(untagFirstCommand.equals(untagFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(untagFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(untagFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(untagFirstCommand.equals(untagSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ UntagCommand untagCommand = new UntagCommand(targetIndex, tags);
+ String expected = UntagCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex
+ + ", tags=" + tags + "}";
+ assertEquals(expected, untagCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..9eaaf1a01e9 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -1,12 +1,9 @@
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.CLASS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.CLASS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_CLASS_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;
@@ -18,14 +15,12 @@
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_CLASS_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_CLASS;
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,11 +32,10 @@
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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
import seedu.address.model.tag.Tag;
import seedu.address.testutil.PersonBuilder;
@@ -53,22 +47,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 + CLASS_DESC_BOB + PHONE_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 + CLASS_DESC_BOB + PHONE_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 + CLASS_DESC_BOB + PHONE_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
@@ -78,19 +73,15 @@ public void parse_repeatedNonTagValue_failure() {
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 classes
+ assertParseFailure(parser, CLASS_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_CLASS));
// multiple fields repeated
assertParseFailure(parser,
- validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY
+ validExpectedPersonString + PHONE_DESC_AMY + CLASS_DESC_AMY + NAME_DESC_AMY
+ validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_CLASS, PREFIX_PHONE));
// invalid value followed by valid value
@@ -98,42 +89,35 @@ 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 class
+ assertParseFailure(parser, INVALID_CLASS_DESC + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_CLASS));
// invalid phone
assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // invalid address
- assertParseFailure(parser, INVALID_ADDRESS_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
-
// valid value followed by invalid value
// invalid name
assertParseFailure(parser, validExpectedPersonString + INVALID_NAME_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
- // invalid email
- assertParseFailure(parser, validExpectedPersonString + INVALID_EMAIL_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
+ // invalid class
+ assertParseFailure(parser, validExpectedPersonString + INVALID_CLASS_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_CLASS));
// invalid phone
assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
- // invalid address
- assertParseFailure(parser, validExpectedPersonString + INVALID_ADDRESS_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
}
@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 + CLASS_DESC_AMY + PHONE_DESC_AMY,
new AddCommand(expectedPerson));
}
@@ -142,55 +126,44 @@ 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,
- expectedMessage);
-
- // missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
+ assertParseFailure(parser, VALID_NAME_BOB + CLASS_DESC_BOB + PHONE_DESC_BOB,
expectedMessage);
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
+ // missing class prefix
+ assertParseFailure(parser, NAME_DESC_BOB + VALID_CLASS_BOB + VALID_PHONE_BOB,
expectedMessage);
- // all prefixes missing
- assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
+ // missing phone prefix
+ assertParseFailure(parser, NAME_DESC_BOB + CLASS_DESC_BOB + VALID_PHONE_BOB,
expectedMessage);
}
@Test
public void parse_invalidValue_failure() {
// invalid name
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ assertParseFailure(parser, INVALID_NAME_DESC + CLASS_DESC_BOB + PHONE_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
+ // invalid class
+ assertParseFailure(parser, NAME_DESC_BOB + INVALID_CLASS_DESC + PHONE_DESC_BOB
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, StudentClass.MESSAGE_CONSTRAINTS);
// invalid phone
- assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ assertParseFailure(parser, NAME_DESC_BOB + CLASS_DESC_BOB + INVALID_PHONE_DESC
+ 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);
-
- // 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 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 + CLASS_DESC_BOB + PHONE_DESC_BOB
+ + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CHAR_CONSTRAINTS);
// two invalid values, only first invalid value reported
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
+ assertParseFailure(parser, INVALID_NAME_DESC + INVALID_CLASS_DESC + PHONE_DESC_BOB,
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 + CLASS_DESC_BOB + PHONE_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..0a141ae5dd0 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -5,10 +5,13 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.GROUP_A;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
@@ -16,16 +19,27 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteGroupCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindGroupCommand;
+import seedu.address.logic.commands.GroupCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListGroupsCommand;
+import seedu.address.logic.commands.TagCommand;
+import seedu.address.logic.commands.UntagCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.GroupUtil;
+import seedu.address.testutil.GroupsUtil;
import seedu.address.testutil.PersonBuilder;
import seedu.address.testutil.PersonUtil;
@@ -40,12 +54,29 @@ public void parseCommand_add() throws Exception {
assertEquals(new AddCommand(person), command);
}
+ @Test
+ public void parseCommand_group() throws Exception {
+ assertTrue(parser.parseCommand(GroupUtil.groupCommand()) instanceof GroupCommand);
+ }
+
+ @Test
+ public void parseCommand_groups() throws Exception {
+ assertTrue(parser.parseCommand(GroupsUtil.groupsCommand()) instanceof ListGroupsCommand);
+ }
+
@Test
public void parseCommand_clear() throws Exception {
assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand);
assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand);
}
+ @Test
+ public void parseCommand_deleteGroup() throws Exception {
+ DeleteGroupCommand command = (DeleteGroupCommand) parser.parseCommand(
+ DeleteGroupCommand.COMMAND_WORD + " " + GROUP_A.getGroupName().toString());
+ assertEquals(new DeleteGroupCommand(GROUP_A.getGroupName()), command);
+ }
+
@Test
public void parseCommand_delete() throws Exception {
DeleteCommand command = (DeleteCommand) parser.parseCommand(
@@ -76,11 +107,41 @@ public void parseCommand_find() throws Exception {
assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command);
}
+ @Test
+ public void parseCommand_findGroup() throws Exception {
+ GroupContainsKeywordsPredicate groupPredicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList(GROUP_A.getGroupName().toString()));
+ FindGroupCommand command = (FindGroupCommand) parser.parseCommand(
+ FindGroupCommand.COMMAND_WORD + " " + GROUP_A.getGroupName().toString());
+ assertEquals(new FindGroupCommand(groupPredicate), command);
+ }
+
@Test
public void parseCommand_help() throws Exception {
assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand);
assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand);
}
+ @Test
+ public void parseCommand_tag() throws Exception {
+ Tag tag = new Tag("test");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ TagCommand expectedCommand = new TagCommand(INDEX_FIRST_PERSON, tags);
+ assertEquals(expectedCommand,
+ parser.parseCommand(TagCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + " "
+ + "t/test"));
+ }
+
+ @Test
+ public void parseCommand_untag() throws Exception {
+ Tag tag = new Tag("test");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ UntagCommand expectedCommand = new UntagCommand(INDEX_FIRST_PERSON, tags);
+ assertEquals(expectedCommand,
+ parser.parseCommand(UntagCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + " "
+ + "t/test"));
+ }
@Test
public void parseCommand_list() throws Exception {
@@ -91,11 +152,13 @@ public void parseCommand_list() throws Exception {
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
- -> parser.parseCommand(""));
+ -> parser.parseCommand(""));
}
@Test
public void parseCommand_unknownCommand_throwsParseException() {
assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand"));
}
+
+
}
diff --git a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java
index 9bf1ccf1cef..b8c468c7a9e 100644
--- a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java
+++ b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java
@@ -18,6 +18,8 @@ public static void assertParseSuccess(Parser extends Command> parser, String u
Command expectedCommand) {
try {
Command command = parser.parse(userInput);
+ System.out.println(command.toString());
+ System.out.println(expectedCommand.toString());
assertEquals(expectedCommand, command);
} catch (ParseException pe) {
throw new IllegalArgumentException("Invalid userInput.", pe);
diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
index 6a40e14a649..4de3e3cd479 100644
--- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.DeleteCommand.MESSAGE_INDEX_UNDER_1;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
@@ -27,6 +27,6 @@ public void parse_validArgs_returnsDeleteCommand() {
@Test
public void parse_invalidArgs_throwsParseException() {
- assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, "a", String.format(MESSAGE_INDEX_UNDER_1, DeleteCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteGroupCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteGroupCommandParserTest.java
new file mode 100644
index 00000000000..cd12aa24c8b
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DeleteGroupCommandParserTest.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.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.DeleteGroupCommand;
+import seedu.address.model.group.GroupName;
+
+public class DeleteGroupCommandParserTest {
+
+ private DeleteGroupCommandParser parser = new DeleteGroupCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsDeleteGroupCommand() {
+ GroupName validGroupName = new GroupName("Study Group");
+ DeleteGroupCommand expectedCommand = new DeleteGroupCommand(validGroupName);
+
+ // no leading and trailing whitespaces
+ assertParseSuccess(parser, "Study Group", expectedCommand);
+
+ // multiple whitespaces
+ assertParseSuccess(parser, " \n Study Group ", expectedCommand);
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ // Empty argument
+ assertParseFailure(parser, "",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE));
+
+ // Invalid argument format (non-alphanumeric)
+ assertParseFailure(parser, "Invalid@Group",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGroupCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..bbef2d7db3b 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -1,12 +1,9 @@
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.CLASS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.CLASS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_CLASS_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;
@@ -15,15 +12,14 @@
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_CLASS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_CLASS_BOB;
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_CLASS;
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,10 +34,9 @@
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.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
import seedu.address.model.tag.Tag;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -84,33 +79,34 @@ public void parse_invalidPreamble_failure() {
@Test
public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
+ assertParseFailure(parser, "1" + INVALID_CLASS_DESC, StudentClass.MESSAGE_CONSTRAINTS); // invalid email
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_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
+ assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CHAR_CONSTRAINTS); // invalid tag
- // invalid phone followed by valid email
- assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
+ // invalid class followed by invalid phone
+ assertParseFailure(parser, "1" + INVALID_CLASS_DESC + INVALID_PHONE_DESC, StudentClass.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_CHAR_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CHAR_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CHAR_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,
+ assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_CLASS_DESC + VALID_PHONE_AMY,
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;
+ String userInput =
+ targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND + CLASS_DESC_BOB
+ + NAME_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)
+ .withStudentClass(VALID_CLASS_BOB)
+ .withPhone(VALID_PHONE_BOB)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
@@ -120,10 +116,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 + CLASS_DESC_AMY;
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_AMY).build();
+ EditPersonDescriptor descriptor =
+ new EditPersonDescriptorBuilder().withStudentClass(VALID_CLASS_AMY).withPhone(VALID_PHONE_BOB).build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
@@ -138,23 +134,18 @@ public void parse_oneFieldSpecified_success() {
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // phone
- userInput = targetIndex.getOneBased() + PHONE_DESC_AMY;
- descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build();
+ // class
+ userInput = targetIndex.getOneBased() + CLASS_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withStudentClass(VALID_CLASS_AMY).build();
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // email
- userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY;
- descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build();
+ // phone
+ userInput = targetIndex.getOneBased() + PHONE_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_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();
- expectedCommand = new EditCommand(targetIndex, descriptor);
- assertParseSuccess(parser, userInput, expectedCommand);
// tags
userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND;
@@ -180,19 +171,19 @@ 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;
+ userInput = targetIndex.getOneBased() + CLASS_DESC_AMY + PHONE_DESC_AMY
+ + TAG_DESC_FRIEND + PHONE_DESC_AMY + CLASS_DESC_AMY + TAG_DESC_FRIEND
+ + PHONE_DESC_BOB + CLASS_DESC_BOB + TAG_DESC_HUSBAND;
assertParseFailure(parser, userInput,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_CLASS, PREFIX_PHONE));
// 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_CLASS_DESC
+ + INVALID_PHONE_DESC + INVALID_CLASS_DESC;
assertParseFailure(parser, userInput,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_CLASS, PREFIX_PHONE));
}
@Test
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
index d92e64d12f9..97318498e20 100644
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
@@ -17,11 +17,12 @@ public class FindCommandParserTest {
@Test
public void parse_emptyArg_throwsParseException() {
- assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, " ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
@Test
- public void parse_validArgs_returnsFindCommand() {
+ public void parse_validNameArgs_returnsFindCommand() {
// no leading and trailing whitespaces
FindCommand expectedFindCommand =
new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")));
@@ -31,4 +32,11 @@ public void parse_validArgs_returnsFindCommand() {
assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand);
}
+
+ @Test
+ public void parse_invalidNameArgs_throwsParseException() {
+ // Empty name args
+ assertParseFailure(parser, " ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
}
diff --git a/src/test/java/seedu/address/logic/parser/FindGroupCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindGroupCommandParserTest.java
new file mode 100644
index 00000000000..8bc50e804d8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FindGroupCommandParserTest.java
@@ -0,0 +1,47 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.FindGroupCommand;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
+
+public class FindGroupCommandParserTest {
+
+ private FindGroupCommandParser parser = new FindGroupCommandParser();
+
+ @Test
+ public void parse_emptyArg_throwsParseException() {
+ assertParseFailure(parser, " ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindGroupCommand.MESSAGE_USAGE));
+ }
+
+
+ @Test
+ public void parse_validGroupArgs_returnsFindCommand() {
+
+ List keyWord = new ArrayList<>();
+ keyWord.add("Gooners");
+ // no leading and trailing whitespaces
+ FindGroupCommand expectedFindGroupCommand =
+ new FindGroupCommand(new GroupContainsKeywordsPredicate(keyWord));
+ assertParseSuccess(parser, "Gooners", expectedFindGroupCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, "\n Gooners \n \t", expectedFindGroupCommand);
+ }
+
+ @Test
+ public void parse_invalidGroupArgs_throwsParseException() {
+ // Missing group name after /group
+ assertParseFailure(parser, " ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindGroupCommand.MESSAGE_USAGE));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/GroupCommandParserTest.java b/src/test/java/seedu/address/logic/parser/GroupCommandParserTest.java
new file mode 100644
index 00000000000..9042e6df123
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/GroupCommandParserTest.java
@@ -0,0 +1,54 @@
+package seedu.address.logic.parser;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STUDENTS;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.GroupCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+
+public class GroupCommandParserTest {
+
+ private final GroupCommandParser parser = new GroupCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() throws Exception {
+ String userInput = " " + PREFIX_GROUP + "Group A " + PREFIX_STUDENTS + "Alice " + PREFIX_STUDENTS + "Bob";
+ GroupCommand expectedCommand = new GroupCommand("Group A", List.of("Alice", "Bob"));
+ GroupCommand command = parser.parse(userInput);
+ assertEquals(expectedCommand, command);
+ }
+
+ @Test
+ public void parse_missingGroupPrefix_throwsParseException() {
+ String userInput = " " + PREFIX_STUDENTS + "Alice " + PREFIX_STUDENTS + "Bob";
+ assertThrows(ParseException.class, () -> parser.parse(userInput));
+ }
+
+ @Test
+ public void parse_missingStudentsPrefix_throwsParseException() {
+ String userInput = " " + PREFIX_GROUP + "Group A";
+ assertThrows(ParseException.class, () -> parser.parse(userInput));
+ }
+
+ @Test
+ public void parse_duplicateGroupPrefix_throwsParseException() {
+ String userInput = " " + PREFIX_GROUP + "Group A " + PREFIX_GROUP + "Group B " + PREFIX_STUDENTS + "Alice";
+ assertThrows(ParseException.class, () -> parser.parse(userInput));
+ }
+
+ @Test
+ public void parse_duplicateStudentsPrefix_success() throws Exception {
+ String userInput = " " + PREFIX_GROUP + "Group A " + PREFIX_STUDENTS + "Alice " + PREFIX_STUDENTS + "Alice";
+ GroupCommand expectedCommand = new GroupCommand("Group A", List.of("Alice", "Alice"));
+ GroupCommand command = parser.parse(userInput);
+ assertEquals(expectedCommand, command);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java
new file mode 100644
index 00000000000..d36afb105d6
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ImportCommandParserTest.java
@@ -0,0 +1,56 @@
+package seedu.address.logic.parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static seedu.address.testutil.TypicalPaths.VALID_MISSING_DATA_IMPORT_FILE;
+import static seedu.address.testutil.TypicalPaths.VALID_NO_DUPS_IMPORT_FILE;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ImportCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class ImportCommandParserTest {
+
+ private final ImportCommandParser parser = new ImportCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() throws Exception {
+ String userInput = VALID_NO_DUPS_IMPORT_FILE.toString();
+ ImportCommand expected = new ImportCommand(VALID_NO_DUPS_IMPORT_FILE);
+ assertEquals(parser.parse(userInput), expected);
+ }
+
+ @Test
+ public void parseInvalidPathFailure() {
+ Path projectRootPath = Paths.get(System.getProperty("user.dir"));
+ Path importCsvPath = projectRootPath.resolve("src")
+ .resolve("test").resolve("data").resolve("ImportCommandTest").resolve("invalid_importFile.csv");
+ // TODO
+ // change importCsvPath to be an imported static variable to go in accordance with abstraction
+ String userInput = importCsvPath.toString();
+ assertThrows(ParseException.class, () -> parser.parse(userInput));
+ }
+
+ @Test
+ public void parseEmptyPathFailure() {
+ String userInput = "";
+ assertThrows(ParseException.class, () -> parser.parse(userInput));
+ }
+
+ @Test
+ public void parseWhitespacePathFailure() {
+ String userInput = " ";
+ assertThrows(ParseException.class, () -> parser.parse(userInput));
+ }
+
+ @Test
+ public void parse_missingDataPath_success() throws Exception {
+ String userInput = VALID_MISSING_DATA_IMPORT_FILE.toString();
+ ImportCommand expected = new ImportCommand(VALID_MISSING_DATA_IMPORT_FILE);
+ assertEquals(parser.parse(userInput), expected);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..587e22cf332 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -19,14 +19,15 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
public class ParserUtilTest {
private static final String INVALID_NAME = "R@chel";
- private static final String INVALID_PHONE = "+651234";
+ 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_TAG = "#friend";
-
+ private static final String LONG_TAG = "abcdefghijklmnopqrstuvxwyz0123456789";
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";
@@ -188,9 +189,14 @@ public void parseTags_emptyCollection_returnsEmptySet() throws Exception {
@Test
public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
- Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2));
+ Tags actualTags = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2));
Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2)));
+ Tags expectedTags = new Tags(expectedTagSet);
+ assertEquals(expectedTags, actualTags);
+ }
- assertEquals(expectedTagSet, actualTagSet);
+ @Test
+ public void parseTags_exceedsLengthLimit_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseTag(LONG_TAG));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java
new file mode 100644
index 00000000000..6c09c1dd895
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/TagCommandParserTest.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.TagCommand;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
+
+public class TagCommandParserTest {
+
+ private TagCommandParser parser = new TagCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsTagCommand() {
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ assertParseSuccess(parser, "1 t/tag", new TagCommand(INDEX_FIRST_PERSON, tags));
+ }
+
+ @Test
+ public void parse_missingArgs_throwsParseException() {
+ assertParseFailure(parser, "1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, "t/tag", String.format(MESSAGE_INVALID_COMMAND_FORMAT, TagCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/UntagCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UntagCommandParserTest.java
new file mode 100644
index 00000000000..6caee21e2e3
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/UntagCommandParserTest.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.UntagCommand;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
+
+public class UntagCommandParserTest {
+
+ private UntagCommandParser parser = new UntagCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsTagCommand() {
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ assertParseSuccess(parser, "1 t/tag", new UntagCommand(INDEX_FIRST_PERSON, tags));
+ }
+
+ @Test
+ public void parse_missingArgs_throwsParseException() {
+ assertParseFailure(parser, "1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, "t/tag", String.format(MESSAGE_INVALID_COMMAND_FORMAT, UntagCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..c90df08882f 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -3,10 +3,10 @@
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.logic.commands.CommandTestUtil.VALID_CLASS_BOB;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BOB;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import java.util.Arrays;
@@ -18,8 +18,10 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.group.Group;
import seedu.address.model.person.Person;
import seedu.address.model.person.exceptions.DuplicatePersonException;
+import seedu.address.testutil.AddressBookBuilder;
import seedu.address.testutil.PersonBuilder;
public class AddressBookTest {
@@ -46,8 +48,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).withClass(VALID_CLASS_BOB).build();
List newPersons = Arrays.asList(ALICE, editedAlice);
AddressBookStub newData = new AddressBookStub(newPersons);
@@ -73,8 +74,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).withClass(VALID_CLASS_BOB).build();
assertTrue(addressBook.hasPerson(editedAlice));
}
@@ -83,17 +83,55 @@ public void getPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0));
}
+ // Additional test cases to cover equals and hashCode methods
+
+ @Test
+ public void equals() {
+ AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BOB).build();
+ AddressBook differentAddressBook = new AddressBook();
+ UserPrefs userPrefs = new UserPrefs();
+
+ // same values -> returns true
+ assertTrue(addressBook.equals(new AddressBookBuilder().withPerson(ALICE).withPerson(BOB).build()));
+
+ // same object -> returns true
+ assertTrue(addressBook.equals(addressBook));
+
+ // null -> returns false
+ assertFalse(addressBook.equals(null));
+
+ // different type -> returns false
+ assertFalse(addressBook.equals(5));
+
+ // different addressBook -> returns false
+ assertFalse(addressBook.equals(differentAddressBook));
+ }
+
@Test
- public void toStringMethod() {
- String expected = AddressBook.class.getCanonicalName() + "{persons=" + addressBook.getPersonList() + "}";
- assertEquals(expected, addressBook.toString());
+ public void hashCode_sameAttributes_sameHashCode() {
+ AddressBook addressBook1 = new AddressBookBuilder().withPerson(ALICE).withPerson(BOB).build();
+ AddressBook addressBook2 = new AddressBookBuilder().withPerson(ALICE).withPerson(BOB).build();
+
+ // Ensure that two objects with the same attributes have the same hashCode
+ assertEquals(addressBook1.hashCode(), addressBook2.hashCode());
+ }
+
+ @Test
+ public void hashCode_differentAttributes_differentHashCode() {
+ AddressBook addressBook1 = new AddressBookBuilder().withPerson(ALICE).build();
+ AddressBook addressBook2 = new AddressBookBuilder().withPerson(BOB).build();
+
+ // Ensure that two objects with different attributes have different hashCodes
+ assertFalse(addressBook1.hashCode() == addressBook2.hashCode());
}
/**
- * A stub ReadOnlyAddressBook whose persons list can violate interface constraints.
+ * A stub ReadOnlyAddressBook whose persons list can violate interface
+ * constraints.
*/
private static class AddressBookStub implements ReadOnlyAddressBook {
private final ObservableList persons = FXCollections.observableArrayList();
+ private final ObservableList groups = FXCollections.observableArrayList();
AddressBookStub(Collection persons) {
this.persons.setAll(persons);
@@ -103,6 +141,10 @@ private static class AddressBookStub implements ReadOnlyAddressBook {
public ObservableList getPersonList() {
return persons;
}
- }
+ @Override
+ public ObservableList getGroupList() {
+ return groups;
+ }
+ }
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..b0f9f3ec18e 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -2,6 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import static seedu.address.testutil.Assert.assertThrows;
@@ -11,12 +12,20 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupContainsKeywordsPredicate;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.PersonBuilder;
public class ModelManagerTest {
@@ -88,11 +97,121 @@ public void hasPerson_personInAddressBook_returnsTrue() {
assertTrue(modelManager.hasPerson(ALICE));
}
+ @Test
+ public void tagExists_correctTag_returnsTrue() {
+ Person person = new PersonBuilder().withTags("tag").build();
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ Boolean actualBool = modelManager.tagExists(person, tags);
+ assertEquals(true, actualBool);
+ }
+
+ @Test
+ public void tagExists_wrongTag_returnsFalse() {
+ Person person = new PersonBuilder().withTags("tag").build();
+ Tag tag = new Tag("taggg");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ Boolean actualBool = modelManager.tagExists(person, tags);
+ assertEquals(false, actualBool);
+ }
+
+ @Test
+ public void addTagTest() {
+ Person expectedPerson = new PersonBuilder().withTags("tag").build();
+ Person actualPerson = new PersonBuilder().build();
+ modelManager.addPerson(actualPerson);
+ Tag tag = new Tag("tag");
+ Set setOfTags = Set.of(tag);
+ Tags tags = new Tags(setOfTags);
+ modelManager.addTag(actualPerson, tags);
+ assertEquals(expectedPerson, actualPerson);
+ }
+
+ @Test
+ public void deleteTagTest() {
+ Person actualPerson = new PersonBuilder().withTags("tag").build();
+ Person expectedPerson = new PersonBuilder().build();
+ modelManager.addPerson(actualPerson);
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ modelManager.deleteTag(actualPerson, tags);
+ assertEquals(expectedPerson, actualPerson);
+ }
+
+ @Test
+ public void addGroup_validGroup_addsGroup() {
+ Group group = new Group("group A", List.of(new PersonBuilder().build()));
+ modelManager.addGroup(group);
+ assertTrue(modelManager.hasGroupName(group));
+ }
+
+ @Test
+ public void hasGroupName_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> modelManager.hasGroupName(null));
+ }
+
+ @Test
+ public void hasGroupName_groupNotInModel_returnsFalse() {
+ Group group = new Group("group A", List.of(new PersonBuilder().build()));
+ assertFalse(modelManager.hasGroupName(group));
+ }
+
+ @Test
+ public void hasGroupName_groupInModel_returnsTrue() {
+ Group group = new Group("group A", List.of(new PersonBuilder().build()));
+ modelManager.addGroup(group);
+ assertTrue(modelManager.hasGroupName(group));
+ }
+
+ @Test
+ public void updateFilteredGroupList_nullPredicate_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> modelManager.updateFilteredGroupList(null));
+ }
+
+ @Test
+ public void addToGroupList_returnsMatchingGroups() {
+ Group groupA = new Group("groupA", List.of(new PersonBuilder().build()));
+ Group groupB = new Group("groupB", List.of(new PersonBuilder().build()));
+ Group groupC = new Group("groupC", List.of(new PersonBuilder().build()));
+ modelManager.addGroup(groupA);
+ modelManager.addGroup(groupB);
+ modelManager.addGroup(groupC);
+
+ assertTrue(modelManager.hasGroupName(groupA));
+ assertTrue(modelManager.hasGroupName(groupB));
+ assertTrue(modelManager.hasGroupName(groupC));
+ }
+
+ @Test
+ public void updateFilteredGroupList_validPredicate_returnsMatchingGroups() {
+ Group groupA = new Group("groupA", List.of(new PersonBuilder().build()));
+ Group groupB = new Group("groupB", List.of(new PersonBuilder().build()));
+ Group groupC = new Group("groupC", List.of(new PersonBuilder().build()));
+ modelManager.addGroup(groupA);
+ modelManager.addGroup(groupB);
+ modelManager.addGroup(groupC);
+
+ GroupContainsKeywordsPredicate predicate = new GroupContainsKeywordsPredicate(List.of("GroupA"));
+ modelManager.updateFilteredGroupList(predicate);
+
+ assertTrue(modelManager.getFilteredGroupList().contains(groupA));
+ assertFalse(modelManager.getFilteredGroupList().contains(groupB));
+ assertFalse(modelManager.getFilteredGroupList().contains(groupC));
+ }
+
@Test
public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
}
+ @Test
+ public void getFilteredGroupList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredGroupList().remove(0));
+ }
+
@Test
public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
@@ -129,4 +248,187 @@ public void equals() {
differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
}
+
+ @Test
+ public void deletePerson_personInGroups_personRemovedFromGroupsAndEmptyGroupsDeleted() {
+ // Set up the model manager with test data
+ ModelManager modelManager = new ModelManager();
+ Person personToDelete = new PersonBuilder().withName("John Doe").build();
+ Person otherPerson = new PersonBuilder().withName("Jane Smith").build();
+ modelManager.addPerson(personToDelete);
+ modelManager.addPerson(otherPerson);
+
+ // Create groups and add the person to them
+ Group groupOnlyPerson = new Group("GroupOnlyPerson", Arrays.asList(personToDelete));
+ Group groupWithOthers = new Group("GroupWithOthers", Arrays.asList(personToDelete, otherPerson));
+ Group groupWithoutPerson = new Group("GroupWithoutPerson", Arrays.asList(otherPerson));
+
+ modelManager.addGroup(groupOnlyPerson);
+ modelManager.addGroup(groupWithOthers);
+ modelManager.addGroup(groupWithoutPerson);
+
+ // Delete the person
+ modelManager.deletePerson(personToDelete);
+
+ // Assertions
+ // The person should no longer exist in the address book
+ assertFalse(modelManager.hasPerson(personToDelete));
+
+ // The group that only had the person should be deleted
+ assertFalse(modelManager.hasGroupName(groupOnlyPerson));
+
+ // The group that had the person and others should still exist
+ assertTrue(modelManager.hasGroupName(groupWithOthers));
+
+ // But the person should be removed from the group
+ Group updatedGroupWithOthers = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.isSameGroup(groupWithOthers))
+ .findFirst()
+ .orElse(null);
+
+ assertNotNull(updatedGroupWithOthers);
+ assertFalse(updatedGroupWithOthers.getMembers().contains(personToDelete));
+ assertTrue(updatedGroupWithOthers.getMembers().contains(otherPerson));
+
+ // The group that didn't have the person should remain unchanged
+ assertTrue(modelManager.hasGroupName(groupWithoutPerson));
+ Group unchangedGroup = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.isSameGroup(groupWithoutPerson))
+ .findFirst()
+ .orElse(null);
+
+ assertNotNull(unchangedGroup);
+ assertEquals(groupWithoutPerson.getMembers(), unchangedGroup.getMembers());
+ }
+
+ @Test
+ public void deletePerson_personNotInAnyGroup_personDeletedAndGroupsUnchanged() {
+ // Set up the model manager with test data
+ ModelManager modelManager = new ModelManager();
+ Person personToDelete = new PersonBuilder().withName("John Doe").build();
+ Person otherPerson = new PersonBuilder().withName("Jane Smith").build();
+ modelManager.addPerson(personToDelete);
+ modelManager.addPerson(otherPerson);
+
+ // Create a group without the person
+ Group groupWithoutPerson = new Group("GroupWithoutPerson", Arrays.asList(otherPerson));
+ modelManager.addGroup(groupWithoutPerson);
+
+ // Delete the person
+ modelManager.deletePerson(personToDelete);
+
+ // Assertions
+ // The person should no longer exist in the address book
+ assertFalse(modelManager.hasPerson(personToDelete));
+
+ // The existing group should remain unchanged
+ assertTrue(modelManager.hasGroupName(groupWithoutPerson));
+ Group unchangedGroup = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.isSameGroup(groupWithoutPerson))
+ .findFirst()
+ .orElse(null);
+
+ assertNotNull(unchangedGroup);
+ assertEquals(groupWithoutPerson.getMembers(), unchangedGroup.getMembers());
+ }
+
+ @Test
+ public void deletePerson_personInMultipleGroups_someGroupsDeleted() {
+ // Set up the model manager with test data
+ ModelManager modelManager = new ModelManager();
+ Person personToDelete = new PersonBuilder().withName("John Doe").build();
+ Person otherPerson1 = new PersonBuilder().withName("Jane Smith").build();
+ Person otherPerson2 = new PersonBuilder().withName("Bob Lee").build();
+ modelManager.addPerson(personToDelete);
+ modelManager.addPerson(otherPerson1);
+ modelManager.addPerson(otherPerson2);
+
+ // Create groups
+ Group groupEmptyAfterDeletion = new Group("EmptyGroup", Arrays.asList(personToDelete));
+ Group groupStillHasMembers = new Group("NonEmptyGroup", Arrays.asList(personToDelete, otherPerson1));
+ Group groupUnaffected = new Group("UnaffectedGroup", Arrays.asList(otherPerson2));
+
+ modelManager.addGroup(groupEmptyAfterDeletion);
+ modelManager.addGroup(groupStillHasMembers);
+ modelManager.addGroup(groupUnaffected);
+
+ // Delete the person
+ modelManager.deletePerson(personToDelete);
+
+ // Assertions
+ // Person should be deleted from address book
+ assertFalse(modelManager.hasPerson(personToDelete));
+
+ // EmptyGroup should be deleted
+ assertFalse(modelManager.hasGroupName(groupEmptyAfterDeletion));
+
+ // NonEmptyGroup should still exist and not contain personToDelete
+ assertTrue(modelManager.hasGroupName(groupStillHasMembers));
+ Group updatedNonEmptyGroup = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.isSameGroup(groupStillHasMembers))
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Test
+ public void editPerson_personInMultipleGroups_personUpdatedInAllGroups() {
+ // Set up the model manager with test data
+ ModelManager modelManager = new ModelManager();
+ Person oldPerson = new PersonBuilder().withName("John Doe").build();
+ Person newPerson = new PersonBuilder().withName("John Smith").build();
+ Person otherPerson1 = new PersonBuilder().withName("Jane Smith").build();
+ Person otherPerson2 = new PersonBuilder().withName("Bob Lee").build();
+ modelManager.addPerson(oldPerson);
+ modelManager.addPerson(otherPerson1);
+ modelManager.addPerson(otherPerson2);
+
+ // Create groups
+ Group groupWithOldPersonOnly = new Group("GroupWithOldPerson", Arrays.asList(oldPerson));
+ Group groupWithOldPersonAndOthers = new Group("GroupMixed", Arrays.asList(oldPerson, otherPerson1));
+ Group groupUnaffected = new Group("GroupUnaffected", Arrays.asList(otherPerson2));
+
+ modelManager.addGroup(groupWithOldPersonOnly);
+ modelManager.addGroup(groupWithOldPersonAndOthers);
+ modelManager.addGroup(groupUnaffected);
+
+ // Edit the person
+ modelManager.setPerson(oldPerson, newPerson);
+
+ // Assertions
+ // Old person should no longer exist in any group
+ assertFalse(modelManager.getAddressBook().getGroupList().stream()
+ .anyMatch(group -> group.getMembers().contains(oldPerson)));
+
+ // New person should exist in all groups where the old person was
+ assertTrue(modelManager.getAddressBook().getGroupList().stream()
+ .anyMatch(group -> group.getMembers().contains(newPerson)));
+
+ // Group with old person only should now have the new person only
+ Group updatedGroupWithOldPersonOnly = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.getGroupName().toString().equals("GroupWithOldPerson"))
+ .findFirst()
+ .orElse(null);
+ assertNotNull(updatedGroupWithOldPersonOnly);
+ assertEquals(1, updatedGroupWithOldPersonOnly.getMembers().size());
+ assertTrue(updatedGroupWithOldPersonOnly.getMembers().contains(newPerson));
+
+ // Group with old person and others should now have the new person and retain others
+ Group updatedGroupMixed = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.getGroupName().toString().equals("GroupMixed"))
+ .findFirst()
+ .orElse(null);
+ assertNotNull(updatedGroupMixed);
+ assertEquals(2, updatedGroupMixed.getMembers().size());
+ assertTrue(updatedGroupMixed.getMembers().contains(newPerson));
+ assertTrue(updatedGroupMixed.getMembers().contains(otherPerson1));
+
+ // Unaffected group should remain unchanged
+ Group unaffectedGroup = modelManager.getAddressBook().getGroupList().stream()
+ .filter(group -> group.getGroupName().toString().equals("GroupUnaffected"))
+ .findFirst()
+ .orElse(null);
+ assertNotNull(unaffectedGroup);
+ assertEquals(1, unaffectedGroup.getMembers().size());
+ assertTrue(unaffectedGroup.getMembers().contains(otherPerson2));
+ }
}
diff --git a/src/test/java/seedu/address/model/group/GroupContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/group/GroupContainsKeywordsPredicateTest.java
new file mode 100644
index 00000000000..cd2b80c0d4f
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/GroupContainsKeywordsPredicateTest.java
@@ -0,0 +1,70 @@
+package seedu.address.model.group;
+
+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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.GroupBuilder;
+
+
+
+public class GroupContainsKeywordsPredicateTest {
+
+ @Test
+ public void equals() {
+ List firstPredicateKeywordList = Collections.singletonList("first");
+ List secondPredicateKeywordList = Arrays.asList("first", "second");
+
+ GroupContainsKeywordsPredicate firstPredicate = new GroupContainsKeywordsPredicate(firstPredicateKeywordList);
+ GroupContainsKeywordsPredicate secondPredicate = new GroupContainsKeywordsPredicate(secondPredicateKeywordList);
+
+ // same object -> returns true
+ assertEquals(firstPredicate, firstPredicate);
+
+ // same values -> returns true
+ GroupContainsKeywordsPredicate firstPredicateCopy =
+ new GroupContainsKeywordsPredicate(firstPredicateKeywordList);
+ assertEquals(firstPredicate, firstPredicateCopy);
+
+ // different types -> returns false
+ assertNotEquals(1, firstPredicate);
+
+ // null -> returns false
+ assertNotEquals(null, firstPredicate);
+
+ // different group -> returns false
+ assertNotEquals(firstPredicate, secondPredicate);
+
+ assertFalse(firstPredicate.equals(null));
+ }
+
+ @Test
+ public void test_groupNameContainsKeywords_returnsTrue() {
+ // One keyword
+ GroupContainsKeywordsPredicate predicate =
+ new GroupContainsKeywordsPredicate(Collections.singletonList("Study"));
+ assertTrue(predicate.test(new GroupBuilder().withGroupName("Study Group").build()));
+
+ // Multiple keywords
+ predicate = new GroupContainsKeywordsPredicate(Arrays.asList("Study", "Group"));
+ assertTrue(predicate.test(new GroupBuilder().withGroupName("Study Group").build()));
+
+ // Only one matching keyword
+ predicate = new GroupContainsKeywordsPredicate(Arrays.asList("Group", "Sports"));
+ assertTrue(predicate.test(new GroupBuilder().withGroupName("Sports Team").build()));
+
+ // Mixed-case keywords
+ predicate = new GroupContainsKeywordsPredicate(Arrays.asList("stuDy", "grOup"));
+ assertTrue(predicate.test(new GroupBuilder().withGroupName("Study Group").build()));
+
+ assertEquals(predicate.getKeywords(), Arrays.asList("stuDy", "grOup"));
+ }
+}
+
diff --git a/src/test/java/seedu/address/model/group/GroupListTest.java b/src/test/java/seedu/address/model/group/GroupListTest.java
new file mode 100644
index 00000000000..444a395021f
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/GroupListTest.java
@@ -0,0 +1,233 @@
+package seedu.address.model.group;
+
+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.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalGroups.GROUP_A;
+import static seedu.address.testutil.TypicalGroups.GROUP_B;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.group.exceptions.DuplicateGroupException;
+import seedu.address.model.group.exceptions.GroupNotFoundException;
+import seedu.address.testutil.GroupBuilder;
+
+public class GroupListTest {
+
+ private final GroupList groupList = new GroupList();
+
+ @Test
+ public void contains_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.contains(null));
+ }
+
+ @Test
+ public void contains_groupNotInList_returnsFalse() {
+ assertFalse(groupList.contains(GROUP_A));
+ }
+
+ @Test
+ public void contains_groupInList_returnsTrue() {
+ groupList.add(GROUP_A);
+ assertTrue(groupList.contains(GROUP_A));
+ }
+
+ @Test
+ public void contains_groupWithSameIdentityFieldsInList_returnsFalse() {
+ groupList.add(GROUP_A);
+ Group editedGroupA = new GroupBuilder(GROUP_A).withGroupName("Different Name").build();
+ assertFalse(groupList.contains(editedGroupA));
+ }
+
+ @Test
+ public void add_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.add(null));
+ }
+
+ @Test
+ public void add_duplicateGroup_throwsDuplicateGroupException() {
+ groupList.add(GROUP_A);
+ assertThrows(DuplicateGroupException.class, () -> groupList.add(GROUP_A));
+ }
+
+ @Test
+ public void setGroup_nullTargetGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.setGroup(null, GROUP_A));
+ }
+
+ @Test
+ public void setGroup_nullEditedGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.setGroup(GROUP_A, null));
+ }
+
+ @Test
+ public void setGroup_targetGroupNotInList_throwsGroupNotFoundException() {
+ assertThrows(GroupNotFoundException.class, () -> groupList.setGroup(GROUP_A, GROUP_A));
+ }
+
+ @Test
+ public void setGroup_editedGroupIsSameGroup_success() {
+ groupList.add(GROUP_A);
+ groupList.setGroup(GROUP_A, GROUP_A);
+ GroupList expectedGroupList = new GroupList();
+ expectedGroupList.add(GROUP_A);
+ assertEquals(expectedGroupList, groupList);
+ }
+
+ @Test
+ public void setGroup_editedGroupHasSameIdentity_success() {
+ groupList.add(GROUP_A);
+ Group editedGroupA = new GroupBuilder(GROUP_A).withGroupName("Different Name").build();
+ groupList.setGroup(GROUP_A, editedGroupA);
+ GroupList expectedGroupList = new GroupList();
+ expectedGroupList.add(editedGroupA);
+ assertEquals(expectedGroupList, groupList);
+ }
+
+ @Test
+ public void setGroup_editedGroupHasDifferentIdentity_success() {
+ groupList.add(GROUP_A);
+ groupList.setGroup(GROUP_A, GROUP_B);
+ GroupList expectedGroupList = new GroupList();
+ expectedGroupList.add(GROUP_B);
+ assertEquals(expectedGroupList, groupList);
+ }
+
+ @Test
+ public void setGroup_editedGroupHasNonUniqueIdentity_throwsDuplicateGroupException() {
+ groupList.add(GROUP_A);
+ groupList.add(GROUP_B);
+ assertThrows(DuplicateGroupException.class, () -> groupList.setGroup(GROUP_A, GROUP_B));
+ }
+
+ @Test
+ public void remove_nullGroup_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.remove(null));
+ }
+
+ @Test
+ public void remove_groupDoesNotExist_throwsGroupNotFoundException() {
+ assertThrows(GroupNotFoundException.class, () -> groupList.remove(GROUP_A));
+ }
+
+ @Test
+ public void remove_existingGroup_removesGroup() {
+ groupList.add(GROUP_A);
+ groupList.remove(GROUP_A);
+ GroupList expectedGroupList = new GroupList();
+ assertEquals(expectedGroupList, groupList);
+ }
+
+ @Test
+ public void setGroups_nullGroupList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.setGroups((GroupList) null));
+ }
+
+ @Test
+ public void setGroups_groupList_replacesOwnListWithProvidedGroupList() {
+ groupList.add(GROUP_A);
+ GroupList expectedGroupList = new GroupList();
+ expectedGroupList.add(GROUP_B);
+ groupList.setGroups(expectedGroupList);
+ assertEquals(expectedGroupList, groupList);
+ }
+
+ @Test
+ public void setGroups_nullList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> groupList.setGroups((List) null));
+ }
+
+ @Test
+ public void setGroups_list_replacesOwnListWithProvidedList() {
+ groupList.add(GROUP_A);
+ List groupList = Collections.singletonList(GROUP_B);
+ this.groupList.setGroups(groupList);
+ GroupList expectedGroupList = new GroupList();
+ expectedGroupList.add(GROUP_B);
+ assertEquals(expectedGroupList, this.groupList);
+ }
+
+ @Test
+ public void setGroups_listWithDuplicateGroups_throwsDuplicateGroupException() {
+ List listWithDuplicateGroups = Arrays.asList(GROUP_A, GROUP_A);
+ assertThrows(DuplicateGroupException.class, () -> groupList.setGroups(listWithDuplicateGroups));
+ }
+
+ @Test
+ public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> groupList.asUnmodifiableObservableList().remove(0));
+ }
+
+ @Test
+ public void toStringMethod() {
+ assertEquals(groupList.asUnmodifiableObservableList().toString(), groupList.toString());
+ }
+
+ @Test
+ public void iterator_emptyList_returnsEmptyIterator() {
+ assertFalse(groupList.iterator().hasNext());
+ }
+
+ @Test
+ public void iterator_nonEmptyList_returnsIterator() {
+ groupList.add(GROUP_A);
+ Iterator iterator = groupList.iterator();
+ assertTrue(iterator.hasNext());
+ assertEquals(GROUP_A, iterator.next());
+ }
+ @Test
+ public void equals_sameObject_returnsTrue() {
+ assertTrue(groupList.equals(groupList));
+ }
+
+ @Test
+ public void equals_nullObject_returnsFalse() {
+ assertFalse(groupList.equals(null));
+ }
+
+ @Test
+ public void equals_differentType_returnsFalse() {
+ assertFalse(groupList.equals("Not a GroupList"));
+ }
+
+ @Test
+ public void equals_differentGroupLists_returnsFalse() {
+ GroupList otherGroupList = new GroupList();
+ otherGroupList.add(GROUP_B);
+ assertFalse(groupList.equals(otherGroupList));
+ }
+
+ @Test
+ public void equals_sameGroupLists_returnsTrue() {
+ GroupList otherGroupList = new GroupList();
+ groupList.add(GROUP_A);
+ otherGroupList.add(GROUP_A);
+ assertTrue(groupList.equals(otherGroupList));
+ }
+
+ @Test
+ public void hashCode_sameGroupLists_returnsSameHashCode() {
+ GroupList otherGroupList = new GroupList();
+ groupList.add(GROUP_A);
+ otherGroupList.add(GROUP_A);
+ assertEquals(groupList.hashCode(), otherGroupList.hashCode());
+ }
+
+ @Test
+ public void hashCode_differentGroupLists_returnsDifferentHashCode() {
+ GroupList otherGroupList = new GroupList();
+ groupList.add(GROUP_A);
+ otherGroupList.add(GROUP_B);
+ assertNotEquals(groupList.hashCode(), otherGroupList.hashCode());
+ }
+
+
+
+}
diff --git a/src/test/java/seedu/address/model/group/GroupNameTest.java b/src/test/java/seedu/address/model/group/GroupNameTest.java
new file mode 100644
index 00000000000..7dfde25f86f
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/GroupNameTest.java
@@ -0,0 +1,73 @@
+package seedu.address.model.group;
+
+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.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class GroupNameTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new GroupName(null));
+ }
+
+ @Test
+ public void constructor_invalidGroupName_throwsIllegalArgumentException() {
+ String invalidGroupName = "";
+ assertThrows(IllegalArgumentException.class, () -> new GroupName(invalidGroupName));
+ }
+
+ @Test
+ public void isValidName() {
+ // null group name
+ assertThrows(NullPointerException.class, () -> GroupName.isValidName(null));
+
+ // invalid group name
+ assertFalse(GroupName.isValidName("")); // empty string
+ assertFalse(GroupName.isValidName(" ")); // spaces only
+ assertFalse(GroupName.isValidName("^")); // only non-alphanumeric characters
+ assertFalse(GroupName.isValidName("group@123")); // contains non-alphanumeric characters
+
+ // valid group name
+ assertTrue(GroupName.isValidName("Study Group")); // alphabets and space
+ assertTrue(GroupName.isValidName("Group123")); // alphanumeric characters
+ assertTrue(GroupName.isValidName("123456")); // numbers only
+ assertTrue(GroupName.isValidName("Group One 1st Edition")); // mixed alphanumeric with spaces
+ }
+
+ @Test
+ public void equals() {
+ GroupName groupName = new GroupName("Valid Group");
+
+ // same values -> returns true
+ assertEquals(groupName, new GroupName("Valid Group"));
+
+ // same object -> returns true
+ assertEquals(groupName, groupName);
+
+ // null -> returns false
+ assertNotEquals(null, groupName);
+
+ // different types -> returns false
+ assertFalse(groupName.equals(5.0f));
+
+ // different values -> returns false
+ assertNotEquals(groupName, new GroupName("Other Group"));
+ }
+
+ @Test
+ public void toStringMethod() {
+ GroupName groupName = new GroupName("Study Group");
+ assertEquals("Study Group", groupName.toString());
+ }
+
+ @Test
+ public void hashCodeTest() {
+ GroupName groupName = new GroupName("Study Group");
+ assertEquals(groupName.hashCode(), new GroupName("Study Group").hashCode());
+ }
+}
diff --git a/src/test/java/seedu/address/model/group/GroupTest.java b/src/test/java/seedu/address/model/group/GroupTest.java
new file mode 100644
index 00000000000..defa6075cca
--- /dev/null
+++ b/src/test/java/seedu/address/model/group/GroupTest.java
@@ -0,0 +1,78 @@
+package seedu.address.model.group;
+
+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 static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BOB;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.GroupCommand;
+import seedu.address.model.person.Person;
+
+public class GroupTest {
+
+ private final List members = Arrays.asList(ALICE, BOB);
+ private final GroupName groupName = new GroupName("Group A");
+ private final Group group = new Group(groupName.toString(), members);
+
+ @Test
+ public void constructor_nullGroupName_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Group(null, members));
+ }
+
+ @Test
+ public void constructor_nullMembers_throwsNullPointerException() {
+
+ assertThrows(NullPointerException.class, () -> new Group(groupName.toString(), null));
+ }
+
+ @Test
+ public void getGroupName() {
+ assertEquals(groupName, group.getGroupName());
+ }
+
+ @Test
+ public void getMembers() {
+ assertEquals(members, group.getMembers());
+ }
+
+ @Test
+ public void toStringMethod() {
+ String expected = "[Group: Group A, Members: Alice Pauline, Bob Choo]";
+ assertEquals(expected, group.toString());
+ }
+
+ @Test
+ public void equals() {
+ List studentNames = Arrays.asList("Alice", "Bob");
+ GroupCommand groupCommand1 = new GroupCommand("Group1", studentNames);
+ GroupCommand groupCommand2 = new GroupCommand("Group1", studentNames);
+
+ // same object -> returns true
+ assertTrue(groupCommand1.equals(groupCommand1));
+
+ // same values -> returns true
+ assertTrue(groupCommand1.equals(groupCommand2));
+
+ // different types -> returns false
+ assertFalse(groupCommand1.equals(1));
+
+ // null -> returns false
+ assertFalse(groupCommand1.equals(null));
+
+ // different group name -> returns false
+ GroupCommand groupCommandDifferentName = new GroupCommand("Group2", studentNames);
+ assertFalse(groupCommand1.equals(groupCommandDifferentName));
+
+ // different student list -> returns false
+ List differentStudentNames = Arrays.asList("Alice");
+ GroupCommand groupCommandDifferentStudents = new GroupCommand("Group1", differentStudentNames);
+ assertFalse(groupCommand1.equals(groupCommandDifferentStudents));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
index 6b3fd90ade7..adb36fca402 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
@@ -69,9 +69,9 @@ public void test_nameDoesNotContainKeywords_returnsFalse() {
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()));
+ predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "4A"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice").withClass("4A").withPhone("12345")
+ .build()));
}
@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..e33de4ba8ac 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -3,8 +3,7 @@
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_CLASS_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;
@@ -12,16 +11,52 @@
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
+import java.util.List;
+import java.util.Set;
+
import org.junit.jupiter.api.Test;
+import seedu.address.model.group.Group;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
import seedu.address.testutil.PersonBuilder;
public class PersonTest {
+ @Test
+ public void addGroups_addsGroupSuccessfully() {
+ Person actualPerson = new PersonBuilder().build();
+ Group group = new Group("StudyGroup", List.of(actualPerson));
+ actualPerson.addGroups(group);
+ assertTrue(actualPerson.getGroups().contains(group));
+ }
+
+ @Test
+ public void deleteTags_tagsNotPresent_noChange() {
+ Person person = new PersonBuilder().withTags(VALID_TAG_HUSBAND).build();
+ Set nonExistentTagSet = Set.of(new Tag("nonexistentTag"));
+ Tags nonExistentTags = new Tags(nonExistentTagSet);
+ Person updatedPerson = person.deleteTags(nonExistentTags);
+
+ // Ensure tags remain the same
+ assertEquals(person.getTags(), updatedPerson.getTags());
+ }
+
+ @Test
+ public void addTags_emptySet_noChange() {
+ Person person = new PersonBuilder().withTags(VALID_TAG_HUSBAND).build();
+ Tags emptyTags = new Tags();
+ Person updatedPerson = person.addTags(emptyTags);
+
+ // Ensure tags remain the same
+ assertEquals(person.getTags(), updatedPerson.getTags());
+ }
+
+
@Test
public void asObservableList_modifyList_throwsUnsupportedOperationException() {
Person person = new PersonBuilder().build();
- assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0));
+ assertThrows(UnsupportedOperationException.class, () -> person.getTagSet().remove(0));
}
@Test
@@ -33,17 +68,18 @@ 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)
+ .withClass(VALID_CLASS_BOB)
+ .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 + " ";
@@ -73,16 +109,13 @@ public void equals() {
Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
assertFalse(ALICE.equals(editedAlice));
- // different phone -> returns false
- editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build();
+ // different class -> returns false
+ editedAlice = new PersonBuilder(ALICE).withClass(VALID_CLASS_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();
+ // different phone -> returns false
+ editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build();
assertFalse(ALICE.equals(editedAlice));
// different tags -> returns false
@@ -90,10 +123,82 @@ public void equals() {
assertFalse(ALICE.equals(editedAlice));
}
+ @Test
+ public void addTags() {
+ Person expectedPerson = new PersonBuilder().withTags("tag").build();
+ Person actualPerson = new PersonBuilder().build();
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ actualPerson.addTags(tags);
+ assertEquals(expectedPerson, actualPerson);
+ }
+
+ @Test
+ public void deleteTags() {
+ Person actualPerson = new PersonBuilder().withTags("tag").build();
+ Person expectedPerson = new PersonBuilder().build();
+ Tag tag = new Tag("tag");
+ Set tagSet = Set.of(tag);
+ Tags tags = new Tags(tagSet);
+ actualPerson.deleteTags(tags);
+ assertEquals(expectedPerson, actualPerson);
+ }
+
+ @Test
+ public void getGroups_returnsImmutableSet() {
+ Person person = new PersonBuilder().build();
+ Group group = new Group("StudyGroup", List.of(person));
+ person.addGroups(group);
+
+ // Verify that modification attempt throws exception
+ Set groups = person.getGroups();
+ assertThrows(UnsupportedOperationException.class, () -> groups.add(group));
+ }
+
+ @Test
+ public void addGroups_groupAlreadyPresent_doesNotDuplicate() {
+ Person actualPerson = new PersonBuilder().build();
+ Group group = new Group("StudyGroup", List.of(actualPerson));
+ actualPerson.addGroups(group); // Add the first time
+ actualPerson.addGroups(group); // Add again
+
+ // Ensure the group is not duplicated
+ assertEquals(1, actualPerson.getGroups().size());
+ assertTrue(actualPerson.getGroups().contains(group));
+ }
+
@Test
public void toStringMethod() {
- String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName()
+ + ", studentClass=" + ALICE.getStudentClass()
+ + ", phone=" + ALICE.getPhone()
+ + ", tags=" + ALICE.getTags()
+ + ", groups=" + ALICE.getGroups()
+ + "}";
assertEquals(expected, ALICE.toString());
}
+
+
+ @Test
+ public void hashCode_sameAttributes_sameHashCode() {
+ // Create two person objects with the same attributes
+ Person person1 = new PersonBuilder(ALICE).build();
+ Person person2 = new PersonBuilder(ALICE).build();
+
+ // Ensure that two objects with the same attributes have the same hashCode
+ assertEquals(person1.hashCode(), person1.hashCode());
+ }
+
+
+ @Test
+ public void hashCode_differentAttributes_differentHashCode() {
+ // Create two person objects with different attributes
+ Person person1 = new PersonBuilder(ALICE).build();
+ Person person2 = new PersonBuilder(BOB).build();
+
+ // Ensure that two objects with different attributes have different hashCodes
+ assertFalse(person1.hashCode() == person2.hashCode());
+ }
+
}
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..d907cdfefe7 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -3,7 +3,7 @@
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_CLASS_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 +42,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).withClass(VALID_CLASS_BOB).withTags(VALID_TAG_HUSBAND)
.build();
assertTrue(uniquePersonList.contains(editedAlice));
}
@@ -85,7 +85,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)
+ Person editedAlice = new PersonBuilder(ALICE).withClass(VALID_CLASS_BOB).withTags(VALID_TAG_HUSBAND)
.build();
uniquePersonList.setPerson(ALICE, editedAlice);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java
index 64d07d79ee2..e229fafae3f 100644
--- a/src/test/java/seedu/address/model/tag/TagTest.java
+++ b/src/test/java/seedu/address/model/tag/TagTest.java
@@ -1,5 +1,7 @@
package seedu.address.model.tag;
+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;
@@ -23,4 +25,20 @@ public void isValidTagName() {
assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null));
}
+ @Test
+ public void constructor_tagExceedsLengthLimit_throwsIllegalArgumentException() {
+ String longTagName = "abcdefghijklmnopqrstuvwxyz123456789";
+ assertThrows(IllegalArgumentException.class, () -> new Tag(longTagName));
+ }
+
+ @Test
+ public void equals() {
+ Tag firstTag = new Tag("1");
+ Tag copy = new Tag("1");
+ Tag secondTag = new Tag("2");
+ assertTrue(firstTag.equals(firstTag));
+ assertFalse(firstTag.equals(null));
+ assertTrue(firstTag.equals(copy));
+ assertFalse(firstTag.equals(secondTag));
+ }
}
diff --git a/src/test/java/seedu/address/model/tag/TagsTest.java b/src/test/java/seedu/address/model/tag/TagsTest.java
new file mode 100644
index 00000000000..0c9540f75d2
--- /dev/null
+++ b/src/test/java/seedu/address/model/tag/TagsTest.java
@@ -0,0 +1,58 @@
+package seedu.address.model.tag;
+
+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.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+public class TagsTest {
+
+ public final Tag testTag = new Tag("test");
+ public final Tags testTags = new Tags(Set.of(testTag));
+
+ @Test
+ public void constructor_null_returnsEmptyTags() {
+ Set expectedTagSet = new HashSet<>();
+ Tags actualTags = new Tags();
+ Set actualTagSet = actualTags.getTags();
+ assertEquals(expectedTagSet, actualTagSet);
+ }
+
+ @Test
+ public void constructor_validTags_returnsCorrectTags() {
+ Set expectedTagSet = new HashSet<>();
+ expectedTagSet.add(testTag);
+ Tags actualTags = new Tags(Set.of(testTag));
+ Set actualTagSet = actualTags.getTags();
+ assertEquals(expectedTagSet, actualTagSet);
+ }
+
+ @Test
+ public void tagExists_validInput_returnsCorrectly() {
+ Tags sameCaseTags = new Tags(Set.of(new Tag("test")));
+ Tags diffCaseTags = new Tags(Set.of(new Tag("TEST")));
+ Tags diffTags = new Tags(Set.of(new Tag("0")));
+ assertTrue(testTags.tagExists(sameCaseTags));
+ assertTrue(testTags.tagExists(diffCaseTags));
+ assertFalse(testTags.tagExists(diffTags));
+ }
+
+ @Test
+ public void equals_validInput_returnsCorrectly() {
+ Tag anotherTestTag = new Tag("testing");
+ Tags tags = new Tags(Set.of(testTag, anotherTestTag));
+
+ Tags sameCaseTags = new Tags(Set.of(testTag, anotherTestTag));
+
+ Tag diffCaseTag = new Tag("TEST");
+ Tags diffCaseTags = new Tags(Set.of(diffCaseTag, anotherTestTag));
+
+ assertTrue(tags.equals(sameCaseTags));
+ assertTrue(tags.equals(diffCaseTags));
+ assertFalse(tags.equals(testTags));
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedGroupTest.java b/src/test/java/seedu/address/storage/JsonAdaptedGroupTest.java
new file mode 100644
index 00000000000..fffdb36793c
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonAdaptedGroupTest.java
@@ -0,0 +1,63 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.group.Group;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class JsonAdaptedGroupTest {
+
+ private static final String VALID_GROUP_NAME = "StudyGroup1";
+ private static final String INVALID_GROUP_NAME = null;
+
+ private static final Person VALID_PERSON_1 = new PersonBuilder().withName("Alice").build();
+ private static final Person VALID_PERSON_2 = new PersonBuilder().withName("Bob").build();
+
+ private static final JsonAdaptedPerson VALID_JSON_PERSON_1 = new JsonAdaptedPerson(VALID_PERSON_1);
+ private static final JsonAdaptedPerson VALID_JSON_PERSON_2 = new JsonAdaptedPerson(VALID_PERSON_2);
+
+ private static final List VALID_MEMBERS =
+ Arrays.asList(VALID_JSON_PERSON_1, VALID_JSON_PERSON_2);
+
+ @Test
+ public void toModelType_validGroupDetails_returnsGroup() throws Exception {
+ JsonAdaptedGroup jsonAdaptedGroup = new JsonAdaptedGroup(VALID_GROUP_NAME, VALID_MEMBERS);
+ Group group = jsonAdaptedGroup.toModelType();
+ assertEquals(VALID_GROUP_NAME, group.getGroupName().toString());
+ assertEquals(Arrays.asList(VALID_PERSON_1, VALID_PERSON_2), group.getMembers());
+ }
+
+ @Test
+ public void toModelType_nullGroupName_throwsIllegalValueException() {
+ JsonAdaptedGroup jsonAdaptedGroup = new JsonAdaptedGroup(INVALID_GROUP_NAME, VALID_MEMBERS);
+ String expectedMessage = String.format(JsonAdaptedGroup.MISSING_FIELD_MESSAGE_FORMAT, "GroupName");
+ assertThrows(IllegalValueException.class, expectedMessage, jsonAdaptedGroup::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullMembers_returnsGroupWithEmptyMembers() throws Exception {
+ JsonAdaptedGroup jsonAdaptedGroup = new JsonAdaptedGroup(VALID_GROUP_NAME, null);
+ Group group = jsonAdaptedGroup.toModelType();
+ assertEquals(VALID_GROUP_NAME, group.getGroupName().toString());
+ assertEquals(Collections.emptyList(), group.getMembers());
+ }
+
+ @Test
+ public void toModelType_invalidMember_throwsIllegalValueException() {
+ JsonAdaptedPerson invalidJsonPerson = new JsonAdaptedPerson(
+ null, "A123", "98765432", Collections.emptyList());
+ List invalidMembers = Collections.singletonList(invalidJsonPerson);
+ JsonAdaptedGroup jsonAdaptedGroup = new JsonAdaptedGroup(VALID_GROUP_NAME, invalidMembers);
+ assertThrows(IllegalValueException.class, jsonAdaptedGroup::toModelType);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..d0aee235117 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -12,23 +12,20 @@
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.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
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_PHONE = "+_651234";
+ private static final String INVALID_CLASS = "#";
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 List VALID_TAGS = BENSON.getTags().stream()
+ private static final String VALID_CLASS = BENSON.getStudentClass().toString();
+ private static final List VALID_TAGS = BENSON.getTagSet().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
@@ -41,14 +38,14 @@ 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_CLASS, VALID_PHONE, 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_CLASS, VALID_PHONE, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,45 +53,31 @@ 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, VALID_CLASS, INVALID_PHONE, 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, VALID_CLASS, null, 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_invalidClass_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
- String expectedMessage = Email.MESSAGE_CONSTRAINTS;
+ new JsonAdaptedPerson(VALID_NAME, INVALID_CLASS, VALID_PHONE, VALID_TAGS);
+ String expectedMessage = StudentClass.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());
- assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
- }
-
- @Test
- public void toModelType_invalidAddress_throwsIllegalValueException() {
- 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);
- }
-
- @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());
+ public void toModelType_nullClass_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_PHONE,
+ VALID_TAGS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, StudentClass.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -103,7 +86,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_CLASS, VALID_PHONE, invalidTags);
assertThrows(IllegalValueException.class, person::toModelType);
}
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..43ec8da1aa0 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -5,12 +5,12 @@
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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.StudentClass;
import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* A utility class to help with building EditPersonDescriptor objects.
@@ -33,9 +33,8 @@ public EditPersonDescriptorBuilder(EditPersonDescriptor descriptor) {
public EditPersonDescriptorBuilder(Person person) {
descriptor = new EditPersonDescriptor();
descriptor.setName(person.getName());
+ descriptor.setStudentClass(person.getStudentClass());
descriptor.setPhone(person.getPhone());
- descriptor.setEmail(person.getEmail());
- descriptor.setAddress(person.getAddress());
descriptor.setTags(person.getTags());
}
@@ -47,37 +46,32 @@ public EditPersonDescriptorBuilder withName(String name) {
return this;
}
- /**
- * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building.
- */
- public EditPersonDescriptorBuilder withPhone(String phone) {
- descriptor.setPhone(new Phone(phone));
- return this;
- }
-
/**
* Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building.
*/
- public EditPersonDescriptorBuilder withEmail(String email) {
- descriptor.setEmail(new Email(email));
+ public EditPersonDescriptorBuilder withStudentClass(String studentClass) {
+ descriptor.setStudentClass(new StudentClass(studentClass));
return this;
}
/**
- * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building.
+ * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building.
*/
- public EditPersonDescriptorBuilder withAddress(String address) {
- descriptor.setAddress(new Address(address));
+ public EditPersonDescriptorBuilder withPhone(String phone) {
+ descriptor.setPhone(new Phone(phone));
return this;
}
+
+
/**
* Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor}
* that we are building.
*/
public EditPersonDescriptorBuilder withTags(String... tags) {
Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet());
- descriptor.setTags(tagSet);
+ Tags tagsToSet = new Tags(tagSet);
+ descriptor.setTags(tagsToSet);
return this;
}
diff --git a/src/test/java/seedu/address/testutil/EmptyPersons.java b/src/test/java/seedu/address/testutil/EmptyPersons.java
new file mode 100644
index 00000000000..b916e2c8a86
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/EmptyPersons.java
@@ -0,0 +1,27 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.person.Person;
+
+/**
+ * A utility class containing a list of {@code Person} objects to be used in tests.
+ */
+public class EmptyPersons {
+ private EmptyPersons() {} // prevents instantiation
+
+ /**
+ * Returns an {@code AddressBook} with all the typical persons.
+ */
+ public static AddressBook getEmptyAddressBook() {
+ AddressBook ab = new AddressBook();
+ return ab;
+ }
+
+ public static List getTypicalPersons() {
+ return new ArrayList<>();
+ }
+}
+
diff --git a/src/test/java/seedu/address/testutil/GroupBuilder.java b/src/test/java/seedu/address/testutil/GroupBuilder.java
new file mode 100644
index 00000000000..f254b15b538
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/GroupBuilder.java
@@ -0,0 +1,60 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import seedu.address.model.group.Group;
+import seedu.address.model.group.GroupName;
+import seedu.address.model.person.Person;
+
+/**
+ * A utility class to help with building Group objects.
+ */
+public class GroupBuilder {
+
+ public static final String DEFAULT_GROUP_NAME = "Study Group";
+ public static final List DEFAULT_MEMBERS = new ArrayList<>();
+
+ private GroupName groupName;
+ private List members;
+
+ /**
+ * Creates a {@code GroupBuilder} with the default details.
+ */
+ public GroupBuilder() {
+ groupName = new GroupName(DEFAULT_GROUP_NAME);
+ members = new ArrayList<>(DEFAULT_MEMBERS);
+ }
+
+ /**
+ * Initializes the GroupBuilder with the data of {@code groupToCopy}.
+ */
+ public GroupBuilder(Group groupToCopy) {
+ groupName = groupToCopy.getGroupName();
+ members = new ArrayList<>(groupToCopy.getMembers());
+ }
+
+ /**
+ * Sets the {@code GroupName} of the {@code Group} that we are building.
+ */
+ public GroupBuilder withGroupName(String groupName) {
+ this.groupName = new GroupName(groupName);
+ return this;
+ }
+
+ /**
+ * Sets the {@code members} of the {@code Group} that we are building.
+ */
+ public GroupBuilder withMembers(Person... persons) {
+ this.members = new ArrayList<>(Arrays.asList(persons));
+ return this;
+ }
+
+ /**
+ * Builds and returns the {@code Group}.
+ */
+ public Group build() {
+ return new Group(groupName.toString(), members);
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/GroupUtil.java b/src/test/java/seedu/address/testutil/GroupUtil.java
new file mode 100644
index 00000000000..1949ef0586a
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/GroupUtil.java
@@ -0,0 +1,17 @@
+package seedu.address.testutil;
+
+import seedu.address.logic.commands.GroupCommand;
+
+/**
+ * A utility class for Group.
+ */
+public class GroupUtil {
+
+ /**
+ * Returns an add command string for adding the {@code group}.
+ */
+ public static String groupCommand() {
+ return GroupCommand.COMMAND_WORD + " g/Meow" + " s/";
+ }
+
+}
diff --git a/src/test/java/seedu/address/testutil/GroupsUtil.java b/src/test/java/seedu/address/testutil/GroupsUtil.java
new file mode 100644
index 00000000000..0d873a7ae8a
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/GroupsUtil.java
@@ -0,0 +1,17 @@
+package seedu.address.testutil;
+
+import seedu.address.logic.commands.ListGroupsCommand;
+
+/**
+ * A utility class for Group.
+ */
+public class GroupsUtil {
+
+ /**
+ * Returns an add command string for adding the {@code group}.
+ */
+ public static String groupsCommand() {
+ return ListGroupsCommand.COMMAND_WORD + " g/Meow" + " s/";
+ }
+
+}
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..ba9c4d1db42 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -1,14 +1,10 @@
package seedu.address.testutil;
-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.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.StudentClass;
+import seedu.address.model.tag.Tags;
import seedu.address.model.util.SampleDataUtil;
/**
@@ -17,25 +13,22 @@
public class PersonBuilder {
public static final String DEFAULT_NAME = "Amy Bee";
+ public static final String DEFAULT_CLASS = "4A";
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";
private Name name;
+ private StudentClass studentClass;
private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
+ private Tags tags;
/**
* Creates a {@code PersonBuilder} with the default details.
*/
public PersonBuilder() {
name = new Name(DEFAULT_NAME);
+ studentClass = new StudentClass(DEFAULT_CLASS);
phone = new Phone(DEFAULT_PHONE);
- email = new Email(DEFAULT_EMAIL);
- address = new Address(DEFAULT_ADDRESS);
- tags = new HashSet<>();
+ tags = new Tags();
}
/**
@@ -43,10 +36,9 @@ public PersonBuilder() {
*/
public PersonBuilder(Person personToCopy) {
name = personToCopy.getName();
+ studentClass = personToCopy.getStudentClass();
phone = personToCopy.getPhone();
- email = personToCopy.getEmail();
- address = personToCopy.getAddress();
- tags = new HashSet<>(personToCopy.getTags());
+ tags = personToCopy.getTags();
}
/**
@@ -61,15 +53,15 @@ public PersonBuilder withName(String name) {
* Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building.
*/
public PersonBuilder withTags(String ... tags) {
- this.tags = SampleDataUtil.getTagSet(tags);
+ this.tags = new Tags(SampleDataUtil.getTagSet(tags));
return this;
}
/**
- * Sets the {@code Address} of the {@code Person} that we are building.
+ * Sets the {@code StudentClass} of the {@code Person} that we are building.
*/
- public PersonBuilder withAddress(String address) {
- this.address = new Address(address);
+ public PersonBuilder withClass(String studentClass) {
+ this.studentClass = new StudentClass(studentClass);
return this;
}
@@ -81,16 +73,9 @@ public PersonBuilder withPhone(String phone) {
return this;
}
- /**
- * Sets the {@code Email} of the {@code Person} that we are building.
- */
- public PersonBuilder withEmail(String email) {
- this.email = new Email(email);
- return this;
- }
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, studentClass, phone, tags);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..87e979f4c2d 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,17 +1,14 @@
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_CLASS;
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.Set;
-
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.person.Person;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.Tags;
/**
* A utility class for Person.
@@ -31,11 +28,10 @@ public static String getAddCommand(Person person) {
public static String getPersonDetails(Person person) {
StringBuilder sb = new StringBuilder();
sb.append(PREFIX_NAME + person.getName().fullName + " ");
+ sb.append(PREFIX_CLASS + person.getStudentClass().value + " ");
sb.append(PREFIX_PHONE + person.getPhone().value + " ");
- sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
- sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
- person.getTags().stream().forEach(
- s -> sb.append(PREFIX_TAG + s.tagName + " ")
+ person.getTagSet().stream().forEach(
+ s -> sb.append(PREFIX_TAG + s.getTagName() + " ")
);
return sb.toString();
}
@@ -47,14 +43,14 @@ 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.getStudentClass()
+ .ifPresent(studentClass -> sb.append(PREFIX_CLASS).append(studentClass.value).append(" "));
if (descriptor.getTags().isPresent()) {
- Set tags = descriptor.getTags().get();
+ Tags tags = descriptor.getTags().get();
if (tags.isEmpty()) {
sb.append(PREFIX_TAG);
} else {
- tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" "));
+ tags.getTags().forEach(s -> sb.append(PREFIX_TAG).append(s.getTagName()).append(" "));
}
}
return sb.toString();
diff --git a/src/test/java/seedu/address/testutil/TypicalGroups.java b/src/test/java/seedu/address/testutil/TypicalGroups.java
new file mode 100644
index 00000000000..cb371d2117f
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalGroups.java
@@ -0,0 +1,23 @@
+package seedu.address.testutil;
+
+import javafx.collections.FXCollections;
+import seedu.address.model.group.Group;
+
+/**
+ * A utility class containing a list of {@code Group} objects to be used in tests.
+ */
+public class TypicalGroups {
+
+ public static final Group GOONERS = new Group("Gooners",
+ FXCollections.observableArrayList(TypicalPersons.ALICE, TypicalPersons.BOB));
+
+ public static final Group GROUP_A = new Group("GroupA",
+ FXCollections.observableArrayList(TypicalPersons.ALICE));
+
+ public static final Group GROUP_B = new Group("GroupB",
+ FXCollections.observableArrayList(TypicalPersons.BOB));
+
+ private TypicalGroups() {} // prevents instantiation
+
+
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalPaths.java b/src/test/java/seedu/address/testutil/TypicalPaths.java
new file mode 100644
index 00000000000..64aabdf3245
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalPaths.java
@@ -0,0 +1,45 @@
+package seedu.address.testutil;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * A utility class containing a list of {@code Path} objects to be used in tests.
+ */
+public class TypicalPaths {
+
+ public static final Path PROJECT_ROOT_PATH = Paths.get(System.getProperty("user.dir"));
+ public static final Path TEST_DATA_FOLDER = PROJECT_ROOT_PATH.resolve("src").resolve("test").resolve("data");
+
+ // Paths for ImportCommandTest
+ public static final Path DUPLICATE_IMPORT_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("dups.csv");
+ public static final Path EMPTY_IMPORT_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("empty.csv");
+ public static final Path INVALID_MISSING_NAME_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("invalid_missing_name.csv");
+ public static final Path INVALID_IMPORT_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("invalid.csv");
+ public static final Path VALID_NO_DUPS_IMPORT_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("valid_noDups_importFile.csv");
+ public static final Path VALID_MISSING_CLASS_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("valid_missing_class.csv");
+ public static final Path VALID_MISSING_DATA_IMPORT_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("valid_missing_data.csv");
+ public static final Path VALID_MISSING_PHONE_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("valid_missing_phone.csv");
+ public static final Path VALID_MISSING_TAGS_FILE = TEST_DATA_FOLDER.resolve("ImportCommandTest")
+ .resolve("valid_missing_tags.csv");
+
+ // Paths for ExportCommandTest
+ public static final Path EXPORT_FILE_PATH = TEST_DATA_FOLDER.resolve("JsonExportTest")
+ .resolve("exported_data.csv");
+ public static final Path TYPICAL_PERSONS_ADDRESS_BOOK = TEST_DATA_FOLDER.resolve("JsonExportTest")
+ .resolve("typicalPersonsAddressBook.json");
+
+ private TypicalPaths() {} // prevents instantiation
+
+ public static Path getTypicalPath() {
+ return PROJECT_ROOT_PATH;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..3c5d861e761 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -1,9 +1,7 @@
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_CLASS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_CLASS_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;
@@ -24,35 +22,51 @@
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")
+ .withClass("4A")
.withTags("friends").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
- .withAddress("311, Clementi Ave 2, #02-25")
- .withEmail("johnd@example.com").withPhone("98765432")
+ .withPhone("98765432")
+ .withClass("6B")
.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 CARL = new PersonBuilder().withName("Carl Kurz")
+ .withClass("4A")
+ .withPhone("95352563")
+ .build();
+ public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier")
+ .withClass("7H")
+ .withPhone("87652533").withTags("friends").build();
+ public static final Person ELLE = new PersonBuilder().withName("Elle Meyer")
+ .withClass("3U")
+ .withPhone("9482224")
+ .build();
+ public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz")
+ .withClass("3B")
+ .withPhone("9482427")
+ .build();
public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
- .withEmail("anna@example.com").withAddress("4th street").build();
+ .withClass("5H")
+ .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")
+ .withClass("11E")
+ .withPhone("8482424")
+ .build();
+ public static final Person IDA = new PersonBuilder().withName("Ida Mueller")
+ .withClass("A5")
+ .withPhone("8482131")
+ .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();
- 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)
+ public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY)
+ .withClass(VALID_CLASS_AMY)
+ .withPhone(VALID_PHONE_AMY)
+ .withTags(VALID_TAG_FRIEND).build();
+ public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB)
+ .withClass(VALID_CLASS_BOB)
+ .withPhone(VALID_PHONE_BOB)
+ .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
.build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER